diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 04:23:18 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 04:23:18 +0000 |
commit | b90161ccd3b318f3314a23cb10c387651ad35831 (patch) | |
tree | a47dc087160299ce02d728cbf031d84af6281537 | |
parent | Adding upstream version 2.1.30. (diff) | |
download | libyang2-upstream.tar.xz libyang2-upstream.zip |
Adding upstream version 2.1.148.upstream/2.1.148upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
301 files changed, 21326 insertions, 8717 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 210b5be..daa55d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -122,7 +122,7 @@ jobs: shell: bash working-directory: ${{ github.workspace }} run: | - git clone --branch uncrustify-0.75.1 https://github.com/uncrustify/uncrustify + git clone --branch uncrustify-0.77.1 https://github.com/uncrustify/uncrustify cd uncrustify mkdir build cd build diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 264faa8..023d159 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -5,8 +5,6 @@ on: branches: [ "master", "devel" ] pull_request: branches: [ "devel" ] - schedule: - - cron: "38 17 * * 4" jobs: analyze: diff --git a/CMakeLists.txt b/CMakeLists.txt index 6470ce3..ee7d21f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,12 +61,12 @@ set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) # set version of the project set(LIBYANG_MAJOR_VERSION 2) set(LIBYANG_MINOR_VERSION 1) -set(LIBYANG_MICRO_VERSION 30) +set(LIBYANG_MICRO_VERSION 148) set(LIBYANG_VERSION ${LIBYANG_MAJOR_VERSION}.${LIBYANG_MINOR_VERSION}.${LIBYANG_MICRO_VERSION}) # set version of the library set(LIBYANG_MAJOR_SOVERSION 2) -set(LIBYANG_MINOR_SOVERSION 29) -set(LIBYANG_MICRO_SOVERSION 2) +set(LIBYANG_MINOR_SOVERSION 46) +set(LIBYANG_MICRO_SOVERSION 3) set(LIBYANG_SOVERSION_FULL ${LIBYANG_MAJOR_SOVERSION}.${LIBYANG_MINOR_SOVERSION}.${LIBYANG_MICRO_SOVERSION}) set(LIBYANG_SOVERSION ${LIBYANG_MAJOR_SOVERSION}) @@ -103,6 +103,7 @@ set(type_plugins src/plugins_types/ipv4_prefix.c src/plugins_types/ipv6_prefix.c src/plugins_types/date_and_time.c + src/plugins_types/hex_string.c src/plugins_types/xpath1.0.c src/plugins_types/node_instanceid.c) @@ -110,6 +111,7 @@ set(libsrc src/common.c src/log.c src/hash_table.c + src/dict.c src/set.c src/path.c src/diff.c @@ -159,6 +161,7 @@ set(libsrc set(headers src/context.h + src/hash_table.h src/dict.h src/in.h src/libyang.h @@ -181,7 +184,7 @@ set(headers set(internal_headers src/common.h src/diff.h - src/hash_table.h + src/hash_table_internal.h src/in_internal.h src/json.h src/lyb.h @@ -314,7 +317,7 @@ endif() if ("${BUILD_TYPE_UPPER}" STREQUAL "DEBUG") # enable before adding tests to let them detect that format checking is available - one of the tests is format checking - source_format_enable(0.76) + source_format_enable(0.77) endif() # generate files @@ -337,9 +340,14 @@ use_compat() # create static libyang library if(ENABLE_STATIC) add_definitions(-DSTATIC) - set(CMAKE_EXE_LINKER_FLAGS -static) - set(CMAKE_FIND_LIBRARY_SUFFIXES .a) + + # allow binaries compilation linking both static and dynamic libraries never linking static glibc + #set(CMAKE_EXE_LINKER_FLAGS -static) + + # prefer static libraries + set(CMAKE_FIND_LIBRARY_SUFFIXES .a;.so) set(CMAKE_LINK_SEARCH_START_STATIC TRUE) + set(CMAKE_EXE_LINK_DYNAMIC_C_FLAGS) # remove -Wl,-Bdynamic set(CMAKE_EXE_LINK_DYNAMIC_CXX_FLAGS) add_library(yang STATIC ${libsrc} ${compatsrc}) @@ -382,19 +390,15 @@ endif() set_target_properties(yang PROPERTIES VERSION ${LIBYANG_SOVERSION_FULL} SOVERSION ${LIBYANG_SOVERSION}) +# link math if(NOT WIN32) - # link math target_link_libraries(yang m) endif() # find pthreads set(CMAKE_THREAD_PREFER_PTHREAD TRUE) find_package(Threads REQUIRED) -if(ENABLE_STATIC) - target_link_libraries(yang -Wl,--whole-archive ${CMAKE_THREAD_LIBS_INIT} -Wl,--no-whole-archive) -else() - target_link_libraries(yang ${CMAKE_THREAD_LIBS_INIT}) -endif() +target_link_libraries(yang ${CMAKE_THREAD_LIBS_INIT}) # find PCRE2 library unset(PCRE2_LIBRARY CACHE) @@ -459,7 +463,7 @@ gen_doc("${doxy_files}" ${LIBYANG_VERSION} ${LIBYANG_DESCRIPTION} ${project_logo # generate API/ABI report if ("${BUILD_TYPE_UPPER}" STREQUAL "ABICHECK") - lib_abi_check(yang "${headers}" ${LIBYANG_SOVERSION_FULL} 003fa46e190930912e4d3f7b178c671c0662f671) + lib_abi_check(yang "${headers}" ${LIBYANG_SOVERSION_FULL} 8b787ba8edf21556c8845722587ac8036400150a) endif() # source code format target for Makefile diff --git a/CMakeModules/FindPCRE2.cmake b/CMakeModules/FindPCRE2.cmake index 19af7b7..a05d998 100644 --- a/CMakeModules/FindPCRE2.cmake +++ b/CMakeModules/FindPCRE2.cmake @@ -22,19 +22,23 @@ else() ${CMAKE_INSTALL_PREFIX}/include) # Look for the library. - find_library(PCRE2_LIBRARY - NAMES - libpcre2.a - pcre2-8 - PATHS - /usr/lib - /usr/lib64 - /usr/local/lib - /usr/local/lib64 - /opt/local/lib - /sw/lib - ${CMAKE_LIBRARY_PATH} - ${CMAKE_INSTALL_PREFIX}/lib) + if (WIN32 AND "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + # For the Debug build, the pcre2 library is called pcre2-8d. The Release build should be pcre2-8. + find_library(PCRE2_LIBRARY pcre2-8d) + else() + find_library(PCRE2_LIBRARY + NAMES + pcre2-8 + PATHS + /usr/lib + /usr/lib64 + /usr/local/lib + /usr/local/lib64 + /opt/local/lib + /sw/lib + ${CMAKE_LIBRARY_PATH} + ${CMAKE_INSTALL_PREFIX}/lib) + endif() if(PCRE2_INCLUDE_DIR AND PCRE2_LIBRARY) # learn pcre2 version diff --git a/CMakeModules/UseCompat.cmake b/CMakeModules/UseCompat.cmake index c1befd7..ef3df89 100644 --- a/CMakeModules/UseCompat.cmake +++ b/CMakeModules/UseCompat.cmake @@ -27,6 +27,7 @@ macro(USE_COMPAT) set(CMAKE_REQUIRED_DEFINITIONS -D_POSIX_C_SOURCE=200809L) list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE) list(APPEND CMAKE_REQUIRED_DEFINITIONS -D__BSD_VISIBLE=1) + list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_DEFAULT_SOURCE) set(CMAKE_REQUIRED_LIBRARIES pthread) check_symbol_exists(vdprintf "stdio.h;stdarg.h" HAVE_VDPRINTF) @@ -47,16 +48,12 @@ macro(USE_COMPAT) check_include_file("stdatomic.h" HAVE_STDATOMIC) - include(CheckStructHasMember) - check_struct_has_member("struct tm" tm_gmtoff time.h HAVE_TM_GMTOFF) - check_symbol_exists(timezone time.h HAVE_TIME_H_TIMEZONE) - check_symbol_exists(realpath "stdlib.h" HAVE_REALPATH) check_symbol_exists(localtime_r "time.h" HAVE_LOCALTIME_R) check_symbol_exists(gmtime_r "time.h" HAVE_GMTIME_R) + check_function_exists(timegm HAVE_TIMEGM) check_symbol_exists(strptime "time.h" HAVE_STRPTIME) check_symbol_exists(mmap "sys/mman.h" HAVE_MMAP) - check_symbol_exists(dirname "libgen.h" HAVE_DIRNAME) check_symbol_exists(setenv "stdlib.h" HAVE_SETENV) unset(CMAKE_REQUIRED_DEFINITIONS) @@ -12,9 +12,6 @@ libyang is a YANG data modelling language parser and toolkit written (and providing API) in C. The library is used e.g. in [libnetconf2](https://github.com/CESNET/libnetconf2), [Netopeer2](https://github.com/CESNET/Netopeer2) or [sysrepo](https://github.com/sysrepo/sysrepo) projects. -If you are interested in future plans announcements, please subscribe to the -[Future Plans issue](https://github.com/CESNET/libyang/issues/880). - ## Branches The project uses 2 main branches `master` and `devel`. Other branches should not be cloned. In `master` there are files of the diff --git a/compat/compat.c b/compat/compat.c index 5fb2be8..24f235c 100644 --- a/compat/compat.c +++ b/compat/compat.c @@ -86,9 +86,9 @@ vasprintf(char **strp, const char *fmt, va_list ap) ssize_t getline(char **lineptr, size_t *n, FILE *stream) { - static char line[256]; + static char chunk[256]; char *ptr; - ssize_t len; + ssize_t len, written; if (!lineptr || !n) { errno = EINVAL; @@ -99,28 +99,31 @@ getline(char **lineptr, size_t *n, FILE *stream) return -1; } - if (!fgets(line, 256, stream)) { - return -1; - } - - ptr = strchr(line, '\n'); - if (ptr) { - *ptr = '\0'; - } - - len = strlen(line); - - if (len + 1 < 256) { - ptr = realloc(*lineptr, 256); - if (!ptr) { - return -1; + *n = *lineptr ? *n : 0; + written = 0; + while (fgets(chunk, sizeof(chunk), stream)) { + len = strlen(chunk); + if (written + len > *n) { + ptr = realloc(*lineptr, *n + sizeof(chunk)); + if (!ptr) { + return -1; + } + *lineptr = ptr; + *n = *n + sizeof(chunk); } - *lineptr = ptr; - *n = 256; + memcpy(*lineptr + written, &chunk, len); + written += len; + if ((*lineptr)[written - 1] == '\n') { + break; + } + } + if (written) { + (*lineptr)[written] = '\0'; + } else { + written = -1; } - strcpy(*lineptr, line); - return len; + return written; } #endif @@ -322,21 +325,6 @@ gmtime_r(const time_t *timep, struct tm *result) #endif #endif -#ifndef HAVE_DIRNAME -#ifdef _WIN32 -#include <shlwapi.h> -char * -dirname(char *path) -{ - PathRemoveFileSpecA(path); - return path; -} - -#else -#error No dirname() implementation for this platform is available. -#endif -#endif - #ifndef HAVE_SETENV #ifdef _WIN32 int diff --git a/compat/compat.h.in b/compat/compat.h.in index c697d6c..3baa489 100644 --- a/compat/compat.h.in +++ b/compat/compat.h.in @@ -3,7 +3,7 @@ * @author Michal Vasko <mvasko@cesnet.cz> * @brief compatibility functions header * - * Copyright (c) 2021 CESNET, z.s.p.o. + * Copyright (c) 2021 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -65,14 +65,12 @@ #cmakedefine HAVE_STRCHRNUL #cmakedefine HAVE_GET_CURRENT_DIR_NAME #cmakedefine HAVE_PTHREAD_MUTEX_TIMEDLOCK -#cmakedefine HAVE_TM_GMTOFF -#cmakedefine HAVE_TIME_H_TIMEZONE #cmakedefine HAVE_REALPATH #cmakedefine HAVE_LOCALTIME_R #cmakedefine HAVE_GMTIME_R +#cmakedefine HAVE_TIMEGM #cmakedefine HAVE_STRPTIME #cmakedefine HAVE_MMAP -#cmakedefine HAVE_DIRNAME #cmakedefine HAVE_STRCASECMP #cmakedefine HAVE_SETENV @@ -188,6 +186,18 @@ char *realpath(const char *path, char *resolved_path); struct tm *localtime_r(const time_t *timep, struct tm *result); #endif +#ifndef HAVE_GMTIME_R +struct tm *gmtime_r(const time_t *timep, struct tm *result); +#endif + +#ifndef HAVE_TIMEGM +# if defined (_WIN32) +# define timegm _mkgmtime +# else +# error No timegm() implementation for this platform is available. +# endif +#endif + #ifndef HAVE_STRPTIME char *strptime(const char *s, const char *format, struct tm *tm); #endif diff --git a/compat/posix-shims/strings.h b/compat/posix-shims/strings.h index c917a66..1ac0519 100644 --- a/compat/posix-shims/strings.h +++ b/compat/posix-shims/strings.h @@ -7,3 +7,11 @@ #error No strcasecmp() implementation for this platform is available. #endif #endif + +#ifndef HAVE_STRNCASECMP +#ifdef _MSC_VER +#define strncasecmp _strnicmp +#else +#error No strncasecmp() implementation for this platform is available. +#endif +#endif diff --git a/distro/README.md b/distro/README.md index 995cb53..dae5d1c 100644 --- a/distro/README.md +++ b/distro/README.md @@ -1,9 +1,9 @@ # upstream packaging ## RPM-based system (Fedora, CentOS, SUSE, ...) quickstart - +``` sudo dnf install -y git rpm-build python3-pip pip3 install apkg apkg build -i - +``` See apkg docs: https://pkg.labs.nic.cz/pages/apkg/ diff --git a/distro/pkg/deb/libyang2.install b/distro/pkg/deb/libyang2.install index e5f2c1c..9d72c7e 100644 --- a/distro/pkg/deb/libyang2.install +++ b/distro/pkg/deb/libyang2.install @@ -1,2 +1,2 @@ usr/lib/*/*.so.* -usr/share/yang +usr/share/yang/modules/libyang diff --git a/distro/pkg/rpm/libyang.spec b/distro/pkg/rpm/libyang.spec index 374fc4e..f57ef91 100644 --- a/distro/pkg/rpm/libyang.spec +++ b/distro/pkg/rpm/libyang.spec @@ -1,3 +1,7 @@ +%if 0%{?rhel} == 8 +%undefine __cmake_in_source_build +%endif + Name: libyang Version: {{ version }} Release: {{ release }}%{?dist} @@ -43,27 +47,46 @@ written (and providing API) in C. %prep %autosetup -p1 +%if 0%{?rhel} && 0%{?rhel} < 8 + mkdir build +%endif %build -%cmake -DCMAKE_BUILD_TYPE=RELWITHDEBINFO -%cmake_build - -%if "x%{?suse_version}" == "x" -cd redhat-linux-build +%if 0%{?rhel} && 0%{?rhel} < 8 + cd build + cmake \ + -DCMAKE_INSTALL_PREFIX:PATH=%{_prefix} \ + -DCMAKE_BUILD_TYPE:String="Release" \ + -DCMAKE_C_FLAGS="${RPM_OPT_FLAGS}" \ + -DCMAKE_CXX_FLAGS="${RPM_OPT_FLAGS}" \ + .. + make +%else + %cmake -DCMAKE_BUILD_TYPE=RELWITHDEBINFO + %cmake_build + %if "x%{?suse_version}" == "x" + cd %{__cmake_builddir} + %endif %endif make doc %check -%if "x%{?suse_version}" == "x" -cd redhat-linux-build +%if ( 0%{?rhel} == 0 ) || 0%{?rhel} > 7 + %if "x%{?suse_version}" == "x" + cd %{__cmake_builddir} + %endif %endif ctest --output-on-failure -V %{?_smp_mflags} %install -%cmake_install - mkdir -m0755 -p %{buildroot}/%{_docdir}/libyang -cp -a doc/html %{buildroot}/%{_docdir}/libyang/html +%if 0%{?rhel} && 0%{?rhel} < 8 + cd build + make DESTDIR=%{buildroot} install +%else + %cmake_install + cp -a doc/html %{buildroot}/%{_docdir}/libyang/html +%endif %files %license LICENSE diff --git a/models/yang@2022-06-16.h b/models/yang@2022-06-16.h index 563398b..2ca2ef0 100644 --- a/models/yang@2022-06-16.h +++ b/models/yang@2022-06-16.h @@ -1,483 +1,485 @@ -unsigned char yang_2022_06_16_yang[] = { +char yang_2022_06_16_yang[] = { 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x79, 0x61, 0x6e, 0x67, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x20, 0x22, 0x75, 0x72, 0x6e, 0x3a, 0x69, 0x65, 0x74, 0x66, 0x3a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x3a, 0x78, 0x6d, 0x6c, 0x3a, 0x6e, 0x73, 0x3a, 0x79, 0x61, 0x6e, 0x67, 0x3a, 0x31, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x20, 0x79, 0x61, 0x6e, 0x67, - 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x20, - 0x69, 0x65, 0x74, 0x66, 0x2d, 0x79, 0x61, 0x6e, 0x67, 0x2d, 0x6d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x20, 0x6d, 0x64, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x2d, 0x64, 0x61, 0x74, 0x65, 0x20, 0x32, 0x30, 0x31, 0x36, 0x2d, 0x30, - 0x38, 0x2d, 0x30, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, - 0x20, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x6c, 0x69, 0x62, 0x79, 0x61, - 0x6e, 0x67, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x74, - 0x61, 0x63, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x57, 0x65, 0x62, - 0x3a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, - 0x2f, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x43, 0x45, 0x53, 0x4e, 0x45, 0x54, 0x2f, 0x6c, 0x69, 0x62, 0x79, - 0x61, 0x6e, 0x67, 0x2f, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x3a, 0x20, 0x52, 0x61, 0x64, 0x65, 0x6b, - 0x20, 0x4b, 0x72, 0x65, 0x6a, 0x63, 0x69, 0x20, 0x3c, 0x72, 0x6b, 0x72, - 0x65, 0x6a, 0x63, 0x69, 0x40, 0x63, 0x65, 0x73, 0x6e, 0x65, 0x74, 0x2e, - 0x63, 0x7a, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x3a, 0x20, 0x4d, 0x69, 0x63, 0x68, 0x61, 0x6c, 0x20, - 0x56, 0x61, 0x73, 0x6b, 0x6f, 0x20, 0x3c, 0x6d, 0x76, 0x61, 0x73, 0x6b, - 0x6f, 0x40, 0x63, 0x65, 0x73, 0x6e, 0x65, 0x74, 0x2e, 0x63, 0x7a, 0x3e, - 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, - 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x64, 0x75, 0x6d, - 0x6d, 0x79, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x77, 0x69, - 0x74, 0x68, 0x20, 0x6e, 0x6f, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x66, - 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x73, 0x75, 0x70, 0x70, - 0x6c, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, - 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x6f, 0x66, 0x20, 0x76, 0x61, 0x72, 0x69, 0x6f, 0x75, - 0x73, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x64, - 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x52, 0x46, - 0x43, 0x20, 0x36, 0x30, 0x32, 0x30, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x52, - 0x46, 0x43, 0x20, 0x37, 0x39, 0x35, 0x30, 0x2e, 0x20, 0x54, 0x68, 0x65, - 0x72, 0x65, 0x20, 0x61, 0x72, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x6d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x75, 0x73, 0x65, 0x64, - 0x20, 0x69, 0x6e, 0x20, 0x6c, 0x69, 0x62, 0x79, 0x61, 0x6e, 0x67, 0x20, - 0x64, 0x69, 0x66, 0x66, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x66, 0x6f, - 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x72, - 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x32, 0x30, 0x32, 0x32, - 0x2d, 0x30, 0x36, 0x2d, 0x31, 0x36, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x41, 0x64, 0x64, 0x65, - 0x64, 0x20, 0x74, 0x79, 0x70, 0x65, 0x64, 0x65, 0x66, 0x20, 0x66, 0x6f, - 0x72, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x20, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x22, 0x3b, 0x0a, 0x20, - 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x20, 0x32, 0x30, 0x32, 0x31, 0x2d, 0x30, 0x34, 0x2d, 0x30, - 0x37, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x41, 0x64, 0x64, 0x65, 0x64, 0x20, 0x6d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x6b, 0x65, - 0x79, 0x2d, 0x6c, 0x65, 0x73, 0x73, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, - 0x61, 0x6e, 0x64, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x6c, 0x65, - 0x61, 0x66, 0x2d, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x64, 0x69, 0x66, 0x66, - 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x72, - 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x32, 0x30, 0x32, 0x30, - 0x2d, 0x30, 0x36, 0x2d, 0x31, 0x37, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x41, 0x64, 0x64, 0x65, - 0x64, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x66, - 0x6f, 0x72, 0x20, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x22, 0x3b, 0x0a, 0x20, - 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x20, 0x32, 0x30, 0x31, 0x37, 0x2d, 0x30, 0x32, 0x2d, 0x32, - 0x30, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x22, 0x41, 0x64, 0x64, 0x65, 0x64, 0x20, 0x6d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x4e, 0x45, - 0x54, 0x43, 0x4f, 0x4e, 0x46, 0x27, 0x73, 0x20, 0x65, 0x64, 0x69, 0x74, - 0x2d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x20, 0x6d, 0x61, 0x6e, 0x69, - 0x70, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x77, 0x69, 0x74, - 0x68, 0x20, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, - 0x6e, 0x64, 0x20, 0x6c, 0x65, 0x61, 0x66, 0x2d, 0x6c, 0x69, 0x73, 0x74, - 0x73, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x66, - 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x22, 0x52, 0x46, 0x43, 0x20, 0x37, 0x39, 0x35, 0x30, 0x3a, 0x20, - 0x54, 0x68, 0x65, 0x20, 0x59, 0x41, 0x4e, 0x47, 0x20, 0x31, 0x2e, 0x31, - 0x20, 0x44, 0x61, 0x74, 0x61, 0x20, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x69, - 0x6e, 0x67, 0x20, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x22, - 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x72, 0x65, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x32, 0x30, 0x31, 0x36, 0x2d, 0x30, - 0x32, 0x2d, 0x31, 0x31, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, - 0x6c, 0x20, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3b, + 0x3b, 0x0a, 0x20, 0x20, 0x79, 0x61, 0x6e, 0x67, 0x2d, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x31, 0x2e, 0x31, 0x3b, 0x0a, 0x0a, 0x20, + 0x20, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x69, 0x65, 0x74, 0x66, + 0x2d, 0x79, 0x61, 0x6e, 0x67, 0x2d, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x65, + 0x66, 0x69, 0x78, 0x20, 0x6d, 0x64, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2d, 0x64, 0x61, 0x74, + 0x65, 0x20, 0x32, 0x30, 0x31, 0x36, 0x2d, 0x30, 0x38, 0x2d, 0x30, 0x35, + 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x6f, 0x72, 0x67, + 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x6c, 0x69, 0x62, 0x79, 0x61, 0x6e, 0x67, 0x22, 0x3b, + 0x0a, 0x0a, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x57, 0x65, 0x62, 0x3a, 0x20, 0x20, 0x20, + 0x20, 0x3c, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x43, 0x45, 0x53, + 0x4e, 0x45, 0x54, 0x2f, 0x6c, 0x69, 0x62, 0x79, 0x61, 0x6e, 0x67, 0x2f, + 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x3a, 0x20, 0x52, 0x61, 0x64, 0x65, 0x6b, 0x20, 0x4b, 0x72, 0x65, + 0x6a, 0x63, 0x69, 0x20, 0x3c, 0x72, 0x6b, 0x72, 0x65, 0x6a, 0x63, 0x69, + 0x40, 0x63, 0x65, 0x73, 0x6e, 0x65, 0x74, 0x2e, 0x63, 0x7a, 0x3e, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x3a, + 0x20, 0x4d, 0x69, 0x63, 0x68, 0x61, 0x6c, 0x20, 0x56, 0x61, 0x73, 0x6b, + 0x6f, 0x20, 0x3c, 0x6d, 0x76, 0x61, 0x73, 0x6b, 0x6f, 0x40, 0x63, 0x65, + 0x73, 0x6e, 0x65, 0x74, 0x2e, 0x63, 0x7a, 0x3e, 0x22, 0x3b, 0x0a, 0x0a, + 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, 0x68, 0x69, 0x73, 0x20, + 0x69, 0x73, 0x20, 0x61, 0x20, 0x64, 0x75, 0x6d, 0x6d, 0x79, 0x20, 0x6d, + 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6e, + 0x6f, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, + 0x6c, 0x6c, 0x79, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x79, 0x69, 0x6e, + 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, + 0x66, 0x20, 0x76, 0x61, 0x72, 0x69, 0x6f, 0x75, 0x73, 0x20, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, + 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x52, 0x46, 0x43, 0x20, 0x36, 0x30, + 0x32, 0x30, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x52, 0x46, 0x43, 0x20, 0x37, + 0x39, 0x35, 0x30, 0x2e, 0x20, 0x54, 0x68, 0x65, 0x72, 0x65, 0x20, 0x61, + 0x72, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x64, 0x64, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, + 0x6c, 0x69, 0x62, 0x79, 0x61, 0x6e, 0x67, 0x20, 0x64, 0x69, 0x66, 0x66, + 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, + 0x2e, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x72, 0x65, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x20, 0x32, 0x30, 0x32, 0x32, 0x2d, 0x30, 0x36, 0x2d, + 0x31, 0x36, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x41, 0x64, 0x64, 0x65, 0x64, 0x20, 0x74, 0x79, + 0x70, 0x65, 0x64, 0x65, 0x66, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x6b, 0x65, + 0x79, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x74, + 0x79, 0x70, 0x65, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x0a, + 0x20, 0x20, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x32, + 0x30, 0x32, 0x31, 0x2d, 0x30, 0x34, 0x2d, 0x30, 0x37, 0x20, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x41, + 0x64, 0x64, 0x65, 0x64, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x6b, 0x65, 0x79, 0x2d, 0x6c, 0x65, + 0x73, 0x73, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x61, 0x6e, 0x64, 0x20, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x6c, 0x65, 0x61, 0x66, 0x2d, 0x6c, + 0x69, 0x73, 0x74, 0x20, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x22, 0x3b, 0x0a, + 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x72, 0x65, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x20, 0x32, 0x30, 0x32, 0x30, 0x2d, 0x30, 0x36, 0x2d, + 0x31, 0x37, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x41, 0x64, 0x64, 0x65, 0x64, 0x20, 0x6d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x64, + 0x69, 0x66, 0x66, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x0a, + 0x20, 0x20, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x32, + 0x30, 0x31, 0x37, 0x2d, 0x30, 0x32, 0x2d, 0x32, 0x30, 0x20, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x41, + 0x64, 0x64, 0x65, 0x64, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x4e, 0x45, 0x54, 0x43, 0x4f, 0x4e, + 0x46, 0x27, 0x73, 0x20, 0x65, 0x64, 0x69, 0x74, 0x2d, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x20, 0x6d, 0x61, 0x6e, 0x69, 0x70, 0x75, 0x6c, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x65, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x6c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6c, + 0x65, 0x61, 0x66, 0x2d, 0x6c, 0x69, 0x73, 0x74, 0x73, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x52, 0x46, - 0x43, 0x20, 0x36, 0x30, 0x32, 0x30, 0x3a, 0x20, 0x59, 0x41, 0x4e, 0x47, - 0x20, 0x2d, 0x20, 0x41, 0x20, 0x44, 0x61, 0x74, 0x61, 0x20, 0x4d, 0x6f, - 0x64, 0x65, 0x6c, 0x69, 0x6e, 0x67, 0x20, 0x4c, 0x61, 0x6e, 0x67, 0x75, - 0x61, 0x67, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x74, 0x68, 0x65, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, - 0x72, 0x6b, 0x20, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, - 0x6c, 0x20, 0x28, 0x4e, 0x45, 0x54, 0x43, 0x4f, 0x4e, 0x46, 0x29, 0x22, - 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x74, 0x79, 0x70, - 0x65, 0x64, 0x65, 0x66, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, - 0x65, 0x2d, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, - 0x2d, 0x6b, 0x65, 0x79, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x74, 0x79, 0x70, 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, - 0x63, 0x65, 0x20, 0x22, 0x52, 0x46, 0x43, 0x37, 0x39, 0x35, 0x30, 0x20, - 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x37, 0x2e, 0x38, 0x2e, - 0x36, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x54, 0x68, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, - 0x70, 0x72, 0x65, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x6f, - 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x20, 0x69, - 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x2d, 0x69, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x62, 0x75, 0x69, 0x6c, 0x74, - 0x2d, 0x69, 0x6e, 0x20, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x22, 0x3b, 0x0a, - 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x64, 0x3a, 0x61, 0x6e, - 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x6e, 0x73, - 0x65, 0x72, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, - 0x70, 0x65, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, - 0x6e, 0x75, 0x6d, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x6c, 0x61, - 0x73, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, - 0x75, 0x6d, 0x20, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x3b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x61, 0x66, - 0x74, 0x65, 0x72, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, - 0x20, 0x22, 0x52, 0x46, 0x43, 0x37, 0x39, 0x35, 0x30, 0x20, 0x73, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x37, 0x2e, 0x38, 0x2e, 0x36, 0x2e, - 0x20, 0x61, 0x6e, 0x64, 0x20, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x20, 0x37, 0x2e, 0x37, 0x2e, 0x39, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x49, 0x6e, 0x20, - 0x75, 0x73, 0x65, 0x72, 0x20, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, - 0x20, 0x6c, 0x65, 0x61, 0x66, 0x2d, 0x6c, 0x69, 0x73, 0x74, 0x2c, 0x20, - 0x74, 0x68, 0x69, 0x73, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x65, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, - 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x68, 0x65, - 0x72, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x65, - 0x61, 0x66, 0x2d, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x65, 0x6e, 0x74, 0x72, 0x79, 0x20, 0x69, 0x73, 0x20, 0x69, 0x6e, 0x73, - 0x65, 0x72, 0x74, 0x65, 0x64, 0x2e, 0x20, 0x49, 0x74, 0x20, 0x63, 0x61, - 0x6e, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x64, 0x75, - 0x72, 0x69, 0x6e, 0x67, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x74, 0x68, 0x65, 0x20, 0x4e, 0x45, 0x54, 0x43, 0x4f, 0x4e, 0x46, 0x20, - 0x3c, 0x65, 0x64, 0x69, 0x74, 0x2d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x3e, 0x20, 0x5c, 0x22, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5c, 0x22, - 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, - 0x74, 0x6f, 0x20, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x20, 0x61, 0x20, - 0x6e, 0x65, 0x77, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x6f, 0x72, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x61, 0x66, 0x2d, - 0x6c, 0x69, 0x73, 0x74, 0x20, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2c, 0x20, - 0x6f, 0x72, 0x20, 0x64, 0x75, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x5c, 0x22, - 0x6d, 0x65, 0x72, 0x67, 0x65, 0x5c, 0x22, 0x20, 0x6f, 0x72, 0x20, 0x5c, - 0x22, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x5c, 0x22, 0x20, 0x6f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x74, 0x6f, - 0x20, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x61, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x6c, 0x69, 0x73, - 0x74, 0x20, 0x6f, 0x72, 0x20, 0x6c, 0x65, 0x61, 0x66, 0x2d, 0x6c, 0x69, - 0x73, 0x74, 0x20, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x20, 0x6f, 0x72, 0x20, - 0x6d, 0x6f, 0x76, 0x65, 0x20, 0x61, 0x6e, 0x20, 0x65, 0x78, 0x69, 0x73, - 0x74, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x6e, 0x65, 0x2e, 0x0a, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x49, 0x66, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x69, 0x73, 0x20, 0x5c, 0x22, - 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x5c, 0x22, 0x20, 0x6f, 0x72, 0x20, - 0x5c, 0x22, 0x61, 0x66, 0x74, 0x65, 0x72, 0x5c, 0x22, 0x2c, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x5c, 0x22, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5c, 0x22, - 0x2f, 0x5c, 0x22, 0x6b, 0x65, 0x79, 0x5c, 0x22, 0x20, 0x61, 0x74, 0x74, - 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x4d, 0x55, 0x53, 0x54, 0x20, 0x61, 0x6c, 0x73, 0x6f, 0x20, - 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x73, - 0x70, 0x65, 0x63, 0x69, 0x66, 0x79, 0x20, 0x61, 0x6e, 0x20, 0x65, 0x78, - 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6e, 0x74, 0x72, 0x79, - 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x69, 0x73, 0x74, - 0x20, 0x6f, 0x72, 0x20, 0x6c, 0x65, 0x61, 0x66, 0x2d, 0x6c, 0x69, 0x73, - 0x74, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x49, - 0x66, 0x20, 0x6e, 0x6f, 0x20, 0x5c, 0x22, 0x69, 0x6e, 0x73, 0x65, 0x72, - 0x74, 0x5c, 0x22, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, - 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, - 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x5c, 0x22, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x5c, 0x22, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x69, 0x74, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, - 0x20, 0x74, 0x6f, 0x20, 0x5c, 0x22, 0x6c, 0x61, 0x73, 0x74, 0x5c, 0x22, - 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x6d, - 0x64, 0x3a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, - 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, - 0x6e, 0x63, 0x65, 0x20, 0x22, 0x52, 0x46, 0x43, 0x37, 0x39, 0x35, 0x30, + 0x43, 0x20, 0x37, 0x39, 0x35, 0x30, 0x3a, 0x20, 0x54, 0x68, 0x65, 0x20, + 0x59, 0x41, 0x4e, 0x47, 0x20, 0x31, 0x2e, 0x31, 0x20, 0x44, 0x61, 0x74, + 0x61, 0x20, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x69, 0x6e, 0x67, 0x20, 0x4c, + 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x22, 0x3b, 0x0a, 0x20, 0x20, + 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x20, 0x32, 0x30, 0x31, 0x36, 0x2d, 0x30, 0x32, 0x2d, 0x31, 0x31, + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x72, 0x65, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x52, 0x46, 0x43, 0x20, 0x36, 0x30, + 0x32, 0x30, 0x3a, 0x20, 0x59, 0x41, 0x4e, 0x47, 0x20, 0x2d, 0x20, 0x41, + 0x20, 0x44, 0x61, 0x74, 0x61, 0x20, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x69, + 0x6e, 0x67, 0x20, 0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x20, + 0x66, 0x6f, 0x72, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x20, 0x28, 0x4e, + 0x45, 0x54, 0x43, 0x4f, 0x4e, 0x46, 0x29, 0x22, 0x3b, 0x0a, 0x20, 0x20, + 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x64, 0x65, 0x66, + 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x2d, 0x69, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2d, 0x6b, 0x65, 0x79, + 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, + 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x20, 0x22, + 0x52, 0x46, 0x43, 0x37, 0x39, 0x35, 0x30, 0x20, 0x73, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x37, 0x2e, 0x38, 0x2e, 0x36, 0x2e, 0x22, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x54, 0x68, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x70, 0x72, 0x65, 0x64, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x2d, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, + 0x65, 0x72, 0x20, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x2d, 0x69, 0x6e, 0x20, + 0x74, 0x79, 0x70, 0x65, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x0a, + 0x0a, 0x20, 0x20, 0x6d, 0x64, 0x3a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x65, + 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, + 0x66, 0x69, 0x72, 0x73, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x6c, 0x61, 0x73, 0x74, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x62, + 0x65, 0x66, 0x6f, 0x72, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x61, 0x66, 0x74, 0x65, 0x72, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, + 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x20, 0x22, 0x52, 0x46, + 0x43, 0x37, 0x39, 0x35, 0x30, 0x20, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x37, 0x2e, 0x38, 0x2e, 0x36, 0x2e, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x37, 0x2e, 0x37, 0x2e, 0x39, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x49, 0x6e, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x20, 0x6c, 0x65, 0x61, 0x66, 0x2d, 0x6c, 0x69, 0x73, 0x74, 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73, - 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x6d, - 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, - 0x69, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, - 0x62, 0x75, 0x74, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x20, 0x69, 0x73, 0x20, 0x75, 0x73, - 0x65, 0x64, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, - 0x66, 0x69, 0x65, 0x73, 0x20, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x2f, - 0x61, 0x66, 0x74, 0x65, 0x72, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, - 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x6e, 0x73, - 0x74, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x69, 0x6e, 0x73, - 0x74, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, - 0x20, 0x62, 0x65, 0x20, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, - 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x6d, - 0x64, 0x3a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x20, 0x6b, 0x65, 0x79, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, - 0x79, 0x70, 0x65, 0x20, 0x75, 0x6e, 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x65, - 0x6d, 0x70, 0x74, 0x79, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x74, 0x79, 0x70, 0x65, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, - 0x65, 0x2d, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, - 0x2d, 0x6b, 0x65, 0x79, 0x73, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, - 0x63, 0x65, 0x20, 0x22, 0x52, 0x46, 0x43, 0x37, 0x39, 0x35, 0x30, 0x20, - 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x37, 0x2e, 0x38, 0x2e, - 0x36, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x49, 0x6e, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x20, 0x6c, 0x69, 0x73, 0x74, - 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, - 0x62, 0x75, 0x74, 0x65, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, - 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x69, 0x66, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, - 0x20, 0x69, 0x73, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x61, 0x6e, 0x64, - 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x73, 0x20, 0x62, - 0x65, 0x66, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x20, - 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, - 0x6e, 0x67, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x20, - 0x74, 0x68, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6e, - 0x65, 0x77, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x20, - 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69, 0x6e, - 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, - 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x64, 0x3a, 0x61, 0x6e, 0x6e, 0x6f, - 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x70, 0x6f, 0x73, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, - 0x70, 0x65, 0x20, 0x75, 0x6e, 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, - 0x65, 0x6d, 0x70, 0x74, 0x79, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x75, 0x69, 0x6e, 0x74, - 0x33, 0x32, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x31, - 0x2e, 0x2e, 0x6d, 0x61, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x49, 0x6e, - 0x20, 0x6b, 0x65, 0x79, 0x2d, 0x6c, 0x65, 0x73, 0x73, 0x20, 0x6c, 0x69, - 0x73, 0x74, 0x20, 0x6f, 0x72, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, - 0x6c, 0x65, 0x61, 0x66, 0x2d, 0x6c, 0x69, 0x73, 0x74, 0x2c, 0x20, 0x74, - 0x68, 0x69, 0x73, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, - 0x65, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, - 0x65, 0x64, 0x20, 0x69, 0x66, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x65, 0x20, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x20, 0x69, 0x73, - 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x73, 0x70, - 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x70, 0x6f, 0x73, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x63, + 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x74, + 0x6f, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x69, + 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x65, 0x61, 0x66, 0x2d, 0x6c, + 0x69, 0x73, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x74, 0x72, + 0x79, 0x20, 0x69, 0x73, 0x20, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, + 0x64, 0x2e, 0x20, 0x49, 0x74, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65, + 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x64, 0x75, 0x72, 0x69, 0x6e, 0x67, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x4e, 0x45, 0x54, 0x43, 0x4f, 0x4e, 0x46, 0x20, 0x3c, 0x65, 0x64, 0x69, + 0x74, 0x2d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x3e, 0x20, 0x5c, 0x22, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5c, 0x22, 0x20, 0x6f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x69, + 0x6e, 0x73, 0x65, 0x72, 0x74, 0x20, 0x61, 0x20, 0x6e, 0x65, 0x77, 0x20, + 0x6c, 0x69, 0x73, 0x74, 0x20, 0x6f, 0x72, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x6c, 0x65, 0x61, 0x66, 0x2d, 0x6c, 0x69, 0x73, 0x74, + 0x20, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x64, + 0x75, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x5c, 0x22, 0x6d, 0x65, 0x72, 0x67, + 0x65, 0x5c, 0x22, 0x20, 0x6f, 0x72, 0x20, 0x5c, 0x22, 0x72, 0x65, 0x70, + 0x6c, 0x61, 0x63, 0x65, 0x5c, 0x22, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x69, 0x6e, 0x73, + 0x65, 0x72, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, + 0x20, 0x6e, 0x65, 0x77, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x6f, 0x72, + 0x20, 0x6c, 0x65, 0x61, 0x66, 0x2d, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x65, + 0x6e, 0x74, 0x72, 0x79, 0x20, 0x6f, 0x72, 0x20, 0x6d, 0x6f, 0x76, 0x65, + 0x20, 0x61, 0x6e, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, + 0x20, 0x6f, 0x6e, 0x65, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x49, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x20, 0x69, 0x73, 0x20, 0x5c, 0x22, 0x62, 0x65, 0x66, 0x6f, + 0x72, 0x65, 0x5c, 0x22, 0x20, 0x6f, 0x72, 0x20, 0x5c, 0x22, 0x61, 0x66, + 0x74, 0x65, 0x72, 0x5c, 0x22, 0x2c, 0x20, 0x74, 0x68, 0x65, 0x20, 0x5c, + 0x22, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5c, 0x22, 0x2f, 0x5c, 0x22, 0x6b, + 0x65, 0x79, 0x5c, 0x22, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x4d, 0x55, + 0x53, 0x54, 0x20, 0x61, 0x6c, 0x73, 0x6f, 0x20, 0x62, 0x65, 0x20, 0x75, + 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, + 0x66, 0x79, 0x20, 0x61, 0x6e, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, + 0x6e, 0x67, 0x20, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x20, 0x69, 0x6e, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x6f, 0x72, 0x20, + 0x6c, 0x65, 0x61, 0x66, 0x2d, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x0a, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x49, 0x66, 0x20, 0x6e, 0x6f, + 0x20, 0x5c, 0x22, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x5c, 0x22, 0x20, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x69, 0x73, + 0x20, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x6e, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x5c, 0x22, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x5c, 0x22, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2c, 0x20, 0x69, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x20, 0x74, 0x6f, 0x20, + 0x5c, 0x22, 0x6c, 0x61, 0x73, 0x74, 0x5c, 0x22, 0x2e, 0x22, 0x3b, 0x0a, + 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x64, 0x3a, 0x61, 0x6e, + 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, + 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x20, + 0x22, 0x52, 0x46, 0x43, 0x37, 0x39, 0x35, 0x30, 0x20, 0x73, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x37, 0x2e, 0x37, 0x2e, 0x39, 0x2e, 0x22, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x49, 0x6e, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x65, 0x64, 0x20, 0x6c, 0x65, 0x61, 0x66, 0x2d, 0x6c, 0x69, + 0x73, 0x74, 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x61, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, + 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x69, 0x66, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x73, 0x65, + 0x72, 0x74, 0x20, 0x69, 0x73, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x73, 0x20, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x66, 0x74, 0x65, - 0x72, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x6e, 0x65, 0x77, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, - 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69, - 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x2e, 0x22, 0x3b, 0x0a, 0x20, - 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x64, 0x3a, 0x61, 0x6e, 0x6e, - 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x70, 0x65, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x74, 0x79, 0x70, 0x65, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x20, 0x7b, + 0x72, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x65, 0x78, 0x69, 0x73, + 0x74, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, + 0x65, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x6e, 0x65, 0x77, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, + 0x65, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, + 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x65, 0x64, 0x2e, 0x22, 0x3b, 0x0a, + 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x64, 0x3a, 0x61, 0x6e, + 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6b, 0x65, 0x79, + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, + 0x75, 0x6e, 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x65, 0x6d, 0x70, 0x74, 0x79, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, + 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x2d, 0x69, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2d, 0x6b, 0x65, 0x79, + 0x73, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x20, 0x22, + 0x52, 0x46, 0x43, 0x37, 0x39, 0x35, 0x30, 0x20, 0x73, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x37, 0x2e, 0x38, 0x2e, 0x36, 0x2e, 0x22, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x49, 0x6e, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x6f, 0x72, 0x64, 0x65, + 0x72, 0x65, 0x64, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x2c, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, + 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, + 0x64, 0x20, 0x69, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x20, 0x69, 0x73, 0x20, + 0x75, 0x73, 0x65, 0x64, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x73, 0x70, 0x65, + 0x63, 0x69, 0x66, 0x69, 0x65, 0x73, 0x20, 0x62, 0x65, 0x66, 0x6f, 0x72, + 0x65, 0x2f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x20, 0x77, 0x68, 0x69, 0x63, + 0x68, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x69, + 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x74, 0x68, 0x65, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x69, + 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x73, 0x68, 0x6f, 0x75, + 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, + 0x65, 0x64, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, + 0x20, 0x6d, 0x64, 0x3a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x75, + 0x6e, 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x65, 0x6d, 0x70, 0x74, + 0x79, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, + 0x79, 0x70, 0x65, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x31, 0x2e, 0x2e, 0x6d, 0x61, + 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x49, 0x6e, 0x20, 0x6b, 0x65, 0x79, + 0x2d, 0x6c, 0x65, 0x73, 0x73, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x6f, + 0x72, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x6c, 0x65, 0x61, 0x66, + 0x2d, 0x6c, 0x69, 0x73, 0x74, 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x6d, 0x75, + 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x69, + 0x66, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x69, + 0x6e, 0x73, 0x65, 0x72, 0x74, 0x20, 0x69, 0x73, 0x20, 0x75, 0x73, 0x65, + 0x64, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, + 0x69, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x69, 0x6e, 0x73, 0x74, + 0x61, 0x6e, 0x63, 0x65, 0x20, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x65, 0x66, + 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x20, 0x77, 0x68, + 0x69, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x65, 0x77, 0x20, + 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x73, 0x68, 0x6f, + 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69, 0x6e, 0x73, 0x65, 0x72, + 0x74, 0x65, 0x64, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x0a, + 0x20, 0x20, 0x6d, 0x64, 0x3a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, + 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x75, + 0x6d, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x22, 0x54, 0x68, 0x65, 0x20, 0x6e, 0x6f, 0x64, 0x65, + 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, + 0x62, 0x6f, 0x74, 0x68, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x74, 0x72, + 0x65, 0x65, 0x73, 0x20, 0x62, 0x75, 0x74, 0x20, 0x74, 0x68, 0x65, 0x72, + 0x65, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x6e, 0x65, 0x73, 0x74, 0x65, + 0x64, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x61, + 0x6e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x20, 0x49, 0x6e, 0x20, 0x63, 0x61, 0x73, + 0x65, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x20, 0x6c, 0x65, 0x61, 0x66, 0x2c, + 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x69, 0x74, 0x73, 0x20, 0x64, 0x65, + 0x66, 0x61, 0x75, 0x6c, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x61, 0x67, 0x20, 0x63, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x64, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, + 0x6e, 0x75, 0x6d, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, 0x68, 0x65, 0x20, - 0x6e, 0x6f, 0x64, 0x65, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x65, 0x64, - 0x20, 0x69, 0x6e, 0x20, 0x62, 0x6f, 0x74, 0x68, 0x20, 0x64, 0x61, 0x74, - 0x61, 0x20, 0x74, 0x72, 0x65, 0x65, 0x73, 0x20, 0x62, 0x75, 0x74, 0x20, - 0x74, 0x68, 0x65, 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x6e, - 0x65, 0x73, 0x74, 0x65, 0x64, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, - 0x74, 0x68, 0x20, 0x61, 0x6e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20, 0x6f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x20, 0x49, 0x6e, - 0x20, 0x63, 0x61, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x20, 0x6c, - 0x65, 0x61, 0x66, 0x2c, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x69, 0x74, - 0x73, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x61, - 0x67, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x2e, 0x22, 0x3b, + 0x6e, 0x6f, 0x64, 0x65, 0x20, 0x64, 0x69, 0x64, 0x20, 0x6e, 0x6f, 0x74, + 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, 0x74, 0x72, 0x65, 0x65, + 0x20, 0x61, 0x6e, 0x64, 0x20, 0x77, 0x61, 0x73, 0x20, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, + 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x74, 0x72, 0x65, 0x65, 0x2e, 0x22, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x64, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x54, 0x68, 0x65, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x20, 0x65, 0x78, + 0x69, 0x73, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, 0x74, 0x72, 0x65, 0x65, 0x20, + 0x61, 0x6e, 0x64, 0x20, 0x77, 0x61, 0x73, 0x20, 0x64, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, + 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x65, 0x65, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x54, 0x68, 0x65, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x20, 0x64, 0x69, 0x64, - 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x20, 0x69, - 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, - 0x74, 0x72, 0x65, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x77, 0x61, 0x73, - 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, - 0x74, 0x68, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x74, 0x72, - 0x65, 0x65, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x75, 0x6d, - 0x20, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, 0x68, 0x65, 0x20, 0x6e, 0x6f, 0x64, - 0x65, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6e, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, 0x74, - 0x72, 0x65, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x77, 0x61, 0x73, 0x20, - 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x65, - 0x65, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, - 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x22, 0x54, 0x68, 0x65, 0x20, 0x6e, 0x6f, 0x64, - 0x65, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20, - 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x20, 0x6f, 0x72, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20, - 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x61, - 0x76, 0x65, 0x73, 0x2f, 0x61, 0x6e, 0x79, 0x78, 0x6d, 0x6c, 0x2f, 0x61, - 0x6e, 0x79, 0x64, 0x61, 0x74, 0x61, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x75, - 0x73, 0x65, 0x72, 0x2d, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x20, - 0x6c, 0x69, 0x73, 0x74, 0x73, 0x2f, 0x6c, 0x65, 0x61, 0x66, 0x2d, 0x6c, - 0x69, 0x73, 0x74, 0x73, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x70, 0x65, 0x63, 0x74, - 0x69, 0x76, 0x65, 0x6c, 0x79, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, - 0x20, 0x22, 0x52, 0x46, 0x43, 0x36, 0x32, 0x34, 0x31, 0x20, 0x73, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x37, 0x2e, 0x32, 0x2e, 0x22, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, - 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, - 0x20, 0x61, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x61, - 0x20, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x20, 0x49, 0x66, 0x20, 0x61, 0x20, - 0x6e, 0x6f, 0x64, 0x65, 0x20, 0x68, 0x61, 0x73, 0x20, 0x6e, 0x6f, 0x20, - 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, - 0x69, 0x6e, 0x68, 0x65, 0x72, 0x69, 0x74, 0x65, 0x64, 0x20, 0x66, 0x72, - 0x6f, 0x6d, 0x20, 0x69, 0x74, 0x73, 0x20, 0x6e, 0x65, 0x61, 0x72, 0x65, - 0x73, 0x74, 0x20, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x77, 0x69, - 0x74, 0x68, 0x20, 0x61, 0x6e, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x54, 0x6f, 0x70, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x20, 0x6e, 0x6f, - 0x64, 0x65, 0x73, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x61, 0x6c, 0x77, - 0x61, 0x79, 0x73, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x61, 0x6e, 0x20, - 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x55, 0x73, 0x65, 0x72, 0x2d, + 0x20, 0x20, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x72, 0x65, 0x70, 0x6c, + 0x61, 0x63, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x22, 0x54, 0x68, 0x65, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x20, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x64, 0x20, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, + 0x6f, 0x64, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20, 0x6d, 0x6f, 0x76, 0x65, + 0x64, 0x20, 0x66, 0x6f, 0x72, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x2f, + 0x61, 0x6e, 0x79, 0x78, 0x6d, 0x6c, 0x2f, 0x61, 0x6e, 0x79, 0x64, 0x61, + 0x74, 0x61, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x75, 0x73, 0x65, 0x72, 0x2d, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x73, 0x2f, 0x6c, 0x65, 0x61, 0x66, 0x2d, 0x6c, 0x69, 0x73, 0x74, 0x73, - 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x20, 0x27, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x27, - 0x20, 0x61, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x27, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x27, 0x20, 0x4d, 0x55, - 0x53, 0x54, 0x20, 0x61, 0x6c, 0x73, 0x6f, 0x20, 0x68, 0x61, 0x76, 0x65, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x27, 0x6b, 0x65, 0x79, 0x27, 0x2c, 0x20, - 0x27, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x27, 0x2c, 0x20, 0x6f, 0x72, 0x20, - 0x27, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x27, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x2e, 0x20, - 0x49, 0x74, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x73, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x65, 0x63, 0x65, 0x64, 0x69, - 0x6e, 0x67, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x2e, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x49, 0x6e, 0x20, 0x63, - 0x61, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x69, 0x73, 0x20, 0x65, 0x6d, - 0x70, 0x74, 0x79, 0x2c, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x6f, 0x64, - 0x65, 0x20, 0x77, 0x61, 0x73, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x6d, 0x6f, 0x76, - 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, - 0x72, 0x73, 0x74, 0x20, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41, 0x6c, - 0x6c, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6b, 0x65, 0x65, 0x70, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x6d, 0x65, 0x61, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x66, - 0x20, 0x65, 0x64, 0x69, 0x74, 0x2d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, - 0x73, 0x69, 0x6d, 0x69, 0x6c, 0x61, 0x72, 0x20, 0x6e, 0x61, 0x6d, 0x65, - 0x73, 0x20, 0x62, 0x75, 0x74, 0x20, 0x73, 0x6f, 0x6d, 0x65, 0x20, 0x61, - 0x72, 0x65, 0x20, 0x66, 0x75, 0x72, 0x74, 0x68, 0x65, 0x72, 0x20, 0x72, - 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x65, 0x64, 0x2c, 0x20, 0x6d, - 0x65, 0x61, 0x6e, 0x69, 0x6e, 0x67, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x74, 0x68, 0x65, 0x79, 0x20, 0x61, 0x72, 0x65, 0x20, 0x75, - 0x73, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x6f, 0x6e, 0x6c, 0x79, - 0x20, 0x61, 0x20, 0x73, 0x75, 0x62, 0x73, 0x65, 0x74, 0x20, 0x6f, 0x66, - 0x20, 0x75, 0x73, 0x65, 0x2d, 0x63, 0x61, 0x73, 0x65, 0x73, 0x2e, 0x22, - 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x64, 0x3a, - 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, - 0x72, 0x69, 0x67, 0x2d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x20, - 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x62, - 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, - 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x49, 0x6e, 0x66, 0x6f, 0x72, - 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, - 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, - 0x73, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x20, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, - 0x6f, 0x64, 0x65, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x0a, + 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x72, 0x65, 0x73, 0x70, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x6c, + 0x79, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, + 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x20, 0x22, 0x52, 0x46, + 0x43, 0x36, 0x32, 0x34, 0x31, 0x20, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x37, 0x2e, 0x32, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x4f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x20, 0x6e, + 0x6f, 0x64, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x64, 0x69, 0x66, + 0x66, 0x2e, 0x20, 0x49, 0x66, 0x20, 0x61, 0x20, 0x6e, 0x6f, 0x64, 0x65, + 0x20, 0x68, 0x61, 0x73, 0x20, 0x6e, 0x6f, 0x20, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x69, 0x6e, 0x68, 0x65, + 0x72, 0x69, 0x74, 0x65, 0x64, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x69, + 0x74, 0x73, 0x20, 0x6e, 0x65, 0x61, 0x72, 0x65, 0x73, 0x74, 0x20, 0x70, + 0x61, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x61, + 0x6e, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x6f, 0x70, 0x2d, + 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x20, + 0x6d, 0x75, 0x73, 0x74, 0x20, 0x61, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x20, + 0x68, 0x61, 0x76, 0x65, 0x20, 0x61, 0x6e, 0x20, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x55, 0x73, 0x65, 0x72, 0x2d, 0x6f, 0x72, 0x64, 0x65, + 0x72, 0x65, 0x64, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x73, 0x2f, 0x6c, 0x65, + 0x61, 0x66, 0x2d, 0x6c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x77, 0x69, 0x74, + 0x68, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x27, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x27, 0x20, 0x61, 0x6e, 0x64, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x27, 0x72, 0x65, 0x70, + 0x6c, 0x61, 0x63, 0x65, 0x27, 0x20, 0x4d, 0x55, 0x53, 0x54, 0x20, 0x61, + 0x6c, 0x73, 0x6f, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x27, 0x6b, 0x65, 0x79, 0x27, 0x2c, 0x20, 0x27, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x27, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x27, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x27, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x64, + 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x2e, 0x20, 0x49, 0x74, 0x20, 0x73, + 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x70, 0x72, 0x65, 0x63, 0x65, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x69, + 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x49, 0x6e, 0x20, 0x63, 0x61, 0x73, 0x65, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x6f, 0x66, + 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x20, 0x69, 0x73, 0x20, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2c, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x20, 0x77, 0x61, + 0x73, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x2f, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x6f, + 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, + 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41, 0x6c, 0x6c, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x20, 0x6b, 0x65, 0x65, 0x70, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x65, + 0x61, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x66, 0x20, 0x65, 0x64, 0x69, + 0x74, 0x2d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x20, 0x6f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x73, 0x69, 0x6d, 0x69, + 0x6c, 0x61, 0x72, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x20, 0x62, 0x75, + 0x74, 0x20, 0x73, 0x6f, 0x6d, 0x65, 0x20, 0x61, 0x72, 0x65, 0x20, 0x66, + 0x75, 0x72, 0x74, 0x68, 0x65, 0x72, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, + 0x69, 0x63, 0x74, 0x65, 0x64, 0x2c, 0x20, 0x6d, 0x65, 0x61, 0x6e, 0x69, + 0x6e, 0x67, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x68, + 0x65, 0x79, 0x20, 0x61, 0x72, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, + 0x66, 0x6f, 0x72, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x61, 0x20, 0x73, + 0x75, 0x62, 0x73, 0x65, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x65, + 0x2d, 0x63, 0x61, 0x73, 0x65, 0x73, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, + 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x64, 0x3a, 0x61, 0x6e, 0x6e, 0x6f, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x72, 0x69, 0x67, 0x2d, + 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x62, 0x6f, 0x6f, 0x6c, 0x65, + 0x61, 0x6e, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x20, 0x64, 0x65, + 0x66, 0x61, 0x75, 0x6c, 0x74, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, + 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x6f, 0x64, 0x65, 0x2e, + 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x64, + 0x3a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x6f, 0x72, 0x69, 0x67, 0x2d, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x73, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x50, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, + 0x73, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x61, + 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x20, 0x6c, 0x65, 0x61, + 0x66, 0x2e, 0x20, 0x41, 0x6c, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x69, + 0x76, 0x65, 0x6c, 0x79, 0x2c, 0x20, 0x69, 0x74, 0x73, 0x20, 0x6d, 0x65, + 0x61, 0x6e, 0x69, 0x6e, 0x67, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x6d, 0x65, + 0x20, 0x61, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x5c, 0x22, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x5c, 0x22, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x65, 0x20, 0x62, 0x75, 0x74, 0x20, 0x69, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, + 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x6c, 0x65, 0x61, 0x66, 0x2d, 0x6c, 0x69, 0x73, 0x74, + 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x72, 0x61, + 0x74, 0x68, 0x65, 0x72, 0x20, 0x74, 0x68, 0x61, 0x6e, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x6f, 0x6e, 0x65, 0x2e, 0x22, 0x3b, + 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x64, 0x3a, 0x61, + 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x72, + 0x69, 0x67, 0x2d, 0x6b, 0x65, 0x79, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x75, 0x6e, 0x69, 0x6f, 0x6e, 0x20, + 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, + 0x20, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x2d, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, + 0x65, 0x72, 0x2d, 0x6b, 0x65, 0x79, 0x73, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x49, 0x74, 0x73, 0x20, 0x6d, 0x65, 0x61, 0x6e, 0x69, 0x6e, + 0x67, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x6d, + 0x65, 0x20, 0x61, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x5c, 0x22, 0x6b, + 0x65, 0x79, 0x5c, 0x22, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x20, 0x62, 0x75, 0x74, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x66, 0x69, 0x65, 0x73, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, + 0x6c, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x20, 0x72, 0x61, 0x74, 0x68, 0x65, 0x72, 0x20, 0x74, + 0x68, 0x61, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x65, 0x77, 0x20, + 0x6f, 0x6e, 0x65, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x64, 0x3a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x72, 0x69, 0x67, 0x2d, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, - 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3b, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x50, 0x72, 0x65, - 0x76, 0x69, 0x6f, 0x75, 0x73, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, - 0x6f, 0x66, 0x20, 0x61, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, - 0x20, 0x6c, 0x65, 0x61, 0x66, 0x2e, 0x20, 0x41, 0x6c, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79, 0x2c, 0x20, 0x69, 0x74, - 0x73, 0x20, 0x6d, 0x65, 0x61, 0x6e, 0x69, 0x6e, 0x67, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x73, 0x61, 0x6d, 0x65, 0x20, 0x61, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x5c, 0x22, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5c, 0x22, 0x20, 0x61, 0x74, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x62, 0x75, 0x74, 0x20, - 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x73, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x61, 0x66, 0x2d, - 0x6c, 0x69, 0x73, 0x74, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, - 0x65, 0x20, 0x72, 0x61, 0x74, 0x68, 0x65, 0x72, 0x20, 0x74, 0x68, 0x61, - 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x6f, 0x6e, - 0x65, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, - 0x6d, 0x64, 0x3a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x20, 0x6f, 0x72, 0x69, 0x67, 0x2d, 0x6b, 0x65, 0x79, 0x20, 0x7b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x75, 0x6e, - 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x74, 0x79, 0x70, 0x65, 0x20, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x3b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x69, - 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x2d, 0x69, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2d, 0x6b, 0x65, 0x79, 0x73, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x49, 0x74, 0x73, 0x20, 0x6d, 0x65, - 0x61, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x73, 0x61, 0x6d, 0x65, 0x20, 0x61, 0x73, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x5c, 0x22, 0x6b, 0x65, 0x79, 0x5c, 0x22, 0x20, 0x61, 0x74, 0x74, - 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x62, 0x75, 0x74, 0x20, 0x69, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x73, 0x0a, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x72, 0x69, - 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x69, - 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x72, 0x61, 0x74, 0x68, - 0x65, 0x72, 0x20, 0x74, 0x68, 0x61, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x6e, 0x65, 0x77, 0x20, 0x6f, 0x6e, 0x65, 0x2e, 0x22, 0x3b, 0x0a, 0x20, - 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x6d, 0x64, 0x3a, 0x61, 0x6e, 0x6e, - 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x72, 0x69, 0x67, - 0x2d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, - 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x75, 0x6e, 0x69, - 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x3b, + 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x72, 0x69, 0x67, 0x2d, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x74, 0x79, 0x70, 0x65, 0x20, 0x75, 0x6e, 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, - 0x65, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x20, 0x7b, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, - 0x61, 0x6e, 0x67, 0x65, 0x20, 0x31, 0x2e, 0x2e, 0x6d, 0x61, 0x78, 0x3b, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, - 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x22, 0x49, 0x74, 0x73, 0x20, 0x6d, 0x65, 0x61, 0x6e, - 0x69, 0x6e, 0x67, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, - 0x61, 0x6d, 0x65, 0x20, 0x61, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x5c, - 0x22, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5c, 0x22, 0x20, - 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x62, 0x75, - 0x74, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x73, - 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x68, 0x65, 0x20, - 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x20, 0x6c, 0x69, 0x73, - 0x74, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x72, - 0x61, 0x74, 0x68, 0x65, 0x72, 0x20, 0x74, 0x68, 0x61, 0x6e, 0x20, 0x74, - 0x68, 0x65, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x6f, 0x6e, 0x65, 0x2e, 0x22, - 0x3b, 0x0a, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x0a, 0x00 + 0x65, 0x20, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x75, 0x69, + 0x6e, 0x74, 0x33, 0x32, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x61, 0x6e, 0x67, 0x65, + 0x20, 0x31, 0x2e, 0x2e, 0x6d, 0x61, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, + 0x49, 0x74, 0x73, 0x20, 0x6d, 0x65, 0x61, 0x6e, 0x69, 0x6e, 0x67, 0x20, + 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x6d, 0x65, 0x20, + 0x61, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x5c, 0x22, 0x70, 0x6f, 0x73, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5c, 0x22, 0x20, 0x61, 0x74, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x65, 0x20, 0x62, 0x75, 0x74, 0x20, 0x69, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x73, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x72, 0x69, 0x67, + 0x69, 0x6e, 0x61, 0x6c, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x69, 0x6e, + 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x72, 0x61, 0x74, 0x68, 0x65, + 0x72, 0x20, 0x74, 0x68, 0x61, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, + 0x65, 0x77, 0x20, 0x6f, 0x6e, 0x65, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, + 0x7d, 0x0a, 0x7d, 0x0a, 0x00 }; diff --git a/models/yang@2022-06-16.yang b/models/yang@2022-06-16.yang index 0d41360..e506127 100644 --- a/models/yang@2022-06-16.yang +++ b/models/yang@2022-06-16.yang @@ -1,6 +1,7 @@ module yang { namespace "urn:ietf:params:xml:ns:yang:1"; prefix yang; + yang-version 1.1; import ietf-yang-metadata { prefix md; diff --git a/src/common.c b/src/common.c index 38f51ea..03dd81c 100644 --- a/src/common.c +++ b/src/common.c @@ -176,19 +176,14 @@ ly_getutf8(const char **input, uint32_t *utf8_char, size_t *bytes_read) uint32_t c, aux; size_t len; - if (bytes_read) { - (*bytes_read) = 0; - } - c = (*input)[0]; - LY_CHECK_RET(!c, LY_EINVAL); if (!(c & 0x80)) { /* one byte character */ len = 1; if ((c < 0x20) && (c != 0x9) && (c != 0xa) && (c != 0xd)) { - return LY_EINVAL; + goto error; } } else if ((c & 0xe0) == 0xc0) { /* two bytes character */ @@ -196,12 +191,12 @@ ly_getutf8(const char **input, uint32_t *utf8_char, size_t *bytes_read) aux = (*input)[1]; if ((aux & 0xc0) != 0x80) { - return LY_EINVAL; + goto error; } c = ((c & 0x1f) << 6) | (aux & 0x3f); if (c < 0x80) { - return LY_EINVAL; + goto error; } } else if ((c & 0xf0) == 0xe0) { /* three bytes character */ @@ -211,14 +206,14 @@ ly_getutf8(const char **input, uint32_t *utf8_char, size_t *bytes_read) for (uint64_t i = 1; i <= 2; i++) { aux = (*input)[i]; if ((aux & 0xc0) != 0x80) { - return LY_EINVAL; + goto error; } c = (c << 6) | (aux & 0x3f); } if ((c < 0x800) || ((c > 0xd7ff) && (c < 0xe000)) || (c > 0xfffd)) { - return LY_EINVAL; + goto error; } } else if ((c & 0xf8) == 0xf0) { /* four bytes character */ @@ -228,17 +223,17 @@ ly_getutf8(const char **input, uint32_t *utf8_char, size_t *bytes_read) for (uint64_t i = 1; i <= 3; i++) { aux = (*input)[i]; if ((aux & 0xc0) != 0x80) { - return LY_EINVAL; + goto error; } c = (c << 6) | (aux & 0x3f); } if ((c < 0x1000) || (c > 0x10ffff)) { - return LY_EINVAL; + goto error; } } else { - return LY_EINVAL; + goto error; } (*utf8_char) = c; @@ -247,6 +242,163 @@ ly_getutf8(const char **input, uint32_t *utf8_char, size_t *bytes_read) (*bytes_read) = len; } return LY_SUCCESS; + +error: + if (bytes_read) { + (*bytes_read) = 0; + } + return LY_EINVAL; +} + +/** + * @brief Check whether an UTF-8 string is equal to a hex string after a bitwise and. + * + * (input & 0x[arg1][arg3][arg5]...) == 0x[arg2][arg4][arg6]... + * + * @param[in] input UTF-8 string. + * @param[in] bytes Number of bytes to compare. + * @param[in] ... 2x @p bytes number of bytes to perform bitwise and and equality operations. + * @return Result of the operation. + */ +static int +ly_utf8_and_equal(const char *input, uint8_t bytes, ...) +{ + va_list ap; + int i, and, byte; + + va_start(ap, bytes); + for (i = 0; i < bytes; ++i) { + and = va_arg(ap, int); + byte = va_arg(ap, int); + + /* compare each byte */ + if (((uint8_t)input[i] & and) != (uint8_t)byte) { + return 0; + } + } + va_end(ap); + + return 1; +} + +/** + * @brief Check whether an UTF-8 string is smaller than a hex string. + * + * input < 0x[arg1][arg2]... + * + * @param[in] input UTF-8 string. + * @param[in] bytes Number of bytes to compare. + * @param[in] ... @p bytes number of bytes to compare with. + * @return Result of the operation. + */ +static int +ly_utf8_less(const char *input, uint8_t bytes, ...) +{ + va_list ap; + int i, byte; + + va_start(ap, bytes); + for (i = 0; i < bytes; ++i) { + byte = va_arg(ap, int); + + /* compare until bytes differ */ + if ((uint8_t)input[i] > (uint8_t)byte) { + return 0; + } else if ((uint8_t)input[i] < (uint8_t)byte) { + return 1; + } + } + va_end(ap); + + /* equals */ + return 0; +} + +/** + * @brief Check whether an UTF-8 string is greater than a hex string. + * + * input > 0x[arg1][arg2]... + * + * @param[in] input UTF-8 string. + * @param[in] bytes Number of bytes to compare. + * @param[in] ... @p bytes number of bytes to compare with. + * @return Result of the operation. + */ +static int +ly_utf8_greater(const char *input, uint8_t bytes, ...) +{ + va_list ap; + int i, byte; + + va_start(ap, bytes); + for (i = 0; i < bytes; ++i) { + byte = va_arg(ap, int); + + /* compare until bytes differ */ + if ((uint8_t)input[i] > (uint8_t)byte) { + return 1; + } else if ((uint8_t)input[i] < (uint8_t)byte) { + return 0; + } + } + va_end(ap); + + /* equals */ + return 0; +} + +LY_ERR +ly_checkutf8(const char *input, size_t in_len, size_t *utf8_len) +{ + size_t len; + + if (!(input[0] & 0x80)) { + /* one byte character */ + len = 1; + + if (ly_utf8_less(input, 1, 0x20) && (input[0] != 0x9) && (input[0] != 0xa) && (input[0] != 0xd)) { + /* invalid control characters */ + return LY_EINVAL; + } + } else if (((input[0] & 0xe0) == 0xc0) && (in_len > 1)) { + /* two bytes character */ + len = 2; + + /* (input < 0xC280) || (input > 0xDFBF) || ((input & 0xE0C0) != 0xC080) */ + if (ly_utf8_less(input, 2, 0xC2, 0x80) || ly_utf8_greater(input, 2, 0xDF, 0xBF) || + !ly_utf8_and_equal(input, 2, 0xE0, 0xC0, 0xC0, 0x80)) { + return LY_EINVAL; + } + } else if (((input[0] & 0xf0) == 0xe0) && (in_len > 2)) { + /* three bytes character */ + len = 3; + + /* (input >= 0xEDA080) && (input <= 0xEDBFBF) */ + if (!ly_utf8_less(input, 3, 0xED, 0xA0, 0x80) && !ly_utf8_greater(input, 3, 0xED, 0xBF, 0xBF)) { + /* reject UTF-16 surrogates */ + return LY_EINVAL; + } + + /* (input < 0xE0A080) || (input > 0xEFBFBF) || ((input & 0xF0C0C0) != 0xE08080) */ + if (ly_utf8_less(input, 3, 0xE0, 0xA0, 0x80) || ly_utf8_greater(input, 3, 0xEF, 0xBF, 0xBF) || + !ly_utf8_and_equal(input, 3, 0xF0, 0xE0, 0xC0, 0x80, 0xC0, 0x80)) { + return LY_EINVAL; + } + } else if (((input[0] & 0xf8) == 0xf0) && (in_len > 3)) { + /* four bytes character */ + len = 4; + + /* (input < 0xF0908080) || (input > 0xF48FBFBF) || ((input & 0xF8C0C0C0) != 0xF0808080) */ + if (ly_utf8_less(input, 4, 0xF0, 0x90, 0x80, 0x80) || ly_utf8_greater(input, 4, 0xF4, 0x8F, 0xBF, 0xBF) || + !ly_utf8_and_equal(input, 4, 0xF8, 0xF0, 0xC0, 0x80, 0xC0, 0x80, 0xC0, 0x80)) { + return LY_EINVAL; + } + } else { + return LY_EINVAL; + } + + *utf8_len = len; + return LY_SUCCESS; } LY_ERR @@ -258,6 +410,7 @@ ly_pututf8(char *dst, uint32_t value, size_t *bytes_written) (value != 0x09) && (value != 0x0a) && (value != 0x0d)) { + /* valid UTF8 but not YANG string character */ return LY_EINVAL; } @@ -337,10 +490,10 @@ ly_utf8len(const char *str, size_t bytes) return len; } -size_t +int LY_VCODE_INSTREXP_len(const char *str) { - size_t len = 0; + int len = 0; if (!str) { return len; diff --git a/src/common.h b/src/common.h index 264fe81..0fedeae 100644 --- a/src/common.h +++ b/src/common.h @@ -1,9 +1,10 @@ /** * @file common.h * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Michal Vasko <mvasko@cesnet.cz> * @brief common internal definitions for libyang * - * Copyright (c) 2015 - 2018 CESNET, z.s.p.o. + * Copyright (c) 2015 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -23,7 +24,7 @@ #include "compat.h" #include "config.h" #include "context.h" -#include "hash_table.h" +#include "hash_table_internal.h" #include "log.h" #include "schema_compile.h" #include "set.h" @@ -45,17 +46,28 @@ struct lysc_node; # error "Cannot define THREAD_LOCAL" #endif +/** platform-specific environment variable path separator */ +#ifndef _WIN32 +# define PATH_SEPARATOR ":" +#else +# define PATH_SEPARATOR ";" +#endif + #define GETMACRO1(_1, NAME, ...) NAME #define GETMACRO2(_1, _2, NAME, ...) NAME #define GETMACRO3(_1, _2, _3, NAME, ...) NAME #define GETMACRO4(_1, _2, _3, _4, NAME, ...) NAME #define GETMACRO5(_1, _2, _3, _4, _5, NAME, ...) NAME #define GETMACRO6(_1, _2, _3, _4, _5, _6, NAME, ...) NAME +#define GETMACRO7(_1, _2, _3, _4, _5, _6, _7, NAME, ...) NAME /****************************************************************************** * Logger *****************************************************************************/ +/** size of the last message buffer */ +#define LY_LAST_MSG_SIZE 512 + extern ATOMIC_T ly_ll; extern ATOMIC_T ly_log_opts; @@ -75,7 +87,16 @@ struct ly_log_location_s { * @param[in] no Error type code. * @param[in] format Format string to print. */ -void ly_log(const struct ly_ctx *ctx, LY_LOG_LEVEL level, LY_ERR no, const char *format, ...); +void ly_log(const struct ly_ctx *ctx, LY_LOG_LEVEL level, LY_ERR no, const char *format, ...) _FORMAT_PRINTF(4, 5); + +/** + * @brief Generate data path based on the data and schema nodes stored in the log location. + * + * @param[in] ctx Context for logging. + * @param[out] path Generated data path. + * @return LY_ERR value. + */ +LY_ERR ly_vlog_build_data_path(const struct ly_ctx *ctx, char **path); /** * @brief Print Validation error and store it into the context (if provided). @@ -85,7 +106,15 @@ void ly_log(const struct ly_ctx *ctx, LY_LOG_LEVEL level, LY_ERR no, const char * @param[in] code Validation error code. * @param[in] format Format string to print. */ -void ly_vlog(const struct ly_ctx *ctx, const char *apptag, LY_VECODE code, const char *format, ...); +void ly_vlog(const struct ly_ctx *ctx, const char *apptag, LY_VECODE code, const char *format, ...) _FORMAT_PRINTF(4, 5); + +/** + * @brief Move error items from source to target context replacing any previous ones. + * + * @param[in] src_ctx Source context to read errors from. + * @param[in] trg_ctx Target context to set the errors for. + */ +void ly_err_move(struct ly_ctx *src_ctx, struct ly_ctx *trg_ctx); /** * @brief Logger's location data setter. @@ -110,6 +139,21 @@ void ly_log_location(const struct lysc_node *scnode, const struct lyd_node *dnod void ly_log_location_revert(uint32_t scnode_steps, uint32_t dnode_steps, uint32_t path_steps, uint32_t in_steps); /** + * @brief Get the stored data node for logging at the index. + * + * @param[in] idx Index of the data node. + * @return Logged data node, NULL if out of range. + */ +const struct lyd_node *ly_log_location_dnode(uint32_t idx); + +/** + * @brief Get the count of stored data nodes for logging. + * + * @return Count of the data nodes. + */ +uint32_t ly_log_location_dnode_count(void); + +/** * @brief Update location data for logger, not provided arguments (NULLs) are kept (does not override). * * @param[in] SCNODE Compiled schema node. @@ -201,8 +245,10 @@ void ly_log_dbg(uint32_t group, const char *format, ...); LY_CHECK_ARG_RET1(CTX, ARG4, RETVAL) #define LY_CHECK_ARG_RET5(CTX, ARG1, ARG2, ARG3, ARG4, ARG5, RETVAL) LY_CHECK_ARG_RET4(CTX, ARG1, ARG2, ARG3, ARG4, RETVAL);\ LY_CHECK_ARG_RET1(CTX, ARG5, RETVAL) -#define LY_CHECK_ARG_RET(CTX, ...) GETMACRO6(__VA_ARGS__, LY_CHECK_ARG_RET5, LY_CHECK_ARG_RET4, LY_CHECK_ARG_RET3, \ - LY_CHECK_ARG_RET2, LY_CHECK_ARG_RET1, DUMMY) (CTX, __VA_ARGS__) +#define LY_CHECK_ARG_RET6(CTX, ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, RETVAL) LY_CHECK_ARG_RET5(CTX, ARG1, ARG2, ARG3, ARG4, ARG5, RETVAL);\ + LY_CHECK_ARG_RET1(CTX, ARG6, RETVAL) +#define LY_CHECK_ARG_RET(CTX, ...) GETMACRO7(__VA_ARGS__, LY_CHECK_ARG_RET6, LY_CHECK_ARG_RET5, LY_CHECK_ARG_RET4, \ + LY_CHECK_ARG_RET3, LY_CHECK_ARG_RET2, LY_CHECK_ARG_RET1, DUMMY) (CTX, __VA_ARGS__) #define LY_CHECK_CTX_EQUAL_RET2(CTX1, CTX2, RETVAL) if ((CTX1) && (CTX2) && ((CTX1) != (CTX2))) \ {LOGERR(CTX1, LY_EINVAL, "Different contexts mixed in a single function call."); return RETVAL;} @@ -212,11 +258,12 @@ void ly_log_dbg(uint32_t group, const char *format, ...); DUMMY) (CTX, __VA_ARGS__) /* count sequence size for LY_VCODE_INCHILDSTMT validation error code */ -size_t LY_VCODE_INSTREXP_len(const char *str); +int LY_VCODE_INSTREXP_len(const char *str); + /* default maximum characters to print in LY_VCODE_INCHILDSTMT */ #define LY_VCODE_INSTREXP_MAXLEN 20 -#define LY_VCODE_INCHAR LYVE_SYNTAX, "Invalid character 0x%x." +#define LY_VCODE_INCHAR LYVE_SYNTAX, "Invalid character 0x%hhx." #define LY_VCODE_INSTREXP LYVE_SYNTAX, "Invalid character sequence \"%.*s\", expected %s." #define LY_VCODE_EOF LYVE_SYNTAX, "Unexpected end-of-input." #define LY_VCODE_NTERM LYVE_SYNTAX, "%s not terminated." @@ -300,10 +347,18 @@ size_t LY_VCODE_INSTREXP_len(const char *str); *****************************************************************************/ /** + * @brief Context error hash table record. + */ +struct ly_ctx_err_rec { + struct ly_err_item *err; /** pointer to the error items, if any */ + pthread_t tid; /** pthread thread ID */ +}; + +/** * @brief Context of the YANG schemas */ struct ly_ctx { - struct dict_table dict; /**< dictionary to effectively store strings used in the context related structures */ + struct ly_dict dict; /**< dictionary to effectively store strings used in the context related structures */ struct ly_set search_paths; /**< set of directories where to search for schema's imports/includes */ struct ly_set list; /**< set of loaded YANG schemas */ ly_module_imp_clb imp_clb; /**< optional callback for retrieving missing included or imported models */ @@ -316,7 +371,7 @@ struct ly_ctx { ly_ext_data_clb ext_clb; /**< optional callback for providing extension-specific run-time data for extensions */ void *ext_clb_data; /**< optional private data for ::ly_ctx.ext_clb */ - pthread_key_t errlist_key; /**< key for the thread-specific list of errors related to the context */ + struct ly_ht *err_ht; /**< hash table of thread-specific list of errors related to the context */ pthread_mutex_t lyb_hash_lock; /**< lock for storing LYB schema hashes in schema nodes */ }; @@ -359,7 +414,6 @@ struct lys_module *ly_ctx_get_module_implemented2(const struct ly_ctx *ctx, cons * * @param[in] ptr Memory to reallocate. * @param[in] size New size of the memory block. - * * @return Pointer to the new memory, NULL on error. */ void *ly_realloc(void *ptr, size_t size); @@ -428,7 +482,8 @@ LY_ERR ly_strntou8(const char *nptr, size_t len, uint8_t *ret); * If no string remains, it is set to NULL. * @return LY_ERR value. */ -LY_ERR ly_value_prefix_next(const char *str_begin, const char *str_end, uint32_t *len, ly_bool *is_prefix, const char **str_next); +LY_ERR ly_value_prefix_next(const char *str_begin, const char *str_end, uint32_t *len, ly_bool *is_prefix, + const char **str_next); /** * @brief Wrapper around strlen() to handle NULL strings. @@ -485,7 +540,18 @@ LY_ERR ly_value_prefix_next(const char *str_begin, const char *str_end, uint32_t LY_ERR ly_getutf8(const char **input, uint32_t *utf8_char, size_t *bytes_read); /** - * Store UTF-8 character specified as 4byte integer into the dst buffer. + * @brief Check an UTF-8 character is valid. + * + * @param[in] input Input string to process. + * @param[in] in_len Bytes left to read in @p input. + * @param[out] utf8_len Length of a valid UTF-8 character. + * @return LY_SUCCESS on success + * @return LY_EINVAL in case of invalid UTF-8 character. + */ +LY_ERR ly_checkutf8(const char *input, size_t in_len, size_t *utf8_len); + +/** + * @brief Store UTF-8 character specified as 4byte integer into the dst buffer. * * UTF-8 mapping: * 00000000 -- 0000007F: 0xxxxxxx @@ -495,7 +561,7 @@ LY_ERR ly_getutf8(const char **input, uint32_t *utf8_char, size_t *bytes_read); * * Includes checking for valid characters (following RFC 7950, sec 9.4) * - * @param[in, out] dst Destination buffer to store the UTF-8 character, must provide enough space (up to 4 bytes) for storing the UTF-8 character. + * @param[in,out] dst Destination buffer to store the UTF-8 character, must provide enough space (up to 4 bytes) for storing the UTF-8 character. * @param[in] value 32b value of the UTF-8 character to store. * @param[out] bytes_written Number of bytes written into @p dst (size of the written UTF-8 character). * @return LY_SUCCESS on success @@ -505,6 +571,7 @@ LY_ERR ly_pututf8(char *dst, uint32_t value, size_t *bytes_written); /** * @brief Get number of characters in the @p str, taking multibyte characters into account. + * * @param[in] str String to examine. * @param[in] bytes Number of valid bytes that are supposed to be taken into account in @p str. * This parameter is useful mainly for non NULL-terminated strings. In case of NULL-terminated @@ -515,6 +582,7 @@ size_t ly_utf8len(const char *str, size_t bytes); /** * @brief Parse signed integer with possible limitation. + * * @param[in] val_str String value containing signed integer, note that * nothing else than whitespaces are expected after the value itself. * @param[in] val_len Length of the @p val_str string. @@ -533,6 +601,7 @@ LY_ERR ly_parse_int(const char *val_str, size_t val_len, int64_t min, int64_t ma /** * @brief Parse unsigned integer with possible limitation. + * * @param[in] val_str String value containing unsigned integer, note that * nothing else than whitespaces are expected after the value itself. * @param[in] val_len Length of the @p val_str string. @@ -553,7 +622,7 @@ LY_ERR ly_parse_uint(const char *val_str, size_t val_len, uint64_t max, int base * * node-identifier = [prefix ":"] identifier * - * @param[in, out] id Identifier to parse. When returned, it points to the first character which is not part of the identifier. + * @param[in,out] id Identifier to parse. When returned, it points to the first character which is not part of the identifier. * @param[out] prefix Node's prefix, NULL if there is not any. * @param[out] prefix_len Length of the node's prefix, 0 if there is not any. * @param[out] name Node's name. @@ -565,7 +634,7 @@ LY_ERR ly_parse_nodeid(const char **id, const char **prefix, size_t *prefix_len, /** * @brief parse instance-identifier's predicate, supports key-predicate, leaf-list-predicate and pos rules from YANG ABNF Grammar. * - * @param[in, out] pred Predicate string (including the leading '[') to parse. The string is updated according to what was parsed + * @param[in,out] pred Predicate string (including the leading '[') to parse. The string is updated according to what was parsed * (even for error case, so it can be used to determine which substring caused failure). * @param[in] limit Limiting length of the @p pred. Function expects NULL terminated string which is not overread. * The limit value is not checked with each character, so it can be overread and the failure is detected later. @@ -610,17 +679,11 @@ LY_ERR ly_munmap(void *addr, size_t length); /** * @brief Concatenate formating string to the @p dest. * - * @param[in, out] dest String to be concatenated by @p format. - * Note that the input string can be reallocated during concatenation. + * @param[in,out] dest String to be concatenated by @p format. + * Note that the input string can be reallocated during concatenation. * @param[in] format Formating string (as for printf) which is supposed to be added after @p dest. * @return LY_SUCCESS or LY_EMEM. */ -LY_ERR ly_strcat(char **dest, const char *format, ...); - -#ifndef _WIN32 -# define PATH_SEPARATOR ":" -#else -# define PATH_SEPARATOR ";" -#endif +LY_ERR ly_strcat(char **dest, const char *format, ...) _FORMAT_PRINTF(2, 3); #endif /* LY_COMMON_H_ */ diff --git a/src/context.c b/src/context.c index 47e63d4..7a14203 100644 --- a/src/context.c +++ b/src/context.c @@ -4,7 +4,7 @@ * @author Michal Vasko <mvasko@cesnet.cz> * @brief Context implementations * - * Copyright (c) 2015 - 2021 CESNET, z.s.p.o. + * Copyright (c) 2015 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -228,6 +228,17 @@ cleanup: return mod; } +/** + * @brief Hash table value-equal callback for comparing context error hash table record. + */ +static ly_bool +ly_ctx_ht_err_equal_cb(void *val1_p, void *val2_p, ly_bool UNUSED(mod), void *UNUSED(cb_data)) +{ + struct ly_ctx_err_rec *err1 = val1_p, *err2 = val2_p; + + return !memcmp(&err1->tid, &err2->tid, sizeof err1->tid); +} + LIBYANG_API_DEF LY_ERR ly_ctx_new(const char *search_dir, uint16_t options, struct ly_ctx **new_ctx) { @@ -251,8 +262,9 @@ ly_ctx_new(const char *search_dir, uint16_t options, struct ly_ctx **new_ctx) /* plugins */ LY_CHECK_ERR_GOTO(lyplg_init(), LOGINT(NULL); rc = LY_EINT, cleanup); - /* initialize thread-specific keys */ - while ((pthread_key_create(&ctx->errlist_key, ly_err_free)) == EAGAIN) {} + /* initialize thread-specific error hash table */ + ctx->err_ht = lyht_new(1, sizeof(struct ly_ctx_err_rec), ly_ctx_ht_err_equal_cb, NULL, 1); + LY_CHECK_ERR_GOTO(!ctx->err_ht, rc = LY_EMEM, cleanup); /* init LYB hash lock */ pthread_mutex_init(&ctx->lyb_hash_lock, NULL); @@ -657,6 +669,39 @@ ly_ctx_get_change_count(const struct ly_ctx *ctx) return ctx->change_count; } +LIBYANG_API_DEF uint32_t +ly_ctx_get_modules_hash(const struct ly_ctx *ctx) +{ + const struct lys_module *mod; + uint32_t i = ly_ctx_internal_modules_count(ctx), hash = 0, fi = 0; + struct lysp_feature *f = NULL; + + LY_CHECK_ARG_RET(ctx, ctx, 0); + + while ((mod = ly_ctx_get_module_iter(ctx, &i))) { + /* name */ + hash = lyht_hash_multi(hash, mod->name, strlen(mod->name)); + + /* revision */ + if (mod->revision) { + hash = lyht_hash_multi(hash, mod->revision, strlen(mod->revision)); + } + + /* enabled features */ + while ((f = lysp_feature_next(f, mod->parsed, &fi))) { + if (f->flags & LYS_FENABLED) { + hash = lyht_hash_multi(hash, f->name, strlen(f->name)); + } + } + + /* imported/implemented */ + hash = lyht_hash_multi(hash, (char *)&mod->implemented, sizeof mod->implemented); + } + + hash = lyht_hash_multi(hash, NULL, 0); + return hash; +} + LIBYANG_API_DEF ly_module_imp_clb ly_ctx_get_module_imp_clb(const struct ly_ctx *ctx, void **user_data) { @@ -1228,6 +1273,19 @@ error: return ret; } +/** + * @brief Callback for freeing context error hash table values. + * + * @param[in] val_p Pointer to a pointer to an error item to free with all the siblings. + */ +static void +ly_ctx_ht_err_rec_free(void *val_p) +{ + struct ly_ctx_err_rec *err = val_p; + + ly_err_free(err->err); +} + LIBYANG_API_DEF void ly_ctx_destroy(struct ly_ctx *ctx) { @@ -1260,9 +1318,8 @@ ly_ctx_destroy(struct ly_ctx *ctx) /* leftover unres */ lys_unres_glob_erase(&ctx->unres); - /* clean the error list */ - ly_err_clean(ctx, 0); - pthread_key_delete(ctx->errlist_key); + /* clean the error hash table */ + lyht_free(ctx->err_ht, ly_ctx_ht_err_rec_free); /* dictionary */ lydict_clean(&ctx->dict); diff --git a/src/context.h b/src/context.h index 331a89f..a6367f5 100644 --- a/src/context.h +++ b/src/context.h @@ -1,9 +1,10 @@ /** * @file context.h * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Michal Vasko <mvasko@cesnet.cz> * @brief internal context structures and functions * - * Copyright (c) 2015 - 2020 CESNET, z.s.p.o. + * Copyright (c) 2015 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -196,6 +197,8 @@ struct ly_ctx; #define LY_CTX_ENABLE_IMP_FEATURES 0x0100 /**< By default, all features of newly implemented imported modules of a module that is being loaded are disabled. With this flag they all become enabled. */ +#define LY_CTX_LEAFREF_EXTENDED 0x0200 /**< By default, path attribute of leafref accepts only path as defined in RFC 7950. + By using this option, the path attribute will also allow using XPath functions as deref() */ /** @} contextoptions */ @@ -370,6 +373,18 @@ LIBYANG_API_DECL LY_ERR ly_ctx_unset_options(struct ly_ctx *ctx, uint16_t option LIBYANG_API_DECL uint16_t ly_ctx_get_change_count(const struct ly_ctx *ctx); /** + * @brief Get the hash of all the modules in the context. Since order of the modules is significant, + * even when 2 contexts have the same modules but loaded in a different order, the hash will differ. + * + * Hash consists of all module names (1), their revisions (2), all enabled features (3), and their + * imported/implemented state (4). + * + * @param[in] ctx Context to be examined. + * @return Context modules hash. + */ +LIBYANG_API_DECL uint32_t ly_ctx_get_modules_hash(const struct ly_ctx *ctx); + +/** * @brief Callback for freeing returned module data in #ly_module_imp_clb. * * @param[in] module_data Data to free. @@ -630,7 +645,8 @@ LIBYANG_API_DECL struct lys_module *ly_ctx_load_module(struct ly_ctx *ctx, const * If the data identifier can be limited to the existence and changes of this context, the following * last 2 parameters can be used: * - * "%u" as @p content_id_format and ::ly_ctx_get_change_count() as its parameter. + * "%u" as @p content_id_format and ::ly_ctx_get_change_count() as its parameter; + * "%u" as @p content_id_format and ::ly_ctx_get_modules_hash() as its parameter. * * @param[in] ctx Context with the modules. * @param[out] root Generated yang-library data. diff --git a/src/dict.c b/src/dict.c new file mode 100644 index 0000000..e1426ca --- /dev/null +++ b/src/dict.c @@ -0,0 +1,271 @@ +/** + * @file dict.c + * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Michal Vasko <mvasko@cesnet.cz> + * @brief libyang dictionary for storing strings + * + * Copyright (c) 2015 - 2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include "dict.h" + +#include <assert.h> +#include <pthread.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "common.h" +#include "compat.h" +#include "log.h" + +/* starting size of the dictionary */ +#define LYDICT_MIN_SIZE 1024 + +/** + * @brief Comparison callback for dictionary's hash table + * + * Implementation of ::lyht_value_equal_cb. + */ +static ly_bool +lydict_val_eq(void *val1_p, void *val2_p, ly_bool UNUSED(mod), void *cb_data) +{ + const char *str1, *str2; + size_t *len1; + + LY_CHECK_ARG_RET(NULL, val1_p, val2_p, cb_data, 0); + + str1 = ((struct ly_dict_rec *)val1_p)->value; + str2 = ((struct ly_dict_rec *)val2_p)->value; + len1 = cb_data; + + LY_CHECK_ERR_RET(!str1, LOGARG(NULL, val1_p), 0); + LY_CHECK_ERR_RET(!str2, LOGARG(NULL, val2_p), 0); + + if (!strncmp(str1, str2, *len1) && !str2[*len1]) { + return 1; + } + + return 0; +} + +void +lydict_init(struct ly_dict *dict) +{ + LY_CHECK_ARG_RET(NULL, dict, ); + + dict->hash_tab = lyht_new(LYDICT_MIN_SIZE, sizeof(struct ly_dict_rec), lydict_val_eq, NULL, 1); + LY_CHECK_ERR_RET(!dict->hash_tab, LOGINT(NULL), ); + pthread_mutex_init(&dict->lock, NULL); +} + +void +lydict_clean(struct ly_dict *dict) +{ + struct ly_dict_rec *dict_rec = NULL; + struct ly_ht_rec *rec = NULL; + uint32_t hlist_idx; + uint32_t rec_idx; + + LY_CHECK_ARG_RET(NULL, dict, ); + + LYHT_ITER_ALL_RECS(dict->hash_tab, hlist_idx, rec_idx, rec) { + /* + * this should not happen, all records inserted into + * dictionary are supposed to be removed using lydict_remove() + * before calling lydict_clean() + */ + dict_rec = (struct ly_dict_rec *)rec->val; + LOGWRN(NULL, "String \"%s\" not freed from the dictionary, refcount %d", dict_rec->value, dict_rec->refcount); + /* if record wasn't removed before free string allocated for that record */ +#ifdef NDEBUG + free(dict_rec->value); +#endif + } + + /* free table and destroy mutex */ + lyht_free(dict->hash_tab, NULL); + pthread_mutex_destroy(&dict->lock); +} + +static ly_bool +lydict_resize_val_eq(void *val1_p, void *val2_p, ly_bool mod, void *UNUSED(cb_data)) +{ + const char *str1, *str2; + + LY_CHECK_ARG_RET(NULL, val1_p, val2_p, 0); + + str1 = ((struct ly_dict_rec *)val1_p)->value; + str2 = ((struct ly_dict_rec *)val2_p)->value; + + LY_CHECK_ERR_RET(!str1, LOGARG(NULL, val1_p), 0); + LY_CHECK_ERR_RET(!str2, LOGARG(NULL, val2_p), 0); + + if (mod) { + /* used when inserting new values */ + if (strcmp(str1, str2) == 0) { + return 1; + } + } else { + /* used when finding the original value again in the resized table */ + if (str1 == str2) { + return 1; + } + } + + return 0; +} + +LIBYANG_API_DEF LY_ERR +lydict_remove(const struct ly_ctx *ctx, const char *value) +{ + LY_ERR ret = LY_SUCCESS; + size_t len; + uint32_t hash; + struct ly_dict_rec rec, *match = NULL; + char *val_p; + + if (!ctx || !value) { + return LY_SUCCESS; + } + + LOGDBG(LY_LDGDICT, "removing \"%s\"", value); + + len = strlen(value); + hash = lyht_hash(value, len); + + /* create record for lyht_find call */ + rec.value = (char *)value; + rec.refcount = 0; + + pthread_mutex_lock((pthread_mutex_t *)&ctx->dict.lock); + /* set len as data for compare callback */ + lyht_set_cb_data(ctx->dict.hash_tab, (void *)&len); + /* check if value is already inserted */ + ret = lyht_find(ctx->dict.hash_tab, &rec, hash, (void **)&match); + + if (ret == LY_SUCCESS) { + LY_CHECK_ERR_GOTO(!match, LOGINT(ctx), finish); + + /* if value is already in dictionary, decrement reference counter */ + match->refcount--; + if (match->refcount == 0) { + /* + * remove record + * save pointer to stored string before lyht_remove to + * free it after it is removed from hash table + */ + val_p = match->value; + ret = lyht_remove_with_resize_cb(ctx->dict.hash_tab, &rec, hash, lydict_resize_val_eq); + free(val_p); + LY_CHECK_ERR_GOTO(ret, LOGINT(ctx), finish); + } + } else if (ret == LY_ENOTFOUND) { + LOGERR(ctx, LY_ENOTFOUND, "Value \"%s\" was not found in the dictionary.", value); + } else { + LOGINT(ctx); + } + +finish: + pthread_mutex_unlock((pthread_mutex_t *)&ctx->dict.lock); + return ret; +} + +LY_ERR +dict_insert(const struct ly_ctx *ctx, char *value, size_t len, ly_bool zerocopy, const char **str_p) +{ + LY_ERR ret = LY_SUCCESS; + struct ly_dict_rec *match = NULL, rec; + uint32_t hash; + + LOGDBG(LY_LDGDICT, "inserting \"%.*s\"", (int)len, value); + + hash = lyht_hash(value, len); + /* set len as data for compare callback */ + lyht_set_cb_data(ctx->dict.hash_tab, (void *)&len); + /* create record for lyht_insert */ + rec.value = value; + rec.refcount = 1; + + ret = lyht_insert_with_resize_cb(ctx->dict.hash_tab, (void *)&rec, hash, lydict_resize_val_eq, (void **)&match); + if (ret == LY_EEXIST) { + match->refcount++; + if (zerocopy) { + free(value); + } + ret = LY_SUCCESS; + } else if (ret == LY_SUCCESS) { + if (!zerocopy) { + /* + * allocate string for new record + * record is already inserted in hash table + */ + match->value = malloc(sizeof *match->value * (len + 1)); + LY_CHECK_ERR_RET(!match->value, LOGMEM(ctx), LY_EMEM); + if (len) { + memcpy(match->value, value, len); + } + match->value[len] = '\0'; + } + } else { + /* lyht_insert returned error */ + if (zerocopy) { + free(value); + } + return ret; + } + + if (str_p) { + *str_p = match->value; + } + + return ret; +} + +LIBYANG_API_DEF LY_ERR +lydict_insert(const struct ly_ctx *ctx, const char *value, size_t len, const char **str_p) +{ + LY_ERR result; + + LY_CHECK_ARG_RET(ctx, ctx, str_p, LY_EINVAL); + + if (!value) { + *str_p = NULL; + return LY_SUCCESS; + } + + if (!len) { + len = strlen(value); + } + + pthread_mutex_lock((pthread_mutex_t *)&ctx->dict.lock); + result = dict_insert(ctx, (char *)value, len, 0, str_p); + pthread_mutex_unlock((pthread_mutex_t *)&ctx->dict.lock); + + return result; +} + +LIBYANG_API_DEF LY_ERR +lydict_insert_zc(const struct ly_ctx *ctx, char *value, const char **str_p) +{ + LY_ERR result; + + LY_CHECK_ARG_RET(ctx, ctx, str_p, LY_EINVAL); + + if (!value) { + *str_p = NULL; + return LY_SUCCESS; + } + + pthread_mutex_lock((pthread_mutex_t *)&ctx->dict.lock); + result = dict_insert(ctx, value, strlen(value), 1, str_p); + pthread_mutex_unlock((pthread_mutex_t *)&ctx->dict.lock); + + return result; +} @@ -161,6 +161,127 @@ cleanup: return rc; } +/** + * @brief Find metadata/an attribute of a node. + * + * @param[in] node Node to search. + * @param[in] name Metadata/attribute name. + * @param[out] meta Metadata found, NULL if not found. + * @param[out] attr Attribute found, NULL if not found. + */ +static void +lyd_diff_find_meta(const struct lyd_node *node, const char *name, struct lyd_meta **meta, struct lyd_attr **attr) +{ + struct lyd_meta *m; + struct lyd_attr *a; + + if (meta) { + *meta = NULL; + } + if (attr) { + *attr = NULL; + } + + if (node->schema) { + assert(meta); + + LY_LIST_FOR(node->meta, m) { + if (!strcmp(m->name, name) && !strcmp(m->annotation->module->name, "yang")) { + *meta = m; + break; + } + } + } else { + assert(attr); + + LY_LIST_FOR(((struct lyd_node_opaq *)node)->attr, a) { + /* name */ + if (strcmp(a->name.name, name)) { + continue; + } + + /* module */ + switch (a->format) { + case LY_VALUE_JSON: + if (strcmp(a->name.module_name, "yang")) { + continue; + } + break; + case LY_VALUE_XML: + if (strcmp(a->name.module_ns, "urn:ietf:params:xml:ns:yang:1")) { + continue; + } + break; + default: + LOGINT(LYD_CTX(node)); + return; + } + + *attr = a; + break; + } + } +} + +/** + * @brief Learn operation of a diff node. + * + * @param[in] diff_node Diff node. + * @param[out] op Operation. + * @return LY_ERR value. + */ +static LY_ERR +lyd_diff_get_op(const struct lyd_node *diff_node, enum lyd_diff_op *op) +{ + struct lyd_meta *meta = NULL; + struct lyd_attr *attr = NULL; + const struct lyd_node *diff_parent; + const char *str; + char *path; + + for (diff_parent = diff_node; diff_parent; diff_parent = lyd_parent(diff_parent)) { + lyd_diff_find_meta(diff_parent, "operation", &meta, &attr); + if (!meta && !attr) { + continue; + } + + str = meta ? lyd_get_meta_value(meta) : attr->value; + if ((str[0] == 'r') && (diff_parent != diff_node)) { + /* we do not care about this operation if it's in our parent */ + continue; + } + *op = lyd_diff_str2op(str); + return LY_SUCCESS; + } + + /* operation not found */ + path = lyd_path(diff_node, LYD_PATH_STD, NULL, 0); + LOGERR(LYD_CTX(diff_node), LY_EINVAL, "Node \"%s\" without an operation.", path); + free(path); + return LY_EINT; +} + +/** + * @brief Remove metadata/an attribute from a node. + * + * @param[in] node Node to update. + * @param[in] name Metadata/attribute name. + */ +static void +lyd_diff_del_meta(struct lyd_node *node, const char *name) +{ + struct lyd_meta *meta; + struct lyd_attr *attr; + + lyd_diff_find_meta(node, name, &meta, &attr); + + if (meta) { + lyd_free_meta_single(meta); + } else if (attr) { + lyd_free_attr_single(LYD_CTX(node), attr); + } +} + LY_ERR lyd_diff_add(const struct lyd_node *node, enum lyd_diff_op op, const char *orig_default, const char *orig_value, const char *key, const char *value, const char *position, const char *orig_key, const char *orig_position, @@ -168,6 +289,9 @@ lyd_diff_add(const struct lyd_node *node, enum lyd_diff_op op, const char *orig_ { struct lyd_node *dup, *siblings, *match = NULL, *diff_parent = NULL, *elem; const struct lyd_node *parent = NULL; + enum lyd_diff_op cur_op; + struct lyd_meta *meta; + uint32_t diff_opts; assert(diff); @@ -189,14 +313,16 @@ lyd_diff_add(const struct lyd_node *node, enum lyd_diff_op op, const char *orig_ /* find the first existing parent */ siblings = *diff; - while (1) { + do { /* find next node parent */ parent = node; while (parent->parent && (!diff_parent || (parent->parent->schema != diff_parent->schema))) { parent = lyd_parent(parent); } - if (parent == node) { - /* no more parents to find */ + + if (lysc_is_dup_inst_list(parent->schema)) { + /* assume it never exists, we are not able to distinguish whether it does or not */ + match = NULL; break; } @@ -210,36 +336,75 @@ lyd_diff_add(const struct lyd_node *node, enum lyd_diff_op op, const char *orig_ /* move down in the diff */ siblings = lyd_child_no_keys(match); - } + } while (parent != node); + + if (match && (parent == node)) { + /* special case when there is already an operation on our descendant */ + assert(!lyd_diff_get_op(diff_parent, &cur_op) && (cur_op == LYD_DIFF_OP_NONE)); + (void)cur_op; + + /* move it to the end where it is expected (matters for user-ordered lists) */ + if (lysc_is_userordered(diff_parent->schema)) { + for (elem = diff_parent; elem->next && (elem->next->schema == elem->schema); elem = elem->next) {} + if (elem != diff_parent) { + LY_CHECK_RET(lyd_insert_after(elem, diff_parent)); + } + } - /* duplicate the subtree (and connect to the diff if possible) */ - LY_CHECK_RET(lyd_dup_single(node, (struct lyd_node_inner *)diff_parent, - LYD_DUP_RECURSIVE | LYD_DUP_NO_META | LYD_DUP_WITH_PARENTS | LYD_DUP_WITH_FLAGS, &dup)); + /* will be replaced by the new operation but keep the current op for descendants */ + lyd_diff_del_meta(diff_parent, "operation"); + LY_LIST_FOR(lyd_child_no_keys(diff_parent), elem) { + lyd_diff_find_meta(elem, "operation", &meta, NULL); + if (meta) { + /* explicit operation, fine */ + continue; + } - /* find the first duplicated parent */ - if (!diff_parent) { - diff_parent = lyd_parent(dup); - while (diff_parent && diff_parent->parent) { - diff_parent = lyd_parent(diff_parent); + /* set the none operation */ + LY_CHECK_RET(lyd_new_meta(NULL, elem, NULL, "yang:operation", "none", 0, NULL)); } + + dup = diff_parent; } else { - diff_parent = dup; - while (diff_parent->parent && (diff_parent->parent->schema == parent->schema)) { - diff_parent = lyd_parent(diff_parent); + diff_opts = LYD_DUP_NO_META | LYD_DUP_WITH_PARENTS | LYD_DUP_WITH_FLAGS; + if ((op != LYD_DIFF_OP_REPLACE) || !lysc_is_userordered(node->schema) || (node->schema->flags & LYS_CONFIG_R)) { + /* move applies only to the user-ordered list, no descendants */ + diff_opts |= LYD_DUP_RECURSIVE; } - } - /* no parent existed, must be manually connected */ - if (!diff_parent) { - /* there actually was no parent to duplicate */ - lyd_insert_sibling(*diff, dup, diff); - } else if (!diff_parent->parent) { - lyd_insert_sibling(*diff, diff_parent, diff); - } + /* duplicate the subtree (and connect to the diff if possible) */ + if (diff_parent) { + LY_CHECK_RET(lyd_dup_single_to_ctx(node, LYD_CTX(diff_parent), (struct lyd_node_inner *)diff_parent, + diff_opts, &dup)); + } else { + LY_CHECK_RET(lyd_dup_single(node, NULL, diff_opts, &dup)); + } + + /* find the first duplicated parent */ + if (!diff_parent) { + diff_parent = lyd_parent(dup); + while (diff_parent && diff_parent->parent) { + diff_parent = lyd_parent(diff_parent); + } + } else { + diff_parent = dup; + while (diff_parent->parent && (diff_parent->parent->schema == parent->schema)) { + diff_parent = lyd_parent(diff_parent); + } + } + + /* no parent existed, must be manually connected */ + if (!diff_parent) { + /* there actually was no parent to duplicate */ + lyd_insert_sibling(*diff, dup, diff); + } else if (!diff_parent->parent) { + lyd_insert_sibling(*diff, diff_parent, diff); + } - /* add parent operation, if any */ - if (diff_parent && (diff_parent != dup)) { - LY_CHECK_RET(lyd_new_meta(NULL, diff_parent, NULL, "yang:operation", "none", 0, NULL)); + /* add parent operation, if any */ + if (diff_parent && (diff_parent != dup)) { + LY_CHECK_RET(lyd_new_meta(NULL, diff_parent, NULL, "yang:operation", "none", 0, NULL)); + } } /* add subtree operation */ @@ -361,7 +526,7 @@ lyd_diff_userord_attrs(const struct lyd_node *first, const struct lyd_node *seco LY_ERR rc = LY_SUCCESS; const struct lysc_node *schema; size_t buflen, bufused; - uint32_t first_pos, second_pos; + uint32_t first_pos, second_pos, comp_opts; assert(first || second); @@ -397,7 +562,8 @@ lyd_diff_userord_attrs(const struct lyd_node *first, const struct lyd_node *seco } else if (!first) { *op = LYD_DIFF_OP_CREATE; } else { - if (lyd_compare_single(second, userord_item->inst[second_pos], 0)) { + comp_opts = lysc_is_dup_inst_list(second->schema) ? LYD_COMPARE_FULL_RECURSION : 0; + if (lyd_compare_single(second, userord_item->inst[second_pos], comp_opts)) { /* in first, there is a different instance on the second position, we are going to move 'first' node */ *op = LYD_DIFF_OP_REPLACE; } else if ((options & LYD_DIFF_DEFAULTS) && ((first->flags & LYD_DEFAULT) != (second->flags & LYD_DEFAULT))) { @@ -648,17 +814,20 @@ lyd_diff_attrs(const struct lyd_node *first, const struct lyd_node *second, uint * @param[in] siblings Siblings to search in. * @param[in] target Target node to search for. * @param[in] defaults Whether to consider (or ignore) default values. - * @param[in,out] dup_inst_cache Duplicate instance cache. + * @param[in,out] dup_inst_ht Duplicate instance cache. * @param[out] match Found match, NULL if no matching node found. * @return LY_ERR value. */ static LY_ERR lyd_diff_find_match(const struct lyd_node *siblings, const struct lyd_node *target, ly_bool defaults, - struct lyd_dup_inst **dup_inst_cache, struct lyd_node **match) + struct ly_ht **dup_inst_ht, struct lyd_node **match) { LY_ERR r; - if (target->schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) { + if (!target->schema) { + /* try to find the same opaque node */ + r = lyd_find_sibling_opaq_next(siblings, LYD_NAME(target), match); + } else if (target->schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) { /* try to find the exact instance */ r = lyd_find_sibling_first(siblings, target, match); } else { @@ -670,7 +839,7 @@ lyd_diff_find_match(const struct lyd_node *siblings, const struct lyd_node *targ } /* update match as needed */ - LY_CHECK_RET(lyd_dup_inst_next(match, siblings, dup_inst_cache)); + LY_CHECK_RET(lyd_dup_inst_next(match, siblings, dup_inst_ht)); if (*match && ((*match)->flags & LYD_DEFAULT) && !defaults) { /* ignore default nodes */ @@ -728,7 +897,7 @@ lyd_diff_siblings_r(const struct lyd_node *first, const struct lyd_node *second, const struct lyd_node *iter_first, *iter_second; struct lyd_node *match_second, *match_first; struct lyd_diff_userord *userord = NULL, *userord_item; - struct lyd_dup_inst *dup_inst_first = NULL, *dup_inst_second = NULL; + struct ly_ht *dup_inst_first = NULL, *dup_inst_second = NULL; LY_ARRAY_COUNT_TYPE u; enum lyd_diff_op op; const char *orig_default; @@ -920,48 +1089,6 @@ lyd_diff_siblings(const struct lyd_node *first, const struct lyd_node *second, u } /** - * @brief Learn operation of a diff node. - * - * @param[in] diff_node Diff node. - * @param[out] op Operation. - * @return LY_ERR value. - */ -static LY_ERR -lyd_diff_get_op(const struct lyd_node *diff_node, enum lyd_diff_op *op) -{ - struct lyd_meta *meta = NULL; - const struct lyd_node *diff_parent; - const char *str; - char *path; - - for (diff_parent = diff_node; diff_parent; diff_parent = lyd_parent(diff_parent)) { - LY_LIST_FOR(diff_parent->meta, meta) { - if (!strcmp(meta->name, "operation") && !strcmp(meta->annotation->module->name, "yang")) { - str = lyd_get_meta_value(meta); - if ((str[0] == 'r') && (diff_parent != diff_node)) { - /* we do not care about this operation if it's in our parent */ - continue; - } - *op = lyd_diff_str2op(str); - break; - } - } - if (meta) { - break; - } - } - - if (!meta) { - path = lyd_path(diff_node, LYD_PATH_STD, NULL, 0); - LOGERR(LYD_CTX(diff_node), LY_EINVAL, "Node \"%s\" without an operation.", path); - free(path); - return LY_EINT; - } - - return LY_SUCCESS; -} - -/** * @brief Insert a diff node into a data tree. * * @param[in,out] first_node First sibling of the data tree. @@ -1079,14 +1206,14 @@ lyd_diff_insert(struct lyd_node **first_node, struct lyd_node *parent_node, stru */ static LY_ERR lyd_diff_apply_r(struct lyd_node **first_node, struct lyd_node *parent_node, const struct lyd_node *diff_node, - lyd_diff_cb diff_cb, void *cb_data, struct lyd_dup_inst **dup_inst) + lyd_diff_cb diff_cb, void *cb_data, struct ly_ht **dup_inst) { LY_ERR ret; struct lyd_node *match, *diff_child; const char *str_val, *meta_str; enum lyd_diff_op op; struct lyd_meta *meta; - struct lyd_dup_inst *child_dup_inst = NULL; + struct ly_ht *child_dup_inst = NULL; const struct ly_ctx *ctx = LYD_CTX(diff_node); /* read all the valid attributes */ @@ -1242,7 +1369,7 @@ lyd_diff_apply_module(struct lyd_node **data, const struct lyd_node *diff, const lyd_diff_cb diff_cb, void *cb_data) { const struct lyd_node *root; - struct lyd_dup_inst *dup_inst = NULL; + struct ly_ht *dup_inst = NULL; LY_ERR ret = LY_SUCCESS; LY_LIST_FOR(diff, root) { @@ -1299,27 +1426,6 @@ lyd_diff_merge_none(struct lyd_node *diff_match, enum lyd_diff_op cur_op, const } /** - * @brief Remove an attribute from a node. - * - * @param[in] node Node with the metadata. - * @param[in] name Metadata name. - */ -static void -lyd_diff_del_meta(struct lyd_node *node, const char *name) -{ - struct lyd_meta *meta; - - LY_LIST_FOR(node->meta, meta) { - if (!strcmp(meta->name, name) && !strcmp(meta->annotation->module->name, "yang")) { - lyd_free_meta_single(meta); - return; - } - } - - assert(0); -} - -/** * @brief Set a specific operation of a node. Delete the previous operation, if any. * Does not change the default flag. * @@ -1330,16 +1436,13 @@ lyd_diff_del_meta(struct lyd_node *node, const char *name) static LY_ERR lyd_diff_change_op(struct lyd_node *node, enum lyd_diff_op op) { - struct lyd_meta *meta; + lyd_diff_del_meta(node, "operation"); - LY_LIST_FOR(node->meta, meta) { - if (!strcmp(meta->name, "operation") && !strcmp(meta->annotation->module->name, "yang")) { - lyd_free_meta_single(meta); - break; - } + if (node->schema) { + return lyd_new_meta(LYD_CTX(node), node, NULL, "yang:operation", lyd_diff_op2str(op), 0, NULL); + } else { + return lyd_new_attr(node, "yang", "operation", lyd_diff_op2str(op), NULL); } - - return lyd_new_meta(LYD_CTX(node), node, NULL, "yang:operation", lyd_diff_op2str(op), 0, NULL); } /** @@ -1431,28 +1534,43 @@ lyd_diff_merge_replace(struct lyd_node *diff_match, enum lyd_diff_op cur_op, con } break; case LYD_DIFF_OP_NONE: - /* it is moved now */ - assert(lysc_is_userordered(diff_match->schema) && (diff_match->schema->nodetype == LYS_LIST)); + switch (diff_match->schema->nodetype) { + case LYS_LIST: + /* it is moved now */ + assert(lysc_is_userordered(diff_match->schema)); - /* change the operation */ - LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_REPLACE)); + /* change the operation */ + LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_REPLACE)); - /* set orig-meta and meta */ - if (lysc_is_dup_inst_list(diff_match->schema)) { - meta_name = "position"; - orig_meta_name = "orig-position"; - } else { - meta_name = "key"; - orig_meta_name = "orig-key"; - } + /* set orig-meta and meta */ + if (lysc_is_dup_inst_list(diff_match->schema)) { + meta_name = "position"; + orig_meta_name = "orig-position"; + } else { + meta_name = "key"; + orig_meta_name = "orig-key"; + } + + meta = lyd_find_meta(src_diff->meta, mod, orig_meta_name); + LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, orig_meta_name, src_diff), LY_EINVAL); + LY_CHECK_RET(lyd_dup_meta_single(meta, diff_match, NULL)); - meta = lyd_find_meta(src_diff->meta, mod, orig_meta_name); - LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, orig_meta_name, src_diff), LY_EINVAL); - LY_CHECK_RET(lyd_dup_meta_single(meta, diff_match, NULL)); + meta = lyd_find_meta(src_diff->meta, mod, meta_name); + LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, meta_name, src_diff), LY_EINVAL); + LY_CHECK_RET(lyd_dup_meta_single(meta, diff_match, NULL)); + break; + case LYS_LEAF: + /* only dflt flag changed, now value changed as well, update the operation */ + LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_REPLACE)); - meta = lyd_find_meta(src_diff->meta, mod, meta_name); - LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, meta_name, src_diff), LY_EINVAL); - LY_CHECK_RET(lyd_dup_meta_single(meta, diff_match, NULL)); + /* modify the node value */ + if (lyd_change_term(diff_match, lyd_get_value(src_diff))) { + LOGINT_RET(LYD_CTX(src_diff)); + } + break; + default: + LOGINT_RET(LYD_CTX(src_diff)); + } break; default: /* delete operation is not valid */ @@ -1466,33 +1584,41 @@ lyd_diff_merge_replace(struct lyd_node *diff_match, enum lyd_diff_op cur_op, con /** * @brief Update operations in a diff node when the new operation is CREATE. * - * @param[in] diff_match Node from the diff. + * @param[in,out] diff_match Node from the diff, may be replaced. + * @param[in,out] diff Diff root node, may be updated. * @param[in] cur_op Current operation of @p diff_match. * @param[in] src_diff Current source diff node. * @param[in] options Diff merge options. * @return LY_ERR value. */ static LY_ERR -lyd_diff_merge_create(struct lyd_node *diff_match, enum lyd_diff_op cur_op, const struct lyd_node *src_diff, uint16_t options) +lyd_diff_merge_create(struct lyd_node **diff_match, struct lyd_node **diff, enum lyd_diff_op cur_op, + const struct lyd_node *src_diff, uint16_t options) { - struct lyd_node *child; + struct lyd_node *child, *src_dup, *to_free = NULL; const struct lysc_node_leaf *sleaf = NULL; uint32_t trg_flags; const char *meta_name, *orig_meta_name; struct lyd_meta *meta, *orig_meta; - const struct ly_ctx *ctx = LYD_CTX(diff_match); + const struct ly_ctx *ctx = LYD_CTX(*diff_match); + LY_ERR r; + + /* create operation is valid only for data nodes */ + LY_CHECK_ERR_RET(!src_diff->schema, LOGINT(ctx), LY_EINT); switch (cur_op) { case LYD_DIFF_OP_DELETE: /* remember current flags */ - trg_flags = diff_match->flags; + trg_flags = (*diff_match)->flags; + + if (lysc_is_userordered(src_diff->schema)) { + assert((*diff_match)->schema); - if (lysc_is_userordered(diff_match->schema)) { /* get anchor metadata */ - if (lysc_is_dup_inst_list(diff_match->schema)) { + if (lysc_is_dup_inst_list((*diff_match)->schema)) { meta_name = "yang:position"; orig_meta_name = "yang:orig-position"; - } else if (diff_match->schema->nodetype == LYS_LIST) { + } else if ((*diff_match)->schema->nodetype == LYS_LIST) { meta_name = "yang:key"; orig_meta_name = "yang:orig-key"; } else { @@ -1501,71 +1627,86 @@ lyd_diff_merge_create(struct lyd_node *diff_match, enum lyd_diff_op cur_op, cons } meta = lyd_find_meta(src_diff->meta, NULL, meta_name); LY_CHECK_ERR_RET(!meta, LOGERR_META(ctx, meta_name, src_diff), LY_EINVAL); - orig_meta = lyd_find_meta(diff_match->meta, NULL, orig_meta_name); - LY_CHECK_ERR_RET(!orig_meta, LOGERR_META(ctx, orig_meta_name, diff_match), LY_EINVAL); + orig_meta = lyd_find_meta((*diff_match)->meta, NULL, orig_meta_name); + LY_CHECK_ERR_RET(!orig_meta, LOGERR_META(ctx, orig_meta_name, *diff_match), LY_EINVAL); /* the (incorrect) assumption made here is that there are no previous diff nodes that would affect * the anchors stored in the metadata */ if (strcmp(lyd_get_meta_value(meta), lyd_get_meta_value(orig_meta))) { /* deleted + created at another position -> operation REPLACE */ - LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_REPLACE)); + LY_CHECK_RET(lyd_diff_change_op(*diff_match, LYD_DIFF_OP_REPLACE)); /* add anchor metadata */ - LY_CHECK_RET(lyd_dup_meta_single(meta, diff_match, NULL)); + LY_CHECK_RET(lyd_dup_meta_single(meta, *diff_match, NULL)); } else { /* deleted + created at the same position -> operation NONE */ - LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_NONE)); + LY_CHECK_RET(lyd_diff_change_op(*diff_match, LYD_DIFF_OP_NONE)); /* delete anchor metadata */ lyd_free_meta_single(orig_meta); } - } else if (diff_match->schema->nodetype == LYS_LEAF) { + } else if (src_diff->schema->nodetype == LYS_LEAF) { if (options & LYD_DIFF_MERGE_DEFAULTS) { /* we are dealing with a leaf and are handling default values specially (as explicit nodes) */ - sleaf = (struct lysc_node_leaf *)diff_match->schema; + sleaf = (struct lysc_node_leaf *)src_diff->schema; } if (sleaf && sleaf->dflt && !sleaf->dflt->realtype->plugin->compare(sleaf->dflt, &((struct lyd_node_term *)src_diff)->value)) { /* we deleted it, so a default value was in-use, and it matches the created value -> operation NONE */ - LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_NONE)); - } else if (!lyd_compare_single(diff_match, src_diff, 0)) { + LY_CHECK_RET(lyd_diff_change_op(*diff_match, LYD_DIFF_OP_NONE)); + } else if (!lyd_compare_single(*diff_match, src_diff, 0)) { /* deleted + created -> operation NONE */ - LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_NONE)); - } else { + LY_CHECK_RET(lyd_diff_change_op(*diff_match, LYD_DIFF_OP_NONE)); + } else if ((*diff_match)->schema) { /* we deleted it, but it was created with a different value -> operation REPLACE */ - LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_REPLACE)); + LY_CHECK_RET(lyd_diff_change_op(*diff_match, LYD_DIFF_OP_REPLACE)); /* current value is the previous one (meta) */ - LY_CHECK_RET(lyd_new_meta(LYD_CTX(src_diff), diff_match, NULL, "yang:orig-value", - lyd_get_value(diff_match), 0, NULL)); + LY_CHECK_RET(lyd_new_meta(LYD_CTX(src_diff), *diff_match, NULL, "yang:orig-value", + lyd_get_value(*diff_match), 0, NULL)); /* update the value itself */ - LY_CHECK_RET(lyd_change_term(diff_match, lyd_get_value(src_diff))); + LY_CHECK_RET(lyd_change_term(*diff_match, lyd_get_value(src_diff))); + } else { + /* also operation REPLACE but we need to change an opaque node into a data node */ + LY_CHECK_RET(lyd_dup_single(src_diff, (*diff_match)->parent, LYD_DUP_NO_META | LYD_DUP_WITH_FLAGS, &src_dup)); + if (!(*diff_match)->parent) { + /* will always be inserted before diff_match, which is opaque */ + LY_CHECK_RET(lyd_insert_sibling(*diff_match, src_dup, diff)); + } + to_free = *diff_match; + *diff_match = src_dup; + + r = lyd_new_meta(ctx, src_dup, NULL, "yang:orig-value", lyd_get_value(to_free), 0, NULL); + lyd_free_tree(to_free); + LY_CHECK_RET(r); + LY_CHECK_RET(lyd_new_meta(ctx, src_dup, NULL, "yang:operation", lyd_diff_op2str(LYD_DIFF_OP_REPLACE), 0, NULL)); } } else { /* deleted + created -> operation NONE */ - LY_CHECK_RET(lyd_diff_change_op(diff_match, LYD_DIFF_OP_NONE)); + LY_CHECK_RET(lyd_diff_change_op(*diff_match, LYD_DIFF_OP_NONE)); } - if (diff_match->schema->nodetype & LYD_NODE_TERM) { + assert((*diff_match)->schema); + if ((*diff_match)->schema->nodetype & LYD_NODE_TERM) { /* add orig-dflt metadata */ - LY_CHECK_RET(lyd_new_meta(LYD_CTX(src_diff), diff_match, NULL, "yang:orig-default", + LY_CHECK_RET(lyd_new_meta(LYD_CTX(src_diff), *diff_match, NULL, "yang:orig-default", trg_flags & LYD_DEFAULT ? "true" : "false", 0, NULL)); /* update dflt flag itself */ - diff_match->flags &= ~LYD_DEFAULT; - diff_match->flags |= src_diff->flags & LYD_DEFAULT; + (*diff_match)->flags &= ~LYD_DEFAULT; + (*diff_match)->flags |= src_diff->flags & LYD_DEFAULT; } /* but the operation of its children should remain DELETE */ - LY_LIST_FOR(lyd_child_no_keys(diff_match), child) { + LY_LIST_FOR(lyd_child_no_keys(*diff_match), child) { LY_CHECK_RET(lyd_diff_change_op(child, LYD_DIFF_OP_DELETE)); } break; default: /* create and replace operations are not valid */ - LOGERR_MERGEOP(LYD_CTX(src_diff), diff_match, cur_op, LYD_DIFF_OP_CREATE); + LOGERR_MERGEOP(LYD_CTX(src_diff), *diff_match, cur_op, LYD_DIFF_OP_CREATE); return LY_EINVAL; } @@ -1599,7 +1740,7 @@ lyd_diff_merge_delete(struct lyd_node *diff_match, enum lyd_diff_op cur_op, cons if (diff_match->schema->nodetype & LYD_NODE_TERM) { /* add orig-default meta because it is expected */ LY_CHECK_RET(lyd_new_meta(LYD_CTX(src_diff), diff_match, NULL, "yang:orig-default", - diff_match->flags & LYD_DEFAULT ? "true" : "false", 0, NULL)); + src_diff->flags & LYD_DEFAULT ? "true" : "false", 0, NULL)); } else if (!lysc_is_dup_inst_list(diff_match->schema)) { /* keep operation for all descendants (for now) */ LY_LIST_FOR(lyd_child_no_keys(diff_match), child) { @@ -1723,17 +1864,24 @@ lyd_diff_is_redundant(struct lyd_node *diff) */ return 1; } - } else if ((op == LYD_DIFF_OP_NONE) && (diff->schema->nodetype & LYD_NODE_TERM)) { - /* check whether at least the default flags are different */ - meta = lyd_find_meta(diff->meta, mod, "orig-default"); - assert(meta); - str = lyd_get_meta_value(meta); - - /* if previous and current dflt flags are the same, this node is redundant */ - if ((!strcmp(str, "true") && (diff->flags & LYD_DEFAULT)) || (!strcmp(str, "false") && !(diff->flags & LYD_DEFAULT))) { + } else if (op == LYD_DIFF_OP_NONE) { + if (!diff->schema) { + /* opaque node with none must be redundant */ return 1; } - return 0; + + if (diff->schema->nodetype & LYD_NODE_TERM) { + /* check whether at least the default flags are different */ + meta = lyd_find_meta(diff->meta, mod, "orig-default"); + assert(meta); + str = lyd_get_meta_value(meta); + + /* if previous and current dflt flags are the same, this node is redundant */ + if ((!strcmp(str, "true") && (diff->flags & LYD_DEFAULT)) || (!strcmp(str, "false") && !(diff->flags & LYD_DEFAULT))) { + return 1; + } + return 0; + } } if (!child && (op == LYD_DIFF_OP_NONE)) { @@ -1757,12 +1905,12 @@ lyd_diff_is_redundant(struct lyd_node *diff) */ static LY_ERR lyd_diff_merge_r(const struct lyd_node *src_diff, struct lyd_node *diff_parent, lyd_diff_cb diff_cb, void *cb_data, - struct lyd_dup_inst **dup_inst, uint16_t options, struct lyd_node **diff) + struct ly_ht **dup_inst, uint16_t options, struct lyd_node **diff) { LY_ERR ret = LY_SUCCESS; struct lyd_node *child, *diff_node = NULL; enum lyd_diff_op src_op, cur_op; - struct lyd_dup_inst *child_dup_inst = NULL; + struct ly_ht *child_dup_inst = NULL; /* get source node operation */ LY_CHECK_RET(lyd_diff_get_op(src_diff, &src_op)); @@ -1785,7 +1933,7 @@ lyd_diff_merge_r(const struct lyd_node *src_diff, struct lyd_node *diff_parent, goto add_diff; } - ret = lyd_diff_merge_create(diff_node, cur_op, src_diff, options); + ret = lyd_diff_merge_create(&diff_node, diff, cur_op, src_diff, options); break; case LYD_DIFF_OP_DELETE: ret = lyd_diff_merge_delete(diff_node, cur_op, src_diff); @@ -1868,7 +2016,7 @@ lyd_diff_merge_module(struct lyd_node **diff, const struct lyd_node *src_diff, c lyd_diff_cb diff_cb, void *cb_data, uint16_t options) { const struct lyd_node *src_root; - struct lyd_dup_inst *dup_inst = NULL; + struct ly_ht *dup_inst = NULL; LY_ERR ret = LY_SUCCESS; LY_LIST_FOR(src_diff, src_root) { @@ -1891,7 +2039,7 @@ lyd_diff_merge_tree(struct lyd_node **diff_first, struct lyd_node *diff_parent, lyd_diff_cb diff_cb, void *cb_data, uint16_t options) { LY_ERR ret; - struct lyd_dup_inst *dup_inst = NULL; + struct ly_ht *dup_inst = NULL; if (!src_sibling) { return LY_SUCCESS; diff --git a/src/hash_table.c b/src/hash_table.c index 4f9dec3..9655bd6 100644 --- a/src/hash_table.c +++ b/src/hash_table.c @@ -1,9 +1,10 @@ /** * @file hash_table.c * @author Radek Krejci <rkrejci@cesnet.cz> - * @brief libyang dictionary for storing strings and generic hash table + * @author Michal Vasko <mvasko@cesnet.cz> + * @brief libyang generic hash table implementation * - * Copyright (c) 2015 - 2018 CESNET, z.s.p.o. + * Copyright (c) 2015 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -25,80 +26,8 @@ #include "dict.h" #include "log.h" -#define LYDICT_MIN_SIZE 1024 - -/** - * @brief Comparison callback for dictionary's hash table - * - * Implementation of ::lyht_value_equal_cb. - */ -static ly_bool -lydict_val_eq(void *val1_p, void *val2_p, ly_bool UNUSED(mod), void *cb_data) -{ - LY_CHECK_ARG_RET(NULL, val1_p, val2_p, cb_data, 0); - - const char *str1 = ((struct dict_rec *)val1_p)->value; - const char *str2 = ((struct dict_rec *)val2_p)->value; - - LY_CHECK_ERR_RET(!str1, LOGARG(NULL, val1_p), 0); - LY_CHECK_ERR_RET(!str2, LOGARG(NULL, val2_p), 0); - - if (strncmp(str1, str2, *(size_t *)cb_data) == 0) { - return 1; - } - - return 0; -} - -void -lydict_init(struct dict_table *dict) -{ - LY_CHECK_ARG_RET(NULL, dict, ); - - dict->hash_tab = lyht_new(LYDICT_MIN_SIZE, sizeof(struct dict_rec), lydict_val_eq, NULL, 1); - LY_CHECK_ERR_RET(!dict->hash_tab, LOGINT(NULL), ); - pthread_mutex_init(&dict->lock, NULL); -} - -void -lydict_clean(struct dict_table *dict) -{ - struct dict_rec *dict_rec = NULL; - struct ht_rec *rec = NULL; - - LY_CHECK_ARG_RET(NULL, dict, ); - - for (uint32_t i = 0; i < dict->hash_tab->size; i++) { - /* get ith record */ - rec = (struct ht_rec *)&dict->hash_tab->recs[i * dict->hash_tab->rec_size]; - if (rec->hits == 1) { - /* - * this should not happen, all records inserted into - * dictionary are supposed to be removed using lydict_remove() - * before calling lydict_clean() - */ - dict_rec = (struct dict_rec *)rec->val; - LOGWRN(NULL, "String \"%s\" not freed from the dictionary, refcount %d", dict_rec->value, dict_rec->refcount); - /* if record wasn't removed before free string allocated for that record */ -#ifdef NDEBUG - free(dict_rec->value); -#endif - } - } - - /* free table and destroy mutex */ - lyht_free(dict->hash_tab); - pthread_mutex_destroy(&dict->lock); -} - -/* - * Usage: - * - init hash to 0 - * - repeatedly call dict_hash_multi(), provide hash from the last call - * - call dict_hash_multi() with key_part = NULL to finish the hash - */ -uint32_t -dict_hash_multi(uint32_t hash, const char *key_part, size_t len) +LIBYANG_API_DEF uint32_t +lyht_hash_multi(uint32_t hash, const char *key_part, size_t len) { uint32_t i; @@ -117,203 +46,47 @@ dict_hash_multi(uint32_t hash, const char *key_part, size_t len) return hash; } -/* - * Bob Jenkin's one-at-a-time hash - * http://www.burtleburtle.net/bob/hash/doobs.html - * - * Spooky hash is faster, but it works only for little endian architectures. - */ -uint32_t -dict_hash(const char *key, size_t len) +LIBYANG_API_DEF uint32_t +lyht_hash(const char *key, size_t len) { uint32_t hash; - hash = dict_hash_multi(0, key, len); - return dict_hash_multi(hash, NULL, len); + hash = lyht_hash_multi(0, key, len); + return lyht_hash_multi(hash, NULL, len); } -static ly_bool -lydict_resize_val_eq(void *val1_p, void *val2_p, ly_bool mod, void *cb_data) -{ - LY_CHECK_ARG_RET(NULL, val1_p, val2_p, 0); - - const char *str1 = ((struct dict_rec *)val1_p)->value; - const char *str2 = ((struct dict_rec *)val2_p)->value; - - LY_CHECK_ERR_RET(!str1, LOGARG(NULL, val1_p), 0); - LY_CHECK_ERR_RET(!str2, LOGARG(NULL, val2_p), 0); - - if (mod) { - /* used when inserting new values */ - if (strcmp(str1, str2) == 0) { - return 1; - } - } else { - /* used when finding the original value again in the resized table */ - return lydict_val_eq(val1_p, val2_p, mod, cb_data); - } - - return 0; -} - -LIBYANG_API_DEF LY_ERR -lydict_remove(const struct ly_ctx *ctx, const char *value) -{ - LY_ERR ret = LY_SUCCESS; - size_t len; - uint32_t hash; - struct dict_rec rec, *match = NULL; - char *val_p; - - if (!ctx || !value) { - return LY_SUCCESS; - } - - LOGDBG(LY_LDGDICT, "removing \"%s\"", value); - - len = strlen(value); - hash = dict_hash(value, len); - - /* create record for lyht_find call */ - rec.value = (char *)value; - rec.refcount = 0; - - pthread_mutex_lock((pthread_mutex_t *)&ctx->dict.lock); - /* set len as data for compare callback */ - lyht_set_cb_data(ctx->dict.hash_tab, (void *)&len); - /* check if value is already inserted */ - ret = lyht_find(ctx->dict.hash_tab, &rec, hash, (void **)&match); - - if (ret == LY_SUCCESS) { - LY_CHECK_ERR_GOTO(!match, LOGINT(ctx), finish); - - /* if value is already in dictionary, decrement reference counter */ - match->refcount--; - if (match->refcount == 0) { - /* - * remove record - * save pointer to stored string before lyht_remove to - * free it after it is removed from hash table - */ - val_p = match->value; - ret = lyht_remove_with_resize_cb(ctx->dict.hash_tab, &rec, hash, lydict_resize_val_eq); - free(val_p); - LY_CHECK_ERR_GOTO(ret, LOGINT(ctx), finish); - } - } else if (ret == LY_ENOTFOUND) { - LOGERR(ctx, LY_ENOTFOUND, "Value \"%s\" was not found in the dictionary.", value); - } else { - LOGINT(ctx); - } - -finish: - pthread_mutex_unlock((pthread_mutex_t *)&ctx->dict.lock); - return ret; -} - -LY_ERR -dict_insert(const struct ly_ctx *ctx, char *value, size_t len, ly_bool zerocopy, const char **str_p) +static LY_ERR +lyht_init_hlists_and_records(struct ly_ht *ht) { - LY_ERR ret = LY_SUCCESS; - struct dict_rec *match = NULL, rec; - uint32_t hash; - - LOGDBG(LY_LDGDICT, "inserting \"%.*s\"", (int)len, value); - - hash = dict_hash(value, len); - /* set len as data for compare callback */ - lyht_set_cb_data(ctx->dict.hash_tab, (void *)&len); - /* create record for lyht_insert */ - rec.value = value; - rec.refcount = 1; + struct ly_ht_rec *rec; + uint32_t i; - ret = lyht_insert_with_resize_cb(ctx->dict.hash_tab, (void *)&rec, hash, lydict_resize_val_eq, (void **)&match); - if (ret == LY_EEXIST) { - match->refcount++; - if (zerocopy) { - free(value); - } - ret = LY_SUCCESS; - } else if (ret == LY_SUCCESS) { - if (!zerocopy) { - /* - * allocate string for new record - * record is already inserted in hash table - */ - match->value = malloc(sizeof *match->value * (len + 1)); - LY_CHECK_ERR_RET(!match->value, LOGMEM(ctx), LY_EMEM); - if (len) { - memcpy(match->value, value, len); - } - match->value[len] = '\0'; - } - } else { - /* lyht_insert returned error */ - if (zerocopy) { - free(value); + ht->recs = calloc(ht->size, ht->rec_size); + LY_CHECK_ERR_RET(!ht->recs, LOGMEM(NULL), LY_EMEM); + for (i = 0; i < ht->size; i++) { + rec = lyht_get_rec(ht->recs, ht->rec_size, i); + if (i != ht->size) { + rec->next = i + 1; + } else { + rec->next = LYHT_NO_RECORD; } - return ret; - } - - if (str_p) { - *str_p = match->value; - } - - return ret; -} - -LIBYANG_API_DEF LY_ERR -lydict_insert(const struct ly_ctx *ctx, const char *value, size_t len, const char **str_p) -{ - LY_ERR result; - - LY_CHECK_ARG_RET(ctx, ctx, str_p, LY_EINVAL); - - if (!value) { - *str_p = NULL; - return LY_SUCCESS; - } - - if (!len) { - len = strlen(value); } - pthread_mutex_lock((pthread_mutex_t *)&ctx->dict.lock); - result = dict_insert(ctx, (char *)value, len, 0, str_p); - pthread_mutex_unlock((pthread_mutex_t *)&ctx->dict.lock); - - return result; -} - -LIBYANG_API_DEF LY_ERR -lydict_insert_zc(const struct ly_ctx *ctx, char *value, const char **str_p) -{ - LY_ERR result; - - LY_CHECK_ARG_RET(ctx, ctx, str_p, LY_EINVAL); - - if (!value) { - *str_p = NULL; - return LY_SUCCESS; + ht->hlists = malloc(sizeof(ht->hlists[0]) * ht->size); + LY_CHECK_ERR_RET(!ht->hlists, free(ht->recs); LOGMEM(NULL), LY_EMEM); + for (i = 0; i < ht->size; i++) { + ht->hlists[i].first = LYHT_NO_RECORD; + ht->hlists[i].last = LYHT_NO_RECORD; } + ht->first_free_rec = 0; - pthread_mutex_lock((pthread_mutex_t *)&ctx->dict.lock); - result = dict_insert(ctx, value, strlen(value), 1, str_p); - pthread_mutex_unlock((pthread_mutex_t *)&ctx->dict.lock); - - return result; -} - -struct ht_rec * -lyht_get_rec(unsigned char *recs, uint16_t rec_size, uint32_t idx) -{ - return (struct ht_rec *)&recs[idx * rec_size]; + return LY_SUCCESS; } -struct hash_table * +LIBYANG_API_DEF struct ly_ht * lyht_new(uint32_t size, uint16_t val_size, lyht_value_equal_cb val_equal, void *cb_data, uint16_t resize) { - struct hash_table *ht; + struct ly_ht *ht; /* check that 2^x == size (power of 2) */ assert(size && !(size & (size - 1))); @@ -329,21 +102,21 @@ lyht_new(uint32_t size, uint16_t val_size, lyht_value_equal_cb val_equal, void * ht->used = 0; ht->size = size; - ht->invalid = 0; ht->val_equal = val_equal; ht->cb_data = cb_data; ht->resize = resize; - ht->rec_size = (sizeof(struct ht_rec) - 1) + val_size; - /* allocate the records correctly */ - ht->recs = calloc(size, ht->rec_size); - LY_CHECK_ERR_RET(!ht->recs, free(ht); LOGMEM(NULL), NULL); + ht->rec_size = SIZEOF_LY_HT_REC + val_size; + if (lyht_init_hlists_and_records(ht) != LY_SUCCESS) { + free(ht); + return NULL; + } return ht; } -lyht_value_equal_cb -lyht_set_cb(struct hash_table *ht, lyht_value_equal_cb new_val_equal) +LIBYANG_API_DEF lyht_value_equal_cb +lyht_set_cb(struct ly_ht *ht, lyht_value_equal_cb new_val_equal) { lyht_value_equal_cb prev; @@ -352,8 +125,8 @@ lyht_set_cb(struct hash_table *ht, lyht_value_equal_cb new_val_equal) return prev; } -void * -lyht_set_cb_data(struct hash_table *ht, void *new_cb_data) +LIBYANG_API_DEF void * +lyht_set_cb_data(struct ly_ht *ht, void *new_cb_data) { void *prev; @@ -362,31 +135,43 @@ lyht_set_cb_data(struct hash_table *ht, void *new_cb_data) return prev; } -struct hash_table * -lyht_dup(const struct hash_table *orig) +LIBYANG_API_DEF struct ly_ht * +lyht_dup(const struct ly_ht *orig) { - struct hash_table *ht; + struct ly_ht *ht; LY_CHECK_ARG_RET(NULL, orig, NULL); - ht = lyht_new(orig->size, orig->rec_size - (sizeof(struct ht_rec) - 1), orig->val_equal, orig->cb_data, orig->resize ? 1 : 0); + ht = lyht_new(orig->size, orig->rec_size - SIZEOF_LY_HT_REC, orig->val_equal, orig->cb_data, orig->resize ? 1 : 0); if (!ht) { return NULL; } - memcpy(ht->recs, orig->recs, (size_t)orig->used * (size_t)orig->rec_size); + memcpy(ht->hlists, orig->hlists, sizeof(ht->hlists[0]) * orig->size); + memcpy(ht->recs, orig->recs, (size_t)orig->size * orig->rec_size); ht->used = orig->used; - ht->invalid = orig->invalid; return ht; } -void -lyht_free(struct hash_table *ht) +LIBYANG_API_DEF void +lyht_free(struct ly_ht *ht, void (*val_free)(void *val_p)) { - if (ht) { - free(ht->recs); - free(ht); + struct ly_ht_rec *rec; + uint32_t hlist_idx; + uint32_t rec_idx; + + if (!ht) { + return; + } + + if (val_free) { + LYHT_ITER_ALL_RECS(ht, hlist_idx, rec_idx, rec) { + val_free(&rec->val); + } } + free(ht->hlists); + free(ht->recs); + free(ht); } /** @@ -397,14 +182,19 @@ lyht_free(struct hash_table *ht) * @return LY_ERR value. */ static LY_ERR -lyht_resize(struct hash_table *ht, int operation) +lyht_resize(struct ly_ht *ht, int operation, int check) { - struct ht_rec *rec; + struct ly_ht_rec *rec; + struct ly_ht_hlist *old_hlists; unsigned char *old_recs; + uint32_t old_first_free_rec; uint32_t i, old_size; + uint32_t rec_idx; + old_hlists = ht->hlists; old_recs = ht->recs; old_size = ht->size; + old_first_free_rec = ht->first_free_rec; if (operation > 0) { /* double the size */ @@ -414,18 +204,29 @@ lyht_resize(struct hash_table *ht, int operation) ht->size >>= 1; } - ht->recs = calloc(ht->size, ht->rec_size); - LY_CHECK_ERR_RET(!ht->recs, LOGMEM(NULL); ht->recs = old_recs; ht->size = old_size, LY_EMEM); + if (lyht_init_hlists_and_records(ht) != LY_SUCCESS) { + ht->hlists = old_hlists; + ht->recs = old_recs; + ht->size = old_size; + ht->first_free_rec = old_first_free_rec; + return LY_EMEM; + } - /* reset used and invalid, it will increase again */ + /* reset used, it will increase again */ ht->used = 0; - ht->invalid = 0; /* add all the old records into the new records array */ - for (i = 0; i < old_size; ++i) { - rec = lyht_get_rec(old_recs, ht->rec_size, i); - if (rec->hits > 0) { - LY_ERR ret = lyht_insert(ht, rec->val, rec->hash, NULL); + for (i = 0; i < old_size; i++) { + for (rec_idx = old_hlists[i].first, rec = lyht_get_rec(old_recs, ht->rec_size, rec_idx); + rec_idx != LYHT_NO_RECORD; + rec_idx = rec->next, rec = lyht_get_rec(old_recs, ht->rec_size, rec_idx)) { + LY_ERR ret; + + if (check) { + ret = lyht_insert(ht, rec->val, rec->hash, NULL); + } else { + ret = lyht_insert_no_check(ht, rec->val, rec->hash, NULL); + } assert(!ret); (void)ret; @@ -434,117 +235,18 @@ lyht_resize(struct hash_table *ht, int operation) /* final touches */ free(old_recs); + free(old_hlists); return LY_SUCCESS; } /** - * @brief Search for the first match. - * - * @param[in] ht Hash table to search in. - * @param[in] hash Hash to find. - * @param[out] rec_p Optional found record. - * @return LY_SUCCESS hash found, returned its record, - * @return LY_ENOTFOUND hash not found, returned the record where it would be inserted. - */ -static LY_ERR -lyht_find_first(struct hash_table *ht, uint32_t hash, struct ht_rec **rec_p) -{ - struct ht_rec *rec; - uint32_t i, idx; - - if (rec_p) { - *rec_p = NULL; - } - - idx = i = hash & (ht->size - 1); - rec = lyht_get_rec(ht->recs, ht->rec_size, idx); - - /* skip through overflow and deleted records */ - while ((rec->hits != 0) && ((rec->hits == -1) || ((rec->hash & (ht->size - 1)) != idx))) { - if ((rec->hits == -1) && rec_p && !(*rec_p)) { - /* remember this record for return */ - *rec_p = rec; - } - i = (i + 1) % ht->size; - if (i == idx) { - /* we went through all the records (very unlikely, but possible when many records are invalid), - * just return not found */ - assert(!rec_p || *rec_p); - return LY_ENOTFOUND; - } - rec = lyht_get_rec(ht->recs, ht->rec_size, i); - } - if (rec->hits == 0) { - /* we could not find the value */ - if (rec_p && !*rec_p) { - *rec_p = rec; - } - return LY_ENOTFOUND; - } - - /* we have found a record with equal (shortened) hash */ - if (rec_p) { - *rec_p = rec; - } - return LY_SUCCESS; -} - -/** - * @brief Search for the next collision. - * - * @param[in] ht Hash table to search in. - * @param[in,out] last Last returned collision record. - * @param[in] first First collision record (hits > 1). - * @return LY_SUCCESS when hash collision found, \p last points to this next collision, - * @return LY_ENOTFOUND when hash collision not found, \p last points to the record where it would be inserted. - */ -static LY_ERR -lyht_find_collision(struct hash_table *ht, struct ht_rec **last, struct ht_rec *first) -{ - struct ht_rec *empty = NULL; - uint32_t i, idx; - - assert(last && *last); - - idx = (*last)->hash & (ht->size - 1); - i = (((unsigned char *)*last) - ht->recs) / ht->rec_size; - - do { - i = (i + 1) % ht->size; - *last = lyht_get_rec(ht->recs, ht->rec_size, i); - if (*last == first) { - /* we went through all the records (very unlikely, but possible when many records are invalid), - * just return an invalid record */ - assert(empty); - *last = empty; - return LY_ENOTFOUND; - } - - if (((*last)->hits == -1) && !empty) { - empty = *last; - } - } while (((*last)->hits != 0) && (((*last)->hits == -1) || (((*last)->hash & (ht->size - 1)) != idx))); - - if ((*last)->hits > 0) { - /* we found a collision */ - assert((*last)->hits == 1); - return LY_SUCCESS; - } - - /* no next collision found, return the record where it would be inserted */ - if (empty) { - *last = empty; - } /* else (*last)->hits == 0, it is already correct */ - return LY_ENOTFOUND; -} - -/** * @brief Search for a record with specific value and hash. * * @param[in] ht Hash table to search in. * @param[in] val_p Pointer to the value to find. * @param[in] hash Hash to find. * @param[in] mod Whether the operation modifies the hash table (insert or remove) or not (find). + * @param[in] val_equal Callback for checking value equivalence. * @param[out] crec_p Optional found first record. * @param[out] col Optional collision number of @p rec_p, 0 for no collision. * @param[out] rec_p Found exact matching record, may be a collision of @p crec_p. @@ -552,12 +254,12 @@ lyht_find_collision(struct hash_table *ht, struct ht_rec **last, struct ht_rec * * @return LY_SUCCESS if record was found. */ static LY_ERR -lyht_find_rec(struct hash_table *ht, void *val_p, uint32_t hash, ly_bool mod, struct ht_rec **crec_p, uint32_t *col, - struct ht_rec **rec_p) +lyht_find_rec(const struct ly_ht *ht, void *val_p, uint32_t hash, ly_bool mod, lyht_value_equal_cb val_equal, + struct ly_ht_rec **crec_p, uint32_t *col, struct ly_ht_rec **rec_p) { - struct ht_rec *rec, *crec; - uint32_t i, c; - LY_ERR r; + uint32_t hlist_idx = hash & (ht->size - 1); + struct ly_ht_rec *rec; + uint32_t rec_idx; if (crec_p) { *crec_p = NULL; @@ -567,53 +269,43 @@ lyht_find_rec(struct hash_table *ht, void *val_p, uint32_t hash, ly_bool mod, st } *rec_p = NULL; - if (lyht_find_first(ht, hash, &rec)) { - /* not found */ - return LY_ENOTFOUND; - } - if ((rec->hash == hash) && ht->val_equal(val_p, &rec->val, mod, ht->cb_data)) { - /* even the value matches */ - if (crec_p) { - *crec_p = rec; - } - if (col) { - *col = 0; - } - *rec_p = rec; - return LY_SUCCESS; - } - - /* some collisions, we need to go through them, too */ - crec = rec; - c = crec->hits; - for (i = 1; i < c; ++i) { - r = lyht_find_collision(ht, &rec, crec); - assert(!r); - (void)r; - - /* compare values */ - if ((rec->hash == hash) && ht->val_equal(val_p, &rec->val, mod, ht->cb_data)) { + LYHT_ITER_HLIST_RECS(ht, hlist_idx, rec_idx, rec) { + if ((rec->hash == hash) && val_equal(val_p, &rec->val, mod, ht->cb_data)) { if (crec_p) { - *crec_p = crec; - } - if (col) { - *col = i; + *crec_p = rec; } *rec_p = rec; return LY_SUCCESS; } + + if (col) { + *col = *col + 1; + } } /* not found even in collisions */ return LY_ENOTFOUND; } -LY_ERR -lyht_find(struct hash_table *ht, void *val_p, uint32_t hash, void **match_p) +LIBYANG_API_DEF LY_ERR +lyht_find(const struct ly_ht *ht, void *val_p, uint32_t hash, void **match_p) +{ + struct ly_ht_rec *rec; + + lyht_find_rec(ht, val_p, hash, 0, ht->val_equal, NULL, NULL, &rec); + + if (rec && match_p) { + *match_p = rec->val; + } + return rec ? LY_SUCCESS : LY_ENOTFOUND; +} + +LIBYANG_API_DEF LY_ERR +lyht_find_with_val_cb(const struct ly_ht *ht, void *val_p, uint32_t hash, lyht_value_equal_cb val_equal, void **match_p) { - struct ht_rec *rec; + struct ly_ht_rec *rec; - lyht_find_rec(ht, val_p, hash, 0, NULL, NULL, &rec); + lyht_find_rec(ht, val_p, hash, 0, val_equal ? val_equal : ht->val_equal, NULL, NULL, &rec); if (rec && match_p) { *match_p = rec->val; @@ -621,26 +313,23 @@ lyht_find(struct hash_table *ht, void *val_p, uint32_t hash, void **match_p) return rec ? LY_SUCCESS : LY_ENOTFOUND; } -LY_ERR -lyht_find_next_with_collision_cb(struct hash_table *ht, void *val_p, uint32_t hash, +LIBYANG_API_DEF LY_ERR +lyht_find_next_with_collision_cb(const struct ly_ht *ht, void *val_p, uint32_t hash, lyht_value_equal_cb collision_val_equal, void **match_p) { - struct ht_rec *rec, *crec; - uint32_t i, c; - LY_ERR r; + struct ly_ht_rec *rec, *crec; + uint32_t rec_idx; + uint32_t i; /* find the record of the previously found value */ - if (lyht_find_rec(ht, val_p, hash, 1, &crec, &i, &rec)) { + if (lyht_find_rec(ht, val_p, hash, 1, ht->val_equal, &crec, &i, &rec)) { /* not found, cannot happen */ LOGINT_RET(NULL); } - /* go through collisions and find the next one after the previous one */ - c = crec->hits; - for (++i; i < c; ++i) { - r = lyht_find_collision(ht, &rec, crec); - assert(!r); - (void)r; + for (rec_idx = rec->next, rec = lyht_get_rec(ht->recs, ht->rec_size, rec_idx); + rec_idx != LYHT_NO_RECORD; + rec_idx = rec->next, rec = lyht_get_rec(ht->recs, ht->rec_size, rec_idx)) { if (rec->hash != hash) { continue; @@ -667,71 +356,51 @@ lyht_find_next_with_collision_cb(struct hash_table *ht, void *val_p, uint32_t ha return LY_ENOTFOUND; } -LY_ERR -lyht_find_next(struct hash_table *ht, void *val_p, uint32_t hash, void **match_p) +LIBYANG_API_DEF LY_ERR +lyht_find_next(const struct ly_ht *ht, void *val_p, uint32_t hash, void **match_p) { return lyht_find_next_with_collision_cb(ht, val_p, hash, NULL, match_p); } -LY_ERR -lyht_insert_with_resize_cb(struct hash_table *ht, void *val_p, uint32_t hash, lyht_value_equal_cb resize_val_equal, - void **match_p) +static LY_ERR +_lyht_insert_with_resize_cb(struct ly_ht *ht, void *val_p, uint32_t hash, lyht_value_equal_cb resize_val_equal, + void **match_p, int check) { + uint32_t hlist_idx = hash & (ht->size - 1); LY_ERR r, ret = LY_SUCCESS; - struct ht_rec *rec, *crec = NULL; - int32_t i; + struct ly_ht_rec *rec, *prev_rec; lyht_value_equal_cb old_val_equal = NULL; + uint32_t rec_idx; - if (!lyht_find_first(ht, hash, &rec)) { - /* we found matching shortened hash */ - if ((rec->hash == hash) && ht->val_equal(val_p, &rec->val, 1, ht->cb_data)) { - /* even the value matches */ - if (match_p) { - *match_p = (void *)&rec->val; + if (check) { + if (lyht_find_rec(ht, val_p, hash, 1, ht->val_equal, NULL, NULL, &rec) == LY_SUCCESS) { + if (rec && match_p) { + *match_p = rec->val; } return LY_EEXIST; } + } - /* some collisions, we need to go through them, too */ - crec = rec; - for (i = 1; i < crec->hits; ++i) { - r = lyht_find_collision(ht, &rec, crec); - assert(!r); - - /* compare values */ - if ((rec->hash == hash) && ht->val_equal(val_p, &rec->val, 1, ht->cb_data)) { - if (match_p) { - *match_p = (void *)&rec->val; - } - return LY_EEXIST; - } - } + rec_idx = ht->first_free_rec; + assert(rec_idx < ht->size); + rec = lyht_get_rec(ht->recs, ht->rec_size, rec_idx); + ht->first_free_rec = rec->next; - /* value not found, get the record where it will be inserted */ - r = lyht_find_collision(ht, &rec, crec); - assert(r); + if (ht->hlists[hlist_idx].first == LYHT_NO_RECORD) { + ht->hlists[hlist_idx].first = rec_idx; + } else { + prev_rec = lyht_get_rec(ht->recs, ht->rec_size, ht->hlists[hlist_idx].last); + prev_rec->next = rec_idx; } + rec->next = LYHT_NO_RECORD; + ht->hlists[hlist_idx].last = rec_idx; - /* insert it into the returned record */ - assert(rec->hits < 1); - if (rec->hits < 0) { - --ht->invalid; - } rec->hash = hash; - rec->hits = 1; - memcpy(&rec->val, val_p, ht->rec_size - (sizeof(struct ht_rec) - 1)); + memcpy(&rec->val, val_p, ht->rec_size - SIZEOF_LY_HT_REC); if (match_p) { *match_p = (void *)&rec->val; } - if (crec) { - /* there was a collision, increase hits */ - if (crec->hits == INT32_MAX) { - LOGINT(NULL); - } - ++crec->hits; - } - /* check size & enlarge if needed */ ++ht->used; if (ht->resize) { @@ -746,10 +415,11 @@ lyht_insert_with_resize_cb(struct hash_table *ht, void *val_p, uint32_t hash, ly } /* enlarge */ - ret = lyht_resize(ht, 1); + ret = lyht_resize(ht, 1, check); /* if hash_table was resized, we need to find new matching value */ if ((ret == LY_SUCCESS) && match_p) { - lyht_find(ht, val_p, hash, match_p); + ret = lyht_find(ht, val_p, hash, match_p); + assert(!ret); } if (resize_val_equal) { @@ -760,61 +430,66 @@ lyht_insert_with_resize_cb(struct hash_table *ht, void *val_p, uint32_t hash, ly return ret; } -LY_ERR -lyht_insert(struct hash_table *ht, void *val_p, uint32_t hash, void **match_p) +LIBYANG_API_DEF LY_ERR +lyht_insert_with_resize_cb(struct ly_ht *ht, void *val_p, uint32_t hash, lyht_value_equal_cb resize_val_equal, + void **match_p) { - return lyht_insert_with_resize_cb(ht, val_p, hash, NULL, match_p); + return _lyht_insert_with_resize_cb(ht, val_p, hash, resize_val_equal, match_p, 1); } -LY_ERR -lyht_remove_with_resize_cb(struct hash_table *ht, void *val_p, uint32_t hash, lyht_value_equal_cb resize_val_equal) +LIBYANG_API_DEF LY_ERR +lyht_insert(struct ly_ht *ht, void *val_p, uint32_t hash, void **match_p) { - struct ht_rec *rec, *crec; - int32_t i; - ly_bool first_matched = 0; - LY_ERR r, ret = LY_SUCCESS; - lyht_value_equal_cb old_val_equal; + return _lyht_insert_with_resize_cb(ht, val_p, hash, NULL, match_p, 1); +} - LY_CHECK_ERR_RET(lyht_find_first(ht, hash, &rec), LOGARG(NULL, hash), LY_ENOTFOUND); /* hash not found */ +LIBYANG_API_DEF LY_ERR +lyht_insert_no_check(struct ly_ht *ht, void *val_p, uint32_t hash, void **match_p) +{ + return _lyht_insert_with_resize_cb(ht, val_p, hash, NULL, match_p, 0); +} - if ((rec->hash == hash) && ht->val_equal(val_p, &rec->val, 1, ht->cb_data)) { - /* even the value matches */ - first_matched = 1; - } +LIBYANG_API_DEF LY_ERR +lyht_remove_with_resize_cb(struct ly_ht *ht, void *val_p, uint32_t hash, lyht_value_equal_cb resize_val_equal) +{ + struct ly_ht_rec *found_rec, *prev_rec, *rec; + uint32_t hlist_idx = hash & (ht->size - 1); + LY_ERR r, ret = LY_SUCCESS; + lyht_value_equal_cb old_val_equal = NULL; + uint32_t prev_rec_idx; + uint32_t rec_idx; - /* we always need to go through collisions */ - crec = rec; - for (i = 1; i < crec->hits; ++i) { - r = lyht_find_collision(ht, &rec, crec); - assert(!r); + if (lyht_find_rec(ht, val_p, hash, 1, ht->val_equal, NULL, NULL, &found_rec)) { + LOGARG(NULL, hash); + return LY_ENOTFOUND; + } - /* compare values */ - if (!first_matched && (rec->hash == hash) && ht->val_equal(val_p, &rec->val, 1, ht->cb_data)) { + prev_rec_idx = LYHT_NO_RECORD; + LYHT_ITER_HLIST_RECS(ht, hlist_idx, rec_idx, rec) { + if (rec == found_rec) { break; } + prev_rec_idx = rec_idx; } - if (i < crec->hits) { - /* one of collisions matched, reduce collision count, remove the record */ - assert(!first_matched); - --crec->hits; - rec->hits = -1; - } else if (first_matched) { - /* the first record matches */ - if (crec != rec) { - /* ... so put the last collision in its place */ - rec->hits = crec->hits - 1; - memcpy(crec, rec, ht->rec_size); + if (prev_rec_idx == LYHT_NO_RECORD) { + ht->hlists[hlist_idx].first = rec->next; + if (rec->next == LYHT_NO_RECORD) { + ht->hlists[hlist_idx].last = LYHT_NO_RECORD; } - rec->hits = -1; } else { - /* value not found even in collisions */ - return LY_ENOTFOUND; + prev_rec = lyht_get_rec(ht->recs, ht->rec_size, prev_rec_idx); + prev_rec->next = rec->next; + if (rec->next == LYHT_NO_RECORD) { + ht->hlists[hlist_idx].last = prev_rec_idx; + } } + rec->next = ht->first_free_rec; + ht->first_free_rec = rec_idx; + /* check size & shrink if needed */ --ht->used; - ++ht->invalid; if (ht->resize == 2) { r = (ht->used * LYHT_HUNDRED_PERCENTAGE) / ht->size; if ((r < LYHT_SHRINK_PERCENTAGE) && (ht->size > LYHT_MIN_SIZE)) { @@ -823,7 +498,7 @@ lyht_remove_with_resize_cb(struct hash_table *ht, void *val_p, uint32_t hash, ly } /* shrink */ - ret = lyht_resize(ht, -1); + ret = lyht_resize(ht, -1, 1); if (resize_val_equal) { lyht_set_cb(ht, old_val_equal); @@ -831,50 +506,29 @@ lyht_remove_with_resize_cb(struct hash_table *ht, void *val_p, uint32_t hash, ly } } - /* rehash all records if needed */ - r = ((ht->size - ht->used - ht->invalid) * 100) / ht->size; - if (r < LYHT_REHASH_PERCENTAGE) { - if (resize_val_equal) { - old_val_equal = lyht_set_cb(ht, resize_val_equal); - } - - /* rehash */ - ret = lyht_resize(ht, 0); - - if (resize_val_equal) { - lyht_set_cb(ht, old_val_equal); - } - } - return ret; } -LY_ERR -lyht_remove(struct hash_table *ht, void *val_p, uint32_t hash) +LIBYANG_API_DEF LY_ERR +lyht_remove(struct ly_ht *ht, void *val_p, uint32_t hash) { return lyht_remove_with_resize_cb(ht, val_p, hash, NULL); } -uint32_t +LIBYANG_API_DEF uint32_t lyht_get_fixed_size(uint32_t item_count) { - uint32_t i, size = 0; - - /* detect number of upper zero bits in the items' counter value ... */ - for (i = (sizeof item_count * CHAR_BIT) - 1; i > 0; i--) { - size = item_count << i; - size = size >> i; - if (size == item_count) { - break; - } + if (item_count == 0) { + return 1; } - assert(i); - - /* ... and then we convert it to the position of the highest non-zero bit ... */ - i = (sizeof item_count * CHAR_BIT) - i; - /* ... and by using it to shift 1 to the left we get the closest sufficient hash table size */ - size = 1 << i; + /* return next power of 2 (greater or equal) */ + item_count--; + item_count |= item_count >> 1; + item_count |= item_count >> 2; + item_count |= item_count >> 4; + item_count |= item_count >> 8; + item_count |= item_count >> 16; - return size; + return item_count + 1; } diff --git a/src/hash_table.h b/src/hash_table.h index 91ae63d..5780f1e 100644 --- a/src/hash_table.h +++ b/src/hash_table.h @@ -4,7 +4,7 @@ * @author Michal Vasko <mvasko@cesnet.cz> * @brief libyang hash table * - * Copyright (c) 2015 - 2022 CESNET, z.s.p.o. + * Copyright (c) 2015 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -16,27 +16,49 @@ #ifndef LY_HASH_TABLE_H_ #define LY_HASH_TABLE_H_ -#include <pthread.h> #include <stddef.h> #include <stdint.h> -#include "compat.h" +#ifdef __cplusplus +extern "C" { +#endif + #include "log.h" /** + * @struct ly_ht + * @brief libyang hash table. + */ +struct ly_ht; + +/** * @brief Compute hash from (several) string(s). * * Usage: * - init hash to 0 - * - repeatedly call ::dict_hash_multi(), provide hash from the last call - * - call ::dict_hash_multi() with key_part = NULL to finish the hash + * - repeatedly call ::lyht_hash_multi(), provide hash from the last call + * - call ::lyht_hash_multi() with key_part = NULL to finish the hash + * + * @param[in] hash Previous hash. + * @param[in] key_part Next key to hash, + * @param[in] len Length of @p key_part. + * @return Hash with the next key. */ -uint32_t dict_hash_multi(uint32_t hash, const char *key_part, size_t len); +LIBYANG_API_DECL uint32_t lyht_hash_multi(uint32_t hash, const char *key_part, size_t len); /** * @brief Compute hash from a string. + * + * Bob Jenkin's one-at-a-time hash + * http://www.burtleburtle.net/bob/hash/doobs.html + * + * Spooky hash is faster, but it works only for little endian architectures. + * + * @param[in] key Key to hash. + * @param[in] len Length of @p key. + * @return Hash of the key. */ -uint32_t dict_hash(const char *key, size_t len); +LIBYANG_API_DECL uint32_t lyht_hash(const char *key, size_t len); /** * @brief Callback for checking hash table values equivalence. @@ -49,82 +71,6 @@ uint32_t dict_hash(const char *key, size_t len); */ typedef ly_bool (*lyht_value_equal_cb)(void *val1_p, void *val2_p, ly_bool mod, void *cb_data); -/** reference value for 100% */ -#define LYHT_HUNDRED_PERCENTAGE 100 - -/** when the table is at least this much percent full, it is enlarged (double the size) */ -#define LYHT_ENLARGE_PERCENTAGE 75 - -/** only once the table is this much percent full, enable shrinking */ -#define LYHT_FIRST_SHRINK_PERCENTAGE 50 - -/** when the table is less than this much percent full, it is shrunk (half the size) */ -#define LYHT_SHRINK_PERCENTAGE 25 - -/** when the table has less than this much percent empty records, it is rehashed to get rid of all the invalid records */ -#define LYHT_REHASH_PERCENTAGE 2 - -/** never shrink beyond this size */ -#define LYHT_MIN_SIZE 8 - -/** - * @brief Generic hash table record. - */ -struct ht_rec { - uint32_t hash; /* hash of the value */ - int32_t hits; /* collision/overflow value count - 1 (a filled entry has 1 hit, - * special value -1 means a deleted record) */ - unsigned char val[1]; /* arbitrary-size value */ -} _PACKED; - -/** - * @brief (Very) generic hash table. - * - * Hash table with open addressing collision resolution and - * linear probing of interval 1 (next free record is used). - * Removal is lazy (removed records are only marked), but - * if possible, they are fully emptied. - */ -struct hash_table { - uint32_t used; /* number of values stored in the hash table (filled records) */ - uint32_t size; /* always holds 2^x == size (is power of 2), actually number of records allocated */ - uint32_t invalid; /* number of invalid records (deleted) */ - lyht_value_equal_cb val_equal; /* callback for testing value equivalence */ - void *cb_data; /* user data callback arbitrary value */ - uint16_t resize; /* 0 - resizing is disabled, * - * 1 - enlarging is enabled, * - * 2 - both shrinking and enlarging is enabled */ - uint16_t rec_size; /* real size (in bytes) of one record for accessing recs array */ - unsigned char *recs; /* pointer to the hash table itself (array of struct ht_rec) */ -}; - -struct dict_rec { - char *value; - uint32_t refcount; -}; - -/** - * dictionary to store repeating strings - */ -struct dict_table { - struct hash_table *hash_tab; - pthread_mutex_t lock; -}; - -/** - * @brief Initiate content (non-zero values) of the dictionary - * - * @param[in] dict Dictionary table to initiate - */ -void lydict_init(struct dict_table *dict); - -/** - * @brief Cleanup the dictionary content - * - * @param[in] dict Dictionary table to cleanup - */ -void lydict_clean(struct dict_table *dict); - /** * @brief Create new hash table. * @@ -135,7 +81,8 @@ void lydict_clean(struct dict_table *dict); * @param[in] resize Whether to resize the table on too few/too many records taken. * @return Empty hash table, NULL on error. */ -struct hash_table *lyht_new(uint32_t size, uint16_t val_size, lyht_value_equal_cb val_equal, void *cb_data, uint16_t resize); +LIBYANG_API_DECL struct ly_ht *lyht_new(uint32_t size, uint16_t val_size, lyht_value_equal_cb val_equal, void *cb_data, + uint16_t resize); /** * @brief Set hash table value equal callback. @@ -144,7 +91,7 @@ struct hash_table *lyht_new(uint32_t size, uint16_t val_size, lyht_value_equal_c * @param[in] new_val_equal New callback for checking value equivalence. * @return Previous callback for checking value equivalence. */ -lyht_value_equal_cb lyht_set_cb(struct hash_table *ht, lyht_value_equal_cb new_val_equal); +LIBYANG_API_DECL lyht_value_equal_cb lyht_set_cb(struct ly_ht *ht, lyht_value_equal_cb new_val_equal); /** * @brief Set hash table value equal callback user data. @@ -153,7 +100,7 @@ lyht_value_equal_cb lyht_set_cb(struct hash_table *ht, lyht_value_equal_cb new_v * @param[in] new_cb_data New data for values callback. * @return Previous data for values callback. */ -void *lyht_set_cb_data(struct hash_table *ht, void *new_cb_data); +LIBYANG_API_DECL void *lyht_set_cb_data(struct ly_ht *ht, void *new_cb_data); /** * @brief Make a duplicate of an existing hash table. @@ -161,14 +108,15 @@ void *lyht_set_cb_data(struct hash_table *ht, void *new_cb_data); * @param[in] orig Original hash table to duplicate. * @return Duplicated hash table @p orig, NULL on error. */ -struct hash_table *lyht_dup(const struct hash_table *orig); +LIBYANG_API_DECL struct ly_ht *lyht_dup(const struct ly_ht *orig); /** * @brief Free a hash table. * * @param[in] ht Hash table to be freed. + * @param[in] val_free Optional callback for freeing all the stored values, @p val_p is a pointer to a stored value. */ -void lyht_free(struct hash_table *ht); +LIBYANG_API_DECL void lyht_free(struct ly_ht *ht, void (*val_free)(void *val_p)); /** * @brief Find a value in a hash table. @@ -180,7 +128,21 @@ void lyht_free(struct hash_table *ht); * @return LY_SUCCESS if value was found, * @return LY_ENOTFOUND if not found. */ -LY_ERR lyht_find(struct hash_table *ht, void *val_p, uint32_t hash, void **match_p); +LIBYANG_API_DECL LY_ERR lyht_find(const struct ly_ht *ht, void *val_p, uint32_t hash, void **match_p); + +/** + * @brief Find a value in a hash table but use a custom val_equal callback. + * + * @param[in] ht Hash table to search in. + * @param[in] val_p Pointer to the value to find. + * @param[in] hash Hash of the stored value. + * @param[in] val_equal Callback for checking value equivalence. + * @param[out] match_p Pointer to the matching value, optional. + * @return LY_SUCCESS if value was found, + * @return LY_ENOTFOUND if not found. + */ +LIBYANG_API_DECL LY_ERR lyht_find_with_val_cb(const struct ly_ht *ht, void *val_p, uint32_t hash, + lyht_value_equal_cb val_equal, void **match_p); /** * @brief Find another equal value in the hash table. @@ -192,7 +154,7 @@ LY_ERR lyht_find(struct hash_table *ht, void *val_p, uint32_t hash, void **match * @return LY_SUCCESS if value was found, * @return LY_ENOTFOUND if not found. */ -LY_ERR lyht_find_next(struct hash_table *ht, void *val_p, uint32_t hash, void **match_p); +LIBYANG_API_DECL LY_ERR lyht_find_next(const struct ly_ht *ht, void *val_p, uint32_t hash, void **match_p); /** * @brief Find another equal value in the hash table. Same functionality as ::lyht_find_next() @@ -206,7 +168,7 @@ LY_ERR lyht_find_next(struct hash_table *ht, void *val_p, uint32_t hash, void ** * @return LY_SUCCESS if value was found, * @return LY_ENOTFOUND if not found. */ -LY_ERR lyht_find_next_with_collision_cb(struct hash_table *ht, void *val_p, uint32_t hash, +LIBYANG_API_DECL LY_ERR lyht_find_next_with_collision_cb(const struct ly_ht *ht, void *val_p, uint32_t hash, lyht_value_equal_cb collision_val_equal, void **match_p); /** @@ -221,7 +183,20 @@ LY_ERR lyht_find_next_with_collision_cb(struct hash_table *ht, void *val_p, uint * @return LY_EEXIST in case the value is already present. * @return LY_EMEM in case of memory allocation failure. */ -LY_ERR lyht_insert(struct hash_table *ht, void *val_p, uint32_t hash, void **match_p); +LIBYANG_API_DECL LY_ERR lyht_insert(struct ly_ht *ht, void *val_p, uint32_t hash, void **match_p); + +/** + * @brief Insert a value into a hash table, without checking whether the value has already been inserted. + * + * @param[in] ht Hash table to insert into. + * @param[in] val_p Pointer to the value to insert. Be careful, if the values stored in the hash table + * are pointers, @p val_p must be a pointer to a pointer. + * @param[in] hash Hash of the stored value. + * @param[out] match_p Pointer to the stored value, optional + * @return LY_SUCCESS on success, + * @return LY_EMEM in case of memory allocation failure. + */ +LIBYANG_API_DECL LY_ERR lyht_insert_no_check(struct ly_ht *ht, void *val_p, uint32_t hash, void **match_p); /** * @brief Insert a value into hash table. Same functionality as ::lyht_insert() @@ -238,8 +213,8 @@ LY_ERR lyht_insert(struct hash_table *ht, void *val_p, uint32_t hash, void **mat * @return LY_EEXIST in case the value is already present. * @return LY_EMEM in case of memory allocation failure. */ -LY_ERR lyht_insert_with_resize_cb(struct hash_table *ht, void *val_p, uint32_t hash, lyht_value_equal_cb resize_val_equal, - void **match_p); +LIBYANG_API_DECL LY_ERR lyht_insert_with_resize_cb(struct ly_ht *ht, void *val_p, uint32_t hash, + lyht_value_equal_cb resize_val_equal, void **match_p); /** * @brief Remove a value from a hash table. @@ -251,7 +226,7 @@ LY_ERR lyht_insert_with_resize_cb(struct hash_table *ht, void *val_p, uint32_t h * @return LY_SUCCESS on success, * @return LY_ENOTFOUND if value was not found. */ -LY_ERR lyht_remove(struct hash_table *ht, void *val_p, uint32_t hash); +LIBYANG_API_DECL LY_ERR lyht_remove(struct ly_ht *ht, void *val_p, uint32_t hash); /** * @brief Remove a value from a hash table. Same functionality as ::lyht_remove() @@ -266,7 +241,8 @@ LY_ERR lyht_remove(struct hash_table *ht, void *val_p, uint32_t hash); * @return LY_SUCCESS on success, * @return LY_ENOTFOUND if value was not found. */ -LY_ERR lyht_remove_with_resize_cb(struct hash_table *ht, void *val_p, uint32_t hash, lyht_value_equal_cb resize_val_equal); +LIBYANG_API_DECL LY_ERR lyht_remove_with_resize_cb(struct ly_ht *ht, void *val_p, uint32_t hash, + lyht_value_equal_cb resize_val_equal); /** * @brief Get suitable size of a hash table for a fixed number of items. @@ -274,6 +250,10 @@ LY_ERR lyht_remove_with_resize_cb(struct hash_table *ht, void *val_p, uint32_t h * @param[in] item_count Number of stored items. * @return Hash table size. */ -uint32_t lyht_get_fixed_size(uint32_t item_count); +LIBYANG_API_DECL uint32_t lyht_get_fixed_size(uint32_t item_count); + +#ifdef __cplusplus +} +#endif #endif /* LY_HASH_TABLE_H_ */ diff --git a/src/hash_table_internal.h b/src/hash_table_internal.h new file mode 100644 index 0000000..22b6cf4 --- /dev/null +++ b/src/hash_table_internal.h @@ -0,0 +1,146 @@ +/** + * @file hash_table_internal.h + * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Michal Vasko <mvasko@cesnet.cz> + * @brief libyang hash table internal header + * + * Copyright (c) 2015 - 2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#ifndef LY_HASH_TABLE_INTERNAL_H_ +#define LY_HASH_TABLE_INTERNAL_H_ + +#include <pthread.h> +#include <stddef.h> +#include <stdint.h> + +#include "compat.h" +#include "hash_table.h" + +/** reference value for 100% */ +#define LYHT_HUNDRED_PERCENTAGE 100 + +/** when the table is at least this much percent full, it is enlarged (double the size) */ +#define LYHT_ENLARGE_PERCENTAGE 75 + +/** only once the table is this much percent full, enable shrinking */ +#define LYHT_FIRST_SHRINK_PERCENTAGE 50 + +/** when the table is less than this much percent full, it is shrunk (half the size) */ +#define LYHT_SHRINK_PERCENTAGE 25 + +/** never shrink beyond this size */ +#define LYHT_MIN_SIZE 8 + +/** + * @brief Generic hash table record. + */ +struct ly_ht_rec { + uint32_t hash; /* hash of the value */ + uint32_t next; /* index of next collision */ + unsigned char val[1]; /* arbitrary-size value */ +} _PACKED; + +/* real size, without taking the val[1] in account */ +#define SIZEOF_LY_HT_REC (sizeof(struct ly_ht_rec) - 1) + +struct ly_ht_hlist { + uint32_t first; + uint32_t last; +}; + +/** + * @brief (Very) generic hash table. + * + * This structure implements a hash table, providing fast accesses to stored + * values from their hash. + * + * The hash table structure contains 2 pointers to tables that are allocated + * at initialization: + * - a table of records: each record is composed of a struct ly_ht_rec header, + * followed by the user value. The header contains the hash value and a + * next index that can be used to chain records. + * - a table of list heads: each list head entry contains the index of the + * first record in the records table whose hash (modulo hash table size) + * is equal to the index of the list head entry. The other matching records + * are chained together. + * + * The unused records are chained in first_free_rec, which contains the index + * of the first unused record entry in the records table. + * + * The LYHT_NO_RECORD magic value is used when an index points to nothing. + */ +struct ly_ht { + uint32_t used; /* number of values stored in the hash table (filled records) */ + uint32_t size; /* always holds 2^x == size (is power of 2), actually number of records allocated */ + lyht_value_equal_cb val_equal; /* callback for testing value equivalence */ + void *cb_data; /* user data callback arbitrary value */ + uint16_t resize; /* 0 - resizing is disabled, * + * 1 - enlarging is enabled, * + * 2 - both shrinking and enlarging is enabled */ + uint16_t rec_size; /* real size (in bytes) of one record for accessing recs array */ + uint32_t first_free_rec; /* index of the first free record */ + struct ly_ht_hlist *hlists; /* pointer to the hlists table */ + unsigned char *recs; /* pointer to the hash table itself (array of struct ht_rec) */ +}; + +/* index that points to nothing */ +#define LYHT_NO_RECORD UINT32_MAX + +/* get the record associated to */ +static inline struct ly_ht_rec * +lyht_get_rec(unsigned char *recs, uint16_t rec_size, uint32_t idx) +{ + return (struct ly_ht_rec *)&recs[idx * rec_size]; +} + +/* Iterate all records in a hlist */ +#define LYHT_ITER_HLIST_RECS(ht, hlist_idx, rec_idx, rec) \ + for (rec_idx = ht->hlists[hlist_idx].first, \ + rec = lyht_get_rec(ht->recs, ht->rec_size, rec_idx); \ + rec_idx != LYHT_NO_RECORD; \ + rec_idx = rec->next, \ + rec = lyht_get_rec(ht->recs, ht->rec_size, rec_idx)) + +/* Iterate all records in the hash table */ +#define LYHT_ITER_ALL_RECS(ht, hlist_idx, rec_idx, rec) \ + for (hlist_idx = 0; hlist_idx < ht->size; hlist_idx++) \ + LYHT_ITER_HLIST_RECS(ht, hlist_idx, rec_idx, rec) + +/** + * @brief Dictionary hash table record. + */ +struct ly_dict_rec { + char *value; /**< stored string */ + uint32_t refcount; /**< reference count of the string */ +}; + +/** + * @brief Dictionary for storing repeated strings. + */ +struct ly_dict { + struct ly_ht *hash_tab; + pthread_mutex_t lock; +}; + +/** + * @brief Initiate content (non-zero values) of the dictionary + * + * @param[in] dict Dictionary table to initiate + */ +void lydict_init(struct ly_dict *dict); + +/** + * @brief Cleanup the dictionary content + * + * @param[in] dict Dictionary table to cleanup + */ +void lydict_clean(struct ly_dict *dict); + +#endif /* LY_HASH_TABLE_INTERNAL_H_ */ @@ -1,9 +1,10 @@ /** * @file json.c * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Michal Vasko <mvasko@cesnet.cz> * @brief Generic JSON format parser for libyang * - * Copyright (c) 2020 CESNET, z.s.p.o. + * Copyright (c) 2020 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -30,30 +31,30 @@ lyjson_token2str(enum LYJSON_PARSER_STATUS status) switch (status) { case LYJSON_ERROR: return "error"; - case LYJSON_ROOT: - return "document root"; - case LYJSON_FALSE: - return "false"; - case LYJSON_TRUE: - return "true"; - case LYJSON_NULL: - return "null"; case LYJSON_OBJECT: return "object"; + case LYJSON_OBJECT_NEXT: + return "object next"; case LYJSON_OBJECT_CLOSED: return "object closed"; - case LYJSON_OBJECT_EMPTY: - return "empty object"; case LYJSON_ARRAY: return "array"; + case LYJSON_ARRAY_NEXT: + return "array next"; case LYJSON_ARRAY_CLOSED: return "array closed"; - case LYJSON_ARRAY_EMPTY: - return "empty array"; + case LYJSON_OBJECT_NAME: + return "object name"; case LYJSON_NUMBER: return "number"; case LYJSON_STRING: return "string"; + case LYJSON_TRUE: + return "true"; + case LYJSON_FALSE: + return "false"; + case LYJSON_NULL: + return "null"; case LYJSON_END: return "end of input"; } @@ -61,25 +62,48 @@ lyjson_token2str(enum LYJSON_PARSER_STATUS status) return ""; } -static LY_ERR -skip_ws(struct lyjson_ctx *jsonctx) +enum LYJSON_PARSER_STATUS +lyjson_ctx_status(struct lyjson_ctx *jsonctx) { - /* skip leading whitespaces */ - while (*jsonctx->in->current != '\0' && is_jsonws(*jsonctx->in->current)) { + assert(jsonctx); + + if (!jsonctx->status.count) { + return LYJSON_END; + } + + return (enum LYJSON_PARSER_STATUS)(uintptr_t)jsonctx->status.objs[jsonctx->status.count - 1]; +} + +uint32_t +lyjson_ctx_depth(struct lyjson_ctx *jsonctx) +{ + return jsonctx->status.count; +} + +/** + * @brief Skip WS in the JSON context. + * + * @param[in] jsonctx JSON parser context. + */ +static void +lyjson_skip_ws(struct lyjson_ctx *jsonctx) +{ + /* skip whitespaces */ + while (is_jsonws(*jsonctx->in->current)) { if (*jsonctx->in->current == '\n') { LY_IN_NEW_LINE(jsonctx->in); } ly_in_skip(jsonctx->in, 1); } - if (*jsonctx->in->current == '\0') { - LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_END); - } - - return LY_SUCCESS; } -/* - * @brief Set value corresponding to the current context's status +/** + * @brief Set value in the JSON context. + * + * @param[in] jsonctx JSON parser context. + * @param[in] value Value to set. + * @param[in] value_len Length of @p value. + * @param[in] dynamic Whether @p value is dynamically-allocated. */ static void lyjson_ctx_set_value(struct lyjson_ctx *jsonctx, const char *value, size_t value_len, ly_bool dynamic) @@ -94,48 +118,24 @@ lyjson_ctx_set_value(struct lyjson_ctx *jsonctx, const char *value, size_t value jsonctx->dynamic = dynamic; } -static LY_ERR -lyjson_check_next(struct lyjson_ctx *jsonctx) -{ - if (jsonctx->status.count == 1) { - /* top level value (JSON-text), ws expected */ - if ((*jsonctx->in->current == '\0') || is_jsonws(*jsonctx->in->current)) { - return LY_SUCCESS; - } - } else if (lyjson_ctx_status(jsonctx, 1) == LYJSON_OBJECT) { - LY_CHECK_RET(skip_ws(jsonctx)); - if ((*jsonctx->in->current == ',') || (*jsonctx->in->current == '}')) { - return LY_SUCCESS; - } - } else if (lyjson_ctx_status(jsonctx, 1) == LYJSON_ARRAY) { - LY_CHECK_RET(skip_ws(jsonctx)); - if ((*jsonctx->in->current == ',') || (*jsonctx->in->current == ']')) { - return LY_SUCCESS; - } - } - - LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Unexpected character \"%c\" after JSON %s.", - *jsonctx->in->current, lyjson_token2str(lyjson_ctx_status(jsonctx, 0))); - return LY_EVALID; -} - /** - * Input is expected to start after the opening quotation-mark. - * When succeeds, input is moved after the closing quotation-mark. + * @brief Parse a JSON string (starting after double quotes) and store it in the context. + * + * @param[in] jsonctx JSON parser context. + * @return LY_ERR value. */ static LY_ERR -lyjson_string_(struct lyjson_ctx *jsonctx) +lyjson_string(struct lyjson_ctx *jsonctx) { -#define BUFSIZE 24 -#define BUFSIZE_STEP 128 - - const char *in = jsonctx->in->current, *start; + const char *in = jsonctx->in->current, *start, *c; char *buf = NULL; size_t offset; /* read offset in input buffer */ size_t len; /* length of the output string (write offset in output buffer) */ size_t size = 0; /* size of the output buffer */ size_t u; uint64_t start_line; + uint32_t value; + uint8_t i; assert(jsonctx); @@ -146,17 +146,15 @@ lyjson_string_(struct lyjson_ctx *jsonctx) /* parse */ while (in[offset]) { - if (in[offset] == '\\') { + switch (in[offset]) { + case '\\': /* escape sequence */ - const char *slash = &in[offset]; - uint32_t value; - uint8_t i = 1; - + c = &in[offset]; if (!buf) { /* prepare output buffer */ - buf = malloc(BUFSIZE); + buf = malloc(LYJSON_STRING_BUF_START); LY_CHECK_ERR_RET(!buf, LOGMEM(jsonctx->ctx), LY_EMEM); - size = BUFSIZE; + size = LYJSON_STRING_BUF_START; } /* allocate enough for the offset and next character, @@ -165,10 +163,10 @@ lyjson_string_(struct lyjson_ctx *jsonctx) if (len + offset + 4 >= size) { size_t increment; - for (increment = BUFSIZE_STEP; len + offset + 4 >= size + increment; increment += BUFSIZE_STEP) {} + for (increment = LYJSON_STRING_BUF_STEP; len + offset + 4 >= size + increment; increment += LYJSON_STRING_BUF_STEP) {} buf = ly_realloc(buf, size + increment); LY_CHECK_ERR_RET(!buf, LOGMEM(jsonctx->ctx), LY_EMEM); - size += BUFSIZE_STEP; + size += LYJSON_STRING_BUF_STEP; } if (offset) { @@ -179,6 +177,7 @@ lyjson_string_(struct lyjson_ctx *jsonctx) offset = 0; } + i = 1; switch (in[++offset]) { case '"': /* quotation mark */ @@ -217,7 +216,7 @@ lyjson_string_(struct lyjson_ctx *jsonctx) offset++; for (value = i = 0; i < 4; i++) { if (!in[offset + i]) { - LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Invalid basic multilingual plane character \"%s\".", slash); + LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Invalid basic multilingual plane character \"%s\".", c); goto error; } else if (isdigit(in[offset + i])) { u = (in[offset + i] - '0'); @@ -239,13 +238,14 @@ lyjson_string_(struct lyjson_ctx *jsonctx) offset += i; /* add read escaped characters */ LY_CHECK_ERR_GOTO(ly_pututf8(&buf[len], value, &u), LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Invalid character reference \"%.*s\" (0x%08x).", - (int)(&in[offset] - slash), slash, value), + (int)(&in[offset] - c), c, value), error); len += u; /* update number of bytes in buffer */ in += offset; /* move the input by the processed bytes stored in the buffer ... */ offset = 0; /* ... and reset the offset index for future moving data into buffer */ + break; - } else if (in[offset] == '"') { + case '"': /* end of string */ if (buf) { /* realloc exact size string */ @@ -263,22 +263,21 @@ lyjson_string_(struct lyjson_ctx *jsonctx) ++offset; in += offset; goto success; - } else { - /* get it as UTF-8 character for check */ - const char *c = &in[offset]; - uint32_t code = 0; - size_t code_len = 0; - LY_CHECK_ERR_GOTO(ly_getutf8(&c, &code, &code_len), + default: + /* get it as UTF-8 character for check */ + c = &in[offset]; + LY_CHECK_ERR_GOTO(ly_getutf8(&c, &value, &u), LOGVAL(jsonctx->ctx, LY_VCODE_INCHAR, in[offset]), error); - LY_CHECK_ERR_GOTO(!is_jsonstrchar(code), + LY_CHECK_ERR_GOTO(!is_jsonstrchar(value), LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Invalid character in JSON string \"%.*s\" (0x%08x).", - (int)(&in[offset] - start + code_len), start, code), + (int)(&in[offset] - start + u), start, value), error); /* character is ok, continue */ - offset += code_len; + offset += u; + break; } } @@ -299,24 +298,6 @@ success: } return LY_SUCCESS; - -#undef BUFSIZE -#undef BUFSIZE_STEP -} - -/* - * - * Wrapper around lyjson_string_() adding LYJSON_STRING status into context to allow using lyjson_string_() for parsing object's name. - */ -static LY_ERR -lyjson_string(struct lyjson_ctx *jsonctx) -{ - LY_CHECK_RET(lyjson_string_(jsonctx)); - - LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_STRING); - LY_CHECK_RET(lyjson_check_next(jsonctx)); - - return LY_SUCCESS; } /** @@ -414,8 +395,7 @@ lyjson_get_buffer_for_number(const struct ly_ctx *ctx, uint64_t num_len, char ** * @return Number of characters written to the @p dst. */ static uint32_t -lyjson_exp_number_copy_num_part(const char *num, uint32_t num_len, - char *dec_point, int32_t dp_position, char *dst) +lyjson_exp_number_copy_num_part(const char *num, uint32_t num_len, char *dec_point, int32_t dp_position, char *dst) { int32_t dec_point_idx; int32_t n, d; @@ -456,8 +436,8 @@ lyjson_exp_number_copy_num_part(const char *num, uint32_t num_len, * @return LY_ERR value. */ static LY_ERR -lyjson_exp_number(const struct ly_ctx *ctx, const char *in, const char *exponent, - uint64_t total_len, char **res, size_t *res_len) +lyjson_exp_number(const struct ly_ctx *ctx, const char *in, const char *exponent, uint64_t total_len, char **res, + size_t *res_len) { #define MAYBE_WRITE_MINUS(ARRAY, INDEX, FLAG) \ @@ -642,6 +622,12 @@ lyjson_exp_number(const struct ly_ctx *ctx, const char *in, const char *exponent return LY_SUCCESS; } +/** + * @brief Parse a JSON number and store it in the context. + * + * @param[in] jsonctx JSON parser context. + * @return LY_ERR value. + */ static LY_ERR lyjson_number(struct lyjson_ctx *jsonctx) { @@ -713,140 +699,270 @@ invalid_character: } ly_in_skip(jsonctx->in, offset); - LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_NUMBER); - LY_CHECK_RET(lyjson_check_next(jsonctx)); - return LY_SUCCESS; } -static LY_ERR -lyjson_object_name(struct lyjson_ctx *jsonctx) +LY_ERR +lyjson_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, struct lyjson_ctx **jsonctx_p) { - if (*jsonctx->in->current != '"') { - LOGVAL(jsonctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current), - jsonctx->in->current, "a JSON object's member"); - return LY_EVALID; - } - ly_in_skip(jsonctx->in, 1); + LY_ERR ret = LY_SUCCESS; + struct lyjson_ctx *jsonctx; - LY_CHECK_RET(lyjson_string_(jsonctx)); - LY_CHECK_RET(skip_ws(jsonctx)); - if (*jsonctx->in->current != ':') { - LOGVAL(jsonctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current), jsonctx->in->current, - "a JSON object's name-separator ':'"); - return LY_EVALID; + assert(ctx && in && jsonctx_p); + + /* new context */ + jsonctx = calloc(1, sizeof *jsonctx); + LY_CHECK_ERR_RET(!jsonctx, LOGMEM(ctx), LY_EMEM); + jsonctx->ctx = ctx; + jsonctx->in = in; + + LOG_LOCSET(NULL, NULL, NULL, in); + + /* WS are always expected to be skipped */ + lyjson_skip_ws(jsonctx); + + if (jsonctx->in->current[0] == '\0') { + /* empty file, invalid */ + LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Empty JSON file."); + ret = LY_EVALID; + goto cleanup; } - ly_in_skip(jsonctx->in, 1); - LY_CHECK_RET(skip_ws(jsonctx)); - return LY_SUCCESS; + /* start JSON parsing */ + LY_CHECK_GOTO(ret = lyjson_ctx_next(jsonctx, NULL), cleanup); + +cleanup: + if (ret) { + lyjson_ctx_free(jsonctx); + } else { + *jsonctx_p = jsonctx; + } + return ret; } +/** + * @brief Parse next JSON token, object-name is expected. + * + * @param[in] jsonctx JSON parser context. + * @return LY_ERR value. + */ static LY_ERR -lyjson_object(struct lyjson_ctx *jsonctx) +lyjson_next_object_name(struct lyjson_ctx *jsonctx) { - LY_CHECK_RET(skip_ws(jsonctx)); + switch (*jsonctx->in->current) { + case '\0': + /* EOF */ + LOGVAL(jsonctx->ctx, LY_VCODE_EOF); + return LY_EVALID; - if (*jsonctx->in->current == '}') { - assert(jsonctx->depth); - jsonctx->depth--; - /* empty object */ + case '"': + /* object name */ ly_in_skip(jsonctx->in, 1); - lyjson_ctx_set_value(jsonctx, NULL, 0, 0); - LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_OBJECT_EMPTY); - return LY_SUCCESS; - } + LY_CHECK_RET(lyjson_string(jsonctx)); + lyjson_skip_ws(jsonctx); - LY_CHECK_RET(lyjson_object_name(jsonctx)); + if (*jsonctx->in->current != ':') { + LOGVAL(jsonctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current), jsonctx->in->current, + "a JSON value name-separator ':'"); + return LY_EVALID; + } + ly_in_skip(jsonctx->in, 1); + LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_OBJECT_NAME); + break; - /* output data are set by lyjson_string_() */ - LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_OBJECT); + case '}': + /* object end */ + ly_in_skip(jsonctx->in, 1); + LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_OBJECT_CLOSED); + break; + + default: + /* unexpected value */ + LOGVAL(jsonctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current), + jsonctx->in->current, "a JSON object name"); + return LY_EVALID; + } return LY_SUCCESS; } /** - * @brief Process JSON array envelope + * @brief Parse next JSON token, value is expected. * - * @param[in] jsonctx JSON parser context - * @return LY_SUCCESS or LY_EMEM + * @param[in] jsonctx JSON parser context. + * @param[in] array_end Whether array-end is accepted or not. + * @return LY_ERR value. */ static LY_ERR -lyjson_array(struct lyjson_ctx *jsonctx) +lyjson_next_value(struct lyjson_ctx *jsonctx, ly_bool array_end) { - LY_CHECK_RET(skip_ws(jsonctx)); + switch (*jsonctx->in->current) { + case '\0': + /* EOF */ + LOGVAL(jsonctx->ctx, LY_VCODE_EOF); + return LY_EVALID; - if (*jsonctx->in->current == ']') { - /* empty array */ + case '"': + /* string */ + ly_in_skip(jsonctx->in, 1); + LY_CHECK_RET(lyjson_string(jsonctx)); + LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_STRING); + break; + + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + /* number */ + LY_CHECK_RET(lyjson_number(jsonctx)); + LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_NUMBER); + break; + + case '{': + /* object */ + ly_in_skip(jsonctx->in, 1); + LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_OBJECT); + break; + + case '[': + /* array */ ly_in_skip(jsonctx->in, 1); - LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_ARRAY_EMPTY); - } else { LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_ARRAY); - } + break; - /* erase previous values, array has no value on its own */ - lyjson_ctx_set_value(jsonctx, NULL, 0, 0); + case 't': + if (strncmp(jsonctx->in->current + 1, "rue", ly_strlen_const("rue"))) { + goto unexpected_value; + } - return LY_SUCCESS; -} + /* true */ + lyjson_ctx_set_value(jsonctx, jsonctx->in->current, ly_strlen_const("true"), 0); + ly_in_skip(jsonctx->in, ly_strlen_const("true")); + LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_TRUE); + break; -static LY_ERR -lyjson_value(struct lyjson_ctx *jsonctx) -{ - if (jsonctx->status.count && (lyjson_ctx_status(jsonctx, 0) == LYJSON_END)) { - return LY_SUCCESS; - } + case 'f': + if (strncmp(jsonctx->in->current + 1, "alse", ly_strlen_const("alse"))) { + goto unexpected_value; + } - if ((*jsonctx->in->current == 'f') && !strncmp(jsonctx->in->current, "false", ly_strlen_const("false"))) { /* false */ lyjson_ctx_set_value(jsonctx, jsonctx->in->current, ly_strlen_const("false"), 0); ly_in_skip(jsonctx->in, ly_strlen_const("false")); LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_FALSE); - LY_CHECK_RET(lyjson_check_next(jsonctx)); + break; - } else if ((*jsonctx->in->current == 't') && !strncmp(jsonctx->in->current, "true", ly_strlen_const("true"))) { - /* true */ - lyjson_ctx_set_value(jsonctx, jsonctx->in->current, ly_strlen_const("true"), 0); - ly_in_skip(jsonctx->in, ly_strlen_const("true")); - LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_TRUE); - LY_CHECK_RET(lyjson_check_next(jsonctx)); + case 'n': + if (strncmp(jsonctx->in->current + 1, "ull", ly_strlen_const("ull"))) { + goto unexpected_value; + } - } else if ((*jsonctx->in->current == 'n') && !strncmp(jsonctx->in->current, "null", ly_strlen_const("null"))) { - /* none */ + /* null */ lyjson_ctx_set_value(jsonctx, "", 0, 0); ly_in_skip(jsonctx->in, ly_strlen_const("null")); LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_NULL); - LY_CHECK_RET(lyjson_check_next(jsonctx)); + break; - } else if (*jsonctx->in->current == '"') { - /* string */ + case ']': + if (!array_end) { + goto unexpected_value; + } + + /* array end */ ly_in_skip(jsonctx->in, 1); - LY_CHECK_RET(lyjson_string(jsonctx)); + LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_ARRAY_CLOSED); + break; - } else if (*jsonctx->in->current == '[') { - /* array */ + default: +unexpected_value: + LOGVAL(jsonctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current), + jsonctx->in->current, "a JSON value"); + return LY_EVALID; + } + + if (jsonctx->status.count > LY_MAX_BLOCK_DEPTH * 10) { + LOGERR(jsonctx->ctx, LY_EINVAL, "Maximum number %d of nestings has been exceeded.", LY_MAX_BLOCK_DEPTH * 10); + return LY_EINVAL; + } + + return LY_SUCCESS; +} + +/** + * @brief Parse next JSON token, object-next-item is expected. + * + * @param[in] jsonctx JSON parser context. + * @return LY_ERR value. + */ +static LY_ERR +lyjson_next_object_item(struct lyjson_ctx *jsonctx) +{ + switch (*jsonctx->in->current) { + case '\0': + /* EOF */ + LOGVAL(jsonctx->ctx, LY_VCODE_EOF); + return LY_EVALID; + + case '}': + /* object end */ ly_in_skip(jsonctx->in, 1); - LY_CHECK_RET(lyjson_array(jsonctx)); - - } else if (*jsonctx->in->current == '{') { - jsonctx->depth++; - if (jsonctx->depth > LY_MAX_BLOCK_DEPTH) { - LOGERR(jsonctx->ctx, LY_EINVAL, - "The maximum number of block nestings has been exceeded."); - return LY_EINVAL; - } - /* object */ + LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_OBJECT_CLOSED); + break; + + case ',': + /* next object item */ ly_in_skip(jsonctx->in, 1); - LY_CHECK_RET(lyjson_object(jsonctx)); + LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_OBJECT_NEXT); + break; - } else if ((*jsonctx->in->current == '-') || ((*jsonctx->in->current >= '0') && (*jsonctx->in->current <= '9'))) { - /* number */ - LY_CHECK_RET(lyjson_number(jsonctx)); + default: + /* unexpected value */ + LOGVAL(jsonctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current), + jsonctx->in->current, "a JSON object-end or next item"); + return LY_EVALID; + } - } else { + return LY_SUCCESS; +} + +/** + * @brief Parse next JSON token, array-next-item is expected. + * + * @param[in] jsonctx JSON parser context. + * @return LY_ERR value. + */ +static LY_ERR +lyjson_next_array_item(struct lyjson_ctx *jsonctx) +{ + switch (*jsonctx->in->current) { + case '\0': + /* EOF */ + LOGVAL(jsonctx->ctx, LY_VCODE_EOF); + return LY_EVALID; + + case ']': + /* array end */ + ly_in_skip(jsonctx->in, 1); + LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_ARRAY_CLOSED); + break; + + case ',': + /* next array item */ + ly_in_skip(jsonctx->in, 1); + LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_ARRAY_NEXT); + break; + + default: /* unexpected value */ LOGVAL(jsonctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current), - jsonctx->in->current, "a JSON value"); + jsonctx->in->current, "a JSON array-end or next item"); return LY_EVALID; } @@ -854,46 +970,72 @@ lyjson_value(struct lyjson_ctx *jsonctx) } LY_ERR -lyjson_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, ly_bool subtree, struct lyjson_ctx **jsonctx_p) +lyjson_ctx_next(struct lyjson_ctx *jsonctx, enum LYJSON_PARSER_STATUS *status) { LY_ERR ret = LY_SUCCESS; - struct lyjson_ctx *jsonctx; + enum LYJSON_PARSER_STATUS cur; - assert(ctx); - assert(in); - assert(jsonctx_p); - - /* new context */ - jsonctx = calloc(1, sizeof *jsonctx); - LY_CHECK_ERR_RET(!jsonctx, LOGMEM(ctx), LY_EMEM); - jsonctx->ctx = ctx; - jsonctx->in = in; + assert(jsonctx); - LOG_LOCSET(NULL, NULL, NULL, in); + cur = lyjson_ctx_status(jsonctx); + switch (cur) { + case LYJSON_OBJECT: + LY_CHECK_GOTO(ret = lyjson_next_object_name(jsonctx), cleanup); + break; + case LYJSON_ARRAY: + LY_CHECK_GOTO(ret = lyjson_next_value(jsonctx, 1), cleanup); + break; + case LYJSON_OBJECT_NEXT: + LYJSON_STATUS_POP(jsonctx); + LY_CHECK_GOTO(ret = lyjson_next_object_name(jsonctx), cleanup); + break; + case LYJSON_ARRAY_NEXT: + LYJSON_STATUS_POP(jsonctx); + LY_CHECK_GOTO(ret = lyjson_next_value(jsonctx, 0), cleanup); + break; + case LYJSON_OBJECT_NAME: + lyjson_ctx_set_value(jsonctx, NULL, 0, 0); + LYJSON_STATUS_POP(jsonctx); + LY_CHECK_GOTO(ret = lyjson_next_value(jsonctx, 0), cleanup); + break; + case LYJSON_OBJECT_CLOSED: + case LYJSON_ARRAY_CLOSED: + LYJSON_STATUS_POP(jsonctx); + /* fallthrough */ + case LYJSON_NUMBER: + case LYJSON_STRING: + case LYJSON_TRUE: + case LYJSON_FALSE: + case LYJSON_NULL: + lyjson_ctx_set_value(jsonctx, NULL, 0, 0); + LYJSON_STATUS_POP(jsonctx); + cur = lyjson_ctx_status(jsonctx); + + if (cur == LYJSON_OBJECT) { + LY_CHECK_GOTO(ret = lyjson_next_object_item(jsonctx), cleanup); + break; + } else if (cur == LYJSON_ARRAY) { + LY_CHECK_GOTO(ret = lyjson_next_array_item(jsonctx), cleanup); + break; + } - /* parse JSON value, if any */ - LY_CHECK_GOTO(ret = skip_ws(jsonctx), cleanup); - if (lyjson_ctx_status(jsonctx, 0) == LYJSON_END) { - /* empty data input */ + assert(cur == LYJSON_END); + goto cleanup; + case LYJSON_END: + LY_CHECK_GOTO(ret = lyjson_next_value(jsonctx, 0), cleanup); + break; + case LYJSON_ERROR: + LOGINT(jsonctx->ctx); + ret = LY_EINT; goto cleanup; } - if (subtree) { - ret = lyjson_object(jsonctx); - jsonctx->depth++; - } else { - ret = lyjson_value(jsonctx); - } - if ((jsonctx->status.count > 1) && (lyjson_ctx_status(jsonctx, 0) == LYJSON_END)) { - LOGVAL(jsonctx->ctx, LY_VCODE_EOF); - ret = LY_EVALID; - } + /* skip WS */ + lyjson_skip_ws(jsonctx); cleanup: - if (ret) { - lyjson_ctx_free(jsonctx); - } else { - *jsonctx_p = jsonctx; + if (!ret && status) { + *status = lyjson_ctx_status(jsonctx); } return ret; } @@ -904,13 +1046,12 @@ lyjson_ctx_backup(struct lyjson_ctx *jsonctx) if (jsonctx->backup.dynamic) { free((char *)jsonctx->backup.value); } - jsonctx->backup.status = lyjson_ctx_status(jsonctx, 0); + jsonctx->backup.status = lyjson_ctx_status(jsonctx); jsonctx->backup.status_count = jsonctx->status.count; jsonctx->backup.value = jsonctx->value; jsonctx->backup.value_len = jsonctx->value_len; jsonctx->backup.input = jsonctx->in->current; jsonctx->backup.dynamic = jsonctx->dynamic; - jsonctx->backup.depth = jsonctx->depth; jsonctx->dynamic = 0; } @@ -926,105 +1067,9 @@ lyjson_ctx_restore(struct lyjson_ctx *jsonctx) jsonctx->value_len = jsonctx->backup.value_len; jsonctx->in->current = jsonctx->backup.input; jsonctx->dynamic = jsonctx->backup.dynamic; - jsonctx->depth = jsonctx->backup.depth; jsonctx->backup.dynamic = 0; } -LY_ERR -lyjson_ctx_next(struct lyjson_ctx *jsonctx, enum LYJSON_PARSER_STATUS *status) -{ - LY_ERR ret = LY_SUCCESS; - ly_bool toplevel = 0; - enum LYJSON_PARSER_STATUS prev; - - assert(jsonctx); - - prev = lyjson_ctx_status(jsonctx, 0); - - if ((prev == LYJSON_OBJECT) || (prev == LYJSON_ARRAY)) { - /* get value for the object's member OR the first value in the array */ - ret = lyjson_value(jsonctx); - goto result; - } else { - /* the previous token is closed and should be completely processed */ - LYJSON_STATUS_POP_RET(jsonctx); - prev = lyjson_ctx_status(jsonctx, 0); - } - - if (!jsonctx->status.count) { - /* we are done with the top level value */ - toplevel = 1; - } - LY_CHECK_RET(skip_ws(jsonctx)); - if (toplevel && !jsonctx->status.count) { - /* EOF expected, but there are some data after the top level token */ - LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Expecting end-of-input, but some data follows the top level JSON value."); - return LY_EVALID; - } - - if (toplevel) { - /* we are done */ - goto result; - } - - /* continue with the next token */ - assert(prev == LYJSON_OBJECT || prev == LYJSON_ARRAY); - - if (*jsonctx->in->current == ',') { - /* sibling item in the ... */ - ly_in_skip(jsonctx->in, 1); - LY_CHECK_RET(skip_ws(jsonctx)); - - if (prev == LYJSON_OBJECT) { - /* ... object - get another object's member */ - ret = lyjson_object_name(jsonctx); - } else { /* LYJSON_ARRAY */ - /* ... array - get another complete value */ - ret = lyjson_value(jsonctx); - } - } else if (((prev == LYJSON_OBJECT) && (*jsonctx->in->current == '}')) || - ((prev == LYJSON_ARRAY) && (*jsonctx->in->current == ']'))) { - if (*jsonctx->in->current == '}') { - assert(jsonctx->depth); - jsonctx->depth--; - } - ly_in_skip(jsonctx->in, 1); - LYJSON_STATUS_POP_RET(jsonctx); - LYJSON_STATUS_PUSH_RET(jsonctx, prev + 1); - } else { - /* unexpected value */ - LOGVAL(jsonctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current), jsonctx->in->current, - prev == LYJSON_ARRAY ? "another JSON value in array" : "another JSON object's member"); - return LY_EVALID; - } - -result: - if ((ret == LY_SUCCESS) && (jsonctx->status.count > 1) && (lyjson_ctx_status(jsonctx, 0) == LYJSON_END)) { - LOGVAL(jsonctx->ctx, LY_VCODE_EOF); - ret = LY_EVALID; - } - - if ((ret == LY_SUCCESS) && status) { - *status = lyjson_ctx_status(jsonctx, 0); - } - - return ret; -} - -enum LYJSON_PARSER_STATUS -lyjson_ctx_status(struct lyjson_ctx *jsonctx, uint32_t index) -{ - assert(jsonctx); - - if (jsonctx->status.count < index) { - return LYJSON_ERROR; - } else if (jsonctx->status.count == index) { - return LYJSON_ROOT; - } else { - return (enum LYJSON_PARSER_STATUS)(uintptr_t)jsonctx->status.objs[jsonctx->status.count - (index + 1)]; - } -} - void lyjson_ctx_free(struct lyjson_ctx *jsonctx) { @@ -1,9 +1,10 @@ /** * @file json.h * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Michal Vasko <mvasko@cesnet.cz> * @brief Generic JSON format parser routines. * - * Copyright (c) 2020 CESNET, z.s.p.o. + * Copyright (c) 2020 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -24,6 +25,9 @@ struct ly_ctx; struct ly_in; +#define LYJSON_STRING_BUF_START 24 +#define LYJSON_STRING_BUF_STEP 128 + /* Macro to test if character is whitespace */ #define is_jsonws(c) (c == 0x20 || c == 0x9 || c == 0xa || c == 0xd) @@ -35,27 +39,27 @@ struct ly_in; LY_CHECK_RET(ly_set_add(&CTX->status, (void *)(uintptr_t)(STATUS), 1, NULL)) /* Macro to pop JSON parser status */ -#define LYJSON_STATUS_POP_RET(CTX) \ +#define LYJSON_STATUS_POP(CTX) \ assert(CTX->status.count); CTX->status.count--; /** * @brief Status of the parser providing information what is expected next (which function is supposed to be called). */ enum LYJSON_PARSER_STATUS { - LYJSON_ERROR, /* JSON parser error - value is used as an error return code */ - LYJSON_ROOT, /* JSON document root, used internally */ - LYJSON_OBJECT, /* JSON object */ - LYJSON_OBJECT_CLOSED, /* JSON object closed */ - LYJSON_OBJECT_EMPTY, /* empty JSON object { } */ - LYJSON_ARRAY, /* JSON array */ - LYJSON_ARRAY_CLOSED, /* JSON array closed */ - LYJSON_ARRAY_EMPTY, /* empty JSON array */ - LYJSON_NUMBER, /* JSON number value */ - LYJSON_STRING, /* JSON string value */ - LYJSON_FALSE, /* JSON false value */ - LYJSON_TRUE, /* JSON true value */ - LYJSON_NULL, /* JSON null value */ - LYJSON_END /* end of input data */ + LYJSON_ERROR = 0, /* JSON parser error - value is used as an error return code */ + LYJSON_OBJECT, /* JSON object */ + LYJSON_OBJECT_NEXT, /* JSON object next item */ + LYJSON_OBJECT_CLOSED, /* JSON object closed */ + LYJSON_ARRAY, /* JSON array */ + LYJSON_ARRAY_NEXT, /* JSON array next item */ + LYJSON_ARRAY_CLOSED, /* JSON array closed */ + LYJSON_OBJECT_NAME, /* JSON object name */ + LYJSON_NUMBER, /* JSON number value */ + LYJSON_STRING, /* JSON string value */ + LYJSON_TRUE, /* JSON true value */ + LYJSON_FALSE, /* JSON false value */ + LYJSON_NULL, /* JSON null value */ + LYJSON_END /* end of input data */ }; struct lyjson_ctx { @@ -64,10 +68,9 @@ struct lyjson_ctx { struct ly_set status; /* stack of ::LYJSON_PARSER_STATUS values corresponding to the JSON items being processed */ - const char *value; /* ::LYJSON_STRING, ::LYJSON_NUMBER, ::LYJSON_OBJECT */ - size_t value_len; /* ::LYJSON_STRING, ::LYJSON_NUMBER, ::LYJSON_OBJECT */ - ly_bool dynamic; /* ::LYJSON_STRING, ::LYJSON_NUMBER, ::LYJSON_OBJECT */ - uint32_t depth; /* current number of nested blocks, see ::LY_MAX_BLOCK_DEPTH */ + const char *value; /* ::LYJSON_STRING, ::LYJSON_NUMBER, ::LYJSON_OBJECT_NAME */ + size_t value_len; /* ::LYJSON_STRING, ::LYJSON_NUMBER, ::LYJSON_OBJECT_NAME */ + ly_bool dynamic; /* ::LYJSON_STRING, ::LYJSON_NUMBER, ::LYJSON_OBJECT_NAME */ struct { enum LYJSON_PARSER_STATUS status; @@ -75,38 +78,44 @@ struct lyjson_ctx { const char *value; size_t value_len; ly_bool dynamic; - uint32_t depth; const char *input; } backup; }; /** - * @brief Create a new JSON parser context and start parsing. + * @brief Get string representation of the JSON context status (token). * - * @param[in] ctx libyang context. - * @param[in] in JSON string data to parse. - * @param[in] subtree Whether this is a special case of parsing a subtree (starting with object name). - * @param[out] jsonctx New JSON context with status referring the parsed value. - * @return LY_ERR value. + * @param[in] status Context status (aka JSON token) + * @return String representation of the @p status. */ -LY_ERR lyjson_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, ly_bool subtree, struct lyjson_ctx **jsonctx); +const char *lyjson_token2str(enum LYJSON_PARSER_STATUS status); /** - * @brief Get status of the parser as the last/previous parsed token + * @brief Get current status of the parser. * - * @param[in] jsonctx JSON context to check. - * @param[in] index Index of the token, starting by 0 for the last token - * @return ::LYJSON_ERROR in case of invalid index, other ::LYJSON_PARSER_STATUS corresponding to the token. + * @param[in] jsonctx JSON parser context to check. + * @return ::LYJSON_PARSER_STATUS according to the last parsed token. */ -enum LYJSON_PARSER_STATUS lyjson_ctx_status(struct lyjson_ctx *jsonctx, uint32_t index); +enum LYJSON_PARSER_STATUS lyjson_ctx_status(struct lyjson_ctx *jsonctx); /** - * @brief Get string representation of the JSON context status (token). + * @brief Get current nesting (object/array) depth. * - * @param[in] status Context status (aka JSON token) - * @return String representation of the @p status. + * @param[in] jsonctx JSON parser context to check. + * @return Current nesting depth. */ -const char *lyjson_token2str(enum LYJSON_PARSER_STATUS status); +uint32_t lyjson_ctx_depth(struct lyjson_ctx *jsonctx); + +/** + * @brief Create a new JSON parser context and start parsing. + * + * @param[in] ctx libyang context. + * @param[in] in JSON string data to parse. + * @param[in] subtree Whether this is a special case of parsing a subtree (starting with object name). + * @param[out] jsonctx New JSON parser context with status referring the parsed value. + * @return LY_ERR value. + */ +LY_ERR lyjson_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, struct lyjson_ctx **jsonctx); /** * @brief Move to the next JSON artifact and update parser status. @@ -119,12 +128,14 @@ LY_ERR lyjson_ctx_next(struct lyjson_ctx *jsonctx, enum LYJSON_PARSER_STATUS *st /** * @brief Backup the JSON parser context's state To restore the backup, use ::lyjson_ctx_restore(). + * * @param[in] jsonctx JSON parser context to backup. */ void lyjson_ctx_backup(struct lyjson_ctx *jsonctx); /** - * @brief REstore the JSON parser context's state from the backup created by ::lyjson_ctx_backup(). + * @brief Restore the JSON parser context's state from the backup created by ::lyjson_ctx_backup(). + * * @param[in] jsonctx JSON parser context to restore. */ void lyjson_ctx_restore(struct lyjson_ctx *jsonctx); @@ -132,7 +143,7 @@ void lyjson_ctx_restore(struct lyjson_ctx *jsonctx); /** * @brief Remove the allocated working memory of the context. * - * @param[in] jsonctx JSON context to clear. + * @param[in] jsonctx JSON parser context to clear. */ void lyjson_ctx_free(struct lyjson_ctx *jsonctx); diff --git a/src/libyang.h b/src/libyang.h index 2bfc6be..f992a78 100644 --- a/src/libyang.h +++ b/src/libyang.h @@ -39,6 +39,7 @@ extern "C" { /* * The following headers are supposed to be included explicitly: + * - hash_table.h * - metadata.h * - plugins_types.h * - plugins_exts.h @@ -4,7 +4,7 @@ * @author Michal Vasko <mvasko@cesnet.cz> * @brief Logger routines implementations * - * Copyright (c) 2015 - 2022 CESNET, z.s.p.o. + * Copyright (c) 2015 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -41,15 +41,13 @@ ATOMIC_T ly_log_opts = (uint_fast32_t)(LY_LOLOG | LY_LOSTORE_LAST); THREAD_LOCAL uint32_t *temp_ly_log_opts; static ly_log_clb log_clb; static ATOMIC_T path_flag = 1; +THREAD_LOCAL char last_msg[LY_LAST_MSG_SIZE]; #ifndef NDEBUG ATOMIC_T ly_ldbg_groups = 0; #endif THREAD_LOCAL struct ly_log_location_s log_location = {0}; -/* how many bytes add when enlarging buffers */ -#define LY_BUF_STEP 128 - LIBYANG_API_DEF LY_ERR ly_errcode(const struct ly_ctx *ctx) { @@ -63,6 +61,47 @@ ly_errcode(const struct ly_ctx *ctx) return LY_SUCCESS; } +LIBYANG_API_DEF const char * +ly_strerrcode(LY_ERR err) +{ + /* ignore plugin flag */ + err &= ~LY_EPLUGIN; + + switch (err) { + case LY_SUCCESS: + return "Success"; + case LY_EMEM: + return "Out of memory"; + case LY_ESYS: + return "System call failed"; + case LY_EINVAL: + return "Invalid value"; + case LY_EEXIST: + return "Already exists"; + case LY_ENOTFOUND: + return "Not found"; + case LY_EINT: + return "Internal error"; + case LY_EVALID: + return "Validation failed"; + case LY_EDENIED: + return "Operation denied"; + case LY_EINCOMPLETE: + return "Operation incomplete"; + case LY_ERECOMPILE: + return "Recompilation required"; + case LY_ENOT: + return "Negative result"; + case LY_EOTHER: + return "Another failure reason"; + case LY_EPLUGIN: + break; + } + + /* unreachable */ + return "Unknown"; +} + LIBYANG_API_DEF LY_VECODE ly_vecode(const struct ly_ctx *ctx) { @@ -77,6 +116,38 @@ ly_vecode(const struct ly_ctx *ctx) } LIBYANG_API_DEF const char * +ly_strvecode(LY_VECODE vecode) +{ + switch (vecode) { + case LYVE_SUCCESS: + return "Success"; + case LYVE_SYNTAX: + return "General syntax error"; + case LYVE_SYNTAX_YANG: + return "YANG syntax error"; + case LYVE_SYNTAX_YIN: + return "YIN syntax error"; + case LYVE_REFERENCE: + return "Reference error"; + case LYVE_XPATH: + return "XPath error"; + case LYVE_SEMANTICS: + return "Semantic error"; + case LYVE_SYNTAX_XML: + return "XML syntax error"; + case LYVE_SYNTAX_JSON: + return "JSON syntax error"; + case LYVE_DATA: + return "YANG data error"; + case LYVE_OTHER: + return "Another error"; + } + + /* unreachable */ + return "Unknown"; +} + +LIBYANG_API_DEF const char * ly_errmsg(const struct ly_ctx *ctx) { struct ly_err_item *i; @@ -92,6 +163,12 @@ ly_errmsg(const struct ly_ctx *ctx) } LIBYANG_API_DEF const char * +ly_last_errmsg(void) +{ + return last_msg; +} + +LIBYANG_API_DEF const char * ly_errpath(const struct ly_ctx *ctx) { struct ly_err_item *i; @@ -169,61 +246,148 @@ ly_err_new(struct ly_err_item **err, LY_ERR ecode, LY_VECODE vecode, char *path, return e->no; } +/** + * @brief Get error record from error hash table of a context for the current thread. + * + * @param[in] ctx Context to use. + * @return Thread error record, if any. + */ +static struct ly_ctx_err_rec * +ly_err_get_rec(const struct ly_ctx *ctx) +{ + struct ly_ctx_err_rec rec, *match; + + /* prepare record */ + rec.tid = pthread_self(); + + /* get the pointer to the matching record */ + if (lyht_find(ctx->err_ht, &rec, lyht_hash((void *)&rec.tid, sizeof rec.tid), (void **)&match)) { + return NULL; + } + + return match; +} + +/** + * @brief Insert new error record to error hash table of a context for the current thread. + * + * @param[in] ctx Context to use. + * @return Thread error record. + */ +static struct ly_ctx_err_rec * +ly_err_new_rec(const struct ly_ctx *ctx) +{ + struct ly_ctx_err_rec new, *rec; + LY_ERR r; + + /* insert a new record */ + new.err = NULL; + new.tid = pthread_self(); + + /* reuse lock */ + /* LOCK */ + pthread_mutex_lock((pthread_mutex_t *)&ctx->lyb_hash_lock); + + r = lyht_insert(ctx->err_ht, &new, lyht_hash((void *)&new.tid, sizeof new.tid), (void **)&rec); + + /* UNLOCK */ + pthread_mutex_unlock((pthread_mutex_t *)&ctx->lyb_hash_lock); + + return r ? NULL : rec; +} + LIBYANG_API_DEF struct ly_err_item * ly_err_first(const struct ly_ctx *ctx) { + struct ly_ctx_err_rec *rec; + LY_CHECK_ARG_RET(NULL, ctx, NULL); - return pthread_getspecific(ctx->errlist_key); + /* get the pointer to the matching record */ + rec = ly_err_get_rec(ctx); + + return rec ? rec->err : NULL; } LIBYANG_API_DEF struct ly_err_item * ly_err_last(const struct ly_ctx *ctx) { - const struct ly_err_item *e; + struct ly_ctx_err_rec *rec; LY_CHECK_ARG_RET(NULL, ctx, NULL); - e = pthread_getspecific(ctx->errlist_key); - return e ? e->prev : NULL; + /* get the pointer to the matching record */ + if (!(rec = ly_err_get_rec(ctx))) { + return NULL; + } + + return rec->err ? rec->err->prev : NULL; +} + +void +ly_err_move(struct ly_ctx *src_ctx, struct ly_ctx *trg_ctx) +{ + struct ly_ctx_err_rec *rec; + struct ly_err_item *err = NULL; + + /* get and remove the errors from src */ + rec = ly_err_get_rec(src_ctx); + if (rec) { + err = rec->err; + rec->err = NULL; + } + + /* set them for trg */ + if (!(rec = ly_err_get_rec(trg_ctx))) { + if (!(rec = ly_err_new_rec(trg_ctx))) { + LOGINT(NULL); + ly_err_free(err); + return; + } + } + ly_err_free(rec->err); + rec->err = err; } LIBYANG_API_DEF void ly_err_free(void *ptr) { - struct ly_err_item *i, *next; + struct ly_err_item *e, *next; /* clean the error list */ - for (i = (struct ly_err_item *)ptr; i; i = next) { - next = i->next; - free(i->msg); - free(i->path); - free(i->apptag); - free(i); + LY_LIST_FOR_SAFE(ptr, next, e) { + free(e->msg); + free(e->path); + free(e->apptag); + free(e); } } LIBYANG_API_DEF void ly_err_clean(struct ly_ctx *ctx, struct ly_err_item *eitem) { - struct ly_err_item *i, *first; + struct ly_ctx_err_rec *rec; + struct ly_err_item *e; - first = ly_err_first(ctx); - if (first == eitem) { + if (!(rec = ly_err_get_rec(ctx))) { + return; + } + if (rec->err == eitem) { eitem = NULL; } - if (eitem) { + + if (!eitem) { + /* free all err */ + ly_err_free(rec->err); + rec->err = NULL; + } else { /* disconnect the error */ - for (i = first; i && (i->next != eitem); i = i->next) {} - assert(i); - i->next = NULL; - first->prev = i; + for (e = rec->err; e && (e->next != eitem); e = e->next) {} + assert(e); + e->next = NULL; + rec->err->prev = e; /* free this err and newer */ ly_err_free(eitem); - } else { - /* free all err */ - ly_err_free(first); - pthread_setspecific(ctx->errlist_key, NULL); } } @@ -336,64 +500,99 @@ ly_log_location_revert(uint32_t scnode_steps, uint32_t dnode_steps, uint32_t pat } } +const struct lyd_node * +ly_log_location_dnode(uint32_t idx) +{ + if (idx < log_location.dnodes.count) { + return log_location.dnodes.dnodes[idx]; + } + + return NULL; +} + +uint32_t +ly_log_location_dnode_count(void) +{ + return log_location.dnodes.count; +} + +/** + * @brief Store generated error in a context. + * + * @param[in] ctx Context to use. + * @param[in] level Message log level. + * @param[in] no Error number. + * @param[in] vecode Error validation error code. + * @param[in] msg Error message, always spent. + * @param[in] path Error path, always spent. + * @param[in] apptag Error app tag, always spent. + * @return LY_ERR value. + */ static LY_ERR log_store(const struct ly_ctx *ctx, LY_LOG_LEVEL level, LY_ERR no, LY_VECODE vecode, char *msg, char *path, char *apptag) { - struct ly_err_item *eitem, *last; + struct ly_ctx_err_rec *rec; + struct ly_err_item *e, *last; assert(ctx && (level < LY_LLVRB)); - eitem = pthread_getspecific(ctx->errlist_key); - if (!eitem) { + if (!(rec = ly_err_get_rec(ctx))) { + if (!(rec = ly_err_new_rec(ctx))) { + goto mem_fail; + } + } + + e = rec->err; + if (!e) { /* if we are only to fill in path, there must have been an error stored */ assert(msg); - eitem = malloc(sizeof *eitem); - LY_CHECK_GOTO(!eitem, mem_fail); - eitem->prev = eitem; - eitem->next = NULL; + e = malloc(sizeof *e); + LY_CHECK_GOTO(!e, mem_fail); + e->prev = e; + e->next = NULL; - pthread_setspecific(ctx->errlist_key, eitem); + rec->err = e; } else if (!msg) { /* only filling the path */ assert(path); /* find last error */ - eitem = eitem->prev; + e = e->prev; do { - if (eitem->level == LY_LLERR) { + if (e->level == LY_LLERR) { /* fill the path */ - free(eitem->path); - eitem->path = path; + free(e->path); + e->path = path; return LY_SUCCESS; } - eitem = eitem->prev; - } while (eitem->prev->next); + e = e->prev; + } while (e->prev->next); /* last error was not found */ assert(0); } else if ((temp_ly_log_opts && ((*temp_ly_log_opts & LY_LOSTORE_LAST) == LY_LOSTORE_LAST)) || (!temp_ly_log_opts && ((ATOMIC_LOAD_RELAXED(ly_log_opts) & LY_LOSTORE_LAST) == LY_LOSTORE_LAST))) { /* overwrite last message */ - free(eitem->msg); - free(eitem->path); - free(eitem->apptag); + free(e->msg); + free(e->path); + free(e->apptag); } else { /* store new message */ - last = eitem->prev; - eitem->prev = malloc(sizeof *eitem); - LY_CHECK_GOTO(!eitem->prev, mem_fail); - eitem = eitem->prev; - eitem->prev = last; - eitem->next = NULL; - last->next = eitem; + last = e->prev; + e->prev = malloc(sizeof *e); + LY_CHECK_GOTO(!e->prev, mem_fail); + e = e->prev; + e->prev = last; + e->next = NULL; + last->next = e; } /* fill in the information */ - eitem->level = level; - eitem->no = no; - eitem->vecode = vecode; - eitem->msg = msg; - eitem->path = path; - eitem->apptag = apptag; + e->level = level; + e->no = no; + e->vecode = vecode; + e->msg = msg; + e->path = path; + e->apptag = apptag; return LY_SUCCESS; mem_fail: @@ -445,14 +644,19 @@ log_vprintf(const struct ly_ctx *ctx, LY_LOG_LEVEL level, LY_ERR no, LY_VECODE v return; } - /* store the error/warning (if we need to store errors internally, it does not matter what are the user log options) */ + /* print into a single message */ + if (vasprintf(&msg, format, args) == -1) { + LOGMEM(ctx); + free(path); + return; + } + + /* store as the last message */ + strncpy(last_msg, msg, LY_LAST_MSG_SIZE - 1); + + /* store the error/warning in the context (if we need to store errors internally, it does not matter what are + * the user log options) */ if ((level < LY_LLVRB) && ctx && lostore) { - assert(format); - if (vasprintf(&msg, format, args) == -1) { - LOGMEM(ctx); - free(path); - return; - } if (((no & ~LY_EPLUGIN) == LY_EVALID) && (vecode == LYVE_SUCCESS)) { /* assume we are inheriting the error, so inherit vecode as well */ vecode = ly_vecode(ctx); @@ -462,11 +666,6 @@ log_vprintf(const struct ly_ctx *ctx, LY_LOG_LEVEL level, LY_ERR no, LY_VECODE v } free_strs = 0; } else { - if (vasprintf(&msg, format, args) == -1) { - LOGMEM(ctx); - free(path); - return; - } free_strs = 1; } @@ -524,6 +723,8 @@ ly_log_dbg(uint32_t group, const char *format, ...) va_start(ap, format); log_vprintf(NULL, LY_LLDBG, 0, 0, NULL, NULL, dbg_format, ap); va_end(ap); + + free(dbg_format); } #endif @@ -556,20 +757,20 @@ ly_vlog_build_path_append(char **str, const struct lysc_node *snode, const struc if (snode->nodetype & (LYS_CHOICE | LYS_CASE)) { /* schema-only node */ return LY_SUCCESS; - } else if (lysc_data_parent(snode) != parent->schema) { + } else if (lysc_data_parent(snode) != lyd_node_schema(parent)) { /* not a direct descendant node */ return LY_SUCCESS; } /* get module to print, if any */ mod = snode->module; - prev_mod = (parent->schema) ? parent->schema->module : lyd_owner_module(parent); + prev_mod = lyd_node_module(parent); if (prev_mod == mod) { mod = NULL; } /* realloc string */ - len = strlen(*str); + len = *str ? strlen(*str) : 0; new_len = len + 1 + (mod ? strlen(mod->name) + 1 : 0) + strlen(snode->name); mem = realloc(*str, new_len + 1); LY_CHECK_ERR_RET(!mem, LOGMEM(LYD_CTX(parent)), LY_EMEM); @@ -580,6 +781,41 @@ ly_vlog_build_path_append(char **str, const struct lysc_node *snode, const struc return LY_SUCCESS; } +LY_ERR +ly_vlog_build_data_path(const struct ly_ctx *ctx, char **path) +{ + LY_ERR rc = LY_SUCCESS; + const struct lyd_node *dnode = NULL; + + *path = NULL; + + if (log_location.dnodes.count) { + dnode = log_location.dnodes.objs[log_location.dnodes.count - 1]; + if (dnode->parent || !lysc_data_parent(dnode->schema)) { + /* data node with all of its parents */ + *path = lyd_path(log_location.dnodes.objs[log_location.dnodes.count - 1], LYD_PATH_STD, NULL, 0); + LY_CHECK_ERR_GOTO(!*path, LOGMEM(ctx); rc = LY_EMEM, cleanup); + } else { + /* data parsers put all the parent nodes in the set, but they are not connected */ + *path = lyd_path_set(&log_location.dnodes, LYD_PATH_STD); + LY_CHECK_ERR_GOTO(!*path, LOGMEM(ctx); rc = LY_EMEM, cleanup); + } + } + + /* sometimes the last node is not created yet and we only have the schema node */ + if (log_location.scnodes.count) { + rc = ly_vlog_build_path_append(path, log_location.scnodes.objs[log_location.scnodes.count - 1], dnode); + LY_CHECK_GOTO(rc, cleanup); + } + +cleanup: + if (rc) { + free(*path); + *path = NULL; + } + return rc; +} + /** * @brief Build log path from the stored log location information. * @@ -592,32 +828,17 @@ ly_vlog_build_path(const struct ly_ctx *ctx, char **path) { int r; char *str = NULL, *prev = NULL; - const struct lyd_node *dnode; *path = NULL; if (log_location.paths.count && ((const char *)(log_location.paths.objs[log_location.paths.count - 1]))[0]) { /* simply get what is in the provided path string */ - *path = strdup((const char *)log_location.paths.objs[log_location.paths.count - 1]); - LY_CHECK_ERR_RET(!(*path), LOGMEM(ctx), LY_EMEM); + r = asprintf(path, "Path \"%s\"", (const char *)log_location.paths.objs[log_location.paths.count - 1]); + LY_CHECK_ERR_RET(r == -1, LOGMEM(ctx), LY_EMEM); } else { /* data/schema node */ if (log_location.dnodes.count) { - dnode = log_location.dnodes.objs[log_location.dnodes.count - 1]; - if (dnode->parent || !lysc_data_parent(dnode->schema)) { - /* data node with all of its parents */ - str = lyd_path(log_location.dnodes.objs[log_location.dnodes.count - 1], LYD_PATH_STD, NULL, 0); - LY_CHECK_ERR_RET(!str, LOGMEM(ctx), LY_EMEM); - } else { - /* data parsers put all the parent nodes in the set, but they are not connected */ - str = lyd_path_set(&log_location.dnodes, LYD_PATH_STD); - LY_CHECK_ERR_RET(!str, LOGMEM(ctx), LY_EMEM); - } - - /* sometimes the last node is not created yet and we only have the schema node */ - if (log_location.scnodes.count) { - ly_vlog_build_path_append(&str, log_location.scnodes.objs[log_location.scnodes.count - 1], dnode); - } + LY_CHECK_RET(ly_vlog_build_data_path(ctx, &str)); r = asprintf(path, "Data location \"%s\"", str); free(str); @@ -630,28 +851,28 @@ ly_vlog_build_path(const struct ly_ctx *ctx, char **path) free(str); LY_CHECK_ERR_RET(r == -1, LOGMEM(ctx), LY_EMEM); } + } - /* line */ - prev = *path; - if (log_location.line) { - r = asprintf(path, "%s%sine number %" PRIu64, prev ? prev : "", prev ? ", l" : "L", log_location.line); - free(prev); - LY_CHECK_ERR_RET(r == -1, LOGMEM(ctx), LY_EMEM); + /* line */ + prev = *path; + if (log_location.line) { + r = asprintf(path, "%s%sine number %" PRIu64, prev ? prev : "", prev ? ", l" : "L", log_location.line); + free(prev); + LY_CHECK_ERR_RET(r == -1, LOGMEM(ctx), LY_EMEM); - log_location.line = 0; - } else if (log_location.inputs.count) { - r = asprintf(path, "%s%sine number %" PRIu64, prev ? prev : "", prev ? ", l" : "L", - ((struct ly_in *)log_location.inputs.objs[log_location.inputs.count - 1])->line); - free(prev); - LY_CHECK_ERR_RET(r == -1, LOGMEM(ctx), LY_EMEM); - } + log_location.line = 0; + } else if (log_location.inputs.count) { + r = asprintf(path, "%s%sine number %" PRIu64, prev ? prev : "", prev ? ", l" : "L", + ((struct ly_in *)log_location.inputs.objs[log_location.inputs.count - 1])->line); + free(prev); + LY_CHECK_ERR_RET(r == -1, LOGMEM(ctx), LY_EMEM); + } - if (*path) { - prev = *path; - r = asprintf(path, "%s.", prev); - free(prev); - LY_CHECK_ERR_RET(r == -1, LOGMEM(ctx), LY_EMEM); - } + if (*path) { + prev = *path; + r = asprintf(path, "%s.", prev); + free(prev); + LY_CHECK_ERR_RET(r == -1, LOGMEM(ctx), LY_EMEM); } return LY_SUCCESS; @@ -680,12 +901,12 @@ ly_vlog(const struct ly_ctx *ctx, const char *apptag, LY_VECODE code, const char * @param[in] plugin_name Name of the plugin generating the message. * @param[in] level Log message level (error, warning, etc.) * @param[in] err_no Error type code. - * @param[in] path Optional path of the error. + * @param[in] path Optional path of the error, used if set. * @param[in] format Format string to print. * @param[in] ap Var arg list for @p format. */ static void -ly_ext_log(const struct ly_ctx *ctx, const char *plugin_name, LY_LOG_LEVEL level, LY_ERR err_no, const char *path, +ly_ext_log(const struct ly_ctx *ctx, const char *plugin_name, LY_LOG_LEVEL level, LY_ERR err_no, char *path, const char *format, va_list ap) { char *plugin_msg; @@ -698,8 +919,7 @@ ly_ext_log(const struct ly_ctx *ctx, const char *plugin_name, LY_LOG_LEVEL level return; } - log_vprintf(ctx, level, (level == LY_LLERR ? LY_EPLUGIN : 0) | err_no, LYVE_OTHER, path ? strdup(path) : NULL, NULL, - plugin_msg, ap); + log_vprintf(ctx, level, (level == LY_LLERR ? LY_EPLUGIN : 0) | err_no, LYVE_OTHER, path, NULL, plugin_msg, ap); free(plugin_msg); } @@ -717,8 +937,6 @@ lyplg_ext_parse_log(const struct lysp_ctx *pctx, const struct lysp_ext_instance va_start(ap, format); ly_ext_log(PARSER_CTX(pctx), ext->record->plugin.id, level, err_no, path, format, ap); va_end(ap); - - free(path); } LIBYANG_API_DEF void @@ -726,9 +944,15 @@ lyplg_ext_compile_log(const struct lysc_ctx *cctx, const struct lysc_ext_instanc const char *format, ...) { va_list ap; + char *path = NULL; + + if (cctx && (asprintf(&path, "Path \"%s\".", cctx->path) == -1)) { + LOGMEM(cctx->ctx); + return; + } va_start(ap, format); - ly_ext_log(ext->module->ctx, ext->def->plugin->id, level, err_no, cctx ? cctx->path : NULL, format, ap); + ly_ext_log(ext->module->ctx, ext->def->plugin->id, level, err_no, path, format, ap); va_end(ap); } @@ -737,12 +961,37 @@ lyplg_ext_compile_log_path(const char *path, const struct lysc_ext_instance *ext const char *format, ...) { va_list ap; + char *log_path = NULL; + + if (path && (asprintf(&log_path, "Path \"%s\".", path) == -1)) { + LOGMEM(ext->module->ctx); + return; + } va_start(ap, format); - ly_ext_log(ext->module->ctx, ext->def->plugin->id, level, err_no, path, format, ap); + ly_ext_log(ext->module->ctx, ext->def->plugin->id, level, err_no, log_path, format, ap); + va_end(ap); +} + +/** + * @brief Serves only for creating ap. + */ +static void +_lyplg_ext_compile_log_err(const struct ly_err_item *err, const struct lysc_ext_instance *ext, ...) +{ + va_list ap; + + va_start(ap, ext); + ly_ext_log(ext->module->ctx, ext->def->plugin->id, err->level, err->no, err->path ? strdup(err->path) : NULL, "%s", ap); va_end(ap); } +LIBYANG_API_DEF void +lyplg_ext_compile_log_err(const struct ly_err_item *err, const struct lysc_ext_instance *ext) +{ + _lyplg_ext_compile_log_err(err, ext, err->msg); +} + /** * @brief Exact same functionality as ::ly_err_print() but has variable arguments so log_vprintf() can be called. */ @@ -141,8 +141,6 @@ LIBYANG_API_DECL uint32_t ly_log_options(uint32_t opts); */ LIBYANG_API_DECL void ly_temp_log_options(uint32_t *opts); -#ifndef NDEBUG - /** * @ingroup log * @defgroup dbggroup Debug messages groups @@ -166,13 +164,12 @@ LIBYANG_API_DECL void ly_temp_log_options(uint32_t *opts); * @brief Enable specific debugging messages (independent of log level). * * To get the current value, the function must be called twice resetting the level by the received value. + * Note: does not have any effect on non-debug (Release) builds * * @param[in] dbg_groups Bitfield of enabled debug message groups (see @ref dbggroup). * @return Previous options bitfield. */ -uint32_t ly_log_dbg_groups(uint32_t dbg_groups); - -#endif +LIBYANG_API_DECL uint32_t ly_log_dbg_groups(uint32_t dbg_groups); /** * @brief Logger callback. @@ -294,17 +291,33 @@ typedef enum { * @brief Libyang full error structure. */ struct ly_err_item { - LY_LOG_LEVEL level; - LY_ERR no; - LY_VECODE vecode; - char *msg; - char *path; - char *apptag; - struct ly_err_item *next; - struct ly_err_item *prev; /* first item's prev points to the last item */ + LY_LOG_LEVEL level; /**< error (message) log level */ + LY_ERR no; /**< error code */ + LY_VECODE vecode; /**< validation error code, if any */ + char *msg; /**< error message */ + char *path; /**< error path that caused the error, if any */ + char *apptag; /**< error-app-tag, if any */ + struct ly_err_item *next; /**< next error item */ + struct ly_err_item *prev; /**< previous error item, points to the last item for the ifrst item */ }; /** + * @brief Get the last (thread, context-specific) error code. + * + * @param[in] ctx Relative context. + * @return LY_ERR value of the last error code. + */ +LIBYANG_API_DECL LY_ERR ly_errcode(const struct ly_ctx *ctx); + +/** + * @brief Get human-readable error message for an error code. + * + * @param[in] err Error code. + * @return String error message. + */ +LIBYANG_API_DECL const char *ly_strerrcode(LY_ERR err); + +/** * @brief Get the last (thread, context-specific) validation error code. * * This value is set only if ly_errno is #LY_EVALID. @@ -315,12 +328,12 @@ struct ly_err_item { LIBYANG_API_DECL LY_VECODE ly_vecode(const struct ly_ctx *ctx); /** - * @brief Get the last (thread, context-specific) error code. + * @brief Get human-readable error message for a validation error code. * - * @param[in] ctx Relative context. - * @return LY_ERR value of the last error code. + * @param[in] vecode Validation error code. + * @return String error message. */ -LIBYANG_API_DECL LY_ERR ly_errcode(const struct ly_ctx *ctx); +LIBYANG_API_DECL const char *ly_strvecode(LY_VECODE vecode); /** * @brief Get the last (thread, context-specific) error message. If the coresponding module defined @@ -335,6 +348,16 @@ LIBYANG_API_DECL LY_ERR ly_errcode(const struct ly_ctx *ctx); LIBYANG_API_DECL const char *ly_errmsg(const struct ly_ctx *ctx); /** + * @brief Get the last (thread-specific) error message. + * + * ::ly_errmsg() should be used instead of this function but this one is useful for getting + * errors from functions that do not have any context accessible. Or as a simple unified logging API. + * + * @return Last generated error message. + */ +LIBYANG_API_DECL const char *ly_last_errmsg(void); + +/** * @brief Get the last (thread, context-specific) path of the element where was an error. * * The path always corresponds to the error message available via ::ly_errmsg(), so @@ -39,8 +39,8 @@ lyb_generate_hash(const struct lysc_node *node, uint8_t collision_id) LYB_HASH hash; /* generate full hash */ - full_hash = dict_hash_multi(0, mod->name, strlen(mod->name)); - full_hash = dict_hash_multi(full_hash, node->name, strlen(node->name)); + full_hash = lyht_hash_multi(0, mod->name, strlen(mod->name)); + full_hash = lyht_hash_multi(full_hash, node->name, strlen(node->name)); if (collision_id) { size_t ext_len; @@ -51,9 +51,9 @@ lyb_generate_hash(const struct lysc_node *node, uint8_t collision_id) /* use one more byte from the module name than before */ ext_len = collision_id; } - full_hash = dict_hash_multi(full_hash, mod->name, ext_len); + full_hash = lyht_hash_multi(full_hash, mod->name, ext_len); } - full_hash = dict_hash_multi(full_hash, NULL, 0); + full_hash = lyht_hash_multi(full_hash, NULL, 0); /* use the shortened hash */ hash = full_hash & (LYB_HASH_MASK >> collision_id); @@ -95,7 +95,7 @@ struct lylyb_ctx { /* LYB printer only */ struct lyd_lyb_sib_ht { struct lysc_node *first_sibling; - struct hash_table *ht; + struct ly_ht *ht; } *sib_hts; }; @@ -417,7 +417,7 @@ ly_vprint_(struct ly_out *out, const char *format, va_list ap) { LY_ERR ret; int written = 0; - char *msg = NULL, *aux; + char *msg = NULL; switch (out->type) { case LY_OUT_FD: @@ -433,15 +433,13 @@ ly_vprint_(struct ly_out *out, const char *format, va_list ap) break; } if (out->method.mem.len + written + 1 > out->method.mem.size) { - aux = ly_realloc(*out->method.mem.buf, out->method.mem.len + written + 1); - if (!aux) { - out->method.mem.buf = NULL; + *out->method.mem.buf = ly_realloc(*out->method.mem.buf, out->method.mem.len + written + 1); + if (!*out->method.mem.buf) { out->method.mem.len = 0; out->method.mem.size = 0; LOGMEM(NULL); return LY_EMEM; } - *out->method.mem.buf = aux; out->method.mem.size = out->method.mem.len + written + 1; } if (written) { @@ -630,9 +628,9 @@ repeat: } LOGERR(NULL, LY_ESYS, "%s: writing data failed (%s).", __func__, strerror(errno)); written = 0; - } else if ((size_t)written != len) { - LOGERR(NULL, LY_ESYS, "%s: writing data failed (unable to write %u from %u data).", __func__, - len - (size_t)written, len); + } else if (written != len) { + LOGERR(NULL, LY_ESYS, "%s: writing data failed (unable to write %" PRIu32 " from %" PRIu32 " data).", __func__, + (uint32_t)(len - written), (uint32_t)len); ret = LY_ESYS; } else { if (out->type == LY_OUT_FDSTREAM) { diff --git a/src/parser_common.c b/src/parser_common.c index 6fe068b..3215275 100644 --- a/src/parser_common.c +++ b/src/parser_common.c @@ -46,6 +46,7 @@ #include "parser_data.h" #include "path.h" #include "plugins_exts/metadata.h" +#include "schema_compile_node.h" #include "schema_features.h" #include "set.h" #include "tree.h" @@ -65,6 +66,50 @@ lyd_ctx_free(struct lyd_ctx *lydctx) } LY_ERR +lyd_parser_notif_eventtime_validate(const struct lyd_node *node) +{ + LY_ERR rc = LY_SUCCESS; + struct ly_ctx *ctx = (struct ly_ctx *)LYD_CTX(node); + struct lysc_ctx cctx; + const struct lys_module *mod; + LY_ARRAY_COUNT_TYPE u; + struct ly_err_item *err = NULL; + struct lysp_type *type_p = NULL; + struct lysc_pattern **patterns = NULL; + const char *value; + + LYSC_CTX_INIT_CTX(cctx, ctx); + + /* get date-and-time parsed type */ + mod = ly_ctx_get_module_latest(ctx, "ietf-yang-types"); + assert(mod); + LY_ARRAY_FOR(mod->parsed->typedefs, u) { + if (!strcmp(mod->parsed->typedefs[u].name, "date-and-time")) { + type_p = &mod->parsed->typedefs[u].type; + break; + } + } + assert(type_p); + + /* compile patterns */ + assert(type_p->patterns); + LY_CHECK_GOTO(rc = lys_compile_type_patterns(&cctx, type_p->patterns, NULL, &patterns), cleanup); + + /* validate */ + value = lyd_get_value(node); + rc = lyplg_type_validate_patterns(patterns, value, strlen(value), &err); + +cleanup: + FREE_ARRAY(&cctx.free_ctx, patterns, lysc_pattern_free); + if (rc && err) { + LOGVAL_ERRITEM(ctx, err); + ly_err_free(err); + LOGVAL(ctx, LYVE_DATA, "Invalid \"eventTime\" in the notification."); + } + return rc; +} + +LY_ERR lyd_parser_find_operation(const struct lyd_node *parent, uint32_t int_opts, struct lyd_node **op) { const struct lyd_node *iter; @@ -112,6 +157,63 @@ lyd_parser_find_operation(const struct lyd_node *parent, uint32_t int_opts, stru return LY_SUCCESS; } +const struct lysc_node * +lyd_parser_node_schema(const struct lyd_node *node) +{ + uint32_t i; + const struct lyd_node *iter; + const struct lysc_node *schema = NULL; + const struct lys_module *mod; + + if (!node) { + return NULL; + } else if (node->schema) { + /* simplest case */ + return node->schema; + } + + /* find the first schema node in the parsed nodes */ + i = ly_log_location_dnode_count(); + if (i) { + do { + --i; + if (ly_log_location_dnode(i)->schema) { + /* this node is processed */ + schema = ly_log_location_dnode(i)->schema; + ++i; + break; + } + } while (i); + } + + /* get schema node of an opaque node */ + do { + /* get next data node */ + if (i == ly_log_location_dnode_count()) { + iter = node; + } else { + iter = ly_log_location_dnode(i); + } + assert(!iter->schema); + + /* get module */ + mod = lyd_node_module(iter); + if (!mod) { + /* unknown module, no schema node */ + schema = NULL; + break; + } + + /* get schema node */ + schema = lys_find_child(schema, mod, LYD_NAME(iter), 0, 0, 0); + + /* move to the descendant */ + ++i; + } while (schema && (iter != node)); + + return schema; +} + LY_ERR lyd_parser_check_schema(struct lyd_ctx *lydctx, const struct lysc_node *snode) { @@ -180,9 +282,16 @@ LY_ERR lyd_parser_create_term(struct lyd_ctx *lydctx, const struct lysc_node *schema, const void *value, size_t value_len, ly_bool *dynamic, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, struct lyd_node **node) { + LY_ERR r; ly_bool incomplete; - LY_CHECK_RET(lyd_create_term(schema, value, value_len, dynamic, format, prefix_data, hints, &incomplete, node)); + if ((r = lyd_create_term(schema, value, value_len, 1, dynamic, format, prefix_data, hints, &incomplete, node))) { + if (lydctx->data_ctx->ctx != schema->module->ctx) { + /* move errors to the main context */ + ly_err_move(schema->module->ctx, (struct ly_ctx *)lydctx->data_ctx->ctx); + } + return r; + } if (incomplete && !(lydctx->parse_opts & LYD_PARSE_ONLY)) { LY_CHECK_RET(ly_set_add(&lydctx->node_types, *node, 1, NULL)); @@ -195,6 +304,8 @@ lyd_parser_create_meta(struct lyd_ctx *lydctx, struct lyd_node *parent, struct l const char *name, size_t name_len, const void *value, size_t value_len, ly_bool *dynamic, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, const struct lysc_node *ctx_node) { + LY_ERR rc = LY_SUCCESS; + char *dpath = NULL, *path = NULL; ly_bool incomplete; struct lyd_meta *first = NULL; @@ -203,11 +314,20 @@ lyd_parser_create_meta(struct lyd_ctx *lydctx, struct lyd_node *parent, struct l first = *meta; } - LY_CHECK_RET(lyd_create_meta(parent, meta, mod, name, name_len, value, value_len, dynamic, format, prefix_data, - hints, ctx_node, 0, &incomplete)); + /* generate path to the metadata */ + LY_CHECK_RET(ly_vlog_build_data_path(lydctx->data_ctx->ctx, &dpath)); + if (asprintf(&path, "%s/@%s:%.*s", dpath, mod->name, (int)name_len, name) == -1) { + LOGMEM(lydctx->data_ctx->ctx); + rc = LY_EMEM; + goto cleanup; + } + LOG_LOCSET(NULL, NULL, path, NULL); + + LY_CHECK_GOTO(rc = lyd_create_meta(parent, meta, mod, name, name_len, value, value_len, 1, dynamic, format, + prefix_data, hints, ctx_node, 0, &incomplete), cleanup); if (incomplete && !(lydctx->parse_opts & LYD_PARSE_ONLY)) { - LY_CHECK_RET(ly_set_add(&lydctx->meta_types, *meta, 1, NULL)); + LY_CHECK_GOTO(rc = ly_set_add(&lydctx->meta_types, *meta, 1, NULL), cleanup); } if (first) { @@ -215,7 +335,11 @@ lyd_parser_create_meta(struct lyd_ctx *lydctx, struct lyd_node *parent, struct l *meta = first; } - return LY_SUCCESS; +cleanup: + LOG_LOCBACK(0, 0, 1, 0); + free(dpath); + free(path); + return rc; } LY_ERR @@ -387,6 +511,16 @@ lysp_stmt_validate_value(struct lysp_ctx *ctx, enum yang_arg val_type, const cha uint32_t c; size_t utf8_char_len; + if (!val) { + if (val_type == Y_MAYBE_STR_ARG) { + /* fine */ + return LY_SUCCESS; + } + + LOGVAL_PARSER(ctx, LYVE_SYNTAX, "Missing an expected string."); + return LY_EVALID; + } + while (*val) { LY_CHECK_ERR_RET(ly_getutf8(&val, &c, &utf8_char_len), LOGVAL_PARSER(ctx, LY_VCODE_INCHAR, (val)[-utf8_char_len]), LY_EVALID); @@ -605,7 +739,7 @@ lysp_stmt_text_fields(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, const static LY_ERR lysp_stmt_status(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint16_t *flags, struct lysp_ext_instance **exts) { - size_t arg_len; + int arg_len; if (*flags & LYS_STATUS_MASK) { LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "status"); @@ -698,7 +832,7 @@ lysp_stmt_when(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, struct lysp_w static LY_ERR lysp_stmt_config(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint16_t *flags, struct lysp_ext_instance **exts) { - size_t arg_len; + int arg_len; if (*flags & LYS_CONFIG_MASK) { LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "config"); @@ -744,7 +878,7 @@ static LY_ERR lysp_stmt_mandatory(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint16_t *flags, struct lysp_ext_instance **exts) { - size_t arg_len; + int arg_len; if (*flags & LYS_MAND_MASK) { LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "mandatory"); @@ -914,7 +1048,7 @@ static LY_ERR lysp_stmt_type_enum_value_pos(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, int64_t *value, uint16_t *flags, struct lysp_ext_instance **exts) { - size_t arg_len; + int arg_len; char *ptr = NULL; long long num = 0; unsigned long long unum = 0; @@ -949,7 +1083,7 @@ lysp_stmt_type_enum_value_pos(struct lysp_ctx *ctx, const struct lysp_stmt *stmt } } /* we have not parsed the whole argument */ - if ((size_t)(ptr - stmt->arg) != arg_len) { + if (ptr - stmt->arg != arg_len) { LOGVAL_PARSER(ctx, LY_VCODE_INVAL, arg_len, stmt->arg, lyplg_ext_stmt2str(stmt->kw)); goto error; } @@ -1056,7 +1190,7 @@ lysp_stmt_type_fracdigits(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, ui struct lysp_ext_instance **exts) { char *ptr; - size_t arg_len; + int arg_len; unsigned long long num; if (*fracdig) { @@ -1074,7 +1208,7 @@ lysp_stmt_type_fracdigits(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, ui errno = 0; num = strtoull(stmt->arg, &ptr, LY_BASE_DEC); /* we have not parsed the whole argument */ - if ((size_t)(ptr - stmt->arg) != arg_len) { + if (ptr - stmt->arg != arg_len) { LOGVAL_PARSER(ctx, LY_VCODE_INVAL, arg_len, stmt->arg, "fraction-digits"); return LY_EVALID; } @@ -1112,7 +1246,7 @@ static LY_ERR lysp_stmt_type_reqinstance(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint8_t *reqinst, uint16_t *flags, struct lysp_ext_instance **exts) { - size_t arg_len; + int arg_len; if (*flags & LYS_SET_REQINST) { LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "require-instance"); @@ -1155,7 +1289,7 @@ static LY_ERR lysp_stmt_type_pattern_modifier(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, const char **pat, struct lysp_ext_instance **exts) { - size_t arg_len; + int arg_len; char *buf; if ((*pat)[0] == LYSP_RESTR_PATTERN_NACK) { @@ -1338,7 +1472,7 @@ lysp_stmt_yangver(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint8_t *v } else if (!strcmp(stmt->arg, "1.1")) { *version = LYS_VERSION_1_1; } else { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, strlen(stmt->arg), stmt->arg, "yang-version"); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)strlen(stmt->arg), stmt->arg, "yang-version"); return LY_EVALID; } @@ -1418,7 +1552,7 @@ lysp_stmt_yinelem(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint16_t * } else if (!strcmp(stmt->arg, "false")) { *flags |= LYS_YINELEM_FALSE; } else { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, strlen(stmt->arg), stmt->arg, "yin-element"); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)strlen(stmt->arg), stmt->arg, "yin-element"); return LY_EVALID; } @@ -1946,7 +2080,7 @@ static LY_ERR lysp_stmt_maxelements(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint32_t *max, uint16_t *flags, struct lysp_ext_instance **exts) { - size_t arg_len; + int arg_len; char *ptr; unsigned long long num; @@ -1969,7 +2103,7 @@ lysp_stmt_maxelements(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint32 errno = 0; num = strtoull(stmt->arg, &ptr, LY_BASE_DEC); /* we have not parsed the whole argument */ - if ((size_t)(ptr - stmt->arg) != arg_len) { + if (ptr - stmt->arg != arg_len) { LOGVAL_PARSER(ctx, LY_VCODE_INVAL, arg_len, stmt->arg, "max-elements"); return LY_EVALID; } @@ -2012,7 +2146,7 @@ static LY_ERR lysp_stmt_minelements(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint32_t *min, uint16_t *flags, struct lysp_ext_instance **exts) { - size_t arg_len; + int arg_len; char *ptr; unsigned long long num; @@ -2034,7 +2168,7 @@ lysp_stmt_minelements(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint32 errno = 0; num = strtoull(stmt->arg, &ptr, LY_BASE_DEC); /* we have not parsed the whole argument */ - if ((size_t)(ptr - stmt->arg) != arg_len) { + if (ptr - stmt->arg != arg_len) { LOGVAL_PARSER(ctx, LY_VCODE_INVAL, arg_len, stmt->arg, "min-elements"); return LY_EVALID; } @@ -2070,7 +2204,7 @@ lysp_stmt_minelements(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint32 static LY_ERR lysp_stmt_orderedby(struct lysp_ctx *ctx, const struct lysp_stmt *stmt, uint16_t *flags, struct lysp_ext_instance **exts) { - size_t arg_len; + int arg_len; if (*flags & LYS_ORDBY_MASK) { LOGVAL_PARSER(ctx, LY_VCODE_DUPSTMT, "ordered-by"); diff --git a/src/parser_data.h b/src/parser_data.h index 050ced3..feedb14 100644 --- a/src/parser_data.h +++ b/src/parser_data.h @@ -1,9 +1,10 @@ /** * @file parser_data.h * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Michal Vasko <mvasko@cesnet.cz> * @brief Data parsers for libyang * - * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -156,9 +157,14 @@ struct ly_in; modified manually. If this flag is used incorrectly (for unordered data), the behavior is undefined and most functions executed with these data will not work correctly. */ -#define LYD_PARSE_SUBTREE 0x400000 /**< Parse only the current data subtree with any descendants, no siblings. +#define LYD_PARSE_SUBTREE 0x400000 /**< Parse only the first child item along with any descendants, but no + siblings. This flag is not required when parsing data which do not + start at the schema root; for that purpose, use lyd_parse_data's parent + argument. Also, a new return value ::LY_ENOT is returned if there is a sibling - subtree following in the input data. */ + subtree following in the input data. Note that if validation is requested, + only the newly parsed subtree is validated. This might result in + an invalid datastore content. */ #define LYD_PARSE_WHEN_TRUE 0x800000 /**< Mark all the parsed nodes dependend on a when condition with the flag that means the condition was satisifed before. This allows for auto-deletion of these nodes during validation. */ @@ -196,6 +202,15 @@ struct ly_in; #define LYD_VALIDATE_NO_STATE 0x0001 /**< Consider state data not allowed and raise an error if they are found. Also, no implicit state data are added. */ #define LYD_VALIDATE_PRESENT 0x0002 /**< Validate only modules whose data actually exist. */ +#define LYD_VALIDATE_MULTI_ERROR 0x0004 /**< Do not stop validation on the first error but generate all the detected errors. */ +#define LYD_VALIDATE_OPERATIONAL 0x0008 /**< Semantic constraint violations are reported only as warnings instead of + errors (see [RFC 8342 sec. 5.3](https://datatracker.ietf.org/doc/html/rfc8342#section-5.3)). */ +#define LYD_VALIDATE_NO_DEFAULTS 0x0010 /**< Do not add any default nodes during validation, other implicit nodes + (such as NP containers) are still added. Validation will fail if a + default node is required for it to pass. */ +#define LYD_VALIDATE_NOT_FINAL 0x0020 /**< Skip final validation tasks that require for all the data nodes to + either exist or not, based on the YANG constraints. Once the data + satisfy this requirement, the final validation should be performed. */ #define LYD_VALIDATE_OPTS_MASK 0x0000FFFF /**< Mask for all the LYD_VALIDATE_* options. */ @@ -205,7 +220,8 @@ struct ly_in; * @brief Parse (and validate) data from the input handler as a YANG data tree. * * @param[in] ctx Context to connect with the tree being built here. - * @param[in] parent Optional parent to connect the parsed nodes to. + * @param[in] parent Optional parent to connect the parsed nodes to. If provided, the data are expected to describe + * a subtree of the YANG model instead of starting at the schema root. * @param[in] in The input handle to provide the dumped data in the specified @p format to parse (and validate). * @param[in] format Format of the input data to be parsed. Can be 0 to try to detect format from the input handler. * @param[in] parse_options Options for parser, see @ref dataparseroptions. @@ -213,6 +229,11 @@ struct ly_in; * @param[out] tree Full parsed data tree, note that NULL can be a valid tree. If @p parent is set, set to NULL. * @return LY_SUCCESS in case of successful parsing (and validation). * @return LY_ERR value in case of error. Additional error information can be obtained from the context using ly_err* functions. + * + * When parsing subtrees (i.e., when @p parent is non-NULL), validation is only performed on the newly parsed data. + * This might result in allowing invalid datastore content when the schema contains cross-branch constraints, + * complicated `must` statements, etc. When a full-datastore validation is desirable, parse all subtrees + * first, and then request validation of the complete datastore content. */ LIBYANG_API_DECL LY_ERR lyd_parse_data(const struct ly_ctx *ctx, struct lyd_node *parent, struct ly_in *in, LYD_FORMAT format, uint32_t parse_options, uint32_t validate_options, struct lyd_node **tree); @@ -303,26 +324,35 @@ LIBYANG_API_DECL LY_ERR lyd_parse_ext_data(const struct lysc_ext_instance *ext, * @{ */ enum lyd_type { - LYD_TYPE_DATA_YANG = 0, /* generic YANG instance data */ - LYD_TYPE_RPC_YANG, /* instance of a YANG RPC/action request with only "input" data children, - including all parents in case of an action */ - LYD_TYPE_NOTIF_YANG, /* instance of a YANG notification, including all parents in case of a nested one */ - LYD_TYPE_REPLY_YANG, /* instance of a YANG RPC/action reply with only "output" data children, - including all parents in case of an action */ + LYD_TYPE_DATA_YANG = 0, /* generic YANG instance data */ + LYD_TYPE_RPC_YANG, /* instance of a YANG RPC/action request with only "input" data children, + including all parents and optional top-level "action" element in case of an action */ + LYD_TYPE_NOTIF_YANG, /* instance of a YANG notification, including all parents in case of a nested one */ + LYD_TYPE_REPLY_YANG, /* instance of a YANG RPC/action reply with only "output" data children, + including all parents in case of an action */ + + LYD_TYPE_RPC_NETCONF, /* complete NETCONF RPC invocation as defined for + [RPC](https://tools.ietf.org/html/rfc7950#section-7.14.4) and + [action](https://tools.ietf.org/html/rfc7950#section-7.15.2) */ + LYD_TYPE_NOTIF_NETCONF, /* complete NETCONF notification message as defined for + [notification](https://tools.ietf.org/html/rfc7950#section-7.16.2) */ + LYD_TYPE_REPLY_NETCONF, /* complete NETCONF RPC reply as defined for + [RPC](https://tools.ietf.org/html/rfc7950#section-7.14.4) and + [action](https://tools.ietf.org/html/rfc7950#section-7.15.2) */ - LYD_TYPE_RPC_NETCONF, /* complete NETCONF RPC invocation as defined for - [RPC](https://tools.ietf.org/html/rfc7950#section-7.14.4) and - [action](https://tools.ietf.org/html/rfc7950#section-7.15.2) */ - LYD_TYPE_NOTIF_NETCONF, /* complete NETCONF notification message as defined for - [notification](https://tools.ietf.org/html/rfc7950#section-7.16.2) */ - LYD_TYPE_REPLY_NETCONF /* complete NETCONF RPC reply as defined for - [RPC](https://tools.ietf.org/html/rfc7950#section-7.14.4) and - [action](https://tools.ietf.org/html/rfc7950#section-7.15.2) */ + LYD_TYPE_RPC_RESTCONF, /* message-body of a RESTCONF operation input parameters + ([ref](https://www.rfc-editor.org/rfc/rfc8040.html#section-3.6.1)) */ + LYD_TYPE_NOTIF_RESTCONF, /* RESTCONF JSON notification data + ([ref](https://www.rfc-editor.org/rfc/rfc8040.html#section-6.4)), to parse + a notification in XML, use ::LYD_TYPE_NOTIF_NETCONF */ + LYD_TYPE_REPLY_RESTCONF /* message-body of a RESTCONF operation output parameters + ([ref](https://www.rfc-editor.org/rfc/rfc8040.html#section-3.6.2)) */ }; /** @} datatype */ /** - * @brief Parse YANG data into an operation data tree. + * @brief Parse YANG data into an operation data tree. Specific parsing flags ::LYD_PARSE_ONLY, ::LYD_PARSE_STRICT and + * no validation flags are used. * * At least one of @p parent, @p tree, or @p op must always be set. * @@ -349,6 +379,28 @@ enum lyd_type { * - @p op - must be NULL, the reply is appended to the RPC; * Note that there are 3 kinds of NETCONF replies - ok, error, and data. Only data reply appends any nodes to the RPC. * + * - ::LYD_TYPE_RPC_RESTCONF: + * - @p parent - must be set, pointing to the invoked RPC operation (RPC or action) node; + * - @p format - can be both ::LYD_JSON and ::LYD_XML; + * - @p tree - must be provided, all the RESTCONF-specific JSON objects will be returned here as + * a separate opaque data tree, even if the function fails, this may be returned; + * - @p op - must be NULL, @p parent points to the operation; + * + * - ::LYD_TYPE_NOTIF_RESTCONF: + * - @p parent - must be NULL, the whole notification is expected; + * - @p format - must be ::LYD_JSON, XML-formatted notifications are parsed using ::LYD_TYPE_NOTIF_NETCONF; + * - @p tree - must be provided, all the RESTCONF-specific JSON objects will be returned here as + * a separate opaque data tree, even if the function fails, this may be returned; + * - @p op - must be provided, the notification data tree itself will be returned here, pointing to the operation; + * + * - ::LYD_TYPE_REPLY_RESTCONF: + * - @p parent - must be set, pointing to the invoked RPC operation (RPC or action) node; + * - @p format - can be both ::LYD_JSON and ::LYD_XML; + * - @p tree - must be provided, all the RESTCONF-specific JSON objects will be returned here as + * a separate opaque data tree, even if the function fails, this may be returned; + * - @p op - must be NULL, @p parent points to the operation; + * Note that error reply should be parsed as 'yang-data' extension data. + * * @param[in] ctx libyang context. * @param[in] parent Optional parent to connect the parsed nodes to. * @param[in] in Input handle to read the input from. @@ -427,6 +479,10 @@ LIBYANG_API_DECL LY_ERR lyd_validate_all(struct lyd_node **tree, const struct ly * The data tree is modified in-place. As a result of the validation, some data might be removed * from the tree. In that case, the removed items are freed, not just unlinked. * + * If several modules need to be validated, the flag ::LYD_VALIDATE_NOT_FINAL should be used first for validation + * of each module and then ::lyd_validate_module_final() should be called also for each module. Otherwise, + * false-positive validation errors for foreign dependencies may occur. + * * @param[in,out] tree Data tree to recursively validate. May be changed by validation, might become NULL. * @param[in] module Module whose data (and schema restrictions) to validate. * @param[in] val_opts Validation options (@ref datavalidationoptions). @@ -438,6 +494,20 @@ LIBYANG_API_DECL LY_ERR lyd_validate_module(struct lyd_node **tree, const struct struct lyd_node **diff); /** + * @brief Finish validation of a module data that have previously been validated with ::LYD_VALIDATE_NOT_FINAL flag. + * + * This final validation will not add or remove any nodes. + * + * @param[in] tree Data tree to recursively validate. + * @param[in] module Module whose data (and schema restrictions) to validate. + * @param[in] val_opts Validation options (@ref datavalidationoptions). + * @return LY_SUCCESS on success. + * @return LY_ERR error on error. + */ +LIBYANG_API_DECL LY_ERR lyd_validate_module_final(struct lyd_node *tree, const struct lys_module *module, + uint32_t val_opts); + +/** * @brief Validate an RPC/action request, reply, or notification. Only the operation data tree (input/output/notif) * is validate, any parents are ignored. * diff --git a/src/parser_internal.h b/src/parser_internal.h index 458d297..92412e2 100644 --- a/src/parser_internal.h +++ b/src/parser_internal.h @@ -1,9 +1,10 @@ /** * @file parser_internal.h * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Michal Vasko <mvasko@cesnet.cz> * @brief Internal structures and functions for libyang parsers * - * Copyright (c) 2020 CESNET, z.s.p.o. + * Copyright (c) 2020 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -27,6 +28,23 @@ struct lysp_yin_ctx; struct lysp_ctx; /** + * @brief Check data parser error taking into account multi-error validation. + * + * @param[in] r Local return value. + * @param[in] err_cmd Command to perform on any error. + * @param[in] lydctx Data parser context. + * @param[in] label Label to go to on fatal error. + */ +#define LY_DPARSER_ERR_GOTO(r, err_cmd, lydctx, label) \ + if (r) { \ + err_cmd; \ + if ((r != LY_EVALID) || !lydctx || !(lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR) || \ + (ly_vecode(((struct lyd_ctx *)lydctx)->data_ctx->ctx) == LYVE_SYNTAX)) { \ + goto label; \ + } \ + } + +/** * @brief Callback for ::lyd_ctx to free the structure * * @param[in] ctx Data parser context to free. @@ -43,6 +61,7 @@ typedef void (*lyd_ctx_free_clb)(struct lyd_ctx *ctx); #define LYD_INTOPT_ANY 0x10 /**< Anydata/anyxml content is being parsed, there can be anything. */ #define LYD_INTOPT_WITH_SIBLINGS 0x20 /**< Parse the whole input with any siblings. */ #define LYD_INTOPT_NO_SIBLINGS 0x40 /**< If there are any siblings, return an error. */ +#define LYD_INTOPT_EVENTTIME 0x80 /**< Parse notification eventTime node. */ /** * @brief Internal (common) context for YANG data parsers. @@ -118,7 +137,8 @@ struct lyd_json_ctx { /* callbacks */ lyd_ctx_free_clb free; - struct lyjson_ctx *jsonctx; /**< JSON context */ + struct lyjson_ctx *jsonctx; /**< JSON context */ + const struct lysc_node *any_schema; /**< parent anyxml/anydata schema node if parsing nested data tree */ }; /** @@ -279,6 +299,27 @@ LY_ERR lyd_parse_json(const struct ly_ctx *ctx, const struct lysc_ext_instance * struct ly_set *parsed, ly_bool *subtree_sibling, struct lyd_ctx **lydctx_p); /** + * @brief Parse JSON string as a RESTCONF message. + * + * @param[in] ctx libyang context. + * @param[in] ext Optional extension instance to parse data following the schema tree specified in the extension instance + * @param[in] parent Parent to connect the parsed nodes to, if any. + * @param[in,out] first_p Pointer to the first top-level parsed node, used only if @p parent is NULL. + * @param[in] in Input structure. + * @param[in] parse_opts Options for parser, see @ref dataparseroptions. + * @param[in] val_opts Options for the validation phase, see @ref datavalidationoptions. + * @param[in] data_type Expected RESTCONF data type of the data. + * @param[out] envp Individual parsed envelopes tree, may be returned possibly even on an error. + * @param[out] parsed Set to add all the parsed siblings into. + * @param[out] subtree_sibling Set if ::LYD_PARSE_SUBTREE is used and another subtree is following in @p in. + * @param[out] lydctx_p Data parser context to finish validation. + * @return LY_ERR value. + */ +LY_ERR lyd_parse_json_restconf(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct lyd_node *parent, + struct lyd_node **first_p, struct ly_in *in, uint32_t parse_opts, uint32_t val_opts, enum lyd_type data_type, + struct lyd_node **envp, struct ly_set *parsed, struct lyd_ctx **lydctx_p); + +/** * @brief Parse binary LYB data as a YANG data tree. * * @param[in] ctx libyang context. @@ -299,6 +340,15 @@ LY_ERR lyd_parse_lyb(const struct ly_ctx *ctx, const struct lysc_ext_instance *e struct ly_set *parsed, ly_bool *subtree_sibling, struct lyd_ctx **lydctx_p); /** + * @brief Validate eventTime date-and-time value. + * + * @param[in] node Opaque eventTime node. + * @return LY_SUCCESS on success. + * @return LY_ERR value on error. + */ +LY_ERR lyd_parser_notif_eventtime_validate(const struct lyd_node *node); + +/** * @brief Search all the parents for an operation node, check validity based on internal parser flags. * * @param[in] parent Parent to connect the parsed nodes to. @@ -309,6 +359,14 @@ LY_ERR lyd_parse_lyb(const struct ly_ctx *ctx, const struct lysc_ext_instance *e LY_ERR lyd_parser_find_operation(const struct lyd_node *parent, uint32_t int_opts, struct lyd_node **op); /** + * @brief Get schema node of a node being parsed, use nodes stored for logging. + * + * @param[in] node Node whose schema node to get. + * @return Schema node even for an opaque node, NULL if none found. + */ +const struct lysc_node *lyd_parser_node_schema(const struct lyd_node *node); + +/** * @brief Check that a data node representing the @p snode is suitable based on options. * * @param[in] lydctx Common data parsers context. diff --git a/src/parser_json.c b/src/parser_json.c index 6219c6e..3655d4c 100644 --- a/src/parser_json.c +++ b/src/parser_json.c @@ -4,7 +4,7 @@ * @author Michal Vasko <mvasko@cesnet.cz> * @brief JSON data parser for libyang * - * Copyright (c) 2020 - 2022 CESNET, z.s.p.o. + * Copyright (c) 2020 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -161,29 +161,32 @@ lydjson_get_node_prefix(struct lyd_node *node, const char *local_prefix, size_t return LY_SUCCESS; } - *prefix_p = NULL; while (node) { if (node->schema) { - *prefix_p = node->schema->module->name; + module_name = node->schema->module->name; break; } onode = (struct lyd_node_opaq *)node; if (onode->name.module_name) { - *prefix_p = onode->name.module_name; + module_name = onode->name.module_name; break; } else if (onode->name.prefix) { - *prefix_p = onode->name.prefix; + module_name = onode->name.prefix; break; } node = lyd_parent(node); } - *prefix_len_p = ly_strlen(module_name); + *prefix_p = module_name; + *prefix_len_p = ly_strlen(module_name); return LY_SUCCESS; } /** - * @brief Skip the current JSON object/array. + * @brief Skip the current JSON item (based on status). + * + * The JSON context is moved to the last "status" of the JSON item so to completely + * finish the skip, one more JSON context move is required. * * @param[in] jsonctx JSON context with the input data to skip. * @return LY_ERR value. @@ -192,34 +195,39 @@ static LY_ERR lydjson_data_skip(struct lyjson_ctx *jsonctx) { enum LYJSON_PARSER_STATUS status, current; - uint32_t orig_depth; + uint32_t depth; - status = lyjson_ctx_status(jsonctx, 0); - assert((status == LYJSON_OBJECT) || (status == LYJSON_ARRAY)); - orig_depth = jsonctx->depth; + status = lyjson_ctx_status(jsonctx); + depth = lyjson_ctx_depth(jsonctx); - /* next */ - LY_CHECK_RET(lyjson_ctx_next(jsonctx, ¤t)); - - if ((status == LYJSON_OBJECT) && (current != LYJSON_OBJECT) && (current != LYJSON_ARRAY)) { - /* no nested objects */ - LY_CHECK_RET(lyjson_ctx_next(jsonctx, NULL)); - return LY_SUCCESS; - } + switch (status) { + case LYJSON_OBJECT: + ++depth; - /* skip after the content */ - while ((jsonctx->depth > orig_depth) || (current != status + 1)) { - if (current == LYJSON_ARRAY) { - /* skip the array separately */ - LY_CHECK_RET(lydjson_data_skip(jsonctx)); - current = lyjson_ctx_status(jsonctx, 0); - } else { + /* skip until object closes */ + do { LY_CHECK_RET(lyjson_ctx_next(jsonctx, ¤t)); - } + } while ((current != LYJSON_OBJECT_CLOSED) || (depth != lyjson_ctx_depth(jsonctx))); + break; + case LYJSON_ARRAY: + ++depth; - if (current == LYJSON_END) { - break; + /* skip until array closes */ + do { + LY_CHECK_RET(lyjson_ctx_next(jsonctx, ¤t)); + } while ((current != LYJSON_ARRAY_CLOSED) || (depth != lyjson_ctx_depth(jsonctx))); + break; + case LYJSON_OBJECT_NAME: + /* just get to the value */ + LY_CHECK_RET(lyjson_ctx_next(jsonctx, ¤t)); + if ((current == LYJSON_OBJECT) || (current == LYJSON_ARRAY)) { + LY_CHECK_RET(lydjson_data_skip(jsonctx)); } + break; + default: + /* no other status really expected, just go to next */ + LY_CHECK_RET(lyjson_ctx_next(jsonctx, ¤t)); + break; } return LY_SUCCESS; @@ -280,14 +288,6 @@ lydjson_get_snode(struct lyd_json_ctx *lydctx, ly_bool is_attr, const char *pref ret = LY_EVALID; goto cleanup; } - if (!(lydctx->parse_opts & LYD_PARSE_OPAQ)) { - /* skip element with children */ - ret = lydjson_data_skip(lydctx->jsonctx); - LY_CHECK_GOTO(ret, cleanup); - - ret = LY_ENOT; - goto cleanup; - } } /* get the schema node */ @@ -295,7 +295,7 @@ lydjson_get_snode(struct lyd_json_ctx *lydctx, ly_bool is_attr, const char *pref if (!parent && lydctx->ext) { *snode = lysc_ext_find_node(lydctx->ext, mod, name, name_len, 0, getnext_opts); } else { - *snode = lys_find_child(parent ? parent->schema : NULL, mod, name, name_len, 0, getnext_opts); + *snode = lys_find_child(lyd_parser_node_schema(parent), mod, name, name_len, 0, getnext_opts); } if (!*snode) { /* check for extension data */ @@ -326,13 +326,6 @@ lydjson_get_snode(struct lyd_json_ctx *lydctx, ly_bool is_attr, const char *pref } ret = LY_EVALID; goto cleanup; - } else if (!(lydctx->parse_opts & LYD_PARSE_OPAQ)) { - /* skip element with children */ - ret = lydjson_data_skip(lydctx->jsonctx); - LY_CHECK_GOTO(ret, cleanup); - - ret = LY_ENOT; - goto cleanup; } } else { /* check that schema node is valid and can be used */ @@ -345,6 +338,55 @@ cleanup: } /** + * @brief Get the hint for the data type parsers according to the current JSON parser context. + * + * @param[in] jsonctx JSON parser context. The context is supposed to be on a value. + * @param[in,out] status Pointer to the current context status, + * in some circumstances the function manipulates with the context so the status is updated. + * @param[out] type_hint_p Pointer to the variable to store the result. + * @return LY_SUCCESS in case of success. + * @return LY_EINVAL in case of invalid context status not referring to a value. + */ +static LY_ERR +lydjson_value_type_hint(struct lyjson_ctx *jsonctx, enum LYJSON_PARSER_STATUS *status_p, uint32_t *type_hint_p) +{ + *type_hint_p = 0; + + if (*status_p == LYJSON_ARRAY) { + /* only [null] */ + LY_CHECK_RET(lyjson_ctx_next(jsonctx, status_p)); + if (*status_p != LYJSON_NULL) { + LOGVAL(jsonctx->ctx, LYVE_SYNTAX_JSON, + "Expected JSON name/value or special name/[null], but input data contains name/[%s].", + lyjson_token2str(*status_p)); + return LY_EINVAL; + } + + LY_CHECK_RET(lyjson_ctx_next(jsonctx, NULL)); + if (lyjson_ctx_status(jsonctx) != LYJSON_ARRAY_CLOSED) { + LOGVAL(jsonctx->ctx, LYVE_SYNTAX_JSON, "Expected array end, but input data contains %s.", + lyjson_token2str(*status_p)); + return LY_EINVAL; + } + + *type_hint_p = LYD_VALHINT_EMPTY; + } else if (*status_p == LYJSON_STRING) { + *type_hint_p = LYD_VALHINT_STRING | LYD_VALHINT_NUM64; + } else if (*status_p == LYJSON_NUMBER) { + *type_hint_p = LYD_VALHINT_DECNUM; + } else if ((*status_p == LYJSON_FALSE) || (*status_p == LYJSON_TRUE)) { + *type_hint_p = LYD_VALHINT_BOOLEAN; + } else if (*status_p == LYJSON_NULL) { + *type_hint_p = 0; + } else { + LOGVAL(jsonctx->ctx, LYVE_SYNTAX_JSON, "Unexpected input data %s.", lyjson_token2str(*status_p)); + return LY_EINVAL; + } + + return LY_SUCCESS; +} + +/** * @brief Check that the input data are parseable as the @p list. * * Checks for all the list's keys. Function does not revert the context state. @@ -357,128 +399,85 @@ cleanup: static LY_ERR lydjson_check_list(struct lyjson_ctx *jsonctx, const struct lysc_node *list) { - LY_ERR ret = LY_SUCCESS; - enum LYJSON_PARSER_STATUS status = lyjson_ctx_status(jsonctx, 0); + LY_ERR rc = LY_SUCCESS; + enum LYJSON_PARSER_STATUS status = lyjson_ctx_status(jsonctx); struct ly_set key_set = {0}; const struct lysc_node *snode; - uint32_t i, status_count; + uint32_t i, hints; assert(list && (list->nodetype == LYS_LIST)); /* get all keys into a set (keys do not have if-features or anything) */ snode = NULL; while ((snode = lys_getnext(snode, list, NULL, 0)) && (snode->flags & LYS_KEY)) { - ret = ly_set_add(&key_set, (void *)snode, 1, NULL); - LY_CHECK_GOTO(ret, cleanup); + rc = ly_set_add(&key_set, (void *)snode, 1, NULL); + LY_CHECK_GOTO(rc, cleanup); + } + if (!key_set.count) { + /* no keys */ + goto cleanup; } if (status == LYJSON_OBJECT) { - status_count = jsonctx->status.count; - - while (key_set.count && (status != LYJSON_OBJECT_CLOSED)) { + do { const char *name, *prefix; size_t name_len, prefix_len; ly_bool is_attr; /* match the key */ + LY_CHECK_GOTO(rc = lyjson_ctx_next(jsonctx, &status), cleanup); + if (status != LYJSON_OBJECT_NAME) { + break; + } snode = NULL; lydjson_parse_name(jsonctx->value, jsonctx->value_len, &name, &name_len, &prefix, &prefix_len, &is_attr); if (!is_attr && !prefix) { for (i = 0; i < key_set.count; ++i) { - snode = (const struct lysc_node *)key_set.objs[i]; - if (!ly_strncmp(snode->name, name, name_len)) { + if (!ly_strncmp(key_set.snodes[i]->name, name, name_len)) { + snode = key_set.snodes[i]; break; } } - /* go into the item to a) process it as a key or b) start skipping it as another list child */ - ret = lyjson_ctx_next(jsonctx, &status); - LY_CHECK_GOTO(ret, cleanup); + + /* get the value */ + LY_CHECK_GOTO(rc = lyjson_ctx_next(jsonctx, &status), cleanup); if (snode) { /* we have the key, validate the value */ - if (status < LYJSON_NUMBER) { + if ((status < LYJSON_NUMBER) || (status > LYJSON_NULL)) { /* not a terminal */ - ret = LY_ENOT; + rc = LY_ENOT; goto cleanup; } - ret = lys_value_validate(NULL, snode, jsonctx->value, jsonctx->value_len, LY_VALUE_JSON, NULL); - LY_CHECK_GOTO(ret, cleanup); + rc = lydjson_value_type_hint(jsonctx, &status, &hints); + LY_CHECK_GOTO(rc, cleanup); + rc = ly_value_validate(NULL, snode, jsonctx->value, jsonctx->value_len, LY_VALUE_JSON, NULL, hints); + LY_CHECK_GOTO(rc, cleanup); /* key with a valid value, remove from the set */ ly_set_rm_index(&key_set, i, NULL); } + + /* next object */ + LY_CHECK_GOTO(rc = lyjson_ctx_next(jsonctx, &status), cleanup); } else { - /* start skipping the member we are not interested in */ - ret = lyjson_ctx_next(jsonctx, &status); - LY_CHECK_GOTO(ret, cleanup); - } - /* move to the next child */ - while (status_count < jsonctx->status.count) { - ret = lyjson_ctx_next(jsonctx, &status); - LY_CHECK_GOTO(ret, cleanup); + /* skip the uninteresting object */ + LY_CHECK_GOTO(rc = lydjson_data_skip(jsonctx), cleanup); + LY_CHECK_GOTO(rc = lyjson_ctx_next(jsonctx, &status), cleanup); } - } + } while (key_set.count && (status == LYJSON_OBJECT_NEXT)); } if (key_set.count) { /* some keys are missing/did not validate */ - ret = LY_ENOT; + rc = LY_ENOT; } cleanup: ly_set_erase(&key_set, NULL); - return ret; -} - -/** - * @brief Get the hint for the data type parsers according to the current JSON parser context. - * - * @param[in] lydctx JSON data parser context. The context is supposed to be on a value. - * @param[in,out] status Pointer to the current context status, - * in some circumstances the function manipulates with the context so the status is updated. - * @param[out] type_hint_p Pointer to the variable to store the result. - * @return LY_SUCCESS in case of success. - * @return LY_EINVAL in case of invalid context status not referring to a value. - */ -static LY_ERR -lydjson_value_type_hint(struct lyd_json_ctx *lydctx, enum LYJSON_PARSER_STATUS *status_p, uint32_t *type_hint_p) -{ - *type_hint_p = 0; - - if (*status_p == LYJSON_ARRAY) { - /* only [null] */ - LY_CHECK_RET(lyjson_ctx_next(lydctx->jsonctx, status_p)); - if (*status_p != LYJSON_NULL) { - LOGVAL(lydctx->jsonctx->ctx, LYVE_SYNTAX_JSON, - "Expected JSON name/value or special name/[null], but input data contains name/[%s].", - lyjson_token2str(*status_p)); - return LY_EINVAL; - } - - LY_CHECK_RET(lyjson_ctx_next(lydctx->jsonctx, NULL)); - if (lyjson_ctx_status(lydctx->jsonctx, 0) != LYJSON_ARRAY_CLOSED) { - LOGVAL(lydctx->jsonctx->ctx, LYVE_SYNTAX_JSON, "Expected array end, but input data contains %s.", - lyjson_token2str(*status_p)); - return LY_EINVAL; - } - - *type_hint_p = LYD_VALHINT_EMPTY; - } else if (*status_p == LYJSON_STRING) { - *type_hint_p = LYD_VALHINT_STRING | LYD_VALHINT_NUM64; - } else if (*status_p == LYJSON_NUMBER) { - *type_hint_p = LYD_VALHINT_DECNUM; - } else if ((*status_p == LYJSON_FALSE) || (*status_p == LYJSON_TRUE)) { - *type_hint_p = LYD_VALHINT_BOOLEAN; - } else if (*status_p == LYJSON_NULL) { - *type_hint_p = 0; - } else { - LOGVAL(lydctx->jsonctx->ctx, LYVE_SYNTAX_JSON, "Unexpected input data %s.", lyjson_token2str(*status_p)); - return LY_EINVAL; - } - - return LY_SUCCESS; + return rc; } /** @@ -512,7 +511,7 @@ lydjson_data_check_opaq(struct lyd_json_ctx *lydctx, const struct lysc_node *sno /* backup parser */ lyjson_ctx_backup(jsonctx); - status = lyjson_ctx_status(jsonctx, 0); + status = lyjson_ctx_status(jsonctx); if (lydctx->parse_opts & LYD_PARSE_OPAQ) { /* check if the node is parseable. if not, NULL the snode to announce that it is supposed to be parsed @@ -521,12 +520,10 @@ lydjson_data_check_opaq(struct lyd_json_ctx *lydctx, const struct lysc_node *sno case LYS_LEAFLIST: case LYS_LEAF: /* value may not be valid in which case we parse it as an opaque node */ - ret = lydjson_value_type_hint(lydctx, &status, type_hint_p); - if (ret) { + if ((ret = lydjson_value_type_hint(jsonctx, &status, type_hint_p))) { break; } - - if (lys_value_validate(NULL, snode, jsonctx->value, jsonctx->value_len, LY_VALUE_JSON, NULL)) { + if (ly_value_validate(NULL, snode, jsonctx->value, jsonctx->value_len, LY_VALUE_JSON, NULL, *type_hint_p)) { ret = LY_ENOT; } break; @@ -539,8 +536,7 @@ lydjson_data_check_opaq(struct lyd_json_ctx *lydctx, const struct lysc_node *sno break; } } else if (snode->nodetype & LYD_NODE_TERM) { - status = lyjson_ctx_status(jsonctx, 0); - ret = lydjson_value_type_hint(lydctx, &status, type_hint_p); + ret = lydjson_value_type_hint(jsonctx, &status, type_hint_p); } /* restore parser */ @@ -725,7 +721,7 @@ cleanup: static LY_ERR lydjson_metadata(struct lyd_json_ctx *lydctx, const struct lysc_node *snode, struct lyd_node *node) { - LY_ERR ret = LY_SUCCESS; + LY_ERR rc = LY_SUCCESS, r; enum LYJSON_PARSER_STATUS status; const char *expected; ly_bool in_parent = 0; @@ -745,33 +741,29 @@ lydjson_metadata(struct lyd_json_ctx *lydctx, const struct lysc_node *snode, str LOG_LOCSET(snode, NULL, NULL, NULL); /* move to the second item in the name/X pair */ - ret = lyjson_ctx_next(lydctx->jsonctx, &status); - LY_CHECK_GOTO(ret, cleanup); + LY_CHECK_GOTO(rc = lyjson_ctx_next(lydctx->jsonctx, &status), cleanup); /* check attribute encoding */ switch (nodetype) { case LYS_LEAFLIST: expected = "@name/array of objects/nulls"; - LY_CHECK_GOTO(status != LYJSON_ARRAY, representation_error); next_entry: - instance++; - - /* move into array/next entry */ - ret = lyjson_ctx_next(lydctx->jsonctx, &status); - LY_CHECK_GOTO(ret, cleanup); - if (status == LYJSON_ARRAY_CLOSED) { /* no more metadata */ goto cleanup; } + + /* move into the array/next item */ + LY_CHECK_GOTO(rc = lyjson_ctx_next(lydctx->jsonctx, &status), cleanup); + instance++; LY_CHECK_GOTO((status != LYJSON_OBJECT) && (status != LYJSON_NULL), representation_error); if (!node || (node->schema != prev->schema)) { LOGVAL(lydctx->jsonctx->ctx, LYVE_REFERENCE, "Missing JSON data instance #%u of %s:%s to be coupled with metadata.", instance, prev->schema->module->name, prev->schema->name); - ret = LY_EVALID; + rc = LY_EVALID; goto cleanup; } @@ -779,6 +771,8 @@ next_entry: /* continue with the next entry in the leaf-list array */ prev = node; node = node->next; + + LY_CHECK_GOTO(rc = lyjson_ctx_next(lydctx->jsonctx, &status), cleanup); goto next_entry; } break; @@ -800,31 +794,32 @@ next_entry: break; default: LOGINT(ctx); - ret = LY_EINT; + rc = LY_EINT; goto cleanup; } /* process all the members inside a single metadata object */ assert(status == LYJSON_OBJECT); - while (status != LYJSON_OBJECT_CLOSED) { - LY_CHECK_GOTO(status != LYJSON_OBJECT, representation_error); + do { + LY_CHECK_GOTO(rc = lyjson_ctx_next(lydctx->jsonctx, &status), cleanup); + LY_CHECK_GOTO(status != LYJSON_OBJECT_NAME, representation_error); lydjson_parse_name(lydctx->jsonctx->value, lydctx->jsonctx->value_len, &name, &name_len, &prefix, &prefix_len, &is_attr); lyjson_ctx_give_dynamic_value(lydctx->jsonctx, &dynamic_prefname); if (!name_len) { LOGVAL(ctx, LYVE_SYNTAX_JSON, "Metadata in JSON found with an empty name, followed by: %.10s", name); - ret = LY_EVALID; + rc = LY_EVALID; goto cleanup; } else if (!prefix_len) { LOGVAL(ctx, LYVE_SYNTAX_JSON, "Metadata in JSON must be namespace-qualified, missing prefix for \"%.*s\".", (int)lydctx->jsonctx->value_len, lydctx->jsonctx->value); - ret = LY_EVALID; + rc = LY_EVALID; goto cleanup; } else if (is_attr) { LOGVAL(ctx, LYVE_SYNTAX_JSON, "Invalid format of the Metadata identifier in JSON, unexpected '@' in \"%.*s\"", (int)lydctx->jsonctx->value_len, lydctx->jsonctx->value); - ret = LY_EVALID; + rc = LY_EVALID; goto cleanup; } @@ -834,14 +829,13 @@ next_entry: if (lydctx->parse_opts & LYD_PARSE_STRICT) { LOGVAL(ctx, LYVE_REFERENCE, "Prefix \"%.*s\" of the metadata \"%.*s\" does not match any module in the context.", (int)prefix_len, prefix, (int)name_len, name); - ret = LY_EVALID; + rc = LY_EVALID; goto cleanup; } if (node->schema) { /* skip element with children */ - ret = lydjson_data_skip(lydctx->jsonctx); - LY_CHECK_GOTO(ret, cleanup); - status = lyjson_ctx_status(lydctx->jsonctx, 0); + LY_CHECK_GOTO(rc = lydjson_data_skip(lydctx->jsonctx), cleanup); + status = lyjson_ctx_status(lydctx->jsonctx); /* end of the item */ continue; } @@ -849,22 +843,20 @@ next_entry: } /* get the value */ - ret = lyjson_ctx_next(lydctx->jsonctx, &status); - LY_CHECK_GOTO(ret, cleanup); + LY_CHECK_GOTO(rc = lyjson_ctx_next(lydctx->jsonctx, &status), cleanup); /* get value hints */ - ret = lydjson_value_type_hint(lydctx, &status, &val_hints); - LY_CHECK_GOTO(ret, cleanup); + LY_CHECK_GOTO(rc = lydjson_value_type_hint(lydctx->jsonctx, &status, &val_hints), cleanup); if (node->schema) { /* create metadata */ - ret = lyd_parser_create_meta((struct lyd_ctx *)lydctx, node, NULL, mod, name, name_len, lydctx->jsonctx->value, + rc = lyd_parser_create_meta((struct lyd_ctx *)lydctx, node, NULL, mod, name, name_len, lydctx->jsonctx->value, lydctx->jsonctx->value_len, &lydctx->jsonctx->dynamic, LY_VALUE_JSON, NULL, val_hints, node->schema); - LY_CHECK_GOTO(ret, cleanup); + LY_CHECK_GOTO(rc, cleanup); /* add/correct flags */ - ret = lyd_parse_set_data_flags(node, &node->meta, (struct lyd_ctx *)lydctx, NULL); - LY_CHECK_GOTO(ret, cleanup); + rc = lyd_parse_set_data_flags(node, &node->meta, (struct lyd_ctx *)lydctx, NULL); + LY_CHECK_GOTO(rc, cleanup); } else { /* create attribute */ const char *module_name; @@ -873,22 +865,24 @@ next_entry: lydjson_get_node_prefix(node, prefix, prefix_len, &module_name, &module_name_len); /* attr2 is always changed to the created attribute */ - ret = lyd_create_attr(node, NULL, lydctx->jsonctx->ctx, name, name_len, prefix, prefix_len, module_name, + rc = lyd_create_attr(node, NULL, lydctx->jsonctx->ctx, name, name_len, prefix, prefix_len, module_name, module_name_len, lydctx->jsonctx->value, lydctx->jsonctx->value_len, &lydctx->jsonctx->dynamic, LY_VALUE_JSON, NULL, val_hints); - LY_CHECK_GOTO(ret, cleanup); + LY_CHECK_GOTO(rc, cleanup); } + /* next member */ - ret = lyjson_ctx_next(lydctx->jsonctx, &status); - LY_CHECK_GOTO(ret, cleanup); - LY_CHECK_GOTO((status != LYJSON_OBJECT) && (status != LYJSON_OBJECT_CLOSED), representation_error); - } + LY_CHECK_GOTO(rc = lyjson_ctx_next(lydctx->jsonctx, &status), cleanup); + } while (status == LYJSON_OBJECT_NEXT); + LY_CHECK_GOTO(status != LYJSON_OBJECT_CLOSED, representation_error); if (nodetype == LYS_LEAFLIST) { /* continue by processing another metadata object for the following * leaf-list instance since they are always instantiated in JSON array */ prev = node; node = node->next; + + LY_CHECK_GOTO(rc = lyjson_ctx_next(lydctx->jsonctx, &status), cleanup); goto next_entry; } @@ -900,12 +894,18 @@ representation_error: "The attribute(s) of %s \"%s\" is expected to be represented as JSON %s, but input data contains @%s/%s.", lys_nodetype2str(nodetype), node ? LYD_NAME(node) : LYD_NAME(prev), expected, lyjson_token2str(status), in_parent ? "" : "name"); - ret = LY_EVALID; + rc = LY_EVALID; cleanup: + if ((rc == LY_EVALID) && (lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR)) { + /* try to skip the invalid data */ + if ((r = lydjson_data_skip(lydctx->jsonctx))) { + rc = r; + } + } free(dynamic_prefname); LOG_LOCBACK(1, 0, 0, 0); - return ret; + return rc; } /** @@ -922,24 +922,26 @@ static void lydjson_maintain_children(struct lyd_node *parent, struct lyd_node **first_p, struct lyd_node **node_p, ly_bool last, struct lysc_ext_instance *ext) { - if (*node_p) { - /* insert, keep first pointer correct */ - if (ext) { - lyplg_ext_insert(parent, *node_p); + if (!*node_p) { + return; + } + + /* insert, keep first pointer correct */ + if (ext) { + lyplg_ext_insert(parent, *node_p); + } else { + lyd_insert_node(parent, first_p, *node_p, last); + } + if (first_p) { + if (parent) { + *first_p = lyd_child(parent); } else { - lyd_insert_node(parent, first_p, *node_p, last); - } - if (first_p) { - if (parent) { - *first_p = lyd_child(parent); - } else { - while ((*first_p)->prev->next) { - *first_p = (*first_p)->prev; - } + while ((*first_p)->prev->next) { + *first_p = (*first_p)->prev; } } - *node_p = NULL; } + *node_p = NULL; } /** @@ -950,8 +952,7 @@ lydjson_maintain_children(struct lyd_node *parent, struct lyd_node **first_p, st * @param[in] name_len Length of the @p name string. * @param[in] prefix Prefix of the opaq node to create. * @param[in] prefix_len Length of the @p prefx string. - * @param[in] parent Data parent of the opaq node to create, can be NULL in case of top level, - * but must be set if @p first is not. + * @param[in] parent Data parent of the opaq node, to inherit module name from. * @param[in,out] status_inner_p In case of processing JSON array, this parameter points to a standalone * context status of the array content. Otherwise, it is supposed to be the same as @p status_p. * @param[out] node_p Pointer to the created opaq node. @@ -967,18 +968,25 @@ lydjson_create_opaq(struct lyd_json_ctx *lydctx, const char *name, size_t name_l ly_bool dynamic = 0; uint32_t type_hint = 0; - if ((*status_inner_p != LYJSON_OBJECT) && (*status_inner_p != LYJSON_OBJECT_EMPTY)) { + if (*status_inner_p != LYJSON_OBJECT) { /* prepare for creating opaq node with a value */ value = lydctx->jsonctx->value; value_len = lydctx->jsonctx->value_len; dynamic = lydctx->jsonctx->dynamic; lydctx->jsonctx->dynamic = 0; - LY_CHECK_RET(lydjson_value_type_hint(lydctx, status_inner_p, &type_hint)); + LY_CHECK_RET(lydjson_value_type_hint(lydctx->jsonctx, status_inner_p, &type_hint)); } - /* create node */ + /* get the module name */ lydjson_get_node_prefix(parent, prefix, prefix_len, &module_name, &module_name_len); + if (!module_name && !parent && lydctx->any_schema) { + /* in an anyxml/anydata tree, parsing first node, use the previous any schema node */ + module_name = lydctx->any_schema->module->name; + module_name_len = strlen(module_name); + } + + /* create node */ ret = lyd_create_opaq(lydctx->jsonctx->ctx, name, name_len, prefix, prefix_len, module_name, module_name_len, value, value_len, &dynamic, LY_VALUE_JSON, NULL, type_hint, node_p); if (dynamic) { @@ -1016,61 +1024,80 @@ lydjson_parse_opaq(struct lyd_json_ctx *lydctx, const char *name, size_t name_le struct lyd_node *parent, enum LYJSON_PARSER_STATUS *status_p, enum LYJSON_PARSER_STATUS *status_inner_p, struct lyd_node **first_p, struct lyd_node **node_p) { - LY_CHECK_RET(lydjson_create_opaq(lydctx, name, name_len, prefix, prefix_len, parent, status_inner_p, node_p)); + LY_ERR ret = LY_SUCCESS; + + LY_CHECK_GOTO(ret = lydjson_create_opaq(lydctx, name, name_len, prefix, prefix_len, parent, status_inner_p, node_p), cleanup); + + assert(*node_p); + LOG_LOCSET(NULL, *node_p, NULL, NULL); if ((*status_p == LYJSON_ARRAY) && (*status_inner_p == LYJSON_NULL)) { /* special array null value */ ((struct lyd_node_opaq *)*node_p)->hints |= LYD_VALHINT_EMPTY; /* must be the only item */ - LY_CHECK_RET(lyjson_ctx_next(lydctx->jsonctx, status_inner_p)); + LY_CHECK_GOTO(ret = lyjson_ctx_next(lydctx->jsonctx, status_inner_p), cleanup); if (*status_inner_p != LYJSON_ARRAY_CLOSED) { LOGVAL(lydctx->jsonctx->ctx, LYVE_SYNTAX, "Array \"null\" member with another member."); - return LY_EVALID; + ret = LY_EVALID; + goto cleanup; } goto finish; } - while ((*status_p == LYJSON_ARRAY) || (*status_p == LYJSON_ARRAY_EMPTY)) { + while (*status_p == LYJSON_ARRAY) { /* process another instance of the same node */ - - if ((*status_inner_p == LYJSON_OBJECT) || (*status_inner_p == LYJSON_OBJECT_EMPTY)) { + if (*status_inner_p == LYJSON_OBJECT) { /* array with objects, list */ ((struct lyd_node_opaq *)*node_p)->hints |= LYD_NODEHINT_LIST; /* but first process children of the object in the array */ - while ((*status_inner_p != LYJSON_OBJECT_CLOSED) && (*status_inner_p != LYJSON_OBJECT_EMPTY)) { - LY_CHECK_RET(lydjson_subtree_r(lydctx, *node_p, lyd_node_child_p(*node_p), NULL)); - *status_inner_p = lyjson_ctx_status(lydctx->jsonctx, 0); - } + do { + LY_CHECK_GOTO(ret = lydjson_subtree_r(lydctx, *node_p, lyd_node_child_p(*node_p), NULL), cleanup); + *status_inner_p = lyjson_ctx_status(lydctx->jsonctx); + } while (*status_inner_p == LYJSON_OBJECT_NEXT); } else { /* array with values, leaf-list */ ((struct lyd_node_opaq *)*node_p)->hints |= LYD_NODEHINT_LEAFLIST; } - LY_CHECK_RET(lyjson_ctx_next(lydctx->jsonctx, status_inner_p)); + LY_CHECK_GOTO(ret = lyjson_ctx_next(lydctx->jsonctx, status_inner_p), cleanup); if (*status_inner_p == LYJSON_ARRAY_CLOSED) { goto finish; } + assert(*status_inner_p == LYJSON_ARRAY_NEXT); /* continue with the next instance */ - assert(node_p); + LY_CHECK_GOTO(ret = lyjson_ctx_next(lydctx->jsonctx, status_inner_p), cleanup); + assert(*node_p); lydjson_maintain_children(parent, first_p, node_p, lydctx->parse_opts & LYD_PARSE_ORDERED ? 1 : 0, NULL); - LY_CHECK_RET(lydjson_create_opaq(lydctx, name, name_len, prefix, prefix_len, parent, status_inner_p, node_p)); + + LOG_LOCBACK(0, 1, 0, 0); + + LY_CHECK_GOTO(ret = lydjson_create_opaq(lydctx, name, name_len, prefix, prefix_len, parent, status_inner_p, node_p), cleanup); + + assert(*node_p); + LOG_LOCSET(NULL, *node_p, NULL, NULL); } - if ((*status_p == LYJSON_OBJECT) || (*status_p == LYJSON_OBJECT_EMPTY)) { + if (*status_p == LYJSON_OBJECT) { /* process children */ - while (*status_p != LYJSON_OBJECT_CLOSED && *status_p != LYJSON_OBJECT_EMPTY) { - LY_CHECK_RET(lydjson_subtree_r(lydctx, *node_p, lyd_node_child_p(*node_p), NULL)); - *status_p = lyjson_ctx_status(lydctx->jsonctx, 0); - } + do { + LY_CHECK_GOTO(ret = lydjson_subtree_r(lydctx, *node_p, lyd_node_child_p(*node_p), NULL), cleanup); + *status_p = lyjson_ctx_status(lydctx->jsonctx); + } while (*status_p == LYJSON_OBJECT_NEXT); } finish: /* finish linking metadata */ - return lydjson_metadata_finish(lydctx, lyd_node_child_p(*node_p)); + ret = lydjson_metadata_finish(lydctx, lyd_node_child_p(*node_p)); + +cleanup: + if (*node_p) { + LOG_LOCBACK(0, 1, 0, 0); + } + return ret; } /** @@ -1094,8 +1121,8 @@ finish: * @return LY_ERR value. */ static LY_ERR -lydjson_ctx_next_parse_opaq(struct lyd_json_ctx *lydctx, const char *name, size_t name_len, - const char *prefix, size_t prefix_len, struct lyd_node *parent, enum LYJSON_PARSER_STATUS *status_p, +lydjson_ctx_next_parse_opaq(struct lyd_json_ctx *lydctx, const char *name, size_t name_len, const char *prefix, + size_t prefix_len, struct lyd_node *parent, enum LYJSON_PARSER_STATUS *status_p, struct lyd_node **first_p, struct lyd_node **node_p) { enum LYJSON_PARSER_STATUS status_inner = 0; @@ -1232,79 +1259,94 @@ static LY_ERR lydjson_parse_any(struct lyd_json_ctx *lydctx, const struct lysc_node *snode, struct lysc_ext_instance *ext, enum LYJSON_PARSER_STATUS *status, struct lyd_node **node) { - LY_ERR rc = LY_SUCCESS; - uint32_t prev_parse_opts, prev_int_opts; + LY_ERR r, rc = LY_SUCCESS; + uint32_t prev_parse_opts = lydctx->parse_opts, prev_int_opts = lydctx->int_opts; struct ly_in in_start; char *val = NULL; - struct lyd_node *tree = NULL; + const char *end; + struct lyd_node *child = NULL; + ly_bool log_node = 0; assert(snode->nodetype & LYD_NODE_ANY); + *node = NULL; + /* status check according to allowed JSON types */ if (snode->nodetype == LYS_ANYXML) { - LY_CHECK_RET((*status != LYJSON_OBJECT) && (*status != LYJSON_OBJECT_EMPTY) && (*status != LYJSON_ARRAY) && - (*status != LYJSON_ARRAY_EMPTY) && (*status != LYJSON_NUMBER) && (*status != LYJSON_STRING) && - (*status != LYJSON_FALSE) && (*status != LYJSON_TRUE) && (*status != LYJSON_NULL), LY_ENOT); + LY_CHECK_RET((*status != LYJSON_OBJECT) && (*status != LYJSON_ARRAY) && (*status != LYJSON_NUMBER) && + (*status != LYJSON_STRING) && (*status != LYJSON_FALSE) && (*status != LYJSON_TRUE) && + (*status != LYJSON_NULL), LY_ENOT); } else { - LY_CHECK_RET((*status != LYJSON_OBJECT) && (*status != LYJSON_OBJECT_EMPTY), LY_ENOT); + LY_CHECK_RET(*status != LYJSON_OBJECT, LY_ENOT); } /* create any node */ switch (*status) { case LYJSON_OBJECT: + /* create node */ + r = lyd_create_any(snode, NULL, LYD_ANYDATA_DATATREE, 1, node); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + + assert(*node); + LOG_LOCSET(NULL, *node, NULL, NULL); + log_node = 1; + /* parse any data tree with correct options, first backup the current options and then make the parser * process data as opaq nodes */ - prev_parse_opts = lydctx->parse_opts; lydctx->parse_opts &= ~LYD_PARSE_STRICT; lydctx->parse_opts |= LYD_PARSE_OPAQ | (ext ? LYD_PARSE_ONLY : 0); - prev_int_opts = lydctx->int_opts; lydctx->int_opts |= LYD_INTOPT_ANY | LYD_INTOPT_WITH_SIBLINGS; + lydctx->any_schema = snode; /* process the anydata content */ - while (*status != LYJSON_OBJECT_CLOSED) { - LY_CHECK_RET(lydjson_subtree_r(lydctx, NULL, &tree, NULL)); - *status = lyjson_ctx_status(lydctx->jsonctx, 0); - } + do { + r = lydjson_subtree_r(lydctx, NULL, &child, NULL); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); - /* restore parser options */ - lydctx->parse_opts = prev_parse_opts; - lydctx->int_opts = prev_int_opts; + *status = lyjson_ctx_status(lydctx->jsonctx); + } while (*status == LYJSON_OBJECT_NEXT); /* finish linking metadata */ - LY_CHECK_RET(lydjson_metadata_finish(lydctx, &tree)); + r = lydjson_metadata_finish(lydctx, &child); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); - LY_CHECK_RET(lyd_create_any(snode, tree, LYD_ANYDATA_DATATREE, 1, node)); - break; - case LYJSON_ARRAY_EMPTY: - /* store the empty array */ - if (asprintf(&val, "[]") == -1) { - LOGMEM(lydctx->jsonctx->ctx); - return LY_EMEM; - } - LY_CHECK_GOTO(rc = lyd_create_any(snode, val, LYD_ANYDATA_JSON, 1, node), val_err); + /* assign the data tree */ + ((struct lyd_node_any *)*node)->value.tree = child; + child = NULL; break; case LYJSON_ARRAY: /* skip until the array end */ in_start = *lydctx->jsonctx->in; - LY_CHECK_RET(lydjson_data_skip(lydctx->jsonctx)); + LY_CHECK_GOTO(rc = lydjson_data_skip(lydctx->jsonctx), cleanup); + + /* return back by all the WS */ + end = lydctx->jsonctx->in->current; + while (is_jsonws(end[-1])) { + --end; + } /* make a copy of the whole array and store it */ - if (asprintf(&val, "[%.*s", (int)(lydctx->jsonctx->in->current - in_start.current), in_start.current) == -1) { + if (asprintf(&val, "[%.*s", (int)(end - in_start.current), in_start.current) == -1) { LOGMEM(lydctx->jsonctx->ctx); - return LY_EMEM; + rc = LY_EMEM; + goto cleanup; } - LY_CHECK_GOTO(rc = lyd_create_any(snode, val, LYD_ANYDATA_JSON, 1, node), val_err); + r = lyd_create_any(snode, val, LYD_ANYDATA_JSON, 1, node); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + val = NULL; break; case LYJSON_STRING: /* string value */ if (lydctx->jsonctx->dynamic) { - LY_CHECK_RET(lyd_create_any(snode, lydctx->jsonctx->value, LYD_ANYDATA_STRING, 1, node)); + LY_CHECK_GOTO(rc = lyd_create_any(snode, lydctx->jsonctx->value, LYD_ANYDATA_STRING, 1, node), cleanup); lydctx->jsonctx->dynamic = 0; } else { val = strndup(lydctx->jsonctx->value, lydctx->jsonctx->value_len); - LY_CHECK_ERR_RET(!val, LOGMEM(lydctx->jsonctx->ctx), LY_EMEM); + LY_CHECK_ERR_GOTO(!val, LOGMEM(lydctx->jsonctx->ctx); rc = LY_EMEM, cleanup); - LY_CHECK_GOTO(rc = lyd_create_any(snode, val, LYD_ANYDATA_STRING, 1, node), val_err); + r = lyd_create_any(snode, val, LYD_ANYDATA_STRING, 1, node); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + val = NULL; } break; case LYJSON_NUMBER: @@ -1313,26 +1355,32 @@ lydjson_parse_any(struct lyd_json_ctx *lydctx, const struct lysc_node *snode, st /* JSON value */ assert(!lydctx->jsonctx->dynamic); val = strndup(lydctx->jsonctx->value, lydctx->jsonctx->value_len); - LY_CHECK_ERR_RET(!val, LOGMEM(lydctx->jsonctx->ctx), LY_EMEM); + LY_CHECK_ERR_GOTO(!val, LOGMEM(lydctx->jsonctx->ctx); rc = LY_EMEM, cleanup); - LY_CHECK_GOTO(rc = lyd_create_any(snode, val, LYD_ANYDATA_JSON, 1, node), val_err); + r = lyd_create_any(snode, val, LYD_ANYDATA_JSON, 1, node); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + val = NULL; break; case LYJSON_NULL: /* no value */ - LY_CHECK_RET(lyd_create_any(snode, NULL, LYD_ANYDATA_JSON, 1, node)); - break; - case LYJSON_OBJECT_EMPTY: - /* empty object */ - LY_CHECK_RET(lyd_create_any(snode, NULL, LYD_ANYDATA_DATATREE, 1, node)); + r = lyd_create_any(snode, NULL, LYD_ANYDATA_JSON, 1, node); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); break; default: - LOGINT_RET(lydctx->jsonctx->ctx); + LOGINT(lydctx->jsonctx->ctx); + rc = LY_EINT; + goto cleanup; } - return LY_SUCCESS; - -val_err: +cleanup: + if (log_node) { + LOG_LOCBACK(0, 1, 0, 0); + } + lydctx->parse_opts = prev_parse_opts; + lydctx->int_opts = prev_int_opts; + lydctx->any_schema = NULL; free(val); + lyd_free_tree(child); return rc; } @@ -1352,10 +1400,10 @@ static LY_ERR lydjson_parse_instance_inner(struct lyd_json_ctx *lydctx, const struct lysc_node *snode, struct lysc_ext_instance *ext, enum LYJSON_PARSER_STATUS *status, struct lyd_node **node) { - LY_ERR ret = LY_SUCCESS; + LY_ERR r, rc = LY_SUCCESS; uint32_t prev_parse_opts = lydctx->parse_opts; - LY_CHECK_RET((*status != LYJSON_OBJECT) && (*status != LYJSON_OBJECT_EMPTY), LY_ENOT); + LY_CHECK_RET(*status != LYJSON_OBJECT, LY_ENOT); /* create inner node */ LY_CHECK_RET(lyd_create_inner(snode, node)); @@ -1369,38 +1417,43 @@ lydjson_parse_instance_inner(struct lyd_json_ctx *lydctx, const struct lysc_node } /* process children */ - while ((*status != LYJSON_OBJECT_CLOSED) && (*status != LYJSON_OBJECT_EMPTY)) { - ret = lydjson_subtree_r(lydctx, *node, lyd_node_child_p(*node), NULL); - LY_CHECK_GOTO(ret, cleanup); - *status = lyjson_ctx_status(lydctx->jsonctx, 0); - } + do { + r = lydjson_subtree_r(lydctx, *node, lyd_node_child_p(*node), NULL); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + + *status = lyjson_ctx_status(lydctx->jsonctx); + } while (*status == LYJSON_OBJECT_NEXT); /* finish linking metadata */ - ret = lydjson_metadata_finish(lydctx, lyd_node_child_p(*node)); - LY_CHECK_GOTO(ret, cleanup); + r = lydjson_metadata_finish(lydctx, lyd_node_child_p(*node)); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); if (snode->nodetype == LYS_LIST) { /* check all keys exist */ - ret = lyd_parse_check_keys(*node); - LY_CHECK_GOTO(ret, cleanup); + r = lyd_parse_check_keys(*node); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); } - if (!(lydctx->parse_opts & LYD_PARSE_ONLY)) { - /* new node validation, autodelete CANNOT occur, all nodes are new */ - ret = lyd_validate_new(lyd_node_child_p(*node), snode, NULL, NULL); - LY_CHECK_GOTO(ret, cleanup); + if (!(lydctx->parse_opts & LYD_PARSE_ONLY) && !rc) { + /* new node validation, autodelete CANNOT occur (it can if multi-error), all nodes are new */ + r = lyd_validate_new(lyd_node_child_p(*node), snode, NULL, lydctx->val_opts, NULL); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); /* add any missing default children */ - ret = lyd_new_implicit_r(*node, lyd_node_child_p(*node), NULL, NULL, &lydctx->node_when, - &lydctx->node_types, &lydctx->ext_node, - (lydctx->val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, NULL); - LY_CHECK_GOTO(ret, cleanup); + r = lyd_new_implicit_r(*node, lyd_node_child_p(*node), NULL, NULL, &lydctx->node_when, &lydctx->node_types, + &lydctx->ext_node, (lydctx->val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, NULL); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); } cleanup: lydctx->parse_opts = prev_parse_opts; LOG_LOCBACK(0, 1, 0, 0); - return ret; + if (!(*node)->hash) { + /* list without keys is unusable */ + lyd_free_tree(*node); + *node = NULL; + } + return rc; } /** @@ -1427,52 +1480,61 @@ lydjson_parse_instance(struct lyd_json_ctx *lydctx, struct lyd_node *parent, str const struct lysc_node *snode, struct lysc_ext_instance *ext, const char *name, size_t name_len, const char *prefix, size_t prefix_len, enum LYJSON_PARSER_STATUS *status, struct lyd_node **node) { - LY_ERR ret = LY_SUCCESS; + LY_ERR r, rc = LY_SUCCESS; uint32_t type_hints = 0; LOG_LOCSET(snode, NULL, NULL, NULL); - ret = lydjson_data_check_opaq(lydctx, snode, &type_hints); - if (ret == LY_SUCCESS) { + r = lydjson_data_check_opaq(lydctx, snode, &type_hints); + if (r == LY_SUCCESS) { assert(snode->nodetype & (LYD_NODE_TERM | LYD_NODE_INNER | LYD_NODE_ANY)); if (snode->nodetype & LYD_NODE_TERM) { if ((*status != LYJSON_ARRAY) && (*status != LYJSON_NUMBER) && (*status != LYJSON_STRING) && (*status != LYJSON_FALSE) && (*status != LYJSON_TRUE) && (*status != LYJSON_NULL)) { - ret = LY_ENOT; + rc = LY_ENOT; goto cleanup; } /* create terminal node */ - LY_CHECK_GOTO(ret = lyd_parser_create_term((struct lyd_ctx *)lydctx, snode, lydctx->jsonctx->value, - lydctx->jsonctx->value_len, &lydctx->jsonctx->dynamic, LY_VALUE_JSON, NULL, type_hints, node), cleanup); + r = lyd_parser_create_term((struct lyd_ctx *)lydctx, snode, lydctx->jsonctx->value, + lydctx->jsonctx->value_len, &lydctx->jsonctx->dynamic, LY_VALUE_JSON, NULL, type_hints, node); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); /* move JSON parser */ if (*status == LYJSON_ARRAY) { /* only [null], 2 more moves are needed */ - LY_CHECK_GOTO(ret = lyjson_ctx_next(lydctx->jsonctx, status), cleanup); + r = lyjson_ctx_next(lydctx->jsonctx, status); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); assert(*status == LYJSON_NULL); - LY_CHECK_GOTO(ret = lyjson_ctx_next(lydctx->jsonctx, status), cleanup); + + r = lyjson_ctx_next(lydctx->jsonctx, status); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); assert(*status == LYJSON_ARRAY_CLOSED); } } else if (snode->nodetype & LYD_NODE_INNER) { /* create inner node */ - LY_CHECK_GOTO(ret = lydjson_parse_instance_inner(lydctx, snode, ext, status, node), cleanup); + r = lydjson_parse_instance_inner(lydctx, snode, ext, status, node); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); } else { /* create any node */ - LY_CHECK_GOTO(ret = lydjson_parse_any(lydctx, snode, ext, status, node), cleanup); + r = lydjson_parse_any(lydctx, snode, ext, status, node); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); } + LY_CHECK_GOTO(!*node, cleanup); /* add/correct flags */ - LY_CHECK_GOTO(ret = lyd_parse_set_data_flags(*node, &(*node)->meta, (struct lyd_ctx *)lydctx, ext), cleanup); + r = lyd_parse_set_data_flags(*node, &(*node)->meta, (struct lyd_ctx *)lydctx, ext); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); if (!(lydctx->parse_opts & LYD_PARSE_ONLY)) { /* store for ext instance node validation, if needed */ - LY_CHECK_GOTO(ret = lyd_validate_node_ext(*node, &lydctx->ext_node), cleanup); + r = lyd_validate_node_ext(*node, &lydctx->ext_node); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); } - } else if (ret == LY_ENOT) { + } else if (r == LY_ENOT) { /* parse it again as an opaq node */ - LY_CHECK_GOTO(ret = lydjson_parse_opaq(lydctx, name, name_len, prefix, prefix_len, parent, status, status, - first_p, node), cleanup); + r = lydjson_parse_opaq(lydctx, name, name_len, prefix, prefix_len, parent, status, status, first_p, node); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); if (snode->nodetype == LYS_LIST) { ((struct lyd_node_opaq *)*node)->hints |= LYD_NODEHINT_LIST; @@ -1481,12 +1543,13 @@ lydjson_parse_instance(struct lyd_json_ctx *lydctx, struct lyd_node *parent, str } } else { /* error */ + rc = r; goto cleanup; } cleanup: LOG_LOCBACK(1, 0, 0, 0); - return ret; + return rc; } /** @@ -1501,36 +1564,80 @@ cleanup: static LY_ERR lydjson_subtree_r(struct lyd_json_ctx *lydctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_set *parsed) { - LY_ERR ret = LY_SUCCESS, r; - enum LYJSON_PARSER_STATUS status = lyjson_ctx_status(lydctx->jsonctx, 0); + LY_ERR r, rc = LY_SUCCESS; + enum LYJSON_PARSER_STATUS status = lyjson_ctx_status(lydctx->jsonctx); const char *name, *prefix = NULL, *expected = NULL; size_t name_len, prefix_len = 0; ly_bool is_meta = 0, parse_subtree; const struct lysc_node *snode = NULL; - struct lysc_ext_instance *ext; + struct lysc_ext_instance *ext = NULL; struct lyd_node *node = NULL, *attr_node = NULL; const struct ly_ctx *ctx = lydctx->jsonctx->ctx; char *value = NULL; assert(parent || first_p); - assert(status == LYJSON_OBJECT); + assert((status == LYJSON_OBJECT) || (status == LYJSON_OBJECT_NEXT)); parse_subtree = lydctx->parse_opts & LYD_PARSE_SUBTREE ? 1 : 0; /* all descendants should be parsed */ lydctx->parse_opts &= ~LYD_PARSE_SUBTREE; + r = lyjson_ctx_next(lydctx->jsonctx, &status); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + if (status == LYJSON_OBJECT_CLOSED) { + /* empty object, fine... */ + goto cleanup; + } + /* process the node name */ + assert(status == LYJSON_OBJECT_NAME); lydjson_parse_name(lydctx->jsonctx->value, lydctx->jsonctx->value_len, &name, &name_len, &prefix, &prefix_len, &is_meta); lyjson_ctx_give_dynamic_value(lydctx->jsonctx, &value); - if (!is_meta || name_len || prefix_len) { + if ((lydctx->int_opts & LYD_INTOPT_EVENTTIME) && !parent && !is_meta && name_len && !prefix_len && + !ly_strncmp("eventTime", name, name_len)) { + /* parse eventTime */ + r = lyjson_ctx_next(lydctx->jsonctx, &status); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + + if (status != LYJSON_STRING) { + LOGVAL(lydctx->jsonctx->ctx, LYVE_SYNTAX_JSON, "Expecting JSON %s but %s found.", lyjson_token2str(LYJSON_STRING), + lyjson_token2str(status)); + rc = LY_EVALID; + goto cleanup; + } + + /* create node */ + r = lyd_create_opaq(lydctx->jsonctx->ctx, name, name_len, prefix, prefix_len, prefix, prefix_len, + lydctx->jsonctx->value, lydctx->jsonctx->value_len, NULL, LY_VALUE_JSON, NULL, LYD_VALHINT_STRING, &node); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + + /* validate the value */ + r = lyd_parser_notif_eventtime_validate(node); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + + goto node_parsed; + } else if (!is_meta || name_len || prefix_len) { /* get the schema node */ r = lydjson_get_snode(lydctx, is_meta, prefix, prefix_len, name, name_len, parent, &snode, &ext); if (r == LY_ENOT) { /* data parsed */ goto cleanup; + } else if ((r == LY_EVALID) && (lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR)) { + rc = r; + + /* skip the invalid data */ + if ((r = lydjson_data_skip(lydctx->jsonctx))) { + rc = r; + } + r = lyjson_ctx_next(lydctx->jsonctx, &status); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + goto cleanup; + } else if (r) { + /* error */ + rc = r; + goto cleanup; } - LY_CHECK_ERR_GOTO(r, ret = r, cleanup); if (!snode) { /* we will not be parsing it as metadata */ @@ -1540,39 +1647,44 @@ lydjson_subtree_r(struct lyd_json_ctx *lydctx, struct lyd_node *parent, struct l if (is_meta) { /* parse as metadata */ - if (!name_len && !prefix_len) { + if (!name_len && !prefix_len && !parent) { + LOGVAL(ctx, LYVE_SYNTAX_JSON, + "Invalid metadata format - \"@\" can be used only inside anydata, container or list entries."); + r = LY_EVALID; + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + } else if (!name_len && !prefix_len) { /* parent's metadata without a name - use the schema from the parent */ - if (!parent) { - LOGVAL(ctx, LYVE_SYNTAX_JSON, - "Invalid metadata format - \"@\" can be used only inside anydata, container or list entries."); - ret = LY_EVALID; - goto cleanup; - } attr_node = parent; snode = attr_node->schema; } - ret = lydjson_parse_attribute(lydctx, attr_node, snode, name, name_len, prefix, prefix_len, parent, &status, + r = lydjson_parse_attribute(lydctx, attr_node, snode, name, name_len, prefix, prefix_len, parent, &status, first_p, &node); - LY_CHECK_GOTO(ret, cleanup); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); } else if (!snode) { - /* parse as an opaq node */ - assert((lydctx->parse_opts & LYD_PARSE_OPAQ) || (lydctx->int_opts)); + if (!(lydctx->parse_opts & LYD_PARSE_OPAQ)) { + /* skip element with children */ + r = lydjson_data_skip(lydctx->jsonctx); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + } else { + /* parse as an opaq node */ - /* opaq node cannot have an empty string as the name. */ - if (name_len == 0) { - LOGVAL(lydctx->jsonctx->ctx, LYVE_SYNTAX_JSON, "A JSON object member name cannot be a zero-length string."); - ret = LY_EVALID; - goto cleanup; - } + /* opaq node cannot have an empty string as the name. */ + if (name_len == 0) { + LOGVAL(lydctx->jsonctx->ctx, LYVE_SYNTAX_JSON, "JSON object member name cannot be a zero-length string."); + r = LY_EVALID; + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + } - /* move to the second item in the name/X pair and parse opaq */ - ret = lydjson_ctx_next_parse_opaq(lydctx, name, name_len, prefix, prefix_len, parent, &status, first_p, &node); - LY_CHECK_GOTO(ret, cleanup); + /* move to the second item in the name/X pair and parse opaq */ + r = lydjson_ctx_next_parse_opaq(lydctx, name, name_len, prefix, prefix_len, parent, &status, first_p, &node); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + } } else { /* parse as a standard lyd_node but it can still turn out to be an opaque node */ /* move to the second item in the name/X pair */ - LY_CHECK_GOTO(ret = lyjson_ctx_next(lydctx->jsonctx, &status), cleanup); + r = lyjson_ctx_next(lydctx->jsonctx, &status); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); /* set expected representation */ switch (snode->nodetype) { @@ -1609,30 +1721,31 @@ lydjson_subtree_r(struct lyd_json_ctx *lydctx, struct lyd_node *parent, struct l switch (snode->nodetype) { case LYS_LEAFLIST: case LYS_LIST: - if (status == LYJSON_ARRAY_EMPTY) { - /* no instances, skip */ - break; - } LY_CHECK_GOTO(status != LYJSON_ARRAY, representation_error); - /* move into array */ - ret = lyjson_ctx_next(lydctx->jsonctx, &status); - LY_CHECK_GOTO(ret, cleanup); - /* process all the values/objects */ do { - ret = lydjson_parse_instance(lydctx, parent, first_p, snode, ext, name, name_len, prefix, prefix_len, + /* move into array/next value */ + r = lyjson_ctx_next(lydctx->jsonctx, &status); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + if (status == LYJSON_ARRAY_CLOSED) { + /* empty array, fine... */ + break; + } + + r = lydjson_parse_instance(lydctx, parent, first_p, snode, ext, name, name_len, prefix, prefix_len, &status, &node); - if (ret == LY_ENOT) { + if (r == LY_ENOT) { goto representation_error; - } else if (ret) { - goto cleanup; } + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + lydjson_maintain_children(parent, first_p, &node, lydctx->parse_opts & LYD_PARSE_ORDERED ? 1 : 0, ext); /* move after the item(s) */ - LY_CHECK_GOTO(ret = lyjson_ctx_next(lydctx->jsonctx, &status), cleanup); - } while (status != LYJSON_ARRAY_CLOSED); + r = lyjson_ctx_next(lydctx->jsonctx, &status); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + } while (status == LYJSON_ARRAY_NEXT); break; case LYS_LEAF: @@ -1643,13 +1756,12 @@ lydjson_subtree_r(struct lyd_json_ctx *lydctx, struct lyd_node *parent, struct l case LYS_ANYDATA: case LYS_ANYXML: /* process the value/object */ - ret = lydjson_parse_instance(lydctx, parent, first_p, snode, ext, name, name_len, prefix, prefix_len, + r = lydjson_parse_instance(lydctx, parent, first_p, snode, ext, name, name_len, prefix, prefix_len, &status, &node); - if (ret == LY_ENOT) { + if (r == LY_ENOT) { goto representation_error; - } else if (ret) { - goto cleanup; } + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); if (snode->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) { /* rememeber the RPC/action/notification */ @@ -1659,31 +1771,39 @@ lydjson_subtree_r(struct lyd_json_ctx *lydctx, struct lyd_node *parent, struct l } } - /* finally connect the parsed node */ - lydjson_maintain_children(parent, first_p, &node, lydctx->parse_opts & LYD_PARSE_ORDERED ? 1 : 0, ext); - +node_parsed: /* rememeber a successfully parsed node */ if (parsed && node) { ly_set_add(parsed, node, 1, NULL); } + /* finally connect the parsed node, is zeroed */ + lydjson_maintain_children(parent, first_p, &node, lydctx->parse_opts & LYD_PARSE_ORDERED ? 1 : 0, ext); + if (!parse_subtree) { /* move after the item(s) */ - LY_CHECK_GOTO(ret = lyjson_ctx_next(lydctx->jsonctx, &status), cleanup); + r = lyjson_ctx_next(lydctx->jsonctx, &status); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); } /* success */ goto cleanup; representation_error: - LOGVAL(ctx, LYVE_SYNTAX_JSON, "The %s \"%s\" is expected to be represented as JSON %s, but input data contains name/%s.", - lys_nodetype2str(snode->nodetype), snode->name, expected, lyjson_token2str(status)); - ret = LY_EVALID; + LOGVAL(ctx, LYVE_SYNTAX_JSON, "Expecting JSON %s but %s \"%s\" is represented in input data as name/%s.", + expected, lys_nodetype2str(snode->nodetype), snode->name, lyjson_token2str(status)); + rc = LY_EVALID; + if (lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR) { + /* try to skip the invalid data */ + if ((r = lydjson_data_skip(lydctx->jsonctx))) { + rc = r; + } + } cleanup: free(value); lyd_free_tree(node); - return ret; + return rc; } /** @@ -1694,20 +1814,17 @@ cleanup: * @param[in] parse_opts Options for parser, see @ref dataparseroptions. * @param[in] val_opts Options for the validation phase, see @ref datavalidationoptions. * @param[out] lydctx_p Data parser context to finish validation. - * @param[out] status Storage for the current context's status * @return LY_ERR value. */ static LY_ERR lyd_parse_json_init(const struct ly_ctx *ctx, struct ly_in *in, uint32_t parse_opts, uint32_t val_opts, - struct lyd_json_ctx **lydctx_p, enum LYJSON_PARSER_STATUS *status) + struct lyd_json_ctx **lydctx_p) { LY_ERR ret = LY_SUCCESS; struct lyd_json_ctx *lydctx; - size_t i; - ly_bool subtree; + enum LYJSON_PARSER_STATUS status; assert(lydctx_p); - assert(status); /* init context */ lydctx = calloc(1, sizeof *lydctx); @@ -1716,28 +1833,20 @@ lyd_parse_json_init(const struct ly_ctx *ctx, struct ly_in *in, uint32_t parse_o lydctx->val_opts = val_opts; lydctx->free = lyd_json_ctx_free; - /* starting top-level */ - for (i = 0; in->current[i] != '\0' && is_jsonws(in->current[i]); i++) { - if (in->current[i] == '\n') { - /* new line */ - LY_IN_NEW_LINE(in); - } - } - - subtree = (parse_opts & LYD_PARSE_SUBTREE) ? 1 : 0; - LY_CHECK_ERR_RET(ret = lyjson_ctx_new(ctx, in, subtree, &lydctx->jsonctx), free(lydctx), ret); - *status = lyjson_ctx_status(lydctx->jsonctx, 0); + LY_CHECK_ERR_RET(ret = lyjson_ctx_new(ctx, in, &lydctx->jsonctx), free(lydctx), ret); + status = lyjson_ctx_status(lydctx->jsonctx); - if ((*status == LYJSON_END) || (*status == LYJSON_OBJECT_EMPTY) || (*status == LYJSON_OBJECT)) { - *lydctx_p = lydctx; - return LY_SUCCESS; - } else { + /* parse_opts & LYD_PARSE_SUBTREE not implemented */ + if (status != LYJSON_OBJECT) { /* expecting top-level object */ - LOGVAL(ctx, LYVE_SYNTAX_JSON, "Expected top-level JSON object, but %s found.", lyjson_token2str(*status)); + LOGVAL(ctx, LYVE_SYNTAX_JSON, "Expected top-level JSON object, but %s found.", lyjson_token2str(status)); *lydctx_p = NULL; lyd_json_ctx_free((struct lyd_ctx *)lydctx); return LY_EVALID; } + + *lydctx_p = lydctx; + return LY_SUCCESS; } LY_ERR @@ -1745,14 +1854,12 @@ lyd_parse_json(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, st struct lyd_node **first_p, struct ly_in *in, uint32_t parse_opts, uint32_t val_opts, uint32_t int_opts, struct ly_set *parsed, ly_bool *subtree_sibling, struct lyd_ctx **lydctx_p) { - LY_ERR rc = LY_SUCCESS; + LY_ERR r, rc = LY_SUCCESS; struct lyd_json_ctx *lydctx = NULL; enum LYJSON_PARSER_STATUS status; - rc = lyd_parse_json_init(ctx, in, parse_opts, val_opts, &lydctx, &status); - LY_CHECK_GOTO(rc || status == LYJSON_END || status == LYJSON_OBJECT_EMPTY, cleanup); - - assert(status == LYJSON_OBJECT); + rc = lyd_parse_json_init(ctx, in, parse_opts, val_opts, &lydctx); + LY_CHECK_GOTO(rc, cleanup); lydctx->int_opts = int_opts; lydctx->ext = ext; @@ -1761,37 +1868,36 @@ lyd_parse_json(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, st LY_CHECK_GOTO(rc = lyd_parser_find_operation(parent, int_opts, &lydctx->op_node), cleanup); /* read subtree(s) */ - while (lydctx->jsonctx->in->current[0] && (status != LYJSON_OBJECT_CLOSED)) { - rc = lydjson_subtree_r(lydctx, parent, first_p, parsed); - LY_CHECK_GOTO(rc, cleanup); + do { + r = lydjson_subtree_r(lydctx, parent, first_p, parsed); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); - status = lyjson_ctx_status(lydctx->jsonctx, 0); + status = lyjson_ctx_status(lydctx->jsonctx); if (!(int_opts & LYD_INTOPT_WITH_SIBLINGS)) { break; } - } + } while (status == LYJSON_OBJECT_NEXT); - if ((int_opts & LYD_INTOPT_NO_SIBLINGS) && lydctx->jsonctx->in->current[0] && - (lyjson_ctx_status(lydctx->jsonctx, 0) != LYJSON_OBJECT_CLOSED)) { + if ((int_opts & LYD_INTOPT_NO_SIBLINGS) && lydctx->jsonctx->in->current[0] && (status != LYJSON_OBJECT_CLOSED)) { LOGVAL(ctx, LYVE_SYNTAX, "Unexpected sibling node."); - rc = LY_EVALID; - goto cleanup; + r = LY_EVALID; + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); } if ((int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_NOTIF | LYD_INTOPT_REPLY)) && !lydctx->op_node) { LOGVAL(ctx, LYVE_DATA, "Missing the operation node."); - rc = LY_EVALID; - goto cleanup; + r = LY_EVALID; + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); } /* finish linking metadata */ - rc = lydjson_metadata_finish(lydctx, parent ? lyd_node_child_p(parent) : first_p); - LY_CHECK_GOTO(rc, cleanup); + r = lydjson_metadata_finish(lydctx, parent ? lyd_node_child_p(parent) : first_p); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); if (parse_opts & LYD_PARSE_SUBTREE) { /* check for a sibling object */ assert(subtree_sibling); - if (lydctx->jsonctx->in->current[0] == ',') { + if (status == LYJSON_OBJECT_NEXT) { *subtree_sibling = 1; /* move to the next object */ @@ -1806,6 +1912,198 @@ cleanup: assert(!(parse_opts & LYD_PARSE_ONLY) || !lydctx || (!lydctx->node_types.count && !lydctx->meta_types.count && !lydctx->node_when.count)); + if (rc && (!lydctx || !(lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR) || (rc != LY_EVALID))) { + lyd_json_ctx_free((struct lyd_ctx *)lydctx); + } else { + *lydctx_p = (struct lyd_ctx *)lydctx; + + /* the JSON context is no more needed, freeing it also stops logging line numbers which would be confusing now */ + lyjson_ctx_free(lydctx->jsonctx); + lydctx->jsonctx = NULL; + } + return rc; +} + +/** + * @brief Parse a specific JSON object into an opaque node. + * + * @param[in] jsonctx JSON parser context. + * @param[in] name Name of the object. + * @param[in] module Module name of the object, NULL if none expected. + * @param[out] evnp Parsed envelope (opaque node). + * @return LY_SUCCESS on success. + * @return LY_ENOT if the specified object did not match. + * @return LY_ERR value on error. + */ +static LY_ERR +lydjson_envelope(struct lyjson_ctx *jsonctx, const char *name, const char *module, struct lyd_node **envp) +{ + LY_ERR rc = LY_SUCCESS, r; + enum LYJSON_PARSER_STATUS status = lyjson_ctx_status(jsonctx); + const char *nam, *prefix; + size_t nam_len, prefix_len; + ly_bool is_meta; + + assert(status == LYJSON_OBJECT); + + *envp = NULL; + + r = lyjson_ctx_next(jsonctx, &status); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + if (status == LYJSON_OBJECT_CLOSED) { + LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Empty JSON object."); + rc = LY_EVALID; + goto cleanup; + } + + /* process the name */ + assert(status == LYJSON_OBJECT_NAME); + lydjson_parse_name(jsonctx->value, jsonctx->value_len, &nam, &nam_len, &prefix, &prefix_len, &is_meta); + if (is_meta) { + LOGVAL(jsonctx->ctx, LYVE_DATA, "Unexpected metadata."); + rc = LY_EVALID; + goto cleanup; + } else if (module && ly_strncmp(module, prefix, prefix_len)) { + LOGVAL(jsonctx->ctx, LYVE_DATA, "Unexpected module \"%.*s\" instead of \"%s\".", (int)prefix_len, prefix, module); + rc = LY_EVALID; + goto cleanup; + } else if (ly_strncmp(name, nam, nam_len)) { + LOGVAL(jsonctx->ctx, LYVE_DATA, "Unexpected object \"%.*s\" instead of \"%s\".", (int)nam_len, nam, name); + rc = LY_EVALID; + goto cleanup; + } + + r = lyjson_ctx_next(jsonctx, &status); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + + /* create node */ + rc = lyd_create_opaq(jsonctx->ctx, name, strlen(name), prefix, prefix_len, prefix, prefix_len, NULL, 0, NULL, + LY_VALUE_JSON, NULL, LYD_VALHINT_STRING, envp); + LY_CHECK_GOTO(rc, cleanup); + +cleanup: + if (rc) { + lyd_free_tree(*envp); + *envp = NULL; + } + return rc; +} + +LY_ERR +lyd_parse_json_restconf(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct lyd_node *parent, + struct lyd_node **first_p, struct ly_in *in, uint32_t parse_opts, uint32_t val_opts, enum lyd_type data_type, + struct lyd_node **envp, struct ly_set *parsed, struct lyd_ctx **lydctx_p) +{ + LY_ERR rc = LY_SUCCESS, r; + struct lyd_json_ctx *lydctx = NULL; + struct lyd_node *node; + uint32_t i, int_opts = 0, close_elem = 0; + + assert(ctx && in && lydctx_p); + assert(!(parse_opts & ~LYD_PARSE_OPTS_MASK)); + assert(!(val_opts & ~LYD_VALIDATE_OPTS_MASK)); + + assert((data_type == LYD_TYPE_RPC_RESTCONF) || (data_type == LYD_TYPE_NOTIF_RESTCONF) || + (data_type == LYD_TYPE_REPLY_RESTCONF)); + assert(!(parse_opts & LYD_PARSE_SUBTREE)); + + /* init context */ + rc = lyd_parse_json_init(ctx, in, parse_opts, val_opts, &lydctx); + LY_CHECK_GOTO(rc, cleanup); + lydctx->ext = ext; + + switch (data_type) { + case LYD_TYPE_RPC_RESTCONF: + assert(parent); + + /* parse "input" */ + rc = lydjson_envelope(lydctx->jsonctx, "input", lyd_node_module(parent)->name, envp); + LY_CHECK_GOTO(rc, cleanup); + + int_opts = LYD_INTOPT_WITH_SIBLINGS | LYD_INTOPT_RPC | LYD_INTOPT_ACTION; + close_elem = 1; + break; + case LYD_TYPE_NOTIF_RESTCONF: + assert(!parent); + + /* parse "notification" */ + rc = lydjson_envelope(lydctx->jsonctx, "notification", "ietf-restconf", envp); + LY_CHECK_GOTO(rc, cleanup); + + /* RESTCONF notification and eventTime */ + int_opts = LYD_INTOPT_WITH_SIBLINGS | LYD_INTOPT_NOTIF | LYD_INTOPT_EVENTTIME; + close_elem = 1; + break; + case LYD_TYPE_REPLY_RESTCONF: + assert(parent); + + /* parse "output" */ + rc = lydjson_envelope(lydctx->jsonctx, "output", lyd_node_module(parent)->name, envp); + LY_CHECK_GOTO(rc, cleanup); + + int_opts = LYD_INTOPT_WITH_SIBLINGS | LYD_INTOPT_REPLY; + close_elem = 1; + break; + default: + LOGINT(ctx); + rc = LY_EINT; + goto cleanup; + } + + lydctx->int_opts = int_opts; + + /* find the operation node if it exists already */ + LY_CHECK_GOTO(rc = lyd_parser_find_operation(parent, int_opts, &lydctx->op_node), cleanup); + + /* read subtree(s) */ + do { + r = lydjson_subtree_r(lydctx, parent, first_p, parsed); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + } while (lyjson_ctx_status(lydctx->jsonctx) == LYJSON_OBJECT_NEXT); + + /* close all opened elements */ + for (i = 0; i < close_elem; ++i) { + if (lyjson_ctx_status(lydctx->jsonctx) != LYJSON_OBJECT_CLOSED) { + LOGVAL(ctx, LYVE_SYNTAX_JSON, "Expecting JSON %s but %s found.", lyjson_token2str(LYJSON_OBJECT_CLOSED), + lyjson_token2str(lyjson_ctx_status(lydctx->jsonctx))); + rc = LY_EVALID; + goto cleanup; + } + + r = lyjson_ctx_next(lydctx->jsonctx, NULL); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + } + + if ((int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_NOTIF | LYD_INTOPT_REPLY)) && !lydctx->op_node) { + LOGVAL(ctx, LYVE_DATA, "Missing the operation node."); + r = LY_EVALID; + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + } + if (int_opts & LYD_INTOPT_EVENTTIME) { + /* parse as a child of the envelope */ + node = (*first_p)->prev; + if (node->schema) { + LOGVAL(ctx, LYVE_DATA, "Missing notification \"eventTime\" node."); + r = LY_EVALID; + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + } else { + /* can be the only opaque node and an operation had to be parsed */ + assert(!strcmp(LYD_NAME(node), "eventTime") && (*first_p)->next); + lyd_unlink(node); + assert(*envp); + lyd_insert_child(*envp, node); + } + } + + /* finish linking metadata */ + r = lydjson_metadata_finish(lydctx, parent ? lyd_node_child_p(parent) : first_p); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + +cleanup: + /* there should be no unres stored if validation should be skipped */ + assert(!(parse_opts & LYD_PARSE_ONLY) || !lydctx || (!lydctx->node_types.count && !lydctx->meta_types.count && + !lydctx->node_when.count)); + if (rc) { lyd_json_ctx_free((struct lyd_ctx *)lydctx); } else { diff --git a/src/parser_lyb.c b/src/parser_lyb.c index f898085..788be94 100644 --- a/src/parser_lyb.c +++ b/src/parser_lyb.c @@ -49,11 +49,15 @@ lylyb_ctx_free(struct lylyb_ctx *ctx) { LY_ARRAY_COUNT_TYPE u; + if (!ctx) { + return; + } + LY_ARRAY_FREE(ctx->siblings); LY_ARRAY_FREE(ctx->models); LY_ARRAY_FOR(ctx->sib_hts, u) { - lyht_free(ctx->sib_hts[u].ht); + lyht_free(ctx->sib_hts[u].ht, NULL); } LY_ARRAY_FREE(ctx->sib_hts); @@ -65,6 +69,10 @@ lyd_lyb_ctx_free(struct lyd_ctx *lydctx) { struct lyd_lyb_ctx *ctx = (struct lyd_lyb_ctx *)lydctx; + if (!lydctx) { + return; + } + lyd_ctx_free(lydctx); lylyb_ctx_free(ctx->lybctx); free(ctx); @@ -1082,7 +1090,7 @@ lyb_validate_node_inner(struct lyd_lyb_ctx *lybctx, const struct lysc_node *snod if (!(lybctx->parse_opts & LYD_PARSE_ONLY)) { /* new node validation, autodelete CANNOT occur, all nodes are new */ - ret = lyd_validate_new(lyd_node_child_p(node), snode, NULL, NULL); + ret = lyd_validate_new(lyd_node_child_p(node), snode, NULL, 0, NULL); LY_CHECK_RET(ret); /* add any missing default children */ @@ -1160,9 +1168,12 @@ lyb_parse_node_opaq(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, struct /* create node */ ret = lyd_create_opaq(ctx, name, strlen(name), prefix, ly_strlen(prefix), module_key, ly_strlen(module_key), - value, strlen(value), &dynamic, format, val_prefix_data, 0, &node); + value, strlen(value), &dynamic, format, val_prefix_data, LYD_HINT_DATA, &node); LY_CHECK_GOTO(ret, cleanup); + assert(node); + LOG_LOCSET(NULL, node, NULL, NULL); + /* process children */ ret = lyb_parse_siblings(lybctx, node, NULL, NULL); LY_CHECK_GOTO(ret, cleanup); @@ -1170,8 +1181,12 @@ lyb_parse_node_opaq(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, struct /* register parsed opaq node */ lyb_finish_opaq(lybctx, parent, flags, &attr, &node, first_p, parsed); assert(!attr && !node); + LOG_LOCBACK(0, 1, 0, 0); cleanup: + if (node) { + LOG_LOCBACK(0, 1, 0, 0); + } free(prefix); free(module_key); free(name); @@ -1257,9 +1272,13 @@ lyb_parse_node_any(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, const st goto error; } + assert(node); + LOG_LOCSET(NULL, node, NULL, NULL); + /* register parsed anydata node */ lyb_finish_node(lybctx, parent, flags, &meta, &node, first_p, parsed); + LOG_LOCBACK(0, 1, 0, 0); return LY_SUCCESS; error: @@ -1296,6 +1315,9 @@ lyb_parse_node_inner(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, const ret = lyd_create_inner(snode, &node); LY_CHECK_GOTO(ret, error); + assert(node); + LOG_LOCSET(NULL, node, NULL, NULL); + /* process children */ ret = lyb_parse_siblings(lybctx, node, NULL, NULL); LY_CHECK_GOTO(ret, error); @@ -1312,9 +1334,13 @@ lyb_parse_node_inner(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, const /* register parsed node */ lyb_finish_node(lybctx, parent, flags, &meta, &node, first_p, parsed); + LOG_LOCBACK(0, 1, 0, 0); return LY_SUCCESS; error: + if (node) { + LOG_LOCBACK(0, 1, 0, 0); + } lyd_free_meta_siblings(meta); lyd_free_tree(node); return ret; @@ -1347,8 +1373,12 @@ lyb_parse_node_leaf(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, const s ret = lyb_create_term(lybctx, snode, &node); LY_CHECK_GOTO(ret, error); + assert(node); + LOG_LOCSET(NULL, node, NULL, NULL); + lyb_finish_node(lybctx, parent, flags, &meta, &node, first_p, parsed); + LOG_LOCBACK(0, 1, 0, 0); return LY_SUCCESS; error: @@ -1408,6 +1438,7 @@ lyb_parse_node_list(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, const s struct lyd_node *node = NULL; struct lyd_meta *meta = NULL; uint32_t flags; + ly_bool log_node = 0; /* register a new sibling */ ret = lyb_read_start_siblings(lybctx->lybctx); @@ -1422,6 +1453,10 @@ lyb_parse_node_list(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, const s ret = lyd_create_inner(snode, &node); LY_CHECK_GOTO(ret, error); + assert(node); + LOG_LOCSET(NULL, node, NULL, NULL); + log_node = 1; + /* process children */ ret = lyb_parse_siblings(lybctx, node, NULL, NULL); LY_CHECK_GOTO(ret, error); @@ -1437,6 +1472,9 @@ lyb_parse_node_list(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, const s /* register parsed list node */ lyb_finish_node(lybctx, parent, flags, &meta, &node, first_p, parsed); + + LOG_LOCBACK(0, 1, 0, 0); + log_node = 0; } /* end the sibling */ @@ -1446,6 +1484,9 @@ lyb_parse_node_list(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, const s return LY_SUCCESS; error: + if (log_node) { + LOG_LOCBACK(0, 1, 0, 0); + } lyd_free_meta_siblings(meta); lyd_free_tree(node); return ret; @@ -1484,7 +1525,7 @@ lyb_parse_node(struct lyd_lyb_ctx *lybctx, struct lyd_node *parent, struct lyd_n case LYB_NODE_CHILD: case LYB_NODE_OPAQ: /* read hash, find the schema node starting from parent schema, if any */ - LY_CHECK_GOTO(ret = lyb_parse_schema_hash(lybctx, parent ? parent->schema : NULL, NULL, &snode), cleanup); + LY_CHECK_GOTO(ret = lyb_parse_schema_hash(lybctx, lyd_parser_node_schema(parent), NULL, &snode), cleanup); break; case LYB_NODE_EXT: /* ext, read module name */ diff --git a/src/parser_xml.c b/src/parser_xml.c index 5a929da..5d97c8e 100644 --- a/src/parser_xml.c +++ b/src/parser_xml.c @@ -40,6 +40,9 @@ #include "validation.h" #include "xml.h" +static LY_ERR lydxml_subtree_r(struct lyd_xml_ctx *lydctx, struct lyd_node *parent, struct lyd_node **first_p, + struct ly_set *parsed); + void lyd_xml_ctx_free(struct lyd_ctx *lydctx) { @@ -72,6 +75,8 @@ lydxml_metadata(struct lyd_xml_ctx *lydctx, const struct lysc_node *sparent, str *meta = NULL; + LOG_LOCSET(sparent, NULL, NULL, NULL); + /* check for NETCONF filter unqualified attributes */ if (!strcmp(sparent->module->name, "notifications")) { /* ancient module that does not even use the extension */ @@ -160,6 +165,7 @@ create_meta: } cleanup: + LOG_LOCBACK(1, 0, 0, 0); if (ret) { lyd_free_meta_siblings(*meta); *meta = NULL; @@ -283,7 +289,7 @@ lydxml_check_list(struct lyxml_ctx *xmlctx, const struct lysc_node *list) assert(xmlctx->status == LYXML_ELEM_CONTENT); if (i < key_set.count) { /* validate the value */ - r = lys_value_validate(NULL, snode, xmlctx->value, xmlctx->value_len, LY_VALUE_XML, &xmlctx->ns); + r = ly_value_validate(NULL, snode, xmlctx->value, xmlctx->value_len, LY_VALUE_XML, &xmlctx->ns, LYD_HINT_DATA); if (!r) { /* key with a valid value, remove from the set */ ly_set_rm_index(&key_set, i, NULL); @@ -389,8 +395,8 @@ lydxml_data_check_opaq(struct lyd_xml_ctx *lydctx, const struct lysc_node **snod if ((*snode)->nodetype & LYD_NODE_TERM) { /* value may not be valid in which case we parse it as an opaque node */ - if (lys_value_validate(NULL, *snode, xmlctx->value, xmlctx->value_len, LY_VALUE_XML, &xmlctx->ns)) { - LOGVRB("Parsing opaque term node \"%s\" with invalid value \"%.*s\".", (*snode)->name, xmlctx->value_len, + if (ly_value_validate(NULL, *snode, xmlctx->value, xmlctx->value_len, LY_VALUE_XML, &xmlctx->ns, LYD_HINT_DATA)) { + LOGVRB("Parsing opaque term node \"%s\" with invalid value \"%.*s\".", (*snode)->name, (int)xmlctx->value_len, xmlctx->value); *snode = NULL; } @@ -430,7 +436,7 @@ restore: * @param[out] anchor Anchor to insert after in case of a list. */ static void -lydxml_get_hints_opaq(const char *name, size_t name_len, const char *value, size_t value_len, struct lyd_node *first, +lydxml_get_hints_opaq(const char *name, size_t name_len, const char *value, size_t value_len, const struct lyd_node *first, const char *ns, uint32_t *hints, struct lyd_node **anchor) { struct lyd_node_opaq *opaq; @@ -441,8 +447,8 @@ lydxml_get_hints_opaq(const char *name, size_t name_len, const char *value, size *anchor = NULL; if (!value_len) { - /* no value */ - *hints |= LYD_VALHINT_EMPTY; + /* no value but it may also be zero-length string */ + *hints |= LYD_VALHINT_EMPTY | LYD_VALHINT_STRING; } else if (!strncmp(value, "true", value_len) || !strncmp(value, "false", value_len)) { /* boolean value */ *hints |= LYD_VALHINT_BOOLEAN; @@ -485,7 +491,7 @@ lydxml_get_hints_opaq(const char *name, size_t name_len, const char *value, size opaq->hints |= LYD_NODEHINT_LIST; *hints |= LYD_NODEHINT_LIST; } - *anchor = first; + *anchor = (struct lyd_node *)first; break; } } while (first->prev->next); @@ -506,7 +512,7 @@ lydxml_get_hints_opaq(const char *name, size_t name_len, const char *value, size * @return LY_ERR on error. */ static LY_ERR -lydxml_subtree_snode(struct lyd_xml_ctx *lydctx, const struct lyd_node *parent, const char *prefix, size_t prefix_len, +lydxml_subtree_get_snode(struct lyd_xml_ctx *lydctx, const struct lyd_node *parent, const char *prefix, size_t prefix_len, const char *name, size_t name_len, const struct lysc_node **snode, struct lysc_ext_instance **ext) { LY_ERR r; @@ -551,338 +557,569 @@ lydxml_subtree_snode(struct lyd_xml_ctx *lydctx, const struct lyd_node *parent, unknown_module: if (lydctx->parse_opts & LYD_PARSE_STRICT) { - LOGVAL(ctx, LYVE_REFERENCE, "No module with namespace \"%s\" in the context.", ns->uri); + if (ns) { + LOGVAL(ctx, LYVE_REFERENCE, "No module with namespace \"%s\" in the context.", ns->uri); + } else if (prefix_len) { + LOGVAL(ctx, LYVE_REFERENCE, "No module with namespace \"%.*s\" in the context.", (int)prefix_len, prefix); + } else { + LOGVAL(ctx, LYVE_REFERENCE, "No default namespace in the context."); + } return LY_EVALID; } return LY_SUCCESS; } /* get the schema node */ - if (mod) { - if (!parent && lydctx->ext) { - *snode = lysc_ext_find_node(lydctx->ext, mod, name, name_len, 0, getnext_opts); - } else { - *snode = lys_find_child(parent ? parent->schema : NULL, mod, name, name_len, 0, getnext_opts); - } - if (!*snode) { - /* check for extension data */ - r = ly_nested_ext_schema(parent, NULL, prefix, prefix_len, LY_VALUE_XML, &lydctx->xmlctx->ns, name, - name_len, snode, ext); - if (r != LY_ENOT) { - /* success or error */ - return r; - } + if (!parent && lydctx->ext) { + *snode = lysc_ext_find_node(lydctx->ext, mod, name, name_len, 0, getnext_opts); + } else { + /* try to find parent schema node even if it is an opaque node (not connected to the parent) */ + *snode = lys_find_child(lyd_parser_node_schema(parent), mod, name, name_len, 0, getnext_opts); + } + if (!*snode) { + /* check for extension data */ + r = ly_nested_ext_schema(parent, NULL, prefix, prefix_len, LY_VALUE_XML, &lydctx->xmlctx->ns, name, + name_len, snode, ext); + if (r != LY_ENOT) { + /* success or error */ + return r; + } - /* unknown data node */ - if (lydctx->parse_opts & LYD_PARSE_STRICT) { - if (parent) { - LOGVAL(ctx, LYVE_REFERENCE, "Node \"%.*s\" not found as a child of \"%s\" node.", - (int)name_len, name, LYD_NAME(parent)); - } else if (lydctx->ext) { - if (lydctx->ext->argument) { - LOGVAL(ctx, LYVE_REFERENCE, "Node \"%.*s\" not found in the \"%s\" %s extension instance.", - (int)name_len, name, lydctx->ext->argument, lydctx->ext->def->name); - } else { - LOGVAL(ctx, LYVE_REFERENCE, "Node \"%.*s\" not found in the %s extension instance.", - (int)name_len, name, lydctx->ext->def->name); - } + /* unknown data node */ + if (lydctx->parse_opts & LYD_PARSE_STRICT) { + if (parent) { + LOGVAL(ctx, LYVE_REFERENCE, "Node \"%.*s\" not found as a child of \"%s\" node.", + (int)name_len, name, LYD_NAME(parent)); + } else if (lydctx->ext) { + if (lydctx->ext->argument) { + LOGVAL(ctx, LYVE_REFERENCE, "Node \"%.*s\" not found in the \"%s\" %s extension instance.", + (int)name_len, name, lydctx->ext->argument, lydctx->ext->def->name); } else { - LOGVAL(ctx, LYVE_REFERENCE, "Node \"%.*s\" not found in the \"%s\" module.", - (int)name_len, name, mod->name); + LOGVAL(ctx, LYVE_REFERENCE, "Node \"%.*s\" not found in the %s extension instance.", + (int)name_len, name, lydctx->ext->def->name); } - return LY_EVALID; + } else { + LOGVAL(ctx, LYVE_REFERENCE, "Node \"%.*s\" not found in the \"%s\" module.", + (int)name_len, name, mod->name); } - return LY_SUCCESS; - } else { - /* check that schema node is valid and can be used */ - LY_CHECK_RET(lyd_parser_check_schema((struct lyd_ctx *)lydctx, *snode)); - LY_CHECK_RET(lydxml_data_check_opaq(lydctx, snode)); + return LY_EVALID; } + return LY_SUCCESS; + } else { + /* check that schema node is valid and can be used */ + LY_CHECK_RET(lyd_parser_check_schema((struct lyd_ctx *)lydctx, *snode)); + LY_CHECK_RET(lydxml_data_check_opaq(lydctx, snode)); } return LY_SUCCESS; } /** - * @brief Parse XML subtree. + * @brief Parse an XML opque node. * * @param[in] lydctx XML YANG data parser context. - * @param[in,out] parent Parent node where the children are inserted. NULL in case of parsing top-level elements. - * @param[in,out] first_p Pointer to the first (@p parent or top-level) child. In case there were already some siblings, - * this may point to a previously existing node. - * @param[in,out] parsed Optional set to add all the parsed siblings into. + * @param[in] sibling Existing sibling node, if any. + * @param[in] prefix Parsed node prefix. + * @param[in] prefix_len Length of @p prefix. + * @param[in] name Parsed node name. + * @param[in] name_len Length of @p name. + * @param[out] insert_anchor Optional anchor node for inserting this node. + * @param[out] node Created node. * @return LY_ERR value. */ static LY_ERR -lydxml_subtree_r(struct lyd_xml_ctx *lydctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_set *parsed) +lydxml_subtree_opaq(struct lyd_xml_ctx *lydctx, const struct lyd_node *sibling, const char *prefix, uint32_t prefix_len, + const char *name, uint32_t name_len, struct lyd_node **insert_anchor, struct lyd_node **node) { - LY_ERR ret = LY_SUCCESS; - const char *prefix, *name, *ns_uri; - size_t prefix_len, name_len; - struct lyxml_ctx *xmlctx; - const struct ly_ctx *ctx; + LY_ERR rc = LY_SUCCESS; + struct lyxml_ctx *xmlctx = lydctx->xmlctx; + struct lyd_node_opaq *opaq; + const char *ns_uri, *value = NULL; + size_t value_len; + ly_bool ws_only, dynamic = 0; const struct lyxml_ns *ns; - struct lyd_meta *meta = NULL; - struct lyd_attr *attr = NULL; - const struct lysc_node *snode; - struct lysc_ext_instance *ext; - uint32_t prev_parse_opts, orig_parse_opts, prev_int_opts, hints; - struct lyd_node *node = NULL, *anchor, *insert_anchor = NULL; + uint32_t hints; void *val_prefix_data = NULL; LY_VALUE_FORMAT format; - ly_bool parse_subtree; - char *val; - assert(parent || first_p); + assert(lydctx->parse_opts & LYD_PARSE_OPAQ); - xmlctx = lydctx->xmlctx; - ctx = xmlctx->ctx; + *node = NULL; - parse_subtree = lydctx->parse_opts & LYD_PARSE_SUBTREE ? 1 : 0; - /* all descendants should be parsed */ - lydctx->parse_opts &= ~LYD_PARSE_SUBTREE; - orig_parse_opts = lydctx->parse_opts; + /* remember the value */ + value = xmlctx->value; + value_len = xmlctx->value_len; + ws_only = xmlctx->ws_only; + dynamic = xmlctx->dynamic; + if (dynamic) { + xmlctx->dynamic = 0; + } - assert(xmlctx->status == LYXML_ELEMENT); + /* get value prefixes, if any */ + rc = ly_store_prefix_data(xmlctx->ctx, value, value_len, LY_VALUE_XML, &xmlctx->ns, &format, &val_prefix_data); + LY_CHECK_GOTO(rc, cleanup); - /* remember element prefix and name */ - prefix = xmlctx->prefix; - prefix_len = xmlctx->prefix_len; - name = xmlctx->name; - name_len = xmlctx->name_len; + /* get NS again, it may have been backed up and restored */ + ns = lyxml_ns_get(&xmlctx->ns, prefix, prefix_len); + ns_uri = ns ? ns->uri : NULL; - /* parser next */ - LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), error); + /* get best-effort node hints */ + lydxml_get_hints_opaq(name, name_len, xmlctx->value, xmlctx->value_len, sibling, ns_uri, &hints, insert_anchor); - /* get the schema node */ - LY_CHECK_GOTO(ret = lydxml_subtree_snode(lydctx, parent, prefix, prefix_len, name, name_len, &snode, &ext), error); + /* create the node without value */ + rc = lyd_create_opaq(xmlctx->ctx, name, name_len, prefix, prefix_len, ns_uri, ns_uri ? strlen(ns_uri) : 0, NULL, 0, + NULL, format, NULL, hints, node); + LY_CHECK_GOTO(rc, cleanup); - if (!snode && !(lydctx->parse_opts & LYD_PARSE_OPAQ)) { - LOGVRB("Skipping parsing of unknown node \"%.*s\".", name_len, name); + assert(*node); + LOG_LOCSET(NULL, *node, NULL, NULL); - /* skip element with children */ - LY_CHECK_GOTO(ret = lydxml_data_skip(xmlctx), error); - return LY_SUCCESS; + /* parser next */ + rc = lyxml_ctx_next(xmlctx); + LY_CHECK_GOTO(rc, cleanup); + + /* process children */ + while (xmlctx->status == LYXML_ELEMENT) { + rc = lydxml_subtree_r(lydctx, *node, lyd_node_child_p(*node), NULL); + LY_CHECK_GOTO(rc, cleanup); } - /* create metadata/attributes */ - if (xmlctx->status == LYXML_ATTRIBUTE) { - if (snode) { - ret = lydxml_metadata(lydctx, snode, &meta); - LY_CHECK_GOTO(ret, error); + /* update the value */ + opaq = (struct lyd_node_opaq *)*node; + if (opaq->child) { + if (!ws_only) { + LOGVAL(xmlctx->ctx, LYVE_SYNTAX_XML, "Mixed XML content node \"%s\" found, not supported.", LYD_NAME(opaq)); + rc = LY_EVALID; + goto cleanup; + } + } else if (value_len) { + lydict_remove(xmlctx->ctx, opaq->value); + if (dynamic) { + LY_CHECK_GOTO(rc = lydict_insert_zc(xmlctx->ctx, (char *)value, &opaq->value), cleanup); + dynamic = 0; } else { - assert(lydctx->parse_opts & LYD_PARSE_OPAQ); - ret = lydxml_attrs(xmlctx, &attr); - LY_CHECK_GOTO(ret, error); + LY_CHECK_GOTO(rc = lydict_insert(xmlctx->ctx, value, value_len, &opaq->value), cleanup); } } - assert(xmlctx->status == LYXML_ELEM_CONTENT); - if (!snode) { - assert(lydctx->parse_opts & LYD_PARSE_OPAQ); + /* always store val_prefix_data because the format requires them */ + assert(!opaq->val_prefix_data); + opaq->val_prefix_data = val_prefix_data; + val_prefix_data = NULL; - if (xmlctx->ws_only) { - /* ignore WS-only value */ - if (xmlctx->dynamic) { - free((char *)xmlctx->value); - } - xmlctx->dynamic = 0; - xmlctx->value = ""; - xmlctx->value_len = 0; - format = LY_VALUE_XML; - } else { - /* get value prefixes */ - ret = ly_store_prefix_data(xmlctx->ctx, xmlctx->value, xmlctx->value_len, LY_VALUE_XML, - &xmlctx->ns, &format, &val_prefix_data); - LY_CHECK_GOTO(ret, error); - } +cleanup: + if (*node) { + LOG_LOCBACK(0, 1, 0, 0); + } + ly_free_prefix_data(format, val_prefix_data); + if (dynamic) { + free((char *)value); + } + if (rc) { + lyd_free_tree(*node); + *node = NULL; + } + return rc; +} - /* get NS again, it may have been backed up and restored */ - ns = lyxml_ns_get(&xmlctx->ns, prefix, prefix_len); - ns_uri = ns ? ns->uri : NULL; +/** + * @brief Parse an XML leaf/leaf-list node. + * + * @param[in] lydctx XML YANG data parser context. + * @param[in] parent Parent node, if any. + * @param[in] snode Schema node of the new node. + * @param[out] node Created node. + * @return LY_ERR value. + */ +static LY_ERR +lydxml_subtree_term(struct lyd_xml_ctx *lydctx, struct lyd_node *parent, const struct lysc_node *snode, + struct lyd_node **node) +{ + LY_ERR r, rc = LY_SUCCESS; + struct lyxml_ctx *xmlctx = lydctx->xmlctx; + struct lyd_node *anchor; - /* get best-effort node hints */ - lydxml_get_hints_opaq(name, name_len, xmlctx->value, xmlctx->value_len, parent ? lyd_child(parent) : *first_p, - ns_uri, &hints, &insert_anchor); + *node = NULL; - /* create node */ - ret = lyd_create_opaq(ctx, name, name_len, prefix, prefix_len, ns_uri, ns_uri ? strlen(ns_uri) : 0, - xmlctx->value, xmlctx->value_len, &xmlctx->dynamic, format, val_prefix_data, hints, &node); - LY_CHECK_GOTO(ret, error); + /* create node */ + r = lyd_parser_create_term((struct lyd_ctx *)lydctx, snode, xmlctx->value, xmlctx->value_len, &xmlctx->dynamic, + LY_VALUE_XML, &xmlctx->ns, LYD_HINT_DATA, node); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); - /* parser next */ - LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), error); + if (*node) { + LOG_LOCSET(NULL, *node, NULL, NULL); + } - /* process children */ - while (xmlctx->status == LYXML_ELEMENT) { - ret = lydxml_subtree_r(lydctx, node, lyd_node_child_p(node), NULL); - LY_CHECK_GOTO(ret, error); - } - } else if (snode->nodetype & LYD_NODE_TERM) { - /* create node */ - LY_CHECK_GOTO(ret = lyd_parser_create_term((struct lyd_ctx *)lydctx, snode, xmlctx->value, xmlctx->value_len, - &xmlctx->dynamic, LY_VALUE_XML, &xmlctx->ns, LYD_HINT_DATA, &node), error); - LOG_LOCSET(snode, node, NULL, NULL); - - if (parent && (node->schema->flags & LYS_KEY)) { - /* check the key order, the anchor must never be a key */ - anchor = lyd_insert_get_next_anchor(lyd_child(parent), node); - if (anchor && anchor->schema && (anchor->schema->flags & LYS_KEY)) { - if (lydctx->parse_opts & LYD_PARSE_STRICT) { - LOGVAL(ctx, LYVE_DATA, "Invalid position of the key \"%s\" in a list.", node->schema->name); - ret = LY_EVALID; - goto error; - } else { - LOGWRN(ctx, "Invalid position of the key \"%s\" in a list.", node->schema->name); - } + if (*node && parent && (snode->flags & LYS_KEY)) { + /* check the key order, the anchor must never be a key */ + anchor = lyd_insert_get_next_anchor(lyd_child(parent), *node); + if (anchor && anchor->schema && (anchor->schema->flags & LYS_KEY)) { + if (lydctx->parse_opts & LYD_PARSE_STRICT) { + LOGVAL(xmlctx->ctx, LYVE_DATA, "Invalid position of the key \"%s\" in a list.", snode->name); + r = LY_EVALID; + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + } else { + LOGWRN(xmlctx->ctx, "Invalid position of the key \"%s\" in a list.", snode->name); } } + } - /* parser next */ - LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), error); + /* parser next */ + r = lyxml_ctx_next(xmlctx); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); - /* no children expected */ - if (xmlctx->status == LYXML_ELEMENT) { - LOGVAL(ctx, LYVE_SYNTAX, "Child element \"%.*s\" inside a terminal node \"%s\" found.", - (int)xmlctx->name_len, xmlctx->name, snode->name); - ret = LY_EVALID; - goto error; - } - } else if (snode->nodetype & LYD_NODE_INNER) { - if (!xmlctx->ws_only) { - /* value in inner node */ - LOGVAL(ctx, LYVE_SYNTAX, "Text value \"%.*s\" inside an inner node \"%s\" found.", - (int)xmlctx->value_len, xmlctx->value, snode->name); - ret = LY_EVALID; - goto error; - } + /* no children expected */ + if (xmlctx->status == LYXML_ELEMENT) { + LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Child element \"%.*s\" inside a terminal node \"%s\" found.", + (int)xmlctx->name_len, xmlctx->name, snode->name); + r = LY_EVALID; + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + } + +cleanup: + if (*node) { + LOG_LOCBACK(0, 1, 0, 0); + } + if (rc && (!(lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR) || (rc != LY_EVALID))) { + lyd_free_tree(*node); + *node = NULL; + } + return rc; +} + +/** + * @brief Parse an XML inner node. + * + * @param[in] lydctx XML YANG data parser context. + * @param[in] snode Schema node of the new node. + * @param[in] ext Extension instance of @p snode, if any. + * @param[out] node Created node. + * @return LY_ERR value. + */ +static LY_ERR +lydxml_subtree_inner(struct lyd_xml_ctx *lydctx, const struct lysc_node *snode, const struct lysc_ext_instance *ext, + struct lyd_node **node) +{ + LY_ERR r, rc = LY_SUCCESS; + struct lyxml_ctx *xmlctx = lydctx->xmlctx; + uint32_t prev_parse_opts = lydctx->parse_opts; + + *node = NULL; + + if (!xmlctx->ws_only) { + /* value in inner node */ + LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Text value \"%.*s\" inside an inner node \"%s\" found.", + (int)xmlctx->value_len, xmlctx->value, snode->name); + r = LY_EVALID; + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + } + + /* create node */ + rc = lyd_create_inner(snode, node); + LY_CHECK_GOTO(rc, cleanup); + + assert(*node); + LOG_LOCSET(NULL, *node, NULL, NULL); + + /* parser next */ + rc = lyxml_ctx_next(xmlctx); + LY_CHECK_GOTO(rc, cleanup); + + if (ext) { + /* only parse these extension data and validate afterwards */ + lydctx->parse_opts |= LYD_PARSE_ONLY; + } + + /* process children */ + while (xmlctx->status == LYXML_ELEMENT) { + r = lydxml_subtree_r(lydctx, *node, lyd_node_child_p(*node), NULL); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + } + + /* restore options */ + lydctx->parse_opts = prev_parse_opts; + + if (snode->nodetype == LYS_LIST) { + /* check all keys exist */ + r = lyd_parse_check_keys(*node); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + } + + if (!(lydctx->parse_opts & LYD_PARSE_ONLY) && !rc) { + /* new node validation, autodelete CANNOT occur (it can if multi-error), all nodes are new */ + r = lyd_validate_new(lyd_node_child_p(*node), snode, NULL, lydctx->val_opts, NULL); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + + /* add any missing default children */ + r = lyd_new_implicit_r(*node, lyd_node_child_p(*node), NULL, NULL, &lydctx->node_when, &lydctx->node_types, + &lydctx->ext_node, (lydctx->val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, NULL); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + } + + if (snode->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) { + /* rememeber the RPC/action/notification */ + lydctx->op_node = *node; + } + +cleanup: + if (*node) { + LOG_LOCBACK(0, 1, 0, 0); + } + lydctx->parse_opts = prev_parse_opts; + if (rc && ((*node && !(*node)->hash) || !(lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR) || (rc != LY_EVALID))) { + /* list without keys is unusable or an error */ + lyd_free_tree(*node); + *node = NULL; + } + return rc; +} + +/** + * @brief Parse an XML anyxml/anydata node. + * + * @param[in] lydctx XML YANG data parser context. + * @param[in] snode Schema node of the new node. + * @param[in] ext Extension instance of @p snode, if any. + * @param[out] node Created node. + * @return LY_ERR value. + */ +static LY_ERR +lydxml_subtree_any(struct lyd_xml_ctx *lydctx, const struct lysc_node *snode, const struct lysc_ext_instance *ext, + struct lyd_node **node) +{ + LY_ERR r, rc = LY_SUCCESS; + struct lyxml_ctx *xmlctx = lydctx->xmlctx; + uint32_t prev_parse_opts = lydctx->parse_opts, prev_int_opts = lydctx->int_opts; + struct lyd_node *child = NULL; + char *val = NULL; + ly_bool log_node = 0; + + *node = NULL; + if ((snode->nodetype == LYS_ANYDATA) && !xmlctx->ws_only) { + /* value in anydata node, we expect a tree */ + LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Text value \"%.*s\" inside an anydata node \"%s\" found.", + xmlctx->value_len < 20 ? (int)xmlctx->value_len : 20, xmlctx->value, snode->name); + r = LY_EVALID; + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + } + + if (!xmlctx->ws_only) { + /* use an arbitrary text value for anyxml */ + val = strndup(xmlctx->value, xmlctx->value_len); + LY_CHECK_ERR_GOTO(!val, LOGMEM(xmlctx->ctx); rc = LY_EMEM, cleanup); + + /* parser next */ + r = lyxml_ctx_next(xmlctx); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + + /* create node */ + r = lyd_create_any(snode, val, LYD_ANYDATA_STRING, 1, node); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); + val = NULL; + } else { /* create node */ - ret = lyd_create_inner(snode, &node); - LY_CHECK_GOTO(ret, error); + r = lyd_create_any(snode, NULL, LYD_ANYDATA_DATATREE, 1, node); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); - LOG_LOCSET(snode, node, NULL, NULL); + assert(*node); + LOG_LOCSET(NULL, *node, NULL, NULL); + log_node = 1; /* parser next */ - LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), error); + r = lyxml_ctx_next(xmlctx); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); - prev_parse_opts = lydctx->parse_opts; - if (ext) { - /* only parse these extension data and validate afterwards */ - lydctx->parse_opts |= LYD_PARSE_ONLY; - } + /* update options so that generic data can be parsed */ + lydctx->parse_opts &= ~LYD_PARSE_STRICT; + lydctx->parse_opts |= LYD_PARSE_OPAQ | (ext ? LYD_PARSE_ONLY : 0); + lydctx->int_opts |= LYD_INTOPT_ANY | LYD_INTOPT_WITH_SIBLINGS; - /* process children */ + /* parse any data tree */ while (xmlctx->status == LYXML_ELEMENT) { - ret = lydxml_subtree_r(lydctx, node, lyd_node_child_p(node), NULL); - LY_CHECK_GOTO(ret, error); + r = lydxml_subtree_r(lydctx, NULL, &child, NULL); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); } - /* restore options */ - lydctx->parse_opts = prev_parse_opts; + /* assign the data tree */ + ((struct lyd_node_any *)*node)->value.tree = child; + child = NULL; + } - if (snode->nodetype == LYS_LIST) { - /* check all keys exist */ - LY_CHECK_GOTO(ret = lyd_parse_check_keys(node), error); - } +cleanup: + if (log_node) { + LOG_LOCBACK(0, 1, 0, 0); + } + lydctx->parse_opts = prev_parse_opts; + lydctx->int_opts = prev_int_opts; + free(val); + lyd_free_tree(child); + if (rc && (!(lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR) || (rc != LY_EVALID))) { + lyd_free_tree(*node); + *node = NULL; + } + return rc; +} - if (!(lydctx->parse_opts & LYD_PARSE_ONLY)) { - /* new node validation, autodelete CANNOT occur, all nodes are new */ - ret = lyd_validate_new(lyd_node_child_p(node), snode, NULL, NULL); - LY_CHECK_GOTO(ret, error); - - /* add any missing default children */ - ret = lyd_new_implicit_r(node, lyd_node_child_p(node), NULL, NULL, &lydctx->node_when, &lydctx->node_types, - &lydctx->ext_node, (lydctx->val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, NULL); - LY_CHECK_GOTO(ret, error); - } - - if (snode->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) { - /* rememeber the RPC/action/notification */ - lydctx->op_node = node; - } - } else if (snode->nodetype & LYD_NODE_ANY) { - if ((snode->nodetype == LYS_ANYDATA) && !xmlctx->ws_only) { - /* value in anydata node, we expect a tree */ - LOGVAL(ctx, LYVE_SYNTAX, "Text value \"%.*s\" inside an anydata node \"%s\" found.", - (int)xmlctx->value_len < 20 ? xmlctx->value_len : 20, xmlctx->value, snode->name); - ret = LY_EVALID; - goto error; - } +/** + * @brief Parse an XML subtree, recursively. + * + * @param[in] lydctx XML YANG data parser context. + * @param[in,out] parent Parent node where the children are inserted. NULL in case of parsing top-level elements. + * @param[in,out] first_p Pointer to the first (@p parent or top-level) child. + * @param[in,out] parsed Optional set to add all the parsed siblings into. + * @return LY_ERR value. + */ +static LY_ERR +lydxml_subtree_r(struct lyd_xml_ctx *lydctx, struct lyd_node *parent, struct lyd_node **first_p, struct ly_set *parsed) +{ + LY_ERR r, rc = LY_SUCCESS; + const char *prefix, *name; + size_t prefix_len, name_len; + struct lyxml_ctx *xmlctx; + const struct ly_ctx *ctx; + struct lyd_meta *meta = NULL; + struct lyd_attr *attr = NULL; + const struct lysc_node *snode = NULL; + struct lysc_ext_instance *ext = NULL; + uint32_t orig_parse_opts; + struct lyd_node *node = NULL, *insert_anchor = NULL; + ly_bool parse_subtree; - if (!xmlctx->ws_only) { - /* use an arbitrary text value for anyxml */ - val = strndup(xmlctx->value, xmlctx->value_len); - LY_CHECK_ERR_GOTO(!val, LOGMEM(xmlctx->ctx); ret = LY_EMEM, error); + assert(parent || first_p); - /* parser next */ - LY_CHECK_ERR_GOTO(ret = lyxml_ctx_next(xmlctx), free(val), error); + xmlctx = lydctx->xmlctx; + ctx = xmlctx->ctx; - /* create node */ - ret = lyd_create_any(snode, val, LYD_ANYDATA_STRING, 1, &node); - LY_CHECK_ERR_GOTO(ret, free(val), error); - } else { - /* parser next */ - LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), error); - - /* update options so that generic data can be parsed */ - prev_parse_opts = lydctx->parse_opts; - lydctx->parse_opts &= ~LYD_PARSE_STRICT; - lydctx->parse_opts |= LYD_PARSE_OPAQ | (ext ? LYD_PARSE_ONLY : 0); - prev_int_opts = lydctx->int_opts; - lydctx->int_opts |= LYD_INTOPT_ANY | LYD_INTOPT_WITH_SIBLINGS; - - /* parse any data tree */ - anchor = NULL; - while (xmlctx->status == LYXML_ELEMENT) { - ret = lydxml_subtree_r(lydctx, NULL, &anchor, NULL); - if (ret) { - lyd_free_siblings(anchor); - break; - } - } + parse_subtree = lydctx->parse_opts & LYD_PARSE_SUBTREE ? 1 : 0; + /* all descendants should be parsed */ + lydctx->parse_opts &= ~LYD_PARSE_SUBTREE; + orig_parse_opts = lydctx->parse_opts; + + assert(xmlctx->status == LYXML_ELEMENT); + + /* remember element prefix and name */ + prefix = xmlctx->prefix; + prefix_len = xmlctx->prefix_len; + name = xmlctx->name; + name_len = xmlctx->name_len; + + /* parser next */ + rc = lyxml_ctx_next(xmlctx); + LY_CHECK_GOTO(rc, cleanup); + + if ((lydctx->int_opts & LYD_INTOPT_EVENTTIME) && !parent && name_len && !prefix_len && + !ly_strncmp("eventTime", name, name_len)) { + /* parse eventTime, create node */ + assert(xmlctx->status == LYXML_ELEM_CONTENT); + rc = lyd_create_opaq(xmlctx->ctx, name, name_len, prefix, prefix_len, + "urn:ietf:params:xml:ns:netconf:notification:1.0", 47, xmlctx->value, + xmlctx->ws_only ? 0 : xmlctx->value_len, NULL, LY_VALUE_XML, NULL, LYD_HINT_DATA, &node); + LY_CHECK_GOTO(rc, cleanup); - /* restore options */ - lydctx->parse_opts = prev_parse_opts; - lydctx->int_opts = prev_int_opts; + /* validate the value */ + r = lyd_parser_notif_eventtime_validate(node); + LY_CHECK_ERR_GOTO(r, rc = r; lyd_free_tree(node), cleanup); - LY_CHECK_GOTO(ret, error); + /* parser next */ + r = lyxml_ctx_next(xmlctx); + LY_CHECK_ERR_GOTO(r, rc = r; lyd_free_tree(node), cleanup); + if (xmlctx->status != LYXML_ELEM_CLOSE) { + LOGVAL(ctx, LYVE_DATA, "Unexpected notification \"eventTime\" node children."); + rc = LY_EVALID; + lyd_free_tree(node); + goto cleanup; + } + + goto node_parsed; + } + + /* get the schema node */ + r = lydxml_subtree_get_snode(lydctx, parent, prefix, prefix_len, name, name_len, &snode, &ext); + if (r) { + rc = r; + if ((r == LY_EVALID) && (lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR)) { + /* skip the invalid data */ + if ((r = lydxml_data_skip(xmlctx))) { + rc = r; + } + } + goto cleanup; + } else if (!snode && !(lydctx->parse_opts & LYD_PARSE_OPAQ)) { + LOGVRB("Skipping parsing of unknown node \"%.*s\".", (int)name_len, name); - /* create node */ - ret = lyd_create_any(snode, anchor, LYD_ANYDATA_DATATREE, 1, &node); - LY_CHECK_GOTO(ret, error); + /* skip element with children */ + rc = lydxml_data_skip(xmlctx); + goto cleanup; + } + + /* create metadata/attributes */ + if (xmlctx->status == LYXML_ATTRIBUTE) { + if (snode) { + rc = lydxml_metadata(lydctx, snode, &meta); + LY_CHECK_GOTO(rc, cleanup); + } else { + assert(lydctx->parse_opts & LYD_PARSE_OPAQ); + rc = lydxml_attrs(xmlctx, &attr); + LY_CHECK_GOTO(rc, cleanup); } } - assert(node); - if (snode) { + assert(xmlctx->status == LYXML_ELEM_CONTENT); + if (!snode) { + /* opaque */ + r = lydxml_subtree_opaq(lydctx, parent ? lyd_child(parent) : *first_p, prefix, prefix_len, name, name_len, + &insert_anchor, &node); + } else if (snode->nodetype & LYD_NODE_TERM) { + /* term */ + r = lydxml_subtree_term(lydctx, parent, snode, &node); + } else if (snode->nodetype & LYD_NODE_INNER) { + /* inner */ + r = lydxml_subtree_inner(lydctx, snode, ext, &node); + } else { + /* any */ + assert(snode->nodetype & LYD_NODE_ANY); + r = lydxml_subtree_any(lydctx, snode, ext, &node); + } + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + +node_parsed: + if (node && snode) { /* add/correct flags */ - LY_CHECK_GOTO(ret = lyd_parse_set_data_flags(node, &meta, (struct lyd_ctx *)lydctx, ext), error); + r = lyd_parse_set_data_flags(node, &meta, (struct lyd_ctx *)lydctx, ext); + LY_CHECK_ERR_GOTO(r, rc = r; lyd_free_tree(node), cleanup); if (!(lydctx->parse_opts & LYD_PARSE_ONLY)) { /* store for ext instance node validation, if needed */ - LY_CHECK_GOTO(ret = lyd_validate_node_ext(node, &lydctx->ext_node), error); + r = lyd_validate_node_ext(node, &lydctx->ext_node); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); } } /* parser next */ assert(xmlctx->status == LYXML_ELEM_CLOSE); if (!parse_subtree) { - LY_CHECK_GOTO(ret = lyxml_ctx_next(xmlctx), error); + r = lyxml_ctx_next(xmlctx); + LY_CHECK_ERR_GOTO(r, rc = r; lyd_free_tree(node), cleanup); } + LY_CHECK_GOTO(!node, cleanup); + /* add metadata/attributes */ if (snode) { lyd_insert_meta(node, meta, 0); + meta = NULL; } else { lyd_insert_attr(node, attr); + attr = NULL; } /* insert, keep first pointer correct */ if (insert_anchor) { lyd_insert_after(insert_anchor, node); } else if (ext) { - LY_CHECK_GOTO(ret = lyplg_ext_insert(parent, node), error); + r = lyplg_ext_insert(parent, node); + LY_CHECK_ERR_GOTO(r, rc = r; lyd_free_tree(node), cleanup); } else { lyd_insert_node(parent, first_p, node, lydctx->parse_opts & LYD_PARSE_ORDERED ? 1 : 0); } @@ -895,17 +1132,11 @@ lydxml_subtree_r(struct lyd_xml_ctx *lydctx, struct lyd_node *parent, struct lyd ly_set_add(parsed, node, 1, NULL); } +cleanup: lydctx->parse_opts = orig_parse_opts; - LOG_LOCBACK(node ? 1 : 0, node ? 1 : 0, 0, 0); - return LY_SUCCESS; - -error: - lydctx->parse_opts = orig_parse_opts; - LOG_LOCBACK(node ? 1 : 0, node ? 1 : 0, 0, 0); lyd_free_meta_siblings(meta); lyd_free_attr_siblings(ctx, attr); - lyd_free_tree(node); - return ret; + return rc; } /** @@ -929,7 +1160,11 @@ lydxml_envelope(struct lyxml_ctx *xmlctx, const char *name, const char *uri, ly_ const char *prefix; size_t prefix_len; - assert(xmlctx->status == LYXML_ELEMENT); + if (xmlctx->status != LYXML_ELEMENT) { + /* nothing to parse */ + return LY_ENOT; + } + if (ly_strncmp(name, xmlctx->name, xmlctx->name_len)) { /* not the expected element */ return LY_ENOT; @@ -987,9 +1222,10 @@ lyd_parse_xml(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, str struct lyd_node **first_p, struct ly_in *in, uint32_t parse_opts, uint32_t val_opts, uint32_t int_opts, struct ly_set *parsed, ly_bool *subtree_sibling, struct lyd_ctx **lydctx_p) { - LY_ERR rc = LY_SUCCESS; + LY_ERR r, rc = LY_SUCCESS; struct lyd_xml_ctx *lydctx; - ly_bool parsed_data_nodes = 0; + ly_bool parsed_data_nodes = 0, close_elem = 0; + struct lyd_node *act = NULL; enum LYXML_PARSER_STATUS status; assert(ctx && in && lydctx_p); @@ -1009,9 +1245,19 @@ lyd_parse_xml(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, str /* find the operation node if it exists already */ LY_CHECK_GOTO(rc = lyd_parser_find_operation(parent, int_opts, &lydctx->op_node), cleanup); + if ((int_opts & LYD_INTOPT_RPC) && (int_opts & LYD_INTOPT_ACTION)) { + /* can be either, try to parse "action" */ + if (!lydxml_envelope(lydctx->xmlctx, "action", "urn:ietf:params:xml:ns:yang:1", 0, &act)) { + close_elem = 1; + int_opts &= ~LYD_INTOPT_RPC; + } + } + /* parse XML data */ while (lydctx->xmlctx->status == LYXML_ELEMENT) { - LY_CHECK_GOTO(rc = lydxml_subtree_r(lydctx, parent, first_p, parsed), cleanup); + r = lydxml_subtree_r(lydctx, parent, first_p, parsed); + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); + parsed_data_nodes = 1; if (!(int_opts & LYD_INTOPT_WITH_SIBLINGS)) { @@ -1019,16 +1265,29 @@ lyd_parse_xml(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, str } } + /* close an opened element */ + if (close_elem) { + if (lydctx->xmlctx->status != LYXML_ELEM_CLOSE) { + assert(lydctx->xmlctx->status == LYXML_ELEMENT); + LOGVAL(lydctx->xmlctx->ctx, LYVE_SYNTAX, "Unexpected child element \"%.*s\".", + (int)lydctx->xmlctx->name_len, lydctx->xmlctx->name); + rc = LY_EVALID; + goto cleanup; + } + + LY_CHECK_GOTO(rc = lyxml_ctx_next(lydctx->xmlctx), cleanup); + } + /* check final state */ if ((int_opts & LYD_INTOPT_NO_SIBLINGS) && (lydctx->xmlctx->status == LYXML_ELEMENT)) { LOGVAL(ctx, LYVE_SYNTAX, "Unexpected sibling node."); - rc = LY_EVALID; - goto cleanup; + r = LY_EVALID; + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); } if ((int_opts & (LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_NOTIF | LYD_INTOPT_REPLY)) && !lydctx->op_node) { LOGVAL(ctx, LYVE_DATA, "Missing the operation node."); - rc = LY_EVALID; - goto cleanup; + r = LY_EVALID; + LY_DPARSER_ERR_GOTO(r, rc = r, lydctx, cleanup); } if (!parsed_data_nodes) { @@ -1051,7 +1310,8 @@ cleanup: assert(!(parse_opts & LYD_PARSE_ONLY) || (!lydctx->node_types.count && !lydctx->meta_types.count && !lydctx->node_when.count)); - if (rc) { + lyd_free_tree(act); + if (rc && (!(lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR) || (rc != LY_EVALID))) { lyd_xml_ctx_free((struct lyd_ctx *)lydctx); } else { *lydctx_p = (struct lyd_ctx *)lydctx; @@ -1081,12 +1341,6 @@ lydxml_env_netconf_rpc(struct lyxml_ctx *xmlctx, struct lyd_node **envp, uint32_ assert(envp && !*envp); - if (xmlctx->status != LYXML_ELEMENT) { - /* nothing to parse */ - assert(xmlctx->status == LYXML_END); - goto cleanup; - } - /* parse "rpc" */ r = lydxml_envelope(xmlctx, "rpc", "urn:ietf:params:xml:ns:netconf:base:1.0", 0, envp); LY_CHECK_ERR_GOTO(r, rc = r, cleanup); @@ -1118,123 +1372,6 @@ cleanup: } /** - * @brief Validate eventTime date-and-time value. - * - * @param[in] node Opaque eventTime node. - * @return LY_SUCCESS on success. - * @return LY_ERR value on error. - */ -static LY_ERR -lydxml_env_netconf_eventtime_validate(const struct lyd_node *node) -{ - LY_ERR rc = LY_SUCCESS; - struct ly_ctx *ctx = (struct ly_ctx *)LYD_CTX(node); - struct lysc_ctx cctx; - const struct lys_module *mod; - LY_ARRAY_COUNT_TYPE u; - struct ly_err_item *err = NULL; - struct lysp_type *type_p = NULL; - struct lysc_pattern **patterns = NULL; - const char *value; - - LYSC_CTX_INIT_CTX(cctx, ctx); - - /* get date-and-time parsed type */ - mod = ly_ctx_get_module_latest(ctx, "ietf-yang-types"); - assert(mod); - LY_ARRAY_FOR(mod->parsed->typedefs, u) { - if (!strcmp(mod->parsed->typedefs[u].name, "date-and-time")) { - type_p = &mod->parsed->typedefs[u].type; - break; - } - } - assert(type_p); - - /* compile patterns */ - assert(type_p->patterns); - LY_CHECK_GOTO(rc = lys_compile_type_patterns(&cctx, type_p->patterns, NULL, &patterns), cleanup); - - /* validate */ - value = lyd_get_value(node); - rc = lyplg_type_validate_patterns(patterns, value, strlen(value), &err); - -cleanup: - FREE_ARRAY(&cctx.free_ctx, patterns, lysc_pattern_free); - if (rc && err) { - LOGVAL_ERRITEM(ctx, err); - ly_err_free(err); - LOGVAL(ctx, LYVE_DATA, "Invalid \"eventTime\" in the notification."); - } - return rc; -} - -/** - * @brief Parse all expected non-data XML elements of a NETCONF notification message. - * - * @param[in] xmlctx XML parser context. - * @param[out] evnp Parsed envelope(s) (opaque node). - * @param[out] int_opts Internal options for parsing the rest of YANG data. - * @param[out] close_elem Number of parsed opened elements that need to be closed. - * @return LY_SUCCESS on success. - * @return LY_ERR value on error. - */ -static LY_ERR -lydxml_env_netconf_notif(struct lyxml_ctx *xmlctx, struct lyd_node **envp, uint32_t *int_opts, uint32_t *close_elem) -{ - LY_ERR rc = LY_SUCCESS, r; - struct lyd_node *child; - - assert(envp && !*envp); - - if (xmlctx->status != LYXML_ELEMENT) { - /* nothing to parse */ - assert(xmlctx->status == LYXML_END); - goto cleanup; - } - - /* parse "notification" */ - r = lydxml_envelope(xmlctx, "notification", "urn:ietf:params:xml:ns:netconf:notification:1.0", 0, envp); - LY_CHECK_ERR_GOTO(r, rc = r, cleanup); - - /* parse "eventTime" */ - r = lydxml_envelope(xmlctx, "eventTime", "urn:ietf:params:xml:ns:netconf:notification:1.0", 1, &child); - if (r == LY_ENOT) { - LOGVAL(xmlctx->ctx, LYVE_REFERENCE, "Unexpected element \"%.*s\" instead of \"eventTime\".", - (int)xmlctx->name_len, xmlctx->name); - r = LY_EVALID; - } - LY_CHECK_ERR_GOTO(r, rc = r, cleanup); - - /* insert */ - lyd_insert_node(*envp, NULL, child, 0); - - /* validate value */ - r = lydxml_env_netconf_eventtime_validate(child); - LY_CHECK_ERR_GOTO(r, rc = r, cleanup); - - /* finish child parsing */ - if (xmlctx->status != LYXML_ELEM_CLOSE) { - assert(xmlctx->status == LYXML_ELEMENT); - LOGVAL(xmlctx->ctx, LYVE_SYNTAX, "Unexpected child element \"%.*s\" of \"eventTime\".", - (int)xmlctx->name_len, xmlctx->name); - rc = LY_EVALID; - goto cleanup; - } - LY_CHECK_GOTO(rc = lyxml_ctx_next(xmlctx), cleanup); - - /* NETCONF notification */ - *int_opts = LYD_INTOPT_NO_SIBLINGS | LYD_INTOPT_NOTIF; - *close_elem = 1; - -cleanup: - if (rc) { - lyd_free_tree(*envp); - *envp = NULL; - } - return rc; -} - -/** * @brief Parse an XML element as an opaque node subtree. * * @param[in] xmlctx XML parser context. @@ -1247,9 +1384,11 @@ lydxml_opaq_r(struct lyxml_ctx *xmlctx, struct lyd_node *parent) LY_ERR rc = LY_SUCCESS; const struct lyxml_ns *ns; struct lyd_attr *attr = NULL; - struct lyd_node *child = NULL; - const char *name, *prefix; - size_t name_len, prefix_len; + struct lyd_node *node = NULL; + struct lyd_node_opaq *opaq; + const char *name, *prefix, *value = NULL; + size_t name_len, prefix_len, value_len; + ly_bool ws_only, dynamic = 0; assert(xmlctx->status == LYXML_ELEMENT); @@ -1270,14 +1409,23 @@ lydxml_opaq_r(struct lyxml_ctx *xmlctx, struct lyd_node *parent) LY_CHECK_RET(lydxml_attrs(xmlctx, &attr)); } - /* create node */ + /* remember the value */ assert(xmlctx->status == LYXML_ELEM_CONTENT); - rc = lyd_create_opaq(xmlctx->ctx, name, name_len, prefix, prefix_len, ns->uri, strlen(ns->uri), xmlctx->value, - xmlctx->ws_only ? 0 : xmlctx->value_len, NULL, LY_VALUE_XML, NULL, 0, &child); + value = xmlctx->value; + value_len = xmlctx->value_len; + ws_only = xmlctx->ws_only; + dynamic = xmlctx->dynamic; + if (dynamic) { + xmlctx->dynamic = 0; + } + + /* create the node without value */ + rc = lyd_create_opaq(xmlctx->ctx, name, name_len, prefix, prefix_len, ns->uri, strlen(ns->uri), NULL, 0, NULL, + LY_VALUE_XML, NULL, 0, &node); LY_CHECK_GOTO(rc, cleanup); /* assign atributes */ - ((struct lyd_node_opaq *)child)->attr = attr; + ((struct lyd_node_opaq *)node)->attr = attr; attr = NULL; /* parser next element */ @@ -1285,17 +1433,38 @@ lydxml_opaq_r(struct lyxml_ctx *xmlctx, struct lyd_node *parent) /* parse all the descendants */ while (xmlctx->status == LYXML_ELEMENT) { - rc = lydxml_opaq_r(xmlctx, child); + rc = lydxml_opaq_r(xmlctx, node); LY_CHECK_GOTO(rc, cleanup); } /* insert */ - lyd_insert_node(parent, NULL, child, 1); + lyd_insert_node(parent, NULL, node, 1); + + /* update the value */ + opaq = (struct lyd_node_opaq *)node; + if (opaq->child) { + if (!ws_only) { + LOGVAL(xmlctx->ctx, LYVE_SYNTAX_XML, "Mixed XML content node \"%s\" found, not supported.", LYD_NAME(node)); + rc = LY_EVALID; + goto cleanup; + } + } else if (value_len) { + lydict_remove(xmlctx->ctx, opaq->value); + if (dynamic) { + LY_CHECK_GOTO(rc = lydict_insert_zc(xmlctx->ctx, (char *)value, &opaq->value), cleanup); + dynamic = 0; + } else { + LY_CHECK_GOTO(rc = lydict_insert(xmlctx->ctx, value, value_len, &opaq->value), cleanup); + } + } cleanup: lyd_free_attr_siblings(xmlctx->ctx, attr); + if (dynamic) { + free((char *)value); + } if (rc) { - lyd_free_tree(child); + lyd_free_tree(node); } return rc; } @@ -1607,12 +1776,6 @@ lydxml_env_netconf_reply(struct lyxml_ctx *xmlctx, struct lyd_node **envp, uint3 assert(envp && !*envp); - if (xmlctx->status != LYXML_ELEMENT) { - /* nothing to parse */ - assert(xmlctx->status == LYXML_END); - goto cleanup; - } - /* parse "rpc-reply" */ r = lydxml_envelope(xmlctx, "rpc-reply", "urn:ietf:params:xml:ns:netconf:base:1.0", 0, envp); LY_CHECK_ERR_GOTO(r, rc = r, cleanup); @@ -1702,15 +1865,13 @@ lyd_parse_xml_netconf(const struct ly_ctx *ctx, const struct lysc_ext_instance * { LY_ERR rc = LY_SUCCESS; struct lyd_xml_ctx *lydctx; + struct lyd_node *node; uint32_t i, int_opts = 0, close_elem = 0; ly_bool parsed_data_nodes = 0; assert(ctx && in && lydctx_p); assert(!(parse_opts & ~LYD_PARSE_OPTS_MASK)); assert(!(val_opts & ~LYD_VALIDATE_OPTS_MASK)); - - assert((data_type == LYD_TYPE_RPC_NETCONF) || (data_type == LYD_TYPE_NOTIF_NETCONF) || - (data_type == LYD_TYPE_REPLY_NETCONF)); assert(!(parse_opts & LYD_PARSE_SUBTREE)); /* init context */ @@ -1733,11 +1894,17 @@ lyd_parse_xml_netconf(const struct ly_ctx *ctx, const struct lysc_ext_instance * break; case LYD_TYPE_NOTIF_NETCONF: assert(!parent); - rc = lydxml_env_netconf_notif(lydctx->xmlctx, envp, &int_opts, &close_elem); + + /* parse "notification" */ + rc = lydxml_envelope(lydctx->xmlctx, "notification", "urn:ietf:params:xml:ns:netconf:notification:1.0", 0, envp); if (rc == LY_ENOT) { LOGVAL(ctx, LYVE_DATA, "Missing NETCONF <notification> envelope or in incorrect namespace."); } LY_CHECK_GOTO(rc, cleanup); + + /* NETCONF notification */ + int_opts = LYD_INTOPT_WITH_SIBLINGS | LYD_INTOPT_NOTIF | LYD_INTOPT_EVENTTIME; + close_elem = 1; break; case LYD_TYPE_REPLY_NETCONF: assert(parent); @@ -1747,6 +1914,32 @@ lyd_parse_xml_netconf(const struct ly_ctx *ctx, const struct lysc_ext_instance * } LY_CHECK_GOTO(rc, cleanup); break; + case LYD_TYPE_RPC_RESTCONF: + assert(parent); + + /* parse "input" */ + rc = lydxml_envelope(lydctx->xmlctx, "input", lyd_node_module(parent)->ns, 0, envp); + if (rc == LY_ENOT) { + LOGVAL(ctx, LYVE_DATA, "Missing RESTCONF \"input\" object or in incorrect namespace."); + } + LY_CHECK_GOTO(rc, cleanup); + + int_opts = LYD_INTOPT_WITH_SIBLINGS | LYD_INTOPT_RPC | LYD_INTOPT_ACTION; + close_elem = 1; + break; + case LYD_TYPE_REPLY_RESTCONF: + assert(parent); + + /* parse "output" */ + rc = lydxml_envelope(lydctx->xmlctx, "output", lyd_node_module(parent)->ns, 0, envp); + if (rc == LY_ENOT) { + LOGVAL(ctx, LYVE_DATA, "Missing RESTCONF \"output\" object or in incorrect namespace."); + } + LY_CHECK_GOTO(rc, cleanup); + + int_opts = LYD_INTOPT_WITH_SIBLINGS | LYD_INTOPT_REPLY; + close_elem = 1; + break; default: LOGINT(ctx); rc = LY_EINT; @@ -1792,6 +1985,21 @@ lyd_parse_xml_netconf(const struct ly_ctx *ctx, const struct lysc_ext_instance * rc = LY_EVALID; goto cleanup; } + if (int_opts & LYD_INTOPT_EVENTTIME) { + /* parse as a child of the envelope */ + node = (*first_p)->prev; + if (node->schema) { + LOGVAL(ctx, LYVE_DATA, "Missing notification \"eventTime\" node."); + rc = LY_EVALID; + goto cleanup; + } else { + /* can be the only opaque node and an operation had to be parsed */ + assert(!strcmp(LYD_NAME(node), "eventTime") && (*first_p)->next); + lyd_unlink(node); + assert(*envp); + lyd_insert_child(*envp, node); + } + } if (!parsed_data_nodes) { /* no data nodes were parsed */ diff --git a/src/parser_yang.c b/src/parser_yang.c index dd84480..e18ed17 100644 --- a/src/parser_yang.c +++ b/src/parser_yang.c @@ -416,10 +416,14 @@ read_qstring(struct lysp_yang_ctx *ctx, enum yang_arg arg, char **word_p, char * } break; case '\r': - if (ctx->in->current[1] != '\n') { + /* newline may be escaped */ + if ((ctx->in->current[1] != '\n') && strncmp(&ctx->in->current[1], "\\n", 2)) { LOGVAL_PARSER(ctx, LY_VCODE_INCHAR, ctx->in->current[0]); return LY_EVALID; } + + /* skip this character, do not store it */ + ++ctx->in->current; /* fallthrough */ case '\n': if (block_indent) { @@ -804,7 +808,8 @@ keyword_start: MOVE_INPUT(ctx, 1); goto extension; case '{': - /* allowed only for input and output statements which can be without arguments */ + case ';': + /* allowed only for input and output statements which are without arguments */ if ((*kw == LY_STMT_INPUT) || (*kw == LY_STMT_OUTPUT)) { break; } @@ -932,7 +937,8 @@ parse_ext(struct lysp_yang_ctx *ctx, const char *ext_name, size_t ext_name_len, LY_ARRAY_NEW_RET(PARSER_CTX(ctx), *exts, e, LY_EMEM); if (!ly_strnchr(ext_name, ':', ext_name_len)) { - LOGVAL_PARSER(ctx, LYVE_SYNTAX, "Extension instance \"%*.s\" without the mandatory prefix.", ext_name_len, ext_name); + LOGVAL_PARSER(ctx, LYVE_SYNTAX, "Extension instance \"%.*s\" without the mandatory prefix.", + (int)ext_name_len, ext_name); return LY_EVALID; } @@ -1039,7 +1045,7 @@ parse_yangversion(struct lysp_yang_ctx *ctx, struct lysp_module *mod) } else if ((word_len == ly_strlen_const("1.1")) && !strncmp(word, "1.1", word_len)) { mod->version = LYS_VERSION_1_1; } else { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "yang-version"); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)word_len, word, "yang-version"); free(buf); return LY_EVALID; } @@ -1450,7 +1456,7 @@ parse_config(struct lysp_yang_ctx *ctx, uint16_t *flags, struct lysp_ext_instanc } else if ((word_len == ly_strlen_const("false")) && !strncmp(word, "false", word_len)) { *flags |= LYS_CONFIG_R; } else { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "config"); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)word_len, word, "config"); free(buf); return LY_EVALID; } @@ -1501,7 +1507,7 @@ parse_mandatory(struct lysp_yang_ctx *ctx, uint16_t *flags, struct lysp_ext_inst } else if ((word_len == ly_strlen_const("false")) && !strncmp(word, "false", word_len)) { *flags |= LYS_MAND_FALSE; } else { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "mandatory"); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)word_len, word, "mandatory"); free(buf); return LY_EVALID; } @@ -1622,7 +1628,7 @@ parse_status(struct lysp_yang_ctx *ctx, uint16_t *flags, struct lysp_ext_instanc } else if ((word_len == ly_strlen_const("obsolete")) && !strncmp(word, "obsolete", word_len)) { *flags |= LYS_STATUS_OBSLT; } else { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "status"); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)word_len, word, "status"); free(buf); return LY_EVALID; } @@ -1803,7 +1809,7 @@ parse_type_enum_value_pos(struct lysp_yang_ctx *ctx, enum ly_stmt val_kw, struct LY_CHECK_RET(get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len)); if (!word_len || (word[0] == '+') || ((word[0] == '0') && (word_len > 1)) || ((val_kw == LY_STMT_POSITION) && !strncmp(word, "-0", 2))) { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, lyplg_ext_stmt2str(val_kw)); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)word_len, word, lyplg_ext_stmt2str(val_kw)); ret = LY_EVALID; goto cleanup; } @@ -1812,26 +1818,26 @@ parse_type_enum_value_pos(struct lysp_yang_ctx *ctx, enum ly_stmt val_kw, struct if (val_kw == LY_STMT_VALUE) { num = strtoll(word, &ptr, LY_BASE_DEC); if ((num < INT64_C(-2147483648)) || (num > INT64_C(2147483647))) { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, lyplg_ext_stmt2str(val_kw)); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)word_len, word, lyplg_ext_stmt2str(val_kw)); ret = LY_EVALID; goto cleanup; } } else { unum = strtoull(word, &ptr, LY_BASE_DEC); if (unum > UINT64_C(4294967295)) { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, lyplg_ext_stmt2str(val_kw)); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)word_len, word, lyplg_ext_stmt2str(val_kw)); ret = LY_EVALID; goto cleanup; } } /* we have not parsed the whole argument */ if ((size_t)(ptr - word) != word_len) { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, lyplg_ext_stmt2str(val_kw)); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)word_len, word, lyplg_ext_stmt2str(val_kw)); ret = LY_EVALID; goto cleanup; } if (errno == ERANGE) { - LOGVAL_PARSER(ctx, LY_VCODE_OOB, word_len, word, lyplg_ext_stmt2str(val_kw)); + LOGVAL_PARSER(ctx, LY_VCODE_OOB, (int)word_len, word, lyplg_ext_stmt2str(val_kw)); ret = LY_EVALID; goto cleanup; } @@ -1953,7 +1959,7 @@ parse_type_fracdigits(struct lysp_yang_ctx *ctx, struct lysp_type *type) LY_CHECK_GOTO(ret = get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len), cleanup); if (!word_len || (word[0] == '0') || !isdigit(word[0])) { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "fraction-digits"); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)word_len, word, "fraction-digits"); ret = LY_EVALID; goto cleanup; } @@ -1962,12 +1968,12 @@ parse_type_fracdigits(struct lysp_yang_ctx *ctx, struct lysp_type *type) num = strtoull(word, &ptr, LY_BASE_DEC); /* we have not parsed the whole argument */ if ((size_t)(ptr - word) != word_len) { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "fraction-digits"); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)word_len, word, "fraction-digits"); ret = LY_EVALID; goto cleanup; } if ((errno == ERANGE) || (num > LY_TYPE_DEC64_FD_MAX)) { - LOGVAL_PARSER(ctx, LY_VCODE_OOB, word_len, word, "fraction-digits"); + LOGVAL_PARSER(ctx, LY_VCODE_OOB, (int)word_len, word, "fraction-digits"); ret = LY_EVALID; goto cleanup; } @@ -2018,7 +2024,7 @@ parse_type_reqinstance(struct lysp_yang_ctx *ctx, struct lysp_type *type) if ((word_len == ly_strlen_const("true")) && !strncmp(word, "true", word_len)) { type->require_instance = 1; } else if ((word_len != ly_strlen_const("false")) || strncmp(word, "false", word_len)) { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "require-instance"); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)word_len, word, "require-instance"); ret = LY_EVALID; goto cleanup; } @@ -2065,7 +2071,7 @@ parse_type_pattern_modifier(struct lysp_yang_ctx *ctx, struct lysp_restr *restr) LY_CHECK_GOTO(ret = get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len), cleanup); if ((word_len != ly_strlen_const("invert-match")) || strncmp(word, "invert-match", word_len)) { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "modifier"); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)word_len, word, "modifier"); ret = LY_EVALID; goto cleanup; } @@ -2395,7 +2401,7 @@ parse_maxelements(struct lysp_yang_ctx *ctx, uint32_t *max, uint16_t *flags, str LY_CHECK_GOTO(ret = get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len), cleanup); if (!word_len || (word[0] == '0') || ((word[0] != 'u') && !isdigit(word[0]))) { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "max-elements"); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)word_len, word, "max-elements"); ret = LY_EVALID; goto cleanup; } @@ -2405,12 +2411,12 @@ parse_maxelements(struct lysp_yang_ctx *ctx, uint32_t *max, uint16_t *flags, str num = strtoull(word, &ptr, LY_BASE_DEC); /* we have not parsed the whole argument */ if ((size_t)(ptr - word) != word_len) { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "max-elements"); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)word_len, word, "max-elements"); ret = LY_EVALID; goto cleanup; } if ((errno == ERANGE) || (num > UINT32_MAX)) { - LOGVAL_PARSER(ctx, LY_VCODE_OOB, word_len, word, "max-elements"); + LOGVAL_PARSER(ctx, LY_VCODE_OOB, (int)word_len, word, "max-elements"); ret = LY_EVALID; goto cleanup; } @@ -2467,7 +2473,7 @@ parse_minelements(struct lysp_yang_ctx *ctx, uint32_t *min, uint16_t *flags, str LY_CHECK_GOTO(ret = get_argument(ctx, Y_STR_ARG, NULL, &word, &buf, &word_len), cleanup); if (!word_len || !isdigit(word[0]) || ((word[0] == '0') && (word_len > 1))) { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "min-elements"); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)word_len, word, "min-elements"); ret = LY_EVALID; goto cleanup; } @@ -2476,12 +2482,12 @@ parse_minelements(struct lysp_yang_ctx *ctx, uint32_t *min, uint16_t *flags, str num = strtoull(word, &ptr, LY_BASE_DEC); /* we have not parsed the whole argument */ if ((size_t)(ptr - word) != word_len) { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "min-elements"); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)word_len, word, "min-elements"); ret = LY_EVALID; goto cleanup; } if ((errno == ERANGE) || (num > UINT32_MAX)) { - LOGVAL_PARSER(ctx, LY_VCODE_OOB, word_len, word, "min-elements"); + LOGVAL_PARSER(ctx, LY_VCODE_OOB, (int)word_len, word, "min-elements"); ret = LY_EVALID; goto cleanup; } @@ -2533,7 +2539,7 @@ parse_orderedby(struct lysp_yang_ctx *ctx, struct lysp_node *llist) } else if ((word_len == ly_strlen_const("user")) && !strncmp(word, "user", word_len)) { llist->flags |= LYS_ORDBY_USER; } else { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "ordered-by"); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)word_len, word, "ordered-by"); ret = LY_EVALID; goto cleanup; } @@ -2860,11 +2866,6 @@ parse_inout(struct lysp_yang_ctx *ctx, enum ly_stmt inout_kw, struct lysp_node * YANG_READ_SUBSTMT_NEXT_ITER(ctx, kw, word, word_len, inout_p->exts, ret, cleanup); } - if (!inout_p->child) { - LOGVAL_PARSER(ctx, LY_VCODE_MISSTMT, "data-def-stmt", lyplg_ext_stmt2str(inout_kw)); - return LY_EVALID; - } - cleanup: return ret; } @@ -3715,7 +3716,7 @@ parse_yinelement(struct lysp_yang_ctx *ctx, struct lysp_ext *ext) } else if ((word_len == ly_strlen_const("false")) && !strncmp(word, "false", word_len)) { ext->flags |= LYS_YINELEM_FALSE; } else { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "yin-element"); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)word_len, word, "yin-element"); free(buf); return LY_EVALID; } @@ -3868,7 +3869,7 @@ parse_deviate(struct lysp_yang_ctx *ctx, struct lysp_deviate **deviates) } else if ((word_len == ly_strlen_const("delete")) && !strncmp(word, "delete", word_len)) { dev_mod = LYS_DEV_DELETE; } else { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, word_len, word, "deviate"); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)word_len, word, "deviate"); ret = LY_EVALID; goto cleanup; } diff --git a/src/parser_yin.c b/src/parser_yin.c index fa44968..36b49f1 100644 --- a/src/parser_yin.c +++ b/src/parser_yin.c @@ -488,7 +488,7 @@ yin_parse_attribute(struct lysp_yin_ctx *ctx, enum yin_argument arg_type, const INSERT_STRING_RET(ctx->xmlctx->ctx, ctx->xmlctx->value, ctx->xmlctx->value_len, ctx->xmlctx->dynamic, *arg_val); LY_CHECK_RET(!(*arg_val), LY_EMEM); } else { - LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_UNEXP_ATTR, ctx->xmlctx->name_len, + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_UNEXP_ATTR, (int)ctx->xmlctx->name_len, ctx->xmlctx->name, lyplg_ext_stmt2str(current_element)); return LY_EVALID; } @@ -3185,7 +3185,7 @@ yin_parse_extension_instance_arg(struct lysp_yin_ctx *ctx, enum ly_stmt parent_s ctx->xmlctx->prefix_len, parent_stmt); if (((parent_stmt == LY_STMT_ERROR_MESSAGE) && (child != LY_STMT_ARG_VALUE)) || ((parent_stmt != LY_STMT_ERROR_MESSAGE) && (child != LY_STMT_ARG_TEXT))) { - LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_UNEXP_SUBELEM, ctx->xmlctx->name_len, ctx->xmlctx->name, + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_UNEXP_SUBELEM, (int)ctx->xmlctx->name_len, ctx->xmlctx->name, lyplg_ext_stmt2str(parent_stmt)); return LY_EVALID; } @@ -3256,7 +3256,7 @@ yin_parse_element_generic(struct lysp_yin_ctx *ctx, enum ly_stmt parent_stmt, st last = (*element)->child; if ((*element)->kw == LY_STMT_NONE) { /* unrecognized element */ - LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_UNEXP_SUBELEM, ctx->xmlctx->name_len, ctx->xmlctx->name, + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_UNEXP_SUBELEM, (int)ctx->xmlctx->name_len, ctx->xmlctx->name, lyplg_ext_stmt2str(parent_stmt)); ret = LY_EVALID; goto cleanup; @@ -3422,7 +3422,7 @@ yin_parse_extension_instance(struct lysp_yin_ctx *ctx, const void *parent, enum } } else if (ctx->xmlctx->value_len) { /* invalid text content */ - LOGVAL_PARSER(ctx, LYVE_SYNTAX, "Extension instance \"%s\" with unexpected text content \".*s\".", ext_name, + LOGVAL_PARSER(ctx, LYVE_SYNTAX, "Extension instance \"%s\" with unexpected text content \"%.*s\".", ext_name, (int)ctx->xmlctx->value_len, ctx->xmlctx->value); return LY_EVALID; } @@ -3479,7 +3479,7 @@ yin_parse_content(struct lysp_yin_ctx *ctx, struct yin_subelement *subelem_info, if ((parent_stmt == LY_STMT_DEVIATE) && isdevsub(cur_stmt)) { LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_INDEV_YIN, lyplg_ext_stmt2str(cur_stmt)); } else { - LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_UNEXP_SUBELEM, ctx->xmlctx->name_len, + LOGVAL_PARSER((struct lysp_ctx *)ctx, LY_VCODE_UNEXP_SUBELEM, (int)ctx->xmlctx->name_len, ctx->xmlctx->name, lyplg_ext_stmt2str(parent_stmt)); } ret = LY_EVALID; @@ -3718,7 +3718,6 @@ yin_parse_content(struct lysp_yin_ctx *ctx, struct yin_subelement *subelem_info, ret = LY_EINT; } LY_CHECK_GOTO(ret, cleanup); - subelem = NULL; LY_CHECK_GOTO(ret = lyxml_ctx_next(ctx->xmlctx), cleanup); } @@ -3,7 +3,7 @@ * @author Michal Vasko <mvasko@cesnet.cz> * @brief Path functions * - * Copyright (c) 2020 CESNET, z.s.p.o. + * Copyright (c) 2020 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -11,6 +11,9 @@ * * https://opensource.org/licenses/BSD-3-Clause */ + +#define _GNU_SOURCE + #include "path.h" #include <assert.h> @@ -45,7 +48,7 @@ */ static LY_ERR ly_path_check_predicate(const struct ly_ctx *ctx, const struct lysc_node *cur_node, const struct lyxp_expr *exp, - uint32_t *tok_idx, uint8_t prefix, uint8_t pred) + uint32_t *tok_idx, uint16_t prefix, uint16_t pred) { LY_ERR ret = LY_SUCCESS; struct ly_set *set = NULL; @@ -106,8 +109,19 @@ ly_path_check_predicate(const struct ly_ctx *ctx, const struct lysc_node *cur_no /* '=' */ LY_CHECK_GOTO(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_OPER_EQUAL), token_error); - /* Literal or Number */ - LY_CHECK_GOTO(lyxp_next_token2(ctx, exp, tok_idx, LYXP_TOKEN_LITERAL, LYXP_TOKEN_NUMBER), token_error); + /* fill repeat */ + exp->repeat[*tok_idx - 2] = calloc(2, sizeof *exp->repeat[*tok_idx]); + LY_CHECK_ERR_GOTO(!exp->repeat[*tok_idx - 2], LOGMEM(NULL); ret = LY_EMEM, cleanup); + exp->repeat[*tok_idx - 2][0] = LYXP_EXPR_EQUALITY; + + /* Literal, Number, or VariableReference */ + if (lyxp_next_token(NULL, exp, tok_idx, LYXP_TOKEN_LITERAL) && + lyxp_next_token(NULL, exp, tok_idx, LYXP_TOKEN_NUMBER) && + lyxp_next_token(NULL, exp, tok_idx, LYXP_TOKEN_VARREF)) { + /* error */ + lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_LITERAL); + goto token_error; + } /* ']' */ LY_CHECK_GOTO(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_BRACK2), token_error); @@ -121,6 +135,11 @@ ly_path_check_predicate(const struct ly_ctx *ctx, const struct lysc_node *cur_no /* '=' */ LY_CHECK_GOTO(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_OPER_EQUAL), token_error); + /* fill repeat */ + exp->repeat[*tok_idx - 2] = calloc(2, sizeof *exp->repeat[*tok_idx]); + LY_CHECK_ERR_GOTO(!exp->repeat[*tok_idx - 2], LOGMEM(NULL); ret = LY_EMEM, cleanup); + exp->repeat[*tok_idx - 2][0] = LYXP_EXPR_EQUALITY; + /* Literal or Number */ LY_CHECK_GOTO(lyxp_next_token2(ctx, exp, tok_idx, LYXP_TOKEN_LITERAL, LYXP_TOKEN_NUMBER), token_error); @@ -178,6 +197,11 @@ ly_path_check_predicate(const struct ly_ctx *ctx, const struct lysc_node *cur_no /* '=' */ LY_CHECK_GOTO(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_OPER_EQUAL), token_error); + /* fill repeat */ + exp->repeat[*tok_idx - 2] = calloc(2, sizeof *exp->repeat[*tok_idx]); + LY_CHECK_ERR_GOTO(!exp->repeat[*tok_idx - 2], LOGMEM(NULL); ret = LY_EMEM, cleanup); + exp->repeat[*tok_idx - 2][0] = LYXP_EXPR_EQUALITY; + /* FuncName */ LY_CHECK_GOTO(lyxp_check_token(ctx, exp, *tok_idx, LYXP_TOKEN_FUNCNAME), token_error); if ((exp->tok_len[*tok_idx] != ly_strlen_const("current")) || @@ -240,45 +264,121 @@ token_error: return LY_EVALID; } +/** + * @brief Parse deref XPath function and perform all additional checks. + * + * @param[in] ctx libyang context. + * @param[in] ctx_node Optional context node, used for logging. + * @param[in] exp Parsed path. + * @param[in,out] tok_idx Index in @p exp, is adjusted. + * @return LY_ERR value. + */ +static LY_ERR +ly_path_parse_deref(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const struct lyxp_expr *exp, + uint32_t *tok_idx) +{ + size_t arg_len; + uint32_t begin_token, end_token; + struct lyxp_expr *arg_expr = NULL; + + /* mandatory FunctionName */ + LY_CHECK_RET(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_FUNCNAME), LY_EVALID); + if (!strncmp(&exp->expr[exp->tok_pos[*tok_idx]], "deref", 5)) { + LOGVAL(ctx, LYVE_XPATH, "Unexpected XPath function \"%.*s\" in path, expected \"deref(...)\"", + exp->tok_len[*tok_idx], exp->expr + exp->tok_pos[*tok_idx]); + return LY_EVALID; + } + + /* mandatory '(' */ + LY_CHECK_RET(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_PAR1), LY_EVALID); + begin_token = *tok_idx; + + /* count tokens till ')' */ + while (lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_PAR2) && *tok_idx < exp->used) { + /* emebedded functions are not allowed */ + if (!lyxp_check_token(NULL, exp, *tok_idx, LYXP_TOKEN_FUNCNAME)) { + LOGVAL(ctx, LYVE_XPATH, "Embedded function XPath function inside deref function within the path" + "is not allowed"); + return LY_EVALID; + } + + (*tok_idx)++; + } + + /* mandatory ')' */ + LY_CHECK_RET(lyxp_next_token(ctx, exp, tok_idx, LYXP_TOKEN_PAR2), LY_EVALID); + end_token = *tok_idx - 1; + + /* parse the path of deref argument */ + arg_len = exp->tok_pos[end_token] - exp->tok_pos[begin_token]; + LY_CHECK_RET(ly_path_parse(ctx, ctx_node, &exp->expr[exp->tok_pos[begin_token]], arg_len, 1, + LY_PATH_BEGIN_EITHER, LY_PATH_PREFIX_OPTIONAL, LY_PATH_PRED_LEAFREF, &arg_expr), LY_EVALID); + lyxp_expr_free(ctx, arg_expr); + + return LY_SUCCESS; +} + LY_ERR ly_path_parse(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const char *str_path, size_t path_len, - ly_bool lref, uint8_t begin, uint8_t prefix, uint8_t pred, struct lyxp_expr **expr) + ly_bool lref, uint16_t begin, uint16_t prefix, uint16_t pred, struct lyxp_expr **expr) { LY_ERR ret = LY_SUCCESS; struct lyxp_expr *exp = NULL; uint32_t tok_idx, cur_len; const char *cur_node, *prev_prefix = NULL, *ptr; + ly_bool is_abs; assert((begin == LY_PATH_BEGIN_ABSOLUTE) || (begin == LY_PATH_BEGIN_EITHER)); assert((prefix == LY_PATH_PREFIX_OPTIONAL) || (prefix == LY_PATH_PREFIX_MANDATORY) || - (prefix == LY_PATH_PREFIX_STRICT_INHERIT)); + (prefix == LY_PATH_PREFIX_FIRST) || (prefix == LY_PATH_PREFIX_STRICT_INHERIT)); assert((pred == LY_PATH_PRED_KEYS) || (pred == LY_PATH_PRED_SIMPLE) || (pred == LY_PATH_PRED_LEAFREF)); LOG_LOCSET(ctx_node, NULL, NULL, NULL); - /* parse as a generic XPath expression */ - LY_CHECK_GOTO(ret = lyxp_expr_parse(ctx, str_path, path_len, 1, &exp), error); + /* parse as a generic XPath expression, reparse is performed manually */ + LY_CHECK_GOTO(ret = lyxp_expr_parse(ctx, str_path, path_len, 0, &exp), error); tok_idx = 0; + /* alloc empty repeat (only '=', filled manually) */ + exp->repeat = calloc(exp->size, sizeof *exp->repeat); + LY_CHECK_ERR_GOTO(!exp->repeat, LOGMEM(ctx); ret = LY_EMEM, error); + if (begin == LY_PATH_BEGIN_EITHER) { /* is the path relative? */ if (lyxp_next_token(NULL, exp, &tok_idx, LYXP_TOKEN_OPER_PATH)) { /* relative path check specific to leafref */ if (lref) { + /* optional function 'deref..' */ + if ((ly_ctx_get_options(ctx) & LY_CTX_LEAFREF_EXTENDED) && + !lyxp_check_token(NULL, exp, tok_idx, LYXP_TOKEN_FUNCNAME)) { + LY_CHECK_ERR_GOTO(ly_path_parse_deref(ctx, ctx_node, exp, &tok_idx), ret = LY_EVALID, error); + + /* '/' */ + LY_CHECK_ERR_GOTO(lyxp_next_token(ctx, exp, &tok_idx, LYXP_TOKEN_OPER_PATH), ret = LY_EVALID, + error); + } + /* mandatory '..' */ LY_CHECK_ERR_GOTO(lyxp_next_token(ctx, exp, &tok_idx, LYXP_TOKEN_DDOT), ret = LY_EVALID, error); do { /* '/' */ - LY_CHECK_ERR_GOTO(lyxp_next_token(ctx, exp, &tok_idx, LYXP_TOKEN_OPER_PATH), ret = LY_EVALID, error); + LY_CHECK_ERR_GOTO(lyxp_next_token(ctx, exp, &tok_idx, LYXP_TOKEN_OPER_PATH), ret = LY_EVALID, + error); /* optional '..' */ } while (!lyxp_next_token(NULL, exp, &tok_idx, LYXP_TOKEN_DDOT)); } + + is_abs = 0; + } else { + is_abs = 1; } } else { /* '/' */ LY_CHECK_ERR_GOTO(lyxp_next_token(ctx, exp, &tok_idx, LYXP_TOKEN_OPER_PATH), ret = LY_EVALID, error); + + is_abs = 1; } do { @@ -294,8 +394,8 @@ ly_path_parse(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const ret = LY_EVALID; goto error; } - } else if (prefix == LY_PATH_PREFIX_STRICT_INHERIT) { - if (!prev_prefix) { + } else if ((prefix == LY_PATH_PREFIX_FIRST) || (prefix == LY_PATH_PREFIX_STRICT_INHERIT)) { + if (!prev_prefix && is_abs) { /* the first node must have a prefix */ if (!strnstr(cur_node, ":", cur_len)) { LOGVAL(ctx, LYVE_XPATH, "Prefix missing for \"%.*s\" in path.", cur_len, cur_node); @@ -305,7 +405,7 @@ ly_path_parse(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const /* remember the first prefix */ prev_prefix = cur_node; - } else { + } else if (prev_prefix && (prefix == LY_PATH_PREFIX_STRICT_INHERIT)) { /* the prefix must be different, if any */ ptr = strnstr(cur_node, ":", cur_len); if (ptr) { @@ -349,7 +449,7 @@ error: LY_ERR ly_path_parse_predicate(const struct ly_ctx *ctx, const struct lysc_node *cur_node, const char *str_path, - size_t path_len, uint8_t prefix, uint8_t pred, struct lyxp_expr **expr) + size_t path_len, uint16_t prefix, uint16_t pred, struct lyxp_expr **expr) { LY_ERR ret = LY_SUCCESS; struct lyxp_expr *exp = NULL; @@ -360,10 +460,14 @@ ly_path_parse_predicate(const struct ly_ctx *ctx, const struct lysc_node *cur_no LOG_LOCSET(cur_node, NULL, NULL, NULL); - /* parse as a generic XPath expression */ + /* parse as a generic XPath expression, reparse is performed manually */ LY_CHECK_GOTO(ret = lyxp_expr_parse(ctx, str_path, path_len, 0, &exp), error); tok_idx = 0; + /* alloc empty repeat (only '=', filled manually) */ + exp->repeat = calloc(exp->size, sizeof *exp->repeat); + LY_CHECK_ERR_GOTO(!exp->repeat, LOGMEM(ctx); ret = LY_EMEM, error); + LY_CHECK_GOTO(ret = ly_path_check_predicate(ctx, cur_node, exp, &tok_idx, prefix, pred), error); /* trailing token check */ @@ -521,7 +625,7 @@ error: LY_ERR ly_path_compile_predicate(const struct ly_ctx *ctx, const struct lysc_node *cur_node, const struct lys_module *cur_mod, const struct lysc_node *ctx_node, const struct lyxp_expr *expr, uint32_t *tok_idx, LY_VALUE_FORMAT format, - void *prefix_data, struct ly_path_predicate **predicates, enum ly_path_pred_type *pred_type) + void *prefix_data, struct ly_path_predicate **predicates) { LY_ERR ret = LY_SUCCESS; struct ly_path_predicate *p; @@ -533,7 +637,7 @@ ly_path_compile_predicate(const struct ly_ctx *ctx, const struct lysc_node *cur_ LOG_LOCSET(cur_node, NULL, NULL, NULL); - *pred_type = 0; + *predicates = NULL; if (lyxp_next_token(NULL, expr, tok_idx, LYXP_TOKEN_BRACK1)) { /* '[', no predicate */ @@ -565,11 +669,7 @@ ly_path_compile_predicate(const struct ly_ctx *ctx, const struct lysc_node *cur_ } ++(*tok_idx); - if (!*pred_type) { - /* new predicate */ - *pred_type = LY_PATH_PREDTYPE_LIST; - } - assert(*pred_type == LY_PATH_PREDTYPE_LIST); + /* new predicate */ LY_ARRAY_NEW_GOTO(ctx, *predicates, p, ret, cleanup); p->key = key; @@ -577,27 +677,38 @@ ly_path_compile_predicate(const struct ly_ctx *ctx, const struct lysc_node *cur_ assert(expr->tokens[*tok_idx] == LYXP_TOKEN_OPER_EQUAL); ++(*tok_idx); - /* Literal or Number */ - assert((expr->tokens[*tok_idx] == LYXP_TOKEN_LITERAL) || (expr->tokens[*tok_idx] == LYXP_TOKEN_NUMBER)); - if (expr->tokens[*tok_idx] == LYXP_TOKEN_LITERAL) { - /* skip quotes */ - val = expr->expr + expr->tok_pos[*tok_idx] + 1; - val_len = expr->tok_len[*tok_idx] - 2; + /* Literal, Number, or VariableReference */ + if (expr->tokens[*tok_idx] == LYXP_TOKEN_VARREF) { + /* store the variable name */ + p->variable = strndup(expr->expr + expr->tok_pos[*tok_idx], expr->tok_len[*tok_idx]); + LY_CHECK_ERR_GOTO(!p->variable, LOGMEM(ctx); ret = LY_EMEM, cleanup); + + p->type = LY_PATH_PREDTYPE_LIST_VAR; + ++(*tok_idx); } else { - val = expr->expr + expr->tok_pos[*tok_idx]; - val_len = expr->tok_len[*tok_idx]; - } + if (expr->tokens[*tok_idx] == LYXP_TOKEN_LITERAL) { + /* skip quotes */ + val = expr->expr + expr->tok_pos[*tok_idx] + 1; + val_len = expr->tok_len[*tok_idx] - 2; + } else { + assert(expr->tokens[*tok_idx] == LYXP_TOKEN_NUMBER); + val = expr->expr + expr->tok_pos[*tok_idx]; + val_len = expr->tok_len[*tok_idx]; + } - /* store the value */ - LOG_LOCSET(key, NULL, NULL, NULL); - ret = lyd_value_store(ctx, &p->value, ((struct lysc_node_leaf *)key)->type, val, val_len, NULL, format, - prefix_data, LYD_HINT_DATA, key, NULL); - LOG_LOCBACK(key ? 1 : 0, 0, 0, 0); - LY_CHECK_ERR_GOTO(ret, p->value.realtype = NULL, cleanup); - ++(*tok_idx); + /* store the value */ + LOG_LOCSET(key, NULL, NULL, NULL); + ret = lyd_value_store(ctx, &p->value, ((struct lysc_node_leaf *)key)->type, val, val_len, 0, NULL, + format, prefix_data, LYD_HINT_DATA, key, NULL); + LOG_LOCBACK(key ? 1 : 0, 0, 0, 0); + LY_CHECK_ERR_GOTO(ret, p->value.realtype = NULL, cleanup); + + /* "allocate" the type to avoid problems when freeing the value after the type was freed */ + LY_ATOMIC_INC_BARRIER(((struct lysc_type *)p->value.realtype)->refcount); - /* "allocate" the type to avoid problems when freeing the value after the type was freed */ - LY_ATOMIC_INC_BARRIER(((struct lysc_type *)p->value.realtype)->refcount); + p->type = LY_PATH_PREDTYPE_LIST; + ++(*tok_idx); + } /* ']' */ assert(expr->tokens[*tok_idx] == LYXP_TOKEN_BRACK2); @@ -615,7 +726,7 @@ ly_path_compile_predicate(const struct ly_ctx *ctx, const struct lysc_node *cur_ /* names (keys) are unique - it was checked when parsing */ LOGVAL(ctx, LYVE_XPATH, "Predicate missing for a key of %s \"%s\" in path.", lys_nodetype2str(ctx_node->nodetype), ctx_node->name); - ly_path_predicates_free(ctx, LY_PATH_PREDTYPE_LIST, *predicates); + ly_path_predicates_free(ctx, *predicates); *predicates = NULL; ret = LY_EVALID; goto cleanup; @@ -631,8 +742,8 @@ ly_path_compile_predicate(const struct ly_ctx *ctx, const struct lysc_node *cur_ ++(*tok_idx); /* new predicate */ - *pred_type = LY_PATH_PREDTYPE_LEAFLIST; LY_ARRAY_NEW_GOTO(ctx, *predicates, p, ret, cleanup); + p->type = LY_PATH_PREDTYPE_LEAFLIST; /* '=' */ assert(expr->tokens[*tok_idx] == LYXP_TOKEN_OPER_EQUAL); @@ -651,8 +762,8 @@ ly_path_compile_predicate(const struct ly_ctx *ctx, const struct lysc_node *cur_ /* store the value */ LOG_LOCSET(ctx_node, NULL, NULL, NULL); - ret = lyd_value_store(ctx, &p->value, ((struct lysc_node_leaflist *)ctx_node)->type, val, val_len, NULL, format, - prefix_data, LYD_HINT_DATA, ctx_node, NULL); + ret = lyd_value_store(ctx, &p->value, ((struct lysc_node_leaflist *)ctx_node)->type, val, val_len, 0, NULL, + format, prefix_data, LYD_HINT_DATA, ctx_node, NULL); LOG_LOCBACK(ctx_node ? 1 : 0, 0, 0, 0); LY_CHECK_ERR_GOTO(ret, p->value.realtype = NULL, cleanup); ++(*tok_idx); @@ -678,8 +789,8 @@ ly_path_compile_predicate(const struct ly_ctx *ctx, const struct lysc_node *cur_ } /* new predicate */ - *pred_type = LY_PATH_PREDTYPE_POSITION; LY_ARRAY_NEW_GOTO(ctx, *predicates, p, ret, cleanup); + p->type = LY_PATH_PREDTYPE_POSITION; /* syntax was already checked */ p->position = strtoull(expr->expr + expr->tok_pos[*tok_idx], (char **)&val, LY_BASE_DEC); @@ -820,6 +931,197 @@ cleanup: } /** + * @brief Duplicate ly_path_predicate structure. + * + * @param[in] ctx libyang context. + * @param[in] pred The array of path predicates. + * @param[out] dup Duplicated predicates. + * @return LY_ERR value. + */ +static LY_ERR +ly_path_dup_predicates(const struct ly_ctx *ctx, const struct ly_path_predicate *pred, struct ly_path_predicate **dup) +{ + LY_ARRAY_COUNT_TYPE u; + + if (!pred) { + return LY_SUCCESS; + } + + LY_ARRAY_CREATE_RET(ctx, *dup, LY_ARRAY_COUNT(pred), LY_EMEM); + LY_ARRAY_FOR(pred, u) { + LY_ARRAY_INCREMENT(*dup); + (*dup)[u].type = pred->type; + + switch (pred[u].type) { + case LY_PATH_PREDTYPE_POSITION: + /* position-predicate */ + (*dup)[u].position = pred[u].position; + break; + case LY_PATH_PREDTYPE_LIST: + case LY_PATH_PREDTYPE_LEAFLIST: + /* key-predicate or leaf-list-predicate */ + (*dup)[u].key = pred[u].key; + pred[u].value.realtype->plugin->duplicate(ctx, &pred[u].value, &(*dup)[u].value); + LY_ATOMIC_INC_BARRIER(((struct lysc_type *)pred[u].value.realtype)->refcount); + break; + case LY_PATH_PREDTYPE_LIST_VAR: + /* key-predicate with a variable */ + (*dup)[u].key = pred[u].key; + (*dup)[u].variable = strdup(pred[u].variable); + break; + } + } + + return LY_SUCCESS; +} + +/** + * @brief Appends path elements from source to destination array + * + * @param[in] ctx libyang context. + * @param[in] src The source path + * @param[in,out] dst The destination path + * @return LY_ERR value. + */ +static LY_ERR +ly_path_append(const struct ly_ctx *ctx, const struct ly_path *src, struct ly_path **dst) +{ + LY_ERR ret = LY_SUCCESS; + LY_ARRAY_COUNT_TYPE u; + struct ly_path *p; + + if (!src) { + return LY_SUCCESS; + } + + LY_ARRAY_CREATE_RET(ctx, *dst, LY_ARRAY_COUNT(src), LY_EMEM); + LY_ARRAY_FOR(src, u) { + LY_ARRAY_NEW_GOTO(ctx, *dst, p, ret, cleanup); + p->node = src[u].node; + p->ext = src[u].ext; + LY_CHECK_GOTO(ret = ly_path_dup_predicates(ctx, src[u].predicates, &p->predicates), cleanup); + } + +cleanup: + return ret; +} + +/** + * @brief Compile deref XPath function into ly_path structure. + * + * @param[in] ctx libyang context. + * @param[in] ctx_node Optional context node, mandatory of @p lref. + * @param[in] top_ext Extension instance containing the definition of the data being created. It is used to find + * the top-level node inside the extension instance instead of a module. Note that this is the case not only if + * the @p ctx_node is NULL, but also if the relative path starting in @p ctx_node reaches the document root + * via double dots. + * @param[in] expr Parsed path. + * @param[in] oper Oper option (@ref path_oper_options). + * @param[in] target Target option (@ref path_target_options). + * @param[in] format Format of the path. + * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). + * @param[in,out] tok_idx Index in @p exp, is adjusted. + * @param[out] path Compiled path. + * @return LY_ERR value. + */ +static LY_ERR +ly_path_compile_deref(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, + const struct lysc_ext_instance *top_ext, const struct lyxp_expr *expr, uint16_t oper, uint16_t target, + LY_VALUE_FORMAT format, void *prefix_data, uint32_t *tok_idx, struct ly_path **path) +{ + LY_ERR ret = LY_SUCCESS; + struct lyxp_expr expr2; + struct ly_path *path2 = NULL; + const struct lysc_node *node2; + const struct lysc_node_leaf *deref_leaf_node; + const struct lysc_type_leafref *lref; + uint32_t begin_token; + + *path = NULL; + + /* properly parsed path must always starts with 'deref' and '(' */ + assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_FUNCNAME)); + assert(!strncmp(&expr->expr[expr->tok_pos[*tok_idx]], "deref", 5)); + (*tok_idx)++; + assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_PAR1)); + (*tok_idx)++; + begin_token = *tok_idx; + + /* emebedded functions were already identified count tokens till ')' */ + while (lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_PAR2) && (*tok_idx < expr->used)) { + (*tok_idx)++; + } + + /* properly parsed path must have ')' within the tokens */ + assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_PAR2)); + + /* prepare expr representing just deref arg */ + expr2.tokens = &expr->tokens[begin_token]; + expr2.tok_pos = &expr->tok_pos[begin_token]; + expr2.tok_len = &expr->tok_len[begin_token]; + expr2.repeat = &expr->repeat[begin_token]; + expr2.used = *tok_idx - begin_token; + expr2.size = expr->size - begin_token; + expr2.expr = expr->expr; + + /* compile just deref arg, append it to the path and find dereferenced lref for next operations */ + LY_CHECK_GOTO(ret = ly_path_compile_leafref(ctx, ctx_node, top_ext, &expr2, oper, target, format, prefix_data, + &path2), cleanup); + node2 = path2[LY_ARRAY_COUNT(path2) - 1].node; + if ((node2->nodetype != LYS_LEAF) && (node2->nodetype != LYS_LEAFLIST)) { + LOGVAL(ctx, LYVE_XPATH, "The deref function target node \"%s\" is not leaf nor leaflist", node2->name); + ret = LY_EVALID; + goto cleanup; + } + deref_leaf_node = (const struct lysc_node_leaf *)node2; + if (deref_leaf_node->type->basetype != LY_TYPE_LEAFREF) { + LOGVAL(ctx, LYVE_XPATH, "The deref function target node \"%s\" is not leafref", node2->name); + ret = LY_EVALID; + goto cleanup; + } + lref = (const struct lysc_type_leafref *)deref_leaf_node->type; + LY_CHECK_GOTO(ret = ly_path_append(ctx, path2, path), cleanup); + ly_path_free(ctx, path2); + path2 = NULL; + + /* compile dereferenced leafref expression and append it to the path */ + LY_CHECK_GOTO(ret = ly_path_compile_leafref(ctx, node2, top_ext, lref->path, oper, target, format, prefix_data, + &path2), cleanup); + node2 = path2[LY_ARRAY_COUNT(path2) - 1].node; + LY_CHECK_GOTO(ret = ly_path_append(ctx, path2, path), cleanup); + ly_path_free(ctx, path2); + path2 = NULL; + + /* properly parsed path must always continue with ')' and '/' */ + assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_PAR2)); + (*tok_idx)++; + assert(!lyxp_check_token(NULL, expr, *tok_idx, LYXP_TOKEN_OPER_PATH)); + (*tok_idx)++; + + /* prepare expr representing rest of the path after deref */ + expr2.tokens = &expr->tokens[*tok_idx]; + expr2.tok_pos = &expr->tok_pos[*tok_idx]; + expr2.tok_len = &expr->tok_len[*tok_idx]; + expr2.repeat = &expr->repeat[*tok_idx]; + expr2.used = expr->used - *tok_idx; + expr2.size = expr->size - *tok_idx; + expr2.expr = expr->expr; + + /* compile rest of the path and append it to the path */ + LY_CHECK_GOTO(ret = ly_path_compile_leafref(ctx, node2, top_ext, &expr2, oper, target, format, prefix_data, &path2), + cleanup); + LY_CHECK_GOTO(ret = ly_path_append(ctx, path2, path), cleanup); + +cleanup: + ly_path_free(ctx, path2); + if (ret) { + ly_path_free(ctx, *path); + *path = NULL; + } + return ret; +} + +/** * @brief Compile path into ly_path structure. Any predicates of a leafref are only checked, not compiled. * * @param[in] ctx libyang context. @@ -843,7 +1145,7 @@ cleanup: */ static LY_ERR _ly_path_compile(const struct ly_ctx *ctx, const struct lys_module *cur_mod, const struct lysc_node *ctx_node, - const struct lysc_ext_instance *top_ext, const struct lyxp_expr *expr, ly_bool lref, uint8_t oper, uint8_t target, + const struct lysc_ext_instance *top_ext, const struct lyxp_expr *expr, ly_bool lref, uint16_t oper, uint16_t target, ly_bool limit_access_tree, LY_VALUE_FORMAT format, void *prefix_data, struct ly_path **path) { LY_ERR ret = LY_SUCCESS; @@ -876,7 +1178,12 @@ _ly_path_compile(const struct ly_ctx *ctx, const struct lys_module *cur_mod, con getnext_opts = 0; } - if (expr->tokens[tok_idx] == LYXP_TOKEN_OPER_PATH) { + if (lref && (ly_ctx_get_options(ctx) & LY_CTX_LEAFREF_EXTENDED) && + (expr->tokens[tok_idx] == LYXP_TOKEN_FUNCNAME)) { + /* deref function */ + ret = ly_path_compile_deref(ctx, ctx_node, top_ext, expr, oper, target, format, prefix_data, &tok_idx, path); + goto cleanup; + } else if (expr->tokens[tok_idx] == LYXP_TOKEN_OPER_PATH) { /* absolute path */ ctx_node = NULL; @@ -940,7 +1247,7 @@ _ly_path_compile(const struct ly_ctx *ctx, const struct lys_module *cur_mod, con ret = ly_path_compile_predicate_leafref(ctx_node, cur_node, expr, &tok_idx, format, prefix_data); } else { ret = ly_path_compile_predicate(ctx, cur_node, cur_mod, ctx_node, expr, &tok_idx, format, prefix_data, - &p->predicates, &p->pred_type); + &p->predicates); } LY_CHECK_GOTO(ret, cleanup); } while (!lyxp_next_token(NULL, expr, &tok_idx, LYXP_TOKEN_OPER_PATH)); @@ -971,7 +1278,7 @@ cleanup: LY_ERR ly_path_compile(const struct ly_ctx *ctx, const struct lys_module *cur_mod, const struct lysc_node *ctx_node, - const struct lysc_ext_instance *top_ext, const struct lyxp_expr *expr, uint8_t oper, uint8_t target, + const struct lysc_ext_instance *top_ext, const struct lyxp_expr *expr, uint16_t oper, uint16_t target, ly_bool limit_access_tree, LY_VALUE_FORMAT format, void *prefix_data, struct ly_path **path) { return _ly_path_compile(ctx, cur_mod, ctx_node, top_ext, expr, 0, oper, target, limit_access_tree, format, @@ -980,15 +1287,15 @@ ly_path_compile(const struct ly_ctx *ctx, const struct lys_module *cur_mod, cons LY_ERR ly_path_compile_leafref(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const struct lysc_ext_instance *top_ext, - const struct lyxp_expr *expr, uint8_t oper, uint8_t target, LY_VALUE_FORMAT format, void *prefix_data, + const struct lyxp_expr *expr, uint16_t oper, uint16_t target, LY_VALUE_FORMAT format, void *prefix_data, struct ly_path **path) { return _ly_path_compile(ctx, ctx_node->module, ctx_node, top_ext, expr, 1, oper, target, 1, format, prefix_data, path); } LY_ERR -ly_path_eval_partial(const struct ly_path *path, const struct lyd_node *start, LY_ARRAY_COUNT_TYPE *path_idx, - struct lyd_node **match) +ly_path_eval_partial(const struct ly_path *path, const struct lyd_node *start, const struct lyxp_var *vars, + ly_bool with_opaq, LY_ARRAY_COUNT_TYPE *path_idx, struct lyd_node **match) { LY_ARRAY_COUNT_TYPE u; struct lyd_node *prev_node = NULL, *elem, *node = NULL, *target; @@ -1010,35 +1317,43 @@ ly_path_eval_partial(const struct ly_path *path, const struct lyd_node *start, L } LY_ARRAY_FOR(path, u) { - switch (path[u].pred_type) { - case LY_PATH_PREDTYPE_POSITION: - /* we cannot use hashes and want an instance on a specific position */ - pos = 1; - node = NULL; - LYD_LIST_FOR_INST(start, path[u].node, elem) { - if (pos == path[u].predicates[0].position) { - node = elem; - break; + if (path[u].predicates) { + switch (path[u].predicates[0].type) { + case LY_PATH_PREDTYPE_POSITION: + /* we cannot use hashes and want an instance on a specific position */ + pos = 1; + node = NULL; + LYD_LIST_FOR_INST(start, path[u].node, elem) { + if (pos == path[u].predicates[0].position) { + node = elem; + break; + } + ++pos; } - ++pos; + break; + case LY_PATH_PREDTYPE_LEAFLIST: + /* we will use hashes to find one leaf-list instance */ + LY_CHECK_RET(lyd_create_term2(path[u].node, &path[u].predicates[0].value, &target)); + lyd_find_sibling_first(start, target, &node); + lyd_free_tree(target); + break; + case LY_PATH_PREDTYPE_LIST_VAR: + case LY_PATH_PREDTYPE_LIST: + /* we will use hashes to find one list instance */ + LY_CHECK_RET(lyd_create_list(path[u].node, path[u].predicates, vars, &target)); + lyd_find_sibling_first(start, target, &node); + lyd_free_tree(target); + break; } - break; - case LY_PATH_PREDTYPE_LEAFLIST: - /* we will use hashes to find one leaf-list instance */ - LY_CHECK_RET(lyd_create_term2(path[u].node, &path[u].predicates[0].value, &target)); - lyd_find_sibling_first(start, target, &node); - lyd_free_tree(target); - break; - case LY_PATH_PREDTYPE_LIST: - /* we will use hashes to find one list instance */ - LY_CHECK_RET(lyd_create_list(path[u].node, path[u].predicates, &target)); - lyd_find_sibling_first(start, target, &node); - lyd_free_tree(target); - break; - case LY_PATH_PREDTYPE_NONE: + } else { /* we will use hashes to find one any/container/leaf instance */ - lyd_find_sibling_val(start, path[u].node, NULL, 0, &node); - break; + if (lyd_find_sibling_val(start, path[u].node, NULL, 0, &node) && with_opaq) { + if (!lyd_find_sibling_opaq_next(start, path[u].node->name, &node) && + (lyd_node_module(node) != path[u].node->module)) { + /* non-matching opaque node */ + node = NULL; + } + } } if (!node) { @@ -1085,12 +1400,12 @@ ly_path_eval_partial(const struct ly_path *path, const struct lyd_node *start, L } LY_ERR -ly_path_eval(const struct ly_path *path, const struct lyd_node *start, struct lyd_node **match) +ly_path_eval(const struct ly_path *path, const struct lyd_node *start, const struct lyxp_var *vars, struct lyd_node **match) { LY_ERR ret; struct lyd_node *m; - ret = ly_path_eval_partial(path, start, NULL, &m); + ret = ly_path_eval_partial(path, start, vars, 0, NULL, &m); if (ret == LY_SUCCESS) { /* last node was found */ @@ -1110,7 +1425,8 @@ ly_path_eval(const struct ly_path *path, const struct lyd_node *start, struct ly LY_ERR ly_path_dup(const struct ly_ctx *ctx, const struct ly_path *path, struct ly_path **dup) { - LY_ARRAY_COUNT_TYPE u, v; + LY_ERR ret = LY_SUCCESS; + LY_ARRAY_COUNT_TYPE u; if (!path) { return LY_SUCCESS; @@ -1120,37 +1436,15 @@ ly_path_dup(const struct ly_ctx *ctx, const struct ly_path *path, struct ly_path LY_ARRAY_FOR(path, u) { LY_ARRAY_INCREMENT(*dup); (*dup)[u].node = path[u].node; - if (path[u].predicates) { - LY_ARRAY_CREATE_RET(ctx, (*dup)[u].predicates, LY_ARRAY_COUNT(path[u].predicates), LY_EMEM); - (*dup)[u].pred_type = path[u].pred_type; - LY_ARRAY_FOR(path[u].predicates, v) { - struct ly_path_predicate *pred = &path[u].predicates[v]; - - LY_ARRAY_INCREMENT((*dup)[u].predicates); - switch (path[u].pred_type) { - case LY_PATH_PREDTYPE_POSITION: - /* position-predicate */ - (*dup)[u].predicates[v].position = pred->position; - break; - case LY_PATH_PREDTYPE_LIST: - case LY_PATH_PREDTYPE_LEAFLIST: - /* key-predicate or leaf-list-predicate */ - (*dup)[u].predicates[v].key = pred->key; - pred->value.realtype->plugin->duplicate(ctx, &pred->value, &(*dup)[u].predicates[v].value); - LY_ATOMIC_INC_BARRIER(((struct lysc_type *)pred->value.realtype)->refcount); - break; - case LY_PATH_PREDTYPE_NONE: - break; - } - } - } + (*dup)[u].ext = path[u].ext; + LY_CHECK_RET(ret = ly_path_dup_predicates(ctx, path[u].predicates, &(*dup)[u].predicates), ret); } return LY_SUCCESS; } void -ly_path_predicates_free(const struct ly_ctx *ctx, enum ly_path_pred_type pred_type, struct ly_path_predicate *predicates) +ly_path_predicates_free(const struct ly_ctx *ctx, struct ly_path_predicate *predicates) { LY_ARRAY_COUNT_TYPE u; struct lysf_ctx fctx = {.ctx = (struct ly_ctx *)ctx}; @@ -1160,9 +1454,8 @@ ly_path_predicates_free(const struct ly_ctx *ctx, enum ly_path_pred_type pred_ty } LY_ARRAY_FOR(predicates, u) { - switch (pred_type) { + switch (predicates[u].type) { case LY_PATH_PREDTYPE_POSITION: - case LY_PATH_PREDTYPE_NONE: /* nothing to free */ break; case LY_PATH_PREDTYPE_LIST: @@ -1172,6 +1465,9 @@ ly_path_predicates_free(const struct ly_ctx *ctx, enum ly_path_pred_type pred_ty lysc_type_free(&fctx, (struct lysc_type *)predicates[u].value.realtype); } break; + case LY_PATH_PREDTYPE_LIST_VAR: + free(predicates[u].variable); + break; } } LY_ARRAY_FREE(predicates); @@ -1187,7 +1483,7 @@ ly_path_free(const struct ly_ctx *ctx, struct ly_path *path) } LY_ARRAY_FOR(path, u) { - ly_path_predicates_free(ctx, path[u].pred_type, path[u].predicates); + ly_path_predicates_free(ctx, path[u].predicates); } LY_ARRAY_FREE(path); } @@ -3,7 +3,7 @@ * @author Michal Vasko <mvasko@cesnet.cz> * @brief Path structure and manipulation routines. * - * Copyright (c) 2020 CESNET, z.s.p.o. + * Copyright (c) 2020 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -29,24 +29,25 @@ struct lysc_node; struct lyxp_expr; enum ly_path_pred_type { - LY_PATH_PREDTYPE_NONE = 0, /**< no predicate */ LY_PATH_PREDTYPE_POSITION, /**< position predicate - [2] */ LY_PATH_PREDTYPE_LIST, /**< keys predicate - [key1='val1'][key2='val2']... */ - LY_PATH_PREDTYPE_LEAFLIST /**< leaflist value predicate - [.='value'] */ + LY_PATH_PREDTYPE_LEAFLIST, /**< leaflist value predicate - [.='value'] */ + LY_PATH_PREDTYPE_LIST_VAR /**< keys predicate with variable instead of value - [key1=$USER]... */ }; /** * @brief Structure for simple path predicate. */ struct ly_path_predicate { + enum ly_path_pred_type type; /**< Predicate type (see YANG ABNF) */ union { - uint64_t position; /**< position value for the position-predicate */ - + uint64_t position; /**< position value for the position-predicate */ struct { - const struct lysc_node *key; /**< key node of the predicate, NULL in - case of a leaf-list predicate */ - struct lyd_value value; /**< value representation according to the - key's type, its realtype is allocated */ + const struct lysc_node *key; /**< key node of the predicate, NULL in case of a leaf-list predicate */ + union { + struct lyd_value value; /**< stored value representation according to the key's type (realtype ref) */ + char *variable; /**< XPath variable used instead of the value */ + }; }; }; }; @@ -61,7 +62,6 @@ struct ly_path { - is inner node - path is relative */ const struct lysc_ext_instance *ext; /**< Extension instance of @p node, if any */ struct ly_path_predicate *predicates; /**< [Sized array](@ref sizedarrays) of the path segment's predicates */ - enum ly_path_pred_type pred_type; /**< Predicate type (see YANG ABNF) */ }; /** @@ -76,9 +76,10 @@ struct ly_path { * @defgroup path_prefix_options Path prefix options. * @{ */ -#define LY_PATH_PREFIX_OPTIONAL 0x10 /**< prefixes in the path are optional */ +#define LY_PATH_PREFIX_OPTIONAL 0x10 /**< prefixes in the path are optional (XML path) */ #define LY_PATH_PREFIX_MANDATORY 0x20 /**< prefixes in the path are mandatory (XML instance-identifier) */ -#define LY_PATH_PREFIX_STRICT_INHERIT 0x30 /**< prefixes in the path are mandatory in case they differ from the +#define LY_PATH_PREFIX_FIRST 0x40 /**< prefixes in the path are mandatory only in the first node of absolute path (JSON path) */ +#define LY_PATH_PREFIX_STRICT_INHERIT 0x80 /**< prefixes in the path are mandatory in case they differ from the previous prefixes, otherwise they are prohibited (JSON instance-identifier) */ /** @} */ @@ -86,10 +87,10 @@ struct ly_path { * @defgroup path_pred_options Path predicate options. * @{ */ -#define LY_PATH_PRED_KEYS 0x40 /* expected predicate only - [node='value']* */ -#define LY_PATH_PRED_SIMPLE 0x80 /* expected predicates - [node='value']*; [.='value']; [1] */ -#define LY_PATH_PRED_LEAFREF 0xC0 /* expected predicates only leafref - [node=current()/../../../node/node]; - at least 1 ".." and 1 "node" after */ +#define LY_PATH_PRED_KEYS 0x0100 /** expected predicate only - [node='value']* */ +#define LY_PATH_PRED_SIMPLE 0x0200 /** expected predicates - ( [node='value'] | [node=$VAR] )*; [.='value']; [1] */ +#define LY_PATH_PRED_LEAFREF 0x0400 /** expected predicates only leafref - [node=current()/../../../node/node]; + at least 1 ".." and 1 "node" after */ /** @} */ /** @@ -107,7 +108,7 @@ struct ly_path { * @return LY_ERR value. */ LY_ERR ly_path_parse(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const char *str_path, size_t path_len, - ly_bool lref, uint8_t begin, uint8_t prefix, uint8_t pred, struct lyxp_expr **expr); + ly_bool lref, uint16_t begin, uint16_t prefix, uint16_t pred, struct lyxp_expr **expr); /** * @brief Parse predicate into XPath token structure and perform all additional checks. @@ -122,7 +123,7 @@ LY_ERR ly_path_parse(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, * @return LY_ERR value. */ LY_ERR ly_path_parse_predicate(const struct ly_ctx *ctx, const struct lysc_node *cur_node, const char *str_path, - size_t path_len, uint8_t prefix, uint8_t pred, struct lyxp_expr **expr); + size_t path_len, uint16_t prefix, uint16_t pred, struct lyxp_expr **expr); /** * @defgroup path_oper_options Path operation options. @@ -162,7 +163,7 @@ LY_ERR ly_path_parse_predicate(const struct ly_ctx *ctx, const struct lysc_node * @return LY_ERR value. */ LY_ERR ly_path_compile(const struct ly_ctx *ctx, const struct lys_module *cur_mod, const struct lysc_node *ctx_node, - const struct lysc_ext_instance *top_ext, const struct lyxp_expr *expr, uint8_t oper, uint8_t target, + const struct lysc_ext_instance *top_ext, const struct lyxp_expr *expr, uint16_t oper, uint16_t target, ly_bool limit_access_tree, LY_VALUE_FORMAT format, void *prefix_data, struct ly_path **path); /** @@ -182,7 +183,7 @@ LY_ERR ly_path_compile(const struct ly_ctx *ctx, const struct lys_module *cur_mo * @return LY_ERR value. */ LY_ERR ly_path_compile_leafref(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, - const struct lysc_ext_instance *top_ext, const struct lyxp_expr *expr, uint8_t oper, uint8_t target, + const struct lysc_ext_instance *top_ext, const struct lyxp_expr *expr, uint16_t oper, uint16_t target, LY_VALUE_FORMAT format, void *prefix_data, struct ly_path **path); /** @@ -198,18 +199,19 @@ LY_ERR ly_path_compile_leafref(const struct ly_ctx *ctx, const struct lysc_node * @param[in] format Format of the path. * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). * @param[out] predicates Compiled predicates. - * @param[out] pred_type Type of the compiled predicate(s). * @return LY_ERR value. */ LY_ERR ly_path_compile_predicate(const struct ly_ctx *ctx, const struct lysc_node *cur_node, const struct lys_module *cur_mod, const struct lysc_node *ctx_node, const struct lyxp_expr *expr, uint32_t *tok_idx, LY_VALUE_FORMAT format, - void *prefix_data, struct ly_path_predicate **predicates, enum ly_path_pred_type *pred_type); + void *prefix_data, struct ly_path_predicate **predicates); /** * @brief Resolve at least partially the target defined by ly_path structure. Not supported for leafref! * * @param[in] path Path structure specifying the target. * @param[in] start Starting node for relative paths, can be any for absolute paths. + * @param[in] vars Array of defined variables to use in predicates, may be NULL. + * @param[in] with_opaq Whether to consider opaque nodes or not. * @param[out] path_idx Last found path segment index, can be NULL, set to 0 if not found. * @param[out] match Last found matching node, can be NULL, set to NULL if not found. * @return LY_ENOTFOUND if no nodes were found, @@ -217,20 +219,22 @@ LY_ERR ly_path_compile_predicate(const struct ly_ctx *ctx, const struct lysc_nod * @return LY_SUCCESS when the last node in the path was found, * @return LY_ERR on another error. */ -LY_ERR ly_path_eval_partial(const struct ly_path *path, const struct lyd_node *start, LY_ARRAY_COUNT_TYPE *path_idx, - struct lyd_node **match); +LY_ERR ly_path_eval_partial(const struct ly_path *path, const struct lyd_node *start, const struct lyxp_var *vars, + ly_bool with_opaq, LY_ARRAY_COUNT_TYPE *path_idx, struct lyd_node **match); /** * @brief Resolve the target defined by ly_path structure. Not supported for leafref! * * @param[in] path Path structure specifying the target. * @param[in] start Starting node for relative paths, can be any for absolute paths. + * @param[in] vars Array of defined variables to use in predicates, may be NULL. * @param[out] match Found matching node, can be NULL, set to NULL if not found. * @return LY_ENOTFOUND if no nodes were found, * @return LY_SUCCESS when the last node in the path was found, * @return LY_ERR on another error. */ -LY_ERR ly_path_eval(const struct ly_path *path, const struct lyd_node *start, struct lyd_node **match); +LY_ERR ly_path_eval(const struct ly_path *path, const struct lyd_node *start, const struct lyxp_var *vars, + struct lyd_node **match); /** * @brief Duplicate ly_path structure. @@ -246,11 +250,9 @@ LY_ERR ly_path_dup(const struct ly_ctx *ctx, const struct ly_path *path, struct * @brief Free ly_path_predicate structure. * * @param[in] ctx libyang context. - * @param[in] pred_type Predicate type. * @param[in] predicates Predicates ([sized array](@ref sizedarrays)) to free. */ -void ly_path_predicates_free(const struct ly_ctx *ctx, enum ly_path_pred_type pred_type, - struct ly_path_predicate *predicates); +void ly_path_predicates_free(const struct ly_ctx *ctx, struct ly_path_predicate *predicates); /** * @brief Free ly_path structure. diff --git a/src/plugins.c b/src/plugins.c index d62da1c..011e464 100644 --- a/src/plugins.c +++ b/src/plugins.c @@ -70,6 +70,7 @@ extern const struct lyplg_type_record plugins_ipv6_prefix[]; * ietf-yang-types */ extern const struct lyplg_type_record plugins_date_and_time[]; +extern const struct lyplg_type_record plugins_hex_string[]; extern const struct lyplg_type_record plugins_xpath10[]; /* @@ -475,6 +476,7 @@ lyplg_init(void) /* ietf-yang-types */ LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_date_and_time), error); + LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_hex_string), error); LY_CHECK_GOTO(ret = plugins_insert(LYPLG_TYPE, plugins_xpath10), error); /* ietf-netconf-acm */ diff --git a/src/plugins_exts.c b/src/plugins_exts.c index 00970fa..f8cdeff 100644 --- a/src/plugins_exts.c +++ b/src/plugins_exts.c @@ -94,6 +94,8 @@ lys_compile_ext_instance_stmt(struct lysc_ctx *ctx, const void *parsed, struct l ly_bool length_restr = 0; LY_DATA_TYPE basetype; + assert(parsed); + /* compilation wthout any storage */ if (substmt->stmt == LY_STMT_IF_FEATURE) { ly_bool enabled; @@ -106,8 +108,8 @@ lys_compile_ext_instance_stmt(struct lysc_ctx *ctx, const void *parsed, struct l } } - if (!substmt->storage || !parsed) { - /* nothing to store or nothing parsed to compile */ + if (!substmt->storage) { + /* nothing to store */ goto cleanup; } @@ -268,6 +270,7 @@ lys_compile_ext_instance_stmt(struct lysc_ctx *ctx, const void *parsed, struct l /* compile */ LY_CHECK_GOTO(rc = lys_compile_type(ctx, NULL, flags, ext->def->name, ptype, substmt->storage, &units, NULL), cleanup); + LY_ATOMIC_INC_BARRIER((*(struct lysc_type **)substmt->storage)->refcount); break; } case LY_STMT_EXTENSION_INSTANCE: { @@ -335,8 +338,8 @@ lyplg_ext_compile_extension_instance(struct lysc_ctx *ctx, const struct lysp_ext stmtp = extp->substmts[u].stmt; storagep = *(void **)extp->substmts[u].storage; - if (ly_set_contains(&storagep_compiled, storagep, NULL)) { - /* this parsed statement has already been compiled (for example, if it is a linked list of parsed nodes) */ + if (!storagep || ly_set_contains(&storagep_compiled, storagep, NULL)) { + /* nothing parsed or already compiled (for example, if it is a linked list of parsed nodes) */ continue; } diff --git a/src/plugins_exts.h b/src/plugins_exts.h index f005391..a2783ff 100644 --- a/src/plugins_exts.h +++ b/src/plugins_exts.h @@ -594,6 +594,14 @@ LIBYANG_API_DECL void lyplg_ext_compile_log_path(const char *path, const struct LY_LOG_LEVEL level, LY_ERR err_no, const char *format, ...); /** + * @brief Log a message from an extension plugin using the compiled extension instance and a generated error item. + * + * @param[in] err Error item to log. + * @param[in] ext Compiled extension instance. + */ +LIBYANG_API_DEF void lyplg_ext_compile_log_err(const struct ly_err_item *err, const struct lysc_ext_instance *ext); + +/** * @brief YANG schema compilation context getter for libyang context. * * @param[in] ctx YANG schema compilation context. @@ -915,7 +923,10 @@ typedef void (*lyplg_ext_compile_free_clb)(const struct ly_ctx *ctx, struct lysc LIBYANG_API_DECL void lyplg_ext_cfree_instance_substatements(const struct ly_ctx *ctx, struct lysc_ext_substmt *substmts); /** - * @brief Extension plugin implementing various aspects of a YANG extension + * @brief Extension plugin implementing various aspects of a YANG extension. + * + * Every plugin should have at least either ::parse() or ::compile() callback defined but other than that **all** + * the callbacks are **optional**. */ struct lyplg_ext { const char *id; /**< plugin identification (mainly for distinguish incompatible versions @@ -1030,11 +1041,10 @@ LIBYANG_API_DECL LY_ERR lyplg_ext_schema_mount_get_parent_ref(const struct lysc_ /** * @brief Allocate a new context for a particular instance of the yangmnt:mount-point extension. - * Caller is responsible for destroying the resulting context. + * Caller is responsible for **freeing** the created context. * * @param[in] ext Compiled extension instance. - * @param[out] ctx A context with modules loaded from the list found in - * the extension data. + * @param[out] ctx Context with modules loaded from the list found in the extension data. * @return LY_ERR value. */ LIBYANG_API_DECL LY_ERR lyplg_ext_schema_mount_create_context(const struct lysc_ext_instance *ext, struct ly_ctx **ctx); diff --git a/src/plugins_exts/nacm.c b/src/plugins_exts/nacm.c index 5ab8daa..df49721 100644 --- a/src/plugins_exts/nacm.c +++ b/src/plugins_exts/nacm.c @@ -101,7 +101,7 @@ nacm_parse(struct lysp_ctx *pctx, struct lysp_ext_instance *ext) /* check for duplication */ LY_ARRAY_FOR(parent->exts, u) { - if ((&parent->exts[u] != ext) && parent->exts[u].record && (parent->exts[u].record->plugin.id == ext->record->plugin.id)) { + if ((&parent->exts[u] != ext) && parent->exts[u].record && !strcmp(parent->exts[u].record->plugin.id, ext->record->plugin.id)) { /* duplication of a NACM extension on a single node * We check for all NACM plugins since we want to catch even the situation that there is default-deny-all * AND default-deny-write */ diff --git a/src/plugins_exts/schema_mount.c b/src/plugins_exts/schema_mount.c index 9800760..cc63431 100644 --- a/src/plugins_exts/schema_mount.c +++ b/src/plugins_exts/schema_mount.c @@ -982,7 +982,7 @@ schema_mount_validate(struct lysc_ext_instance *ext, struct lyd_node *sibling, c if (!err) { lyplg_ext_compile_log(NULL, ext, LY_LLERR, ret, "Unknown validation error (err code %d).", ret); } else { - lyplg_ext_compile_log_path(err->path, ext, LY_LLERR, err->no, "%s", err->msg); + lyplg_ext_compile_log_err(err, ext); } goto cleanup; } diff --git a/src/plugins_types.c b/src/plugins_types.c index cb4b896..de7273d 100644 --- a/src/plugins_types.c +++ b/src/plugins_types.c @@ -224,24 +224,52 @@ ly_schema_resolved_get_prefix(const struct lys_module *mod, void *prefix_data) } /** - * @brief Simply return module local prefix. Also, store the module in a set. + * @brief Get prefix for XML print. + * + * @param[in] mod Module whose prefix to get. + * @param[in,out] prefix_data Set of used modules in the print. If @p mod is found in this set, no string (prefix) is + * returned. + * @return Prefix to print, may be NULL if the default namespace should be used. */ static const char * ly_xml_get_prefix(const struct lys_module *mod, void *prefix_data) { - struct ly_set *ns_list = prefix_data; + struct ly_set *mods = prefix_data; + uint32_t i; + + /* first is the local module */ + assert(mods->count); + if (mods->objs[0] == mod) { + return NULL; + } - LY_CHECK_RET(ly_set_add(ns_list, (void *)mod, 0, NULL), NULL); + /* check for duplicates in the rest of the modules and add there */ + for (i = 1; i < mods->count; ++i) { + if (mods->objs[i] == mod) { + break; + } + } + if (i == mods->count) { + LY_CHECK_RET(ly_set_add(mods, (void *)mod, 1, NULL), NULL); + } + + /* return the prefix */ return mod->prefix; } /** - * @brief Simply return module name. + * @brief Get prefix for JSON print. + * + * @param[in] mod Module whose prefix to get. + * @param[in] prefix_data Current local module, may be NULL. If it matches @p mod, no string (preifx) is returned. + * @return Prefix (module name) to print, may be NULL if the default module should be used. */ static const char * -ly_json_get_prefix(const struct lys_module *mod, void *UNUSED(prefix_data)) +ly_json_get_prefix(const struct lys_module *mod, void *prefix_data) { - return mod->name; + const struct lys_module *local_mod = prefix_data; + + return (local_mod == mod) ? NULL : mod->name; } const char * @@ -279,10 +307,6 @@ lyplg_type_get_prefix(const struct lys_module *mod, LY_VALUE_FORMAT format, void LIBYANG_API_DEF LY_ERR lyplg_type_compare_simple(const struct lyd_value *val1, const struct lyd_value *val2) { - if (val1->realtype != val2->realtype) { - return LY_ENOT; - } - if (val1->_canonical == val2->_canonical) { return LY_SUCCESS; } @@ -783,6 +807,8 @@ lyplg_type_lypath_new(const struct ly_ctx *ctx, const char *value, size_t value_ LY_ERR ret = LY_SUCCESS; struct lyxp_expr *exp = NULL; uint32_t prefix_opt = 0; + struct ly_err_item *e; + const char *err_fmt = NULL; LY_CHECK_ARG_RET(ctx, ctx, value, ctx_node, path, err, LY_EINVAL); @@ -803,31 +829,43 @@ lyplg_type_lypath_new(const struct ly_ctx *ctx, const char *value, size_t value_ break; } + /* remember the current last error */ + e = ly_err_last(ctx); + /* parse the value */ ret = ly_path_parse(ctx, ctx_node, value, value_len, 0, LY_PATH_BEGIN_ABSOLUTE, prefix_opt, LY_PATH_PRED_SIMPLE, &exp); if (ret) { - ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, - "Invalid instance-identifier \"%.*s\" value - syntax error.", (int)value_len, value); + err_fmt = "Invalid instance-identifier \"%.*s\" value - syntax error%s%s"; goto cleanup; } if (options & LYPLG_TYPE_STORE_IMPLEMENT) { /* implement all prefixes */ - LY_CHECK_GOTO(ret = lys_compile_expr_implement(ctx, exp, format, prefix_data, 1, unres, NULL), cleanup); + ret = lys_compile_expr_implement(ctx, exp, format, prefix_data, 1, unres, NULL); + if (ret) { + err_fmt = "Failed to implement a module referenced by instance-identifier \"%.*s\"%s%s"; + goto cleanup; + } } /* resolve it on schema tree */ ret = ly_path_compile(ctx, NULL, ctx_node, NULL, exp, (ctx_node->flags & LYS_IS_OUTPUT) ? LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT, LY_PATH_TARGET_SINGLE, 1, format, prefix_data, path); if (ret) { - ret = ly_err_new(err, ret, LYVE_DATA, NULL, NULL, - "Invalid instance-identifier \"%.*s\" value - semantic error.", (int)value_len, value); + err_fmt = "Invalid instance-identifier \"%.*s\" value - semantic error%s%s"; goto cleanup; } cleanup: lyxp_expr_free(ctx, exp); if (ret) { + /* generate error, spend the context error, if any */ + e = e ? e->next : ly_err_last(ctx); + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, err_fmt, (int)value_len, value, e ? ": " : ".", e ? e->msg : ""); + if (e) { + ly_err_clean((struct ly_ctx *)ctx, e); + } + ly_path_free(ctx, *path); *path = NULL; } @@ -1016,6 +1054,9 @@ lyplg_type_resolve_leafref(const struct lysc_type_leafref *lref, const struct ly if (set.val.nodes[i].type != LYXP_NODE_ELEM) { continue; } + if (((struct lyd_node_term *)set.val.nodes[i].node)->value.realtype != value->realtype) { + continue; + } if (!lref->plugin->compare(&((struct lyd_node_term *)set.val.nodes[i].node)->value, value)) { break; diff --git a/src/plugins_types.h b/src/plugins_types.h index 3ec1d3b..b4ecd43 100644 --- a/src/plugins_types.h +++ b/src/plugins_types.h @@ -367,7 +367,7 @@ LIBYANG_API_DEF LY_ERR lyplg_type_xpath10_print_token(const char *token, uint16_ * @param[in] format Format of the prefix (::lyplg_type_print_clb's format parameter). * @param[in] prefix_data Format-specific data (::lyplg_type_print_clb's prefix_data parameter). * @return Module prefix to print. - * @return NULL on error. + * @return NULL on using the current module/namespace. */ LIBYANG_API_DECL const char *lyplg_type_get_prefix(const struct lys_module *mod, LY_VALUE_FORMAT format, void *prefix_data); @@ -473,6 +473,7 @@ LIBYANG_API_DECL LY_ERR lyplg_type_print_xpath10_value(const struct lyd_value_xp value after calling the type's store callback with this option. */ #define LYPLG_TYPE_STORE_IMPLEMENT 0x02 /**< If a foreign module is needed to be implemented to successfully instantiate the value, make the module implemented. */ +#define LYPLG_TYPE_STORE_IS_UTF8 0x04 /**< The value is guaranteed to be a valid UTF-8 string, if applicable for the type. */ /** @} plugintypestoreopts */ /** @@ -522,7 +523,7 @@ LIBYANG_API_DECL typedef LY_ERR (*lyplg_type_store_clb)(const struct ly_ctx *ctx * @param[in] tree External data tree (e.g. when validating RPC/Notification) with possibly referenced data. * @param[in,out] storage Storage of the value successfully filled by ::lyplg_type_store_clb. May be modified. * @param[out] err Optionally provided error information in case of failure. If not provided to the caller, a generic - * error message is prepared instead. The error structure can be created by ::ly_err_new(). + * error message is prepared instead. The error structure can be created by ::ly_err_new(). * @return LY_SUCCESS on success, * @return LY_ERR value on error. */ @@ -532,12 +533,12 @@ LIBYANG_API_DECL typedef LY_ERR (*lyplg_type_validate_clb)(const struct ly_ctx * /** * @brief Callback for comparing 2 values of the same type. * - * In case the value types (::lyd_value.realtype) are different, ::LY_ENOT must always be returned. - * It can be assumed that the same context (dictionary) was used for storing both values. + * It can be assumed that the same context (dictionary) was used for storing both values and the realtype + * member of both the values is the same. * * @param[in] val1 First value to compare. * @param[in] val2 Second value to compare. - * @return LY_SUCCESS if values are same (according to the type's definition of being same). + * @return LY_SUCCESS if values are considered equal. * @return LY_ENOT if values differ. */ typedef LY_ERR (*lyplg_type_compare_clb)(const struct lyd_value *val1, const struct lyd_value *val2); diff --git a/src/plugins_types/binary.c b/src/plugins_types/binary.c index 519ec2e..542b54b 100644 --- a/src/plugins_types/binary.c +++ b/src/plugins_types/binary.c @@ -340,10 +340,6 @@ lyplg_type_compare_binary(const struct lyd_value *val1, const struct lyd_value * { struct lyd_value_binary *v1, *v2; - if (val1->realtype != val2->realtype) { - return LY_ENOT; - } - LYD_VALUE_GET(val1, v1); LYD_VALUE_GET(val2, v2); diff --git a/src/plugins_types/bits.c b/src/plugins_types/bits.c index 04adace..2f87ed3 100644 --- a/src/plugins_types/bits.c +++ b/src/plugins_types/bits.c @@ -373,10 +373,6 @@ lyplg_type_compare_bits(const struct lyd_value *val1, const struct lyd_value *va struct lyd_value_bits *v1, *v2; struct lysc_type_bits *type_bits = (struct lysc_type_bits *)val1->realtype; - if (val1->realtype != val2->realtype) { - return LY_ENOT; - } - LYD_VALUE_GET(val1, v1); LYD_VALUE_GET(val2, v2); diff --git a/src/plugins_types/boolean.c b/src/plugins_types/boolean.c index f8b19f6..415b8b9 100644 --- a/src/plugins_types/boolean.c +++ b/src/plugins_types/boolean.c @@ -106,10 +106,6 @@ cleanup: LIBYANG_API_DEF LY_ERR lyplg_type_compare_boolean(const struct lyd_value *val1, const struct lyd_value *val2) { - if (val1->realtype != val2->realtype) { - return LY_ENOT; - } - if (val1->boolean != val2->boolean) { return LY_ENOT; } diff --git a/src/plugins_types/date_and_time.c b/src/plugins_types/date_and_time.c index 5ccb86d..594b0a8 100644 --- a/src/plugins_types/date_and_time.c +++ b/src/plugins_types/date_and_time.c @@ -3,7 +3,7 @@ * @author Michal Vasko <mvasko@cesnet.cz> * @brief ietf-yang-types date-and-time type plugin. * - * Copyright (c) 2019-2021 CESNET, z.s.p.o. + * Copyright (c) 2019-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -53,7 +53,6 @@ lyplg_type_store_date_and_time(const struct ly_ctx *ctx, const struct lysc_type LY_ERR ret = LY_SUCCESS; struct lysc_type_str *type_dat = (struct lysc_type_str *)type; struct lyd_value_date_and_time *val; - struct tm tm; uint32_t i; char c; @@ -111,34 +110,17 @@ lyplg_type_store_date_and_time(const struct ly_ctx *ctx, const struct lysc_type ret = lyplg_type_validate_patterns(type_dat->patterns, value, value_len, err); LY_CHECK_GOTO(ret, cleanup); - /* pattern validation succeeded, convert to UNIX time and fractions of second */ + /* convert to UNIX time and fractions of second */ ret = ly_time_str2time(value, &val->time, &val->fractions_s); - LY_CHECK_GOTO(ret, cleanup); + if (ret) { + ret = ly_err_new(err, ret, 0, NULL, NULL, "%s", ly_last_errmsg()); + goto cleanup; + } -#ifdef HAVE_TIME_H_TIMEZONE if (!strncmp(((char *)value + value_len) - 6, "-00:00", 6)) { - /* unknown timezone, move the timestamp to UTC */ - tzset(); - val->time += (time_t)timezone; + /* unknown timezone */ val->unknown_tz = 1; - - /* DST may apply, adjust accordingly */ - if (!localtime_r(&val->time, &tm)) { - ret = ly_err_new(err, LY_ESYS, LYVE_DATA, NULL, NULL, "localtime_r() call failed (%s).", strerror(errno)); - goto cleanup; - } else if (tm.tm_isdst < 0) { - ret = ly_err_new(err, LY_EINT, LYVE_DATA, NULL, NULL, "Failed to get DST information."); - goto cleanup; - } - if (tm.tm_isdst) { - /* move an hour back */ - val->time -= 3600; - } } -#else - (void)tm; - val->unknown_tz = 1; -#endif if (format == LY_VALUE_CANON) { /* store canonical value */ @@ -171,10 +153,6 @@ lyplg_type_compare_date_and_time(const struct lyd_value *val1, const struct lyd_ { struct lyd_value_date_and_time *v1, *v2; - if (val1->realtype != val2->realtype) { - return LY_ENOT; - } - LYD_VALUE_GET(val1, v1); LYD_VALUE_GET(val2, v2); @@ -199,6 +177,7 @@ lyplg_type_print_date_and_time(const struct ly_ctx *ctx, const struct lyd_value void *UNUSED(prefix_data), ly_bool *dynamic, size_t *value_len) { struct lyd_value_date_and_time *val; + struct tm tm; char *ret; LYD_VALUE_GET(value, val); @@ -229,14 +208,20 @@ lyplg_type_print_date_and_time(const struct ly_ctx *ctx, const struct lyd_value /* generate canonical value if not already */ if (!value->_canonical) { - /* get the canonical value */ - if (ly_time_time2str(val->time, val->fractions_s, &ret)) { - return NULL; - } - if (val->unknown_tz) { - /* date and time is correct, fix only the timezone */ - strcpy((ret + strlen(ret)) - 6, "-00:00"); + /* ly_time_time2str but always using GMT */ + if (!gmtime_r(&val->time, &tm)) { + return NULL; + } + if (asprintf(&ret, "%04d-%02d-%02dT%02d:%02d:%02d%s%s-00:00", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, + val->fractions_s ? "." : "", val->fractions_s ? val->fractions_s : "") == -1) { + return NULL; + } + } else { + if (ly_time_time2str(val->time, val->fractions_s, &ret)) { + return NULL; + } } /* store it */ diff --git a/src/plugins_types/decimal64.c b/src/plugins_types/decimal64.c index 25a88d9..cbd6934 100644 --- a/src/plugins_types/decimal64.c +++ b/src/plugins_types/decimal64.c @@ -161,10 +161,6 @@ cleanup: LIBYANG_API_DEF LY_ERR lyplg_type_compare_decimal64(const struct lyd_value *val1, const struct lyd_value *val2) { - if (val1->realtype != val2->realtype) { - return LY_ENOT; - } - /* if type is the same, the fraction digits are, too */ if (val1->dec64 != val2->dec64) { return LY_ENOT; diff --git a/src/plugins_types/hex_string.c b/src/plugins_types/hex_string.c new file mode 100644 index 0000000..f72ce66 --- /dev/null +++ b/src/plugins_types/hex_string.c @@ -0,0 +1,171 @@ +/** + * @file hex_string.c + * @author Michal Vasko <mvasko@cesnet.cz> + * @brief Built-in hex-string and associated types plugin. + * + * Copyright (c) 2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include "plugins_types.h" + +#include <ctype.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "libyang.h" + +/* additional internal headers for some useful simple macros */ +#include "common.h" +#include "compat.h" + +/** + * @page howtoDataLYB LYB Binary Format + * @subsection howtoDataLYBTypesHexString phys-address, mac-address, hex-string, uuid (ietf-yang-types) + * + * | Size (B) | Mandatory | Type | Meaning | + * | :------ | :-------: | :--: | :-----: | + * | string length | yes | `char *` | string itself | + */ + +LIBYANG_API_DEF LY_ERR +lyplg_type_store_hex_string(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, + uint32_t options, LY_VALUE_FORMAT format, void *UNUSED(prefix_data), uint32_t hints, + const struct lysc_node *UNUSED(ctx_node), struct lyd_value *storage, struct lys_glob_unres *UNUSED(unres), + struct ly_err_item **err) +{ + LY_ERR ret = LY_SUCCESS; + struct lysc_type_str *type_str = (struct lysc_type_str *)type; + uint32_t i; + + /* init storage */ + memset(storage, 0, sizeof *storage); + storage->realtype = type; + + /* check hints */ + ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, NULL, err); + LY_CHECK_GOTO(ret, cleanup); + + /* length restriction of the string */ + if (type_str->length) { + /* value_len is in bytes, but we need number of characters here */ + ret = lyplg_type_validate_range(LY_TYPE_STRING, type_str->length, ly_utf8len(value, value_len), value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + } + + /* pattern restrictions */ + ret = lyplg_type_validate_patterns(type_str->patterns, value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + + /* make a copy, it is needed for canonization */ + if ((format != LY_VALUE_CANON) && !(options & LYPLG_TYPE_STORE_DYNAMIC)) { + value = strndup(value, value_len); + LY_CHECK_ERR_GOTO(!value, ret = LY_EMEM, cleanup); + options |= LYPLG_TYPE_STORE_DYNAMIC; + } + + /* store canonical value */ + if (format != LY_VALUE_CANON) { + /* make lowercase and store, the value must be dynamic */ + for (i = 0; i < value_len; ++i) { + ((char *)value)[i] = tolower(((char *)value)[i]); + } + + ret = lydict_insert_zc(ctx, (char *)value, &storage->_canonical); + options &= ~LYPLG_TYPE_STORE_DYNAMIC; + LY_CHECK_GOTO(ret, cleanup); + } else { + /* store directly */ + ret = lydict_insert(ctx, value_len ? value : "", value_len, &storage->_canonical); + LY_CHECK_GOTO(ret, cleanup); + } + +cleanup: + if (options & LYPLG_TYPE_STORE_DYNAMIC) { + free((void *)value); + } + + if (ret) { + lyplg_type_free_simple(ctx, storage); + } + return ret; +} + +/** + * @brief Plugin information for string type implementation. + * + * Note that external plugins are supposed to use: + * + * LYPLG_TYPES = { + */ +const struct lyplg_type_record plugins_hex_string[] = { + { + .module = "ietf-yang-types", + .revision = "2013-07-15", + .name = "phys-address", + + .plugin.id = "libyang 2 - hex-string, version 1", + .plugin.store = lyplg_type_store_hex_string, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_simple, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_simple, + .plugin.duplicate = lyplg_type_dup_simple, + .plugin.free = lyplg_type_free_simple, + .plugin.lyb_data_len = -1, + }, + { + .module = "ietf-yang-types", + .revision = "2013-07-15", + .name = "mac-address", + + .plugin.id = "libyang 2 - hex-string, version 1", + .plugin.store = lyplg_type_store_hex_string, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_simple, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_simple, + .plugin.duplicate = lyplg_type_dup_simple, + .plugin.free = lyplg_type_free_simple, + .plugin.lyb_data_len = -1, + }, + { + .module = "ietf-yang-types", + .revision = "2013-07-15", + .name = "hex-string", + + .plugin.id = "libyang 2 - hex-string, version 1", + .plugin.store = lyplg_type_store_hex_string, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_simple, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_simple, + .plugin.duplicate = lyplg_type_dup_simple, + .plugin.free = lyplg_type_free_simple, + .plugin.lyb_data_len = -1, + }, + { + .module = "ietf-yang-types", + .revision = "2013-07-15", + .name = "uuid", + + .plugin.id = "libyang 2 - hex-string, version 1", + .plugin.store = lyplg_type_store_hex_string, + .plugin.validate = NULL, + .plugin.compare = lyplg_type_compare_simple, + .plugin.sort = NULL, + .plugin.print = lyplg_type_print_simple, + .plugin.duplicate = lyplg_type_dup_simple, + .plugin.free = lyplg_type_free_simple, + .plugin.lyb_data_len = -1, + }, + {0} +}; diff --git a/src/plugins_types/identityref.c b/src/plugins_types/identityref.c index 8b7985d..588a969 100644 --- a/src/plugins_types/identityref.c +++ b/src/plugins_types/identityref.c @@ -1,9 +1,10 @@ /** * @file identityref.c * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Michal Vasko <mvasko@cesnet.cz> * @brief Built-in identityref type plugin. * - * Copyright (c) 2019-2021 CESNET, z.s.p.o. + * Copyright (c) 2019-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -50,8 +51,16 @@ static LY_ERR identityref_ident2str(const struct lysc_ident *ident, LY_VALUE_FORMAT format, void *prefix_data, char **str, size_t *str_len) { int len; + const char *prefix; - len = asprintf(str, "%s:%s", lyplg_type_get_prefix(ident->module, format, prefix_data), ident->name); + /* get the prefix, may be NULL for no prefix and the default namespace */ + prefix = lyplg_type_get_prefix(ident->module, format, prefix_data); + + if (prefix) { + len = asprintf(str, "%s:%s", prefix, ident->name); + } else { + len = asprintf(str, "%s", ident->name); + } if (len == -1) { return LY_EMEM; } @@ -270,8 +279,11 @@ lyplg_type_store_identityref(const struct ly_ctx *ctx, const struct lysc_type *t } } else { /* JSON format with prefix is the canonical one */ - ret = identityref_ident2str(ident, LY_VALUE_JSON, NULL, &canon, NULL); - LY_CHECK_GOTO(ret, cleanup); + if (asprintf(&canon, "%s:%s", ident->module->name, ident->name) == -1) { + LOGMEM(ctx); + ret = LY_EMEM; + goto cleanup; + } ret = lydict_insert_zc(ctx, canon, &storage->_canonical); LY_CHECK_GOTO(ret, cleanup); @@ -291,10 +303,6 @@ cleanup: LIBYANG_API_DEF LY_ERR lyplg_type_compare_identityref(const struct lyd_value *val1, const struct lyd_value *val2) { - if (val1->realtype != val2->realtype) { - return LY_ENOT; - } - if (val1->ident == val2->ident) { return LY_SUCCESS; } @@ -307,7 +315,7 @@ lyplg_type_print_identityref(const struct ly_ctx *UNUSED(ctx), const struct lyd_ { char *ret; - if ((format == LY_VALUE_CANON) || (format == LY_VALUE_JSON) || (format == LY_VALUE_LYB)) { + if (format == LY_VALUE_CANON) { if (dynamic) { *dynamic = 0; } diff --git a/src/plugins_types/instanceid.c b/src/plugins_types/instanceid.c index d151d0a..c15ff64 100644 --- a/src/plugins_types/instanceid.c +++ b/src/plugins_types/instanceid.c @@ -50,12 +50,19 @@ instanceid_path2str(const struct ly_path *path, LY_VALUE_FORMAT format, void *pr LY_ERR ret = LY_SUCCESS; LY_ARRAY_COUNT_TYPE u, v; char *result = NULL, quot; - const struct lys_module *mod = NULL; + const struct lys_module *mod = NULL, *local_mod = NULL; + struct ly_set *mods = NULL; ly_bool inherit_prefix = 0, d; const char *strval; switch (format) { case LY_VALUE_XML: + /* null the local module so that all the prefixes are printed */ + mods = prefix_data; + local_mod = mods->objs[0]; + mods->objs[0] = NULL; + + /* fallthrough */ case LY_VALUE_SCHEMA: case LY_VALUE_SCHEMA_RESOLVED: /* everything is prefixed */ @@ -84,9 +91,7 @@ instanceid_path2str(const struct ly_path *path, LY_VALUE_FORMAT format, void *pr LY_ARRAY_FOR(path[u].predicates, v) { struct ly_path_predicate *pred = &path[u].predicates[v]; - switch (path[u].pred_type) { - case LY_PATH_PREDTYPE_NONE: - break; + switch (pred->type) { case LY_PATH_PREDTYPE_POSITION: /* position predicate */ ret = ly_strcat(&result, "[%" PRIu64 "]", pred->position); @@ -127,6 +132,10 @@ instanceid_path2str(const struct ly_path *path, LY_VALUE_FORMAT format, void *pr free((char *)strval); } break; + case LY_PATH_PREDTYPE_LIST_VAR: + LOGINT(path[u].node->module->ctx); + ret = LY_EINT; + goto cleanup; } LY_CHECK_GOTO(ret, cleanup); @@ -134,6 +143,9 @@ instanceid_path2str(const struct ly_path *path, LY_VALUE_FORMAT format, void *pr } cleanup: + if (local_mod) { + mods->objs[0] = (void *)local_mod; + } if (ret) { free(result); } else { @@ -162,7 +174,7 @@ lyplg_type_store_instanceid(const struct ly_ctx *ctx, const struct lysc_type *ty /* compile instance-identifier into path */ if (format == LY_VALUE_LYB) { - /* The @p value in LYB format is the same as in JSON format. */ + /* value in LYB format is the same as in JSON format. */ ret = lyplg_type_lypath_new(ctx, value, value_len, options, LY_VALUE_JSON, prefix_data, ctx_node, unres, &path, err); } else { @@ -231,7 +243,7 @@ lyplg_type_validate_instanceid(const struct ly_ctx *ctx, const struct lysc_type } /* find the target in data */ - if ((ret = ly_path_eval(storage->target, tree, NULL))) { + if ((ret = ly_path_eval(storage->target, tree, NULL, NULL))) { value = lyplg_type_print_instanceid(ctx, storage, LY_VALUE_CANON, NULL, NULL, NULL); path = lyd_path(ctx_node, LYD_PATH_STD, NULL, 0); return ly_err_new(err, ret, LYVE_DATA, path, strdup("instance-required"), LY_ERRMSG_NOINST, value); @@ -245,10 +257,6 @@ lyplg_type_compare_instanceid(const struct lyd_value *val1, const struct lyd_val { LY_ARRAY_COUNT_TYPE u, v; - if (val1->realtype != val2->realtype) { - return LY_ENOT; - } - if (val1 == val2) { return LY_SUCCESS; } else if (LY_ARRAY_COUNT(val1->target) != LY_ARRAY_COUNT(val2->target)) { @@ -259,37 +267,43 @@ lyplg_type_compare_instanceid(const struct lyd_value *val1, const struct lyd_val struct ly_path *s1 = &val1->target[u]; struct ly_path *s2 = &val2->target[u]; - if ((s1->node != s2->node) || (s1->pred_type != s2->pred_type) || - (s1->predicates && (LY_ARRAY_COUNT(s1->predicates) != LY_ARRAY_COUNT(s2->predicates)))) { + if ((s1->node != s2->node) || (s1->predicates && (LY_ARRAY_COUNT(s1->predicates) != LY_ARRAY_COUNT(s2->predicates)))) { return LY_ENOT; } - if (s1->predicates) { - LY_ARRAY_FOR(s1->predicates, v) { - struct ly_path_predicate *pred1 = &s1->predicates[v]; - struct ly_path_predicate *pred2 = &s2->predicates[v]; - - switch (s1->pred_type) { - case LY_PATH_PREDTYPE_NONE: - break; - case LY_PATH_PREDTYPE_POSITION: - /* position predicate */ - if (pred1->position != pred2->position) { - return LY_ENOT; - } - break; - case LY_PATH_PREDTYPE_LIST: - /* key-predicate */ - if ((pred1->key != pred2->key) || - ((struct lysc_node_leaf *)pred1->key)->type->plugin->compare(&pred1->value, &pred2->value)) { - return LY_ENOT; - } - break; - case LY_PATH_PREDTYPE_LEAFLIST: - /* leaf-list predicate */ - if (((struct lysc_node_leaflist *)s1->node)->type->plugin->compare(&pred1->value, &pred2->value)) { - return LY_ENOT; - } + LY_ARRAY_FOR(s1->predicates, v) { + struct ly_path_predicate *pred1 = &s1->predicates[v]; + struct ly_path_predicate *pred2 = &s2->predicates[v]; + + if (pred1->type != pred2->type) { + return LY_ENOT; + } + + switch (pred1->type) { + case LY_PATH_PREDTYPE_POSITION: + /* position predicate */ + if (pred1->position != pred2->position) { + return LY_ENOT; } + break; + case LY_PATH_PREDTYPE_LIST: + /* key-predicate */ + if ((pred1->key != pred2->key) || + ((struct lysc_node_leaf *)pred1->key)->type->plugin->compare(&pred1->value, &pred2->value)) { + return LY_ENOT; + } + break; + case LY_PATH_PREDTYPE_LEAFLIST: + /* leaf-list predicate */ + if (((struct lysc_node_leaflist *)s1->node)->type->plugin->compare(&pred1->value, &pred2->value)) { + return LY_ENOT; + } + break; + case LY_PATH_PREDTYPE_LIST_VAR: + /* key-predicate with a variable */ + if ((pred1->key != pred2->key) || strcmp(pred1->variable, pred2->variable)) { + return LY_ENOT; + } + break; } } } diff --git a/src/plugins_types/instanceid_keys.c b/src/plugins_types/instanceid_keys.c index 0cd08f7..ab7751c 100644 --- a/src/plugins_types/instanceid_keys.c +++ b/src/plugins_types/instanceid_keys.c @@ -67,10 +67,18 @@ instanceid_keys_print_value(const struct lyd_value_instance_identifier_keys *val void *mem; const char *cur_exp_ptr; ly_bool is_nt; - const struct lys_module *context_mod = NULL; + const struct lys_module *context_mod = NULL, *local_mod = NULL; + struct ly_set *mods; *str_value = NULL; + if (format == LY_VALUE_XML) { + /* null the local module so that all the prefixes are printed */ + mods = prefix_data; + local_mod = mods->objs[0]; + mods->objs[0] = NULL; + } + while (cur_idx < val->keys->used) { cur_tok = val->keys->tokens[cur_idx]; cur_exp_ptr = val->keys->expr + val->keys->tok_pos[cur_idx]; @@ -79,11 +87,15 @@ instanceid_keys_print_value(const struct lyd_value_instance_identifier_keys *val /* tokens that may include prefixes, get them in the target format */ is_nt = (cur_tok == LYXP_TOKEN_NAMETEST) ? 1 : 0; LY_CHECK_GOTO(ret = lyplg_type_xpath10_print_token(cur_exp_ptr, val->keys->tok_len[cur_idx], is_nt, &context_mod, - val->ctx, val->format, val->prefix_data, format, prefix_data, &str_tok, err), error); + val->ctx, val->format, val->prefix_data, format, prefix_data, &str_tok, err), cleanup); /* append the converted token */ mem = realloc(*str_value, str_len + strlen(str_tok) + 1); - LY_CHECK_ERR_GOTO(!mem, free(str_tok), error_mem); + if (!mem) { + free(str_tok); + ret = ly_err_new(err, LY_EMEM, LYVE_DATA, NULL, NULL, "No memory."); + goto cleanup; + } *str_value = mem; str_len += sprintf(*str_value + str_len, "%s", str_tok); free(str_tok); @@ -93,7 +105,10 @@ instanceid_keys_print_value(const struct lyd_value_instance_identifier_keys *val } else { /* just copy the token */ mem = realloc(*str_value, str_len + val->keys->tok_len[cur_idx] + 1); - LY_CHECK_GOTO(!mem, error_mem); + if (!mem) { + ret = ly_err_new(err, LY_EMEM, LYVE_DATA, NULL, NULL, "No memory."); + goto cleanup; + } *str_value = mem; str_len += sprintf(*str_value + str_len, "%.*s", (int)val->keys->tok_len[cur_idx], cur_exp_ptr); @@ -102,13 +117,14 @@ instanceid_keys_print_value(const struct lyd_value_instance_identifier_keys *val } } - return LY_SUCCESS; - -error_mem: - ret = ly_err_new(err, LY_EMEM, LYVE_DATA, NULL, NULL, "No memory."); - -error: - free(*str_value); +cleanup: + if (local_mod) { + mods->objs[0] = (void *)local_mod; + } + if (ret) { + free(*str_value); + *str_value = NULL; + } return ret; } @@ -123,6 +139,7 @@ lyplg_type_store_instanceid_keys(const struct ly_ctx *ctx, const struct lysc_typ LY_ERR ret = LY_SUCCESS; struct lysc_type_str *type_str = (struct lysc_type_str *)type; struct lyd_value_instance_identifier_keys *val; + uint32_t log_opts = LY_LOSTORE; char *canon; /* init storage */ @@ -152,10 +169,15 @@ lyplg_type_store_instanceid_keys(const struct ly_ctx *ctx, const struct lysc_typ ((char *)value)[0]); goto cleanup; } + + /* do not log */ + ly_temp_log_options(&log_opts); ret = ly_path_parse_predicate(ctx, NULL, value_len ? value : "", value_len, LY_PATH_PREFIX_OPTIONAL, LY_PATH_PRED_KEYS, &val->keys); + ly_temp_log_options(NULL); if (ret) { ret = ly_err_new(err, ret, LYVE_DATA, NULL, NULL, "%s", ly_errmsg(ctx)); + ly_err_clean((struct ly_ctx *)ctx, NULL); goto cleanup; } val->ctx = ctx; diff --git a/src/plugins_types/integer.c b/src/plugins_types/integer.c index 0903365..05d6801 100644 --- a/src/plugins_types/integer.c +++ b/src/plugins_types/integer.c @@ -373,10 +373,6 @@ cleanup: LIBYANG_API_DEF LY_ERR lyplg_type_compare_uint(const struct lyd_value *val1, const struct lyd_value *val2) { - if (val1->realtype != val2->realtype) { - return LY_ENOT; - } - switch (val1->realtype->basetype) { case LY_TYPE_UINT8: if (val1->uint8 != val2->uint8) { diff --git a/src/plugins_types/ipv4_address.c b/src/plugins_types/ipv4_address.c index f7b297c..9e54a0b 100644 --- a/src/plugins_types/ipv4_address.c +++ b/src/plugins_types/ipv4_address.c @@ -214,10 +214,6 @@ lyplg_type_compare_ipv4_address(const struct lyd_value *val1, const struct lyd_v { struct lyd_value_ipv4_address *v1, *v2; - if (val1->realtype != val2->realtype) { - return LY_ENOT; - } - LYD_VALUE_GET(val1, v1); LYD_VALUE_GET(val2, v2); diff --git a/src/plugins_types/ipv4_address_no_zone.c b/src/plugins_types/ipv4_address_no_zone.c index 91fe677..a693912 100644 --- a/src/plugins_types/ipv4_address_no_zone.c +++ b/src/plugins_types/ipv4_address_no_zone.c @@ -132,10 +132,6 @@ lyplg_type_compare_ipv4_address_no_zone(const struct lyd_value *val1, const stru { struct lyd_value_ipv4_address_no_zone *v1, *v2; - if (val1->realtype != val2->realtype) { - return LY_ENOT; - } - LYD_VALUE_GET(val1, v1); LYD_VALUE_GET(val2, v2); diff --git a/src/plugins_types/ipv4_prefix.c b/src/plugins_types/ipv4_prefix.c index 6f13eee..b8d77b5 100644 --- a/src/plugins_types/ipv4_prefix.c +++ b/src/plugins_types/ipv4_prefix.c @@ -203,10 +203,6 @@ lyplg_type_compare_ipv4_prefix(const struct lyd_value *val1, const struct lyd_va { struct lyd_value_ipv4_prefix *v1, *v2; - if (val1->realtype != val2->realtype) { - return LY_ENOT; - } - LYD_VALUE_GET(val1, v1); LYD_VALUE_GET(val2, v2); diff --git a/src/plugins_types/ipv6_address.c b/src/plugins_types/ipv6_address.c index 74f5c62..344a7fc 100644 --- a/src/plugins_types/ipv6_address.c +++ b/src/plugins_types/ipv6_address.c @@ -216,10 +216,6 @@ lyplg_type_compare_ipv6_address(const struct lyd_value *val1, const struct lyd_v { struct lyd_value_ipv6_address *v1, *v2; - if (val1->realtype != val2->realtype) { - return LY_ENOT; - } - LYD_VALUE_GET(val1, v1); LYD_VALUE_GET(val2, v2); diff --git a/src/plugins_types/ipv6_address_no_zone.c b/src/plugins_types/ipv6_address_no_zone.c index 26fbf80..98ca754 100644 --- a/src/plugins_types/ipv6_address_no_zone.c +++ b/src/plugins_types/ipv6_address_no_zone.c @@ -180,10 +180,6 @@ lyplg_type_compare_ipv6_address_no_zone(const struct lyd_value *val1, const stru { struct lyd_value_ipv6_address_no_zone *v1, *v2; - if (val1->realtype != val2->realtype) { - return LY_ENOT; - } - LYD_VALUE_GET(val1, v1); LYD_VALUE_GET(val2, v2); diff --git a/src/plugins_types/ipv6_prefix.c b/src/plugins_types/ipv6_prefix.c index 8e62311..aaec395 100644 --- a/src/plugins_types/ipv6_prefix.c +++ b/src/plugins_types/ipv6_prefix.c @@ -217,10 +217,6 @@ lyplg_type_compare_ipv6_prefix(const struct lyd_value *val1, const struct lyd_va { struct lyd_value_ipv6_prefix *v1, *v2; - if (val1->realtype != val2->realtype) { - return LY_ENOT; - } - LYD_VALUE_GET(val1, v1); LYD_VALUE_GET(val2, v2); diff --git a/src/plugins_types/node_instanceid.c b/src/plugins_types/node_instanceid.c index 04fb164..7833263 100644 --- a/src/plugins_types/node_instanceid.c +++ b/src/plugins_types/node_instanceid.c @@ -50,7 +50,8 @@ node_instanceid_path2str(const struct ly_path *path, LY_VALUE_FORMAT format, voi LY_ERR ret = LY_SUCCESS; LY_ARRAY_COUNT_TYPE u, v; char *result = NULL, quot; - const struct lys_module *mod = NULL; + const struct lys_module *mod = NULL, *local_mod = NULL; + struct ly_set *mods; ly_bool inherit_prefix = 0, d; const char *strval; @@ -62,6 +63,12 @@ node_instanceid_path2str(const struct ly_path *path, LY_VALUE_FORMAT format, voi switch (format) { case LY_VALUE_XML: + /* null the local module so that all the prefixes are printed */ + mods = prefix_data; + local_mod = mods->objs[0]; + mods->objs[0] = NULL; + + /* fallthrough */ case LY_VALUE_SCHEMA: case LY_VALUE_SCHEMA_RESOLVED: /* everything is prefixed */ @@ -90,9 +97,7 @@ node_instanceid_path2str(const struct ly_path *path, LY_VALUE_FORMAT format, voi LY_ARRAY_FOR(path[u].predicates, v) { struct ly_path_predicate *pred = &path[u].predicates[v]; - switch (path[u].pred_type) { - case LY_PATH_PREDTYPE_NONE: - break; + switch (pred->type) { case LY_PATH_PREDTYPE_POSITION: /* position predicate */ ret = ly_strcat(&result, "[%" PRIu64 "]", pred->position); @@ -133,6 +138,16 @@ node_instanceid_path2str(const struct ly_path *path, LY_VALUE_FORMAT format, voi free((char *)strval); } break; + case LY_PATH_PREDTYPE_LIST_VAR: + /* key-predicate with a variable */ + if (inherit_prefix) { + /* always the same prefix as the parent */ + ret = ly_strcat(&result, "[%s=$%s]", pred->key->name, pred->variable); + } else { + ret = ly_strcat(&result, "[%s:%s=$%s]", lyplg_type_get_prefix(pred->key->module, format, prefix_data), + pred->key->name, pred->variable); + } + break; } LY_CHECK_GOTO(ret, cleanup); @@ -140,6 +155,9 @@ node_instanceid_path2str(const struct ly_path *path, LY_VALUE_FORMAT format, voi } cleanup: + if (local_mod) { + mods->objs[0] = (void *)local_mod; + } if (ret) { free(result); } else { @@ -193,7 +211,7 @@ lyplg_type_store_node_instanceid(const struct ly_ctx *ctx, const struct lysc_typ ret = ly_path_parse(ctx, ctx_node, value, value_len, 0, LY_PATH_BEGIN_ABSOLUTE, prefix_opt, LY_PATH_PRED_SIMPLE, &exp); if (ret) { ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, - "Invalid instance-identifier \"%.*s\" value - syntax error.", (int)value_len, (char *)value); + "Invalid node-instance-identifier \"%.*s\" value - syntax error.", (int)value_len, (char *)value); goto cleanup; } @@ -209,7 +227,7 @@ lyplg_type_store_node_instanceid(const struct ly_ctx *ctx, const struct lysc_typ LY_VALUE_JSON : format, prefix_data, &path); if (ret) { ret = ly_err_new(err, ret, LYVE_DATA, NULL, NULL, - "Invalid instance-identifier \"%.*s\" value - semantic error.", (int)value_len, (char *)value); + "Invalid node-instance-identifier \"%.*s\" value - semantic error.", (int)value_len, (char *)value); goto cleanup; } diff --git a/src/plugins_types/string.c b/src/plugins_types/string.c index 4f988ef..f8143d0 100644 --- a/src/plugins_types/string.c +++ b/src/plugins_types/string.c @@ -1,9 +1,10 @@ /** * @file string.c * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Michal Vasko <mvasko@cesnet.cz> * @brief Built-in string type plugin. * - * Copyright (c) 2019-2021 CESNET, z.s.p.o. + * Copyright (c) 2019 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -33,6 +34,29 @@ * | string length | yes | `char *` | string itself | */ +/** + * @brief Check string value for invalid characters. + * + * @param[in] value String to check. + * @param[in] value_len Length of @p value. + * @param[out] err Generated error on error. + * @return LY_ERR value. + */ +static LY_ERR +string_check_chars(const char *value, size_t value_len, struct ly_err_item **err) +{ + size_t len, parsed = 0; + + while (value_len - parsed) { + if (ly_checkutf8(value + parsed, value_len - parsed, &len)) { + return ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid character 0x%hhx.", value[parsed]); + } + parsed += len; + } + + return LY_SUCCESS; +} + LIBYANG_API_DEF LY_ERR lyplg_type_store_string(const struct ly_ctx *ctx, const struct lysc_type *type, const void *value, size_t value_len, uint32_t options, LY_VALUE_FORMAT UNUSED(format), void *UNUSED(prefix_data), uint32_t hints, @@ -46,6 +70,12 @@ lyplg_type_store_string(const struct ly_ctx *ctx, const struct lysc_type *type, memset(storage, 0, sizeof *storage); storage->realtype = type; + if (!(options & LYPLG_TYPE_STORE_IS_UTF8)) { + /* check the UTF-8 encoding */ + ret = string_check_chars(value, value_len, err); + LY_CHECK_GOTO(ret, cleanup); + } + /* check hints */ ret = lyplg_type_check_hints(hints, value, value_len, type->basetype, NULL, err); LY_CHECK_GOTO(ret, cleanup); diff --git a/src/plugins_types/union.c b/src/plugins_types/union.c index 6e31d1e..a5b7610 100644 --- a/src/plugins_types/union.c +++ b/src/plugins_types/union.c @@ -172,6 +172,8 @@ union_store_type(const struct ly_ctx *ctx, struct lysc_type *type, struct lyd_va const void *value = NULL; size_t value_len = 0; + *err = NULL; + if (subvalue->format == LY_VALUE_LYB) { lyb_parse_union(subvalue->original, subvalue->orig_len, NULL, &value, &value_len); } else { @@ -220,36 +222,63 @@ union_find_type(const struct ly_ctx *ctx, struct lysc_type **types, struct lyd_v { LY_ERR ret = LY_SUCCESS; LY_ARRAY_COUNT_TYPE u; + struct ly_err_item **errs = NULL, *e; uint32_t temp_lo = 0; + char *msg = NULL; + int msg_len = 0; + + *err = NULL; if (!types || !LY_ARRAY_COUNT(types)) { return LY_EINVAL; } - *err = NULL; + /* alloc errors */ + errs = calloc(LY_ARRAY_COUNT(types), sizeof *errs); + LY_CHECK_RET(!errs, LY_EMEM); /* turn logging temporarily off */ ly_temp_log_options(&temp_lo); /* use the first usable subtype to store the value */ for (u = 0; u < LY_ARRAY_COUNT(types); ++u) { - ret = union_store_type(ctx, types[u], subvalue, resolve, ctx_node, tree, unres, err); + ret = union_store_type(ctx, types[u], subvalue, resolve, ctx_node, tree, unres, &e); if ((ret == LY_SUCCESS) || (ret == LY_EINCOMPLETE)) { break; } - ly_err_free(*err); - *err = NULL; + errs[u] = e; } if (u == LY_ARRAY_COUNT(types)) { - ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "Invalid union value \"%.*s\" - no matching subtype found.", + /* create the full error */ + msg_len = asprintf(&msg, "Invalid union value \"%.*s\" - no matching subtype found:\n", (int)subvalue->orig_len, (char *)subvalue->original); + if (msg_len == -1) { + LY_CHECK_ERR_GOTO(!errs, ret = LY_EMEM, cleanup); + } + for (u = 0; u < LY_ARRAY_COUNT(types); ++u) { + if (!errs[u]) { + /* no error for some reason */ + continue; + } + + msg = ly_realloc(msg, msg_len + 4 + strlen(types[u]->plugin->id) + 2 + strlen(errs[u]->msg) + 2); + LY_CHECK_ERR_GOTO(!msg, ret = LY_EMEM, cleanup); + msg_len += sprintf(msg + msg_len, " %s: %s\n", types[u]->plugin->id, errs[u]->msg); + } + + ret = ly_err_new(err, LY_EVALID, LYVE_DATA, NULL, NULL, "%s", msg); } else if (type_idx) { *type_idx = u; } - /* restore logging */ +cleanup: + for (u = 0; u < LY_ARRAY_COUNT(types); ++u) { + ly_err_free(errs[u]); + } + free(errs); + free(msg); ly_temp_log_options(NULL); return ret; } @@ -281,29 +310,26 @@ lyb_fill_subvalue(const struct ly_ctx *ctx, struct lysc_type_union *type_u, cons ret = lyb_union_validate(lyb_data, lyb_data_len, type_u, err); LY_CHECK_RET(ret); - /* Parse lyb_data and set the lyb_value and lyb_value_len. */ + /* parse lyb_data and set the lyb_value and lyb_value_len */ lyb_parse_union(lyb_data, lyb_data_len, &type_idx, &lyb_value, &lyb_value_len); LY_CHECK_RET(ret); - /* Store lyb_data to subvalue. */ - ret = union_subvalue_assignment(lyb_data, lyb_data_len, - &subvalue->original, &subvalue->orig_len, options); + /* store lyb_data to subvalue */ + ret = union_subvalue_assignment(lyb_data, lyb_data_len, &subvalue->original, &subvalue->orig_len, options); LY_CHECK_RET(ret); if (lyb_value) { - /* Resolve prefix_data and set format. */ + /* resolve prefix_data and set format */ ret = lyplg_type_prefix_data_new(ctx, lyb_value, lyb_value_len, LY_VALUE_LYB, prefix_data, &subvalue->format, &subvalue->prefix_data); LY_CHECK_RET(ret); assert(subvalue->format == LY_VALUE_LYB); } else { - /* The lyb_parse_union() did not find lyb_value. - * Just set format. - */ + /* lyb_parse_union() did not find lyb_value, just set format */ subvalue->format = LY_VALUE_LYB; } - /* Use the specific type to store the value. */ + /* use the specific type to store the value */ ret = union_store_type(ctx, type_u->types[type_idx], subvalue, 0, NULL, NULL, unres, err); return ret; @@ -329,13 +355,11 @@ lyplg_type_store_union(const struct ly_ctx *ctx, const struct lysc_type *type, c subvalue->ctx_node = ctx_node; if (format == LY_VALUE_LYB) { - ret = lyb_fill_subvalue(ctx, type_u, value, value_len, - prefix_data, subvalue, &options, unres, err); + ret = lyb_fill_subvalue(ctx, type_u, value, value_len, prefix_data, subvalue, &options, unres, err); LY_CHECK_GOTO((ret != LY_SUCCESS) && (ret != LY_EINCOMPLETE), cleanup); } else { /* Store @p value to subvalue. */ - ret = union_subvalue_assignment(value, value_len, - &subvalue->original, &subvalue->orig_len, &options); + ret = union_subvalue_assignment(value, value_len, &subvalue->original, &subvalue->orig_len, &options); LY_CHECK_GOTO(ret, cleanup); /* store format-specific data for later prefix resolution */ @@ -370,6 +394,7 @@ lyplg_type_validate_union(const struct ly_ctx *ctx, const struct lysc_type *type LY_ERR ret = LY_SUCCESS; struct lysc_type_union *type_u = (struct lysc_type_union *)type; struct lyd_value_union *subvalue = storage->subvalue; + uint32_t type_idx; *err = NULL; @@ -378,9 +403,16 @@ lyplg_type_validate_union(const struct ly_ctx *ctx, const struct lysc_type *type * we have to perform union value storing again from scratch */ subvalue->value.realtype->plugin->free(ctx, &subvalue->value); - /* use the first usable subtype to store the value */ - ret = union_find_type(ctx, type_u->types, subvalue, 1, ctx_node, tree, NULL, NULL, err); - LY_CHECK_RET(ret); + if (subvalue->format == LY_VALUE_LYB) { + /* use the specific type to store the value */ + lyb_parse_union(subvalue->original, 0, &type_idx, NULL, NULL); + ret = union_store_type(ctx, type_u->types[type_idx], subvalue, 1, ctx_node, tree, NULL, err); + LY_CHECK_RET(ret); + } else { + /* use the first usable subtype to store the value */ + ret = union_find_type(ctx, type_u->types, subvalue, 1, ctx_node, tree, NULL, NULL, err); + LY_CHECK_RET(ret); + } /* success, update the canonical value, if any generated */ lydict_remove(ctx, storage->_canonical); @@ -391,10 +423,6 @@ lyplg_type_validate_union(const struct ly_ctx *ctx, const struct lysc_type *type LIBYANG_API_DEF LY_ERR lyplg_type_compare_union(const struct lyd_value *val1, const struct lyd_value *val2) { - if (val1->realtype != val2->realtype) { - return LY_ENOT; - } - if (val1->subvalue->value.realtype != val2->subvalue->value.realtype) { return LY_ENOT; } @@ -418,10 +446,10 @@ lyb_union_print(const struct ly_ctx *ctx, struct lysc_type_union *type_u, struct void *prefix_data, size_t *value_len) { void *ret = NULL; - LY_ERR retval; + LY_ERR r; struct ly_err_item *err; uint64_t num = 0; - uint32_t type_idx; + uint32_t type_idx = 0; ly_bool dynamic; size_t pval_len; void *pval; @@ -435,8 +463,9 @@ lyb_union_print(const struct ly_ctx *ctx, struct lysc_type_union *type_u, struct ctx = subvalue->ctx_node->module->ctx; } subvalue->value.realtype->plugin->free(ctx, &subvalue->value); - retval = union_find_type(ctx, type_u->types, subvalue, 0, NULL, NULL, &type_idx, NULL, &err); - LY_CHECK_RET((retval != LY_SUCCESS) && (retval != LY_EINCOMPLETE), NULL); + r = union_find_type(ctx, type_u->types, subvalue, 0, NULL, NULL, &type_idx, NULL, &err); + ly_err_free(err); + LY_CHECK_RET((r != LY_SUCCESS) && (r != LY_EINCOMPLETE), NULL); /* Print subvalue in LYB format. */ pval = (void *)subvalue->value.realtype->plugin->print(NULL, &subvalue->value, LY_VALUE_LYB, prefix_data, &dynamic, diff --git a/src/plugins_types/xpath1.0.c b/src/plugins_types/xpath1.0.c index a15e5b7..fdfc2a9 100644 --- a/src/plugins_types/xpath1.0.c +++ b/src/plugins_types/xpath1.0.c @@ -3,7 +3,7 @@ * @author Michal Vasko <mvasko@cesnet.cz> * @brief ietf-yang-types xpath1.0 type plugin. * - * Copyright (c) 2021 CESNET, z.s.p.o. + * Copyright (c) 2021 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -57,12 +57,9 @@ lyplg_type_xpath10_print_token(const char *token, uint16_t tok_len, ly_bool is_n while (!(ret = ly_value_prefix_next(str_begin, token + tok_len, &len, &is_prefix, &str_next)) && len) { if (!is_prefix) { if (!has_prefix && is_nametest && (get_format == LY_VALUE_XML) && *context_mod) { - /* prefix is always needed, get it in the target format */ + /* get the prefix */ prefix = lyplg_type_get_prefix(*context_mod, get_format, get_prefix_data); - if (!prefix) { - ret = ly_err_new(err, LY_EINT, LYVE_DATA, NULL, NULL, "Internal error."); - goto cleanup; - } + assert(prefix); /* append the nametest and prefix */ mem = realloc(str, str_len + strlen(prefix) + 1 + len + 1); @@ -94,10 +91,7 @@ lyplg_type_xpath10_print_token(const char *token, uint16_t tok_len, ly_bool is_n if (mod) { /* get the prefix in the target format */ prefix = lyplg_type_get_prefix(mod, get_format, get_prefix_data); - if (!prefix) { - ret = ly_err_new(err, LY_EINT, LYVE_DATA, NULL, NULL, "Internal error."); - goto cleanup; - } + assert(prefix); pref_len = strlen(prefix); } else { /* invalid prefix, just copy it */ @@ -223,13 +217,25 @@ lyplg_type_print_xpath10_value(const struct lyd_value_xpath10 *xp_val, LY_VALUE_ LY_ERR ret = LY_SUCCESS; uint16_t expr_idx = 0; uint32_t str_len = 0; + const struct lys_module *local_mod = NULL; + struct ly_set *mods; *str_value = NULL; *err = NULL; + if (format == LY_VALUE_XML) { + /* null the local module so that all the prefixes are printed */ + mods = prefix_data; + local_mod = mods->objs[0]; + mods->objs[0] = NULL; + } + /* recursively print the expression */ ret = xpath10_print_subexpr_r(&expr_idx, 0, NULL, xp_val, format, prefix_data, str_value, &str_len, err); + if (local_mod) { + mods->objs[0] = (void *)local_mod; + } if (ret) { free(*str_value); *str_value = NULL; @@ -326,6 +332,49 @@ cleanup: } /** + * @brief Create a namespace and add it into a set. + * + * @param[in] set Set of namespaces to add to. + * @param[in] pref Namespace prefix. + * @param[in] uri Namespace URI. + * @return LY_ERR value. + */ +static LY_ERR +xpath10_add_ns(struct ly_set *set, const char *pref, const char *uri) +{ + LY_ERR rc = LY_SUCCESS; + struct lyxml_ns *ns = NULL; + + /* create new ns */ + ns = calloc(1, sizeof *ns); + if (!ns) { + rc = LY_EMEM; + goto cleanup; + } + ns->prefix = strdup(pref); + ns->uri = strdup(uri); + if (!ns->prefix || !ns->uri) { + rc = LY_EMEM; + goto cleanup; + } + ns->depth = 1; + + /* add into the XML namespace set */ + if ((rc = ly_set_add(set, ns, 1, NULL))) { + goto cleanup; + } + ns = NULL; + +cleanup: + if (ns) { + free(ns->prefix); + free(ns->uri); + free(ns); + } + return rc; +} + +/** * @brief Implementation of ::lyplg_type_validate_clb for the xpath1.0 ietf-yang-types type. */ static LY_ERR @@ -338,7 +387,6 @@ lyplg_type_validate_xpath10(const struct ly_ctx *UNUSED(ctx), const struct lysc_ struct ly_set *set = NULL; uint32_t i; const char *pref, *uri; - struct lyxml_ns *ns; *err = NULL; LYD_VALUE_GET(storage, val); @@ -371,28 +419,8 @@ lyplg_type_validate_xpath10(const struct ly_ctx *UNUSED(ctx), const struct lysc_ assert(!strcmp(LYD_NAME(lyd_child(set->dnodes[i])->next), "uri")); uri = lyd_get_value(lyd_child(set->dnodes[i])->next); - /* create new ns */ - ns = calloc(1, sizeof *ns); - if (!ns) { - ret = LY_EMEM; - goto cleanup; - } - ns->prefix = strdup(pref); - ns->uri = strdup(uri); - if (!ns->prefix || !ns->uri) { - free(ns->prefix); - free(ns->uri); - free(ns); - ret = LY_EMEM; - goto cleanup; - } - ns->depth = 1; - - /* add into the XML namespace set */ - if ((ret = ly_set_add(val->prefix_data, ns, 1, NULL))) { - free(ns->prefix); - free(ns->uri); - free(ns); + /* new NS */ + if ((ret = xpath10_add_ns(val->prefix_data, pref, uri))) { goto cleanup; } } diff --git a/src/printer_json.c b/src/printer_json.c index 327799b..66ae154 100644 --- a/src/printer_json.c +++ b/src/printer_json.c @@ -4,7 +4,7 @@ * @author Michal Vasko <mvasko@cesnet.cz> * @brief JSON printer for libyang data structure * - * Copyright (c) 2015 - 2022 CESNET, z.s.p.o. + * Copyright (c) 2015 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -14,6 +14,7 @@ */ #include <assert.h> +#include <ctype.h> #include <stdint.h> #include <stdlib.h> @@ -45,7 +46,7 @@ struct jsonpr_ctx { uint16_t level_printed; /* level where some data were already printed */ struct ly_set open; /* currently open array(s) */ - const struct lyd_node *print_sibling_metadata; + const struct lyd_node *first_leaflist; /**< first printed leaf-list instance, used when printing its metadata/attributes */ }; /** @@ -219,31 +220,38 @@ json_nscmp(const struct lyd_node *node1, const struct lyd_node *node2) static LY_ERR json_print_string(struct ly_out *out, const char *text) { - uint64_t i, n; + uint64_t i; if (!text) { return LY_SUCCESS; } ly_write_(out, "\"", 1); - for (i = n = 0; text[i]; i++) { - const unsigned char ascii = text[i]; + for (i = 0; text[i]; i++) { + const unsigned char byte = text[i]; - if (ascii < 0x20) { - /* control character */ - ly_print_(out, "\\u%.4X", ascii); - } else { - switch (ascii) { - case '"': - ly_print_(out, "\\\""); - break; - case '\\': - ly_print_(out, "\\\\"); - break; - default: + switch (byte) { + case '"': + ly_print_(out, "\\\""); + break; + case '\\': + ly_print_(out, "\\\\"); + break; + case '\r': + ly_print_(out, "\\r"); + break; + case '\t': + ly_print_(out, "\\t"); + break; + default: + if (iscntrl(byte)) { + /* control character */ + ly_print_(out, "\\u%.4X", byte); + } else { + /* printable character (even non-ASCII UTF8) */ ly_write_(out, &text[i], 1); - n++; } + break; } } ly_write_(out, "\"", 1); @@ -294,18 +302,21 @@ json_print_member2(struct jsonpr_ctx *pctx, const struct lyd_node *parent, LY_VA /* determine prefix string */ if (name) { - const struct lys_module *mod; - switch (format) { case LY_VALUE_JSON: module_name = name->module_name; break; - case LY_VALUE_XML: - mod = ly_ctx_get_module_implemented_ns(pctx->ctx, name->module_ns); + case LY_VALUE_XML: { + const struct lys_module *mod = NULL; + + if (name->module_ns) { + mod = ly_ctx_get_module_implemented_ns(pctx->ctx, name->module_ns); + } if (mod) { module_name = mod->name; } break; + } default: /* cannot be created */ LOGINT_RET(pctx->ctx); @@ -332,15 +343,19 @@ json_print_member2(struct jsonpr_ctx *pctx, const struct lyd_node *parent, LY_VA * @param[in] pctx JSON printer context. * @param[in] ctx Context used to print the value. * @param[in] val Data value to be printed. + * @param[in] local_mod Module of the current node. * @return LY_ERR value. */ static LY_ERR -json_print_value(struct jsonpr_ctx *pctx, const struct ly_ctx *ctx, const struct lyd_value *val) +json_print_value(struct jsonpr_ctx *pctx, const struct ly_ctx *ctx, const struct lyd_value *val, + const struct lys_module *local_mod) { ly_bool dynamic; LY_DATA_TYPE basetype; - const char *value = val->realtype->plugin->print(ctx, val, LY_VALUE_JSON, NULL, &dynamic, NULL); + const char *value; + value = val->realtype->plugin->print(ctx, val, LY_VALUE_JSON, (void *)local_mod, &dynamic, NULL); + LY_CHECK_RET(!value, LY_EINVAL); basetype = val->realtype->basetype; print_val: @@ -348,7 +363,8 @@ print_val: switch (basetype) { case LY_TYPE_UNION: /* use the resolved type */ - basetype = val->subvalue->value.realtype->basetype; + val = &val->subvalue->value; + basetype = val->realtype->basetype; goto print_val; case LY_TYPE_BINARY: @@ -394,19 +410,13 @@ print_val: * * @param[in] ctx JSON printer context. * @param[in] node Opaq node where the attributes are placed. - * @param[in] wdmod With-defaults module to mark that default attribute is supposed to be printed. * @return LY_ERR value. */ static LY_ERR -json_print_attribute(struct jsonpr_ctx *pctx, const struct lyd_node_opaq *node, const struct lys_module *wdmod) +json_print_attribute(struct jsonpr_ctx *pctx, const struct lyd_node_opaq *node) { struct lyd_attr *attr; - if (wdmod) { - ly_print_(pctx->out, "%*s\"%s:default\":true", INDENT, wdmod->name); - LEVEL_PRINTED; - } - for (attr = node->attr; attr; attr = attr->next) { json_print_member2(pctx, &node->node, attr->format, &attr->name, 0); @@ -440,14 +450,14 @@ json_print_metadata(struct jsonpr_ctx *pctx, const struct lyd_node *node, const struct lyd_meta *meta; if (wdmod) { - ly_print_(pctx->out, "%*s\"%s:default\":true", INDENT, wdmod->name); + ly_print_(pctx->out, "%*s\"%s:default\":%strue", INDENT, wdmod->name, DO_FORMAT ? " " : ""); LEVEL_PRINTED; } for (meta = node->meta; meta; meta = meta->next) { PRINT_COMMA; ly_print_(pctx->out, "%*s\"%s:%s\":%s", INDENT, meta->annotation->module->name, meta->name, DO_FORMAT ? " " : ""); - LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &meta->value)); + LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &meta->value, NULL)); LEVEL_PRINTED; } @@ -495,7 +505,7 @@ json_print_attributes(struct jsonpr_ctx *pctx, const struct lyd_node *node, ly_b } ly_print_(pctx->out, "{%s", (DO_FORMAT ? "\n" : "")); LEVEL_INC; - LY_CHECK_RET(json_print_attribute(pctx, (struct lyd_node_opaq *)node, wdmod)); + LY_CHECK_RET(json_print_attribute(pctx, (struct lyd_node_opaq *)node)); LEVEL_DEC; ly_print_(pctx->out, "%s%*s}", DO_FORMAT ? "\n" : "", INDENT); LEVEL_PRINTED; @@ -515,7 +525,7 @@ static LY_ERR json_print_leaf(struct jsonpr_ctx *pctx, const struct lyd_node *node) { LY_CHECK_RET(json_print_member(pctx, node, 0)); - LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &((const struct lyd_node_term *)node)->value)); + LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &((const struct lyd_node_term *)node)->value, node->schema->module)); LEVEL_PRINTED; /* print attributes as sibling */ @@ -780,16 +790,16 @@ json_print_leaf_list(struct jsonpr_ctx *pctx, const struct lyd_node *node) } else { assert(node->schema->nodetype == LYS_LEAFLIST); - LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &((const struct lyd_node_term *)node)->value)); + LY_CHECK_RET(json_print_value(pctx, LYD_CTX(node), &((const struct lyd_node_term *)node)->value, node->schema->module)); - if (!pctx->print_sibling_metadata) { + if (!pctx->first_leaflist) { if ((node->flags & LYD_DEFAULT) && (pctx->options & (LYD_PRINT_WD_ALL_TAG | LYD_PRINT_WD_IMPL_TAG))) { /* we have implicit OR explicit default node, get with-defaults module */ wdmod = ly_ctx_get_module_implemented(LYD_CTX(node), "ietf-netconf-with-defaults"); } if (node->meta || wdmod) { /* we will be printing metadata for these siblings */ - pctx->print_sibling_metadata = node; + pctx->first_leaflist = node; } } } @@ -802,21 +812,20 @@ json_print_leaf_list(struct jsonpr_ctx *pctx, const struct lyd_node *node) } /** - * @brief Print leaf-list's metadata in case they were marked in the last call to json_print_leaf_list(). + * @brief Print leaf-list's metadata or opaque nodes attributes. * This function is supposed to be called when the leaf-list array is closed. * * @param[in] ctx JSON printer context. * @return LY_ERR value. */ static LY_ERR -json_print_metadata_leaflist(struct jsonpr_ctx *pctx) +json_print_meta_attr_leaflist(struct jsonpr_ctx *pctx) { const struct lyd_node *prev, *node, *iter; const struct lys_module *wdmod = NULL; + const struct lyd_node_opaq *opaq = NULL; - if (!pctx->print_sibling_metadata) { - return LY_SUCCESS; - } + assert(pctx->first_leaflist); if (pctx->options & (LYD_PRINT_WD_ALL_TAG | LYD_PRINT_WD_IMPL_TAG)) { /* get with-defaults module */ @@ -824,19 +833,31 @@ json_print_metadata_leaflist(struct jsonpr_ctx *pctx) } /* node is the first instance of the leaf-list */ - for (node = pctx->print_sibling_metadata, prev = pctx->print_sibling_metadata->prev; + for (node = pctx->first_leaflist, prev = pctx->first_leaflist->prev; prev->next && matching_node(node, prev); node = prev, prev = node->prev) {} - LY_CHECK_RET(json_print_member(pctx, node, 1)); + if (node->schema) { + LY_CHECK_RET(json_print_member(pctx, node, 1)); + } else { + opaq = (struct lyd_node_opaq *)node; + LY_CHECK_RET(json_print_member2(pctx, lyd_parent(node), opaq->format, &opaq->name, 1)); + } + ly_print_(pctx->out, "[%s", (DO_FORMAT ? "\n" : "")); LEVEL_INC; LY_LIST_FOR(node, iter) { PRINT_COMMA; - if (iter->meta || (iter->flags & LYD_DEFAULT)) { + if ((iter->schema && (iter->meta || (iter->flags & LYD_DEFAULT))) || (opaq && opaq->attr)) { ly_print_(pctx->out, "%*s%s", INDENT, DO_FORMAT ? "{\n" : "{"); LEVEL_INC; - LY_CHECK_RET(json_print_metadata(pctx, iter, (iter->flags & LYD_DEFAULT) ? wdmod : NULL)); + + if (iter->schema) { + LY_CHECK_RET(json_print_metadata(pctx, iter, (iter->flags & LYD_DEFAULT) ? wdmod : NULL)); + } else { + LY_CHECK_RET(json_print_attribute(pctx, (struct lyd_node_opaq *)iter)); + } + LEVEL_DEC; ly_print_(pctx->out, "%s%*s}", DO_FORMAT ? "\n" : "", INDENT); } else { @@ -897,12 +918,17 @@ json_print_opaq(struct jsonpr_ctx *pctx, const struct lyd_node_opaq *node) ly_print_(pctx->out, "%s", node->value); } else { /* string or a large number */ - ly_print_(pctx->out, "\"%s\"", node->value); + json_print_string(pctx->out, node->value); } LEVEL_PRINTED; - /* attributes */ - json_print_attributes(pctx, (const struct lyd_node *)node, 0); + if (!(node->hints & LYD_NODEHINT_LEAFLIST)) { + /* attributes */ + json_print_attributes(pctx, (const struct lyd_node *)node, 0); + } else if (!pctx->first_leaflist && node->attr) { + /* attributes printed later */ + pctx->first_leaflist = &node->node; + } } if (last && (node->hints & (LYD_NODEHINT_LIST | LYD_NODEHINT_LEAFLIST))) { @@ -959,9 +985,9 @@ json_print_node(struct jsonpr_ctx *pctx, const struct lyd_node *node) pctx->level_printed = pctx->level; - if (pctx->print_sibling_metadata && !matching_node(node->next, pctx->print_sibling_metadata)) { - json_print_metadata_leaflist(pctx); - pctx->print_sibling_metadata = NULL; + if (pctx->first_leaflist && !matching_node(node->next, pctx->first_leaflist)) { + json_print_meta_attr_leaflist(pctx); + pctx->first_leaflist = NULL; } return LY_SUCCESS; diff --git a/src/printer_lyb.c b/src/printer_lyb.c index 686c2d8..820e81e 100644 --- a/src/printer_lyb.c +++ b/src/printer_lyb.c @@ -81,7 +81,7 @@ lyb_ptr_equal_cb(void *val1_p, void *val2_p, ly_bool UNUSED(mod), void *UNUSED(c * @return LY_EEXIST when the whole hash sequence sollides. */ static LY_ERR -lyb_hash_sequence_check(struct hash_table *ht, struct lysc_node *sibling, LYB_HASH ht_col_id, LYB_HASH compare_col_id) +lyb_hash_sequence_check(struct ly_ht *ht, struct lysc_node *sibling, LYB_HASH ht_col_id, LYB_HASH compare_col_id) { struct lysc_node **col_node; @@ -123,9 +123,9 @@ lyb_hash_sequence_check(struct hash_table *ht, struct lysc_node *sibling, LYB_HA * @return LY_ERR value. */ static LY_ERR -lyb_hash_siblings(struct lysc_node *sibling, struct hash_table **ht_p) +lyb_hash_siblings(struct lysc_node *sibling, struct ly_ht **ht_p) { - struct hash_table *ht; + struct ly_ht *ht; const struct lysc_node *parent; const struct lys_module *mod; LYB_HASH i; @@ -172,7 +172,7 @@ lyb_hash_siblings(struct lysc_node *sibling, struct hash_table **ht_p) if (lyht_insert(ht, &sibling, lyb_get_hash(sibling, i), NULL)) { LOGINT(sibling->module->ctx); lyht_set_cb(ht, lyb_hash_equal_cb); - lyht_free(ht); + lyht_free(ht, NULL); return LY_EINT; } lyht_set_cb(ht, lyb_hash_equal_cb); @@ -184,7 +184,7 @@ lyb_hash_siblings(struct lysc_node *sibling, struct hash_table **ht_p) if (i == LYB_HASH_BITS) { /* wow */ LOGINT(sibling->module->ctx); - lyht_free(ht); + lyht_free(ht, NULL); return LY_EINT; } } @@ -205,7 +205,7 @@ lyb_hash_siblings(struct lysc_node *sibling, struct hash_table **ht_p) * @return LY_ERR value. */ static LY_ERR -lyb_hash_find(struct hash_table *ht, struct lysc_node *node, LYB_HASH *hash_p) +lyb_hash_find(struct ly_ht *ht, struct lysc_node *node, LYB_HASH *hash_p) { LYB_HASH hash; uint32_t i; @@ -701,7 +701,7 @@ lyb_print_term_value(struct lyd_node_term *term, struct ly_out *out, struct lyly if (value_len > UINT32_MAX) { LOGERR(lybctx->ctx, LY_EINT, "The maximum length of the LYB data " - "from a term node must not exceed %lu.", UINT32_MAX); + "from a term node must not exceed %" PRIu32 ".", UINT32_MAX); ret = LY_EINT; goto cleanup; } @@ -856,7 +856,7 @@ lyb_print_attributes(struct ly_out *out, const struct lyd_node_opaq *node, struc * @return LY_ERR value. */ static LY_ERR -lyb_print_schema_hash(struct ly_out *out, struct lysc_node *schema, struct hash_table **sibling_ht, struct lylyb_ctx *lybctx) +lyb_print_schema_hash(struct ly_out *out, struct lysc_node *schema, struct ly_ht **sibling_ht, struct lylyb_ctx *lybctx) { LY_ARRAY_COUNT_TYPE u; uint32_t i; @@ -1205,7 +1205,7 @@ lyb_print_node_list(struct ly_out *out, const struct lyd_node *node, struct lyd_ * @return LY_ERR value. */ static LY_ERR -lyb_print_node(struct ly_out *out, const struct lyd_node **printed_node, struct hash_table **sibling_ht, +lyb_print_node(struct ly_out *out, const struct lyd_node **printed_node, struct ly_ht **sibling_ht, struct lyd_lyb_ctx *lybctx) { const struct lyd_node *node = *printed_node; @@ -1256,32 +1256,23 @@ lyb_print_node(struct ly_out *out, const struct lyd_node **printed_node, struct static LY_ERR lyb_print_siblings(struct ly_out *out, const struct lyd_node *node, struct lyd_lyb_ctx *lybctx) { - struct hash_table *sibling_ht = NULL; + struct ly_ht *sibling_ht = NULL; const struct lys_module *prev_mod = NULL; - ly_bool top_level; - - top_level = !LY_ARRAY_COUNT(lybctx->lybctx->siblings); LY_CHECK_RET(lyb_write_start_siblings(out, lybctx->lybctx)); - if (top_level) { - /* write all the siblings */ - LY_LIST_FOR(node, node) { - /* do not reuse sibling hash tables from different modules */ - if (!node->schema || (node->schema->module != prev_mod)) { - sibling_ht = NULL; - prev_mod = node->schema ? node->schema->module : NULL; - } + /* write all the siblings */ + LY_LIST_FOR(node, node) { + /* do not reuse top-level sibling hash tables from different modules */ + if (!node->schema || (!lysc_data_parent(node->schema) && (node->schema->module != prev_mod))) { + sibling_ht = NULL; + prev_mod = node->schema ? node->schema->module : NULL; + } - LY_CHECK_RET(lyb_print_node(out, &node, &sibling_ht, lybctx)); + LY_CHECK_RET(lyb_print_node(out, &node, &sibling_ht, lybctx)); - if (!(lybctx->print_options & LYD_PRINT_WITHSIBLINGS)) { - break; - } - } - } else { - LY_LIST_FOR(node, node) { - LY_CHECK_RET(lyb_print_node(out, &node, &sibling_ht, lybctx)); + if (!lyd_parent(node) && !(lybctx->print_options & LYD_PRINT_WITHSIBLINGS)) { + break; } } @@ -1299,9 +1290,9 @@ lyb_print_data(struct ly_out *out, const struct lyd_node *root, uint32_t options const struct ly_ctx *ctx = root ? LYD_CTX(root) : NULL; lybctx = calloc(1, sizeof *lybctx); - LY_CHECK_ERR_RET(!lybctx, LOGMEM(ctx), LY_EMEM); + LY_CHECK_ERR_GOTO(!lybctx, LOGMEM(ctx); ret = LY_EMEM, cleanup); lybctx->lybctx = calloc(1, sizeof *lybctx->lybctx); - LY_CHECK_ERR_RET(!lybctx->lybctx, LOGMEM(ctx), LY_EMEM); + LY_CHECK_ERR_GOTO(!lybctx->lybctx, LOGMEM(ctx); ret = LY_EMEM, cleanup); lybctx->print_options = options; if (root) { diff --git a/src/printer_schema.c b/src/printer_schema.c index 075c519..12213bb 100644 --- a/src/printer_schema.c +++ b/src/printer_schema.c @@ -70,11 +70,6 @@ lys_print_module(struct ly_out *out, const struct lys_module *module, LYS_OUTFOR } ret = tree_print_module(out, module, options, line_length); break; - /* TODO not yet implemented - case LYS_OUT_INFO: - ret = info_print_model(out, module, target_node); - break; - */ default: LOGERR(module->ctx, LY_EINVAL, "Unsupported output format."); ret = LY_EINVAL; @@ -105,11 +100,6 @@ lys_print_submodule(struct ly_out *out, const struct lysp_submodule *submodule, case LYS_OUT_TREE: ret = tree_print_parsed_submodule(out, submodule, options, line_length); break; - /* TODO not yet implemented - case LYS_OUT_INFO: - ret = info_print_model(out, module, target_node); - break; - */ default: LOGERR(submodule->mod->ctx, LY_EINVAL, "Unsupported output format."); ret = LY_EINVAL; @@ -204,11 +194,6 @@ lys_print_node(struct ly_out *out, const struct lysc_node *node, LYS_OUTFORMAT f case LYS_OUT_YANG_COMPILED: ret = yang_print_compiled_node(out, node, options); break; - /* TODO not yet implemented - case LYS_OUT_YIN: - ret = yin_print_parsed(out, module); - break; - */ case LYS_OUT_TREE: ret = tree_print_compiled_node(out, node, options, line_length); break; diff --git a/src/printer_tree.c b/src/printer_tree.c index 6a7e7ce..6aa2814 100644 --- a/src/printer_tree.c +++ b/src/printer_tree.c @@ -1873,7 +1873,7 @@ trp_ext_is_present(ly_bool lysc_tree, const void *node) } else { pn = (const struct lysp_node *)node; LY_ARRAY_FOR(pn->exts, i) { - if (!(pn->exts && pn->exts->record->plugin.printer_ptree)) { + if (!(pn->exts && pn->exts->record && pn->exts->record->plugin.printer_ptree)) { continue; } if (!trp_ext_parent_is_valid(0, &pn->exts[i])) { @@ -3426,7 +3426,7 @@ troc_modi_first_sibling(struct trt_parent_cache ca, struct trt_tree_ctx *tc) /* current node is top-node */ switch (tc->section) { case TRD_SECT_MODULE: - tc->cn = tc->cmod->data; + tc->cn = tc->cn->module->compiled->data; break; case TRD_SECT_RPCS: tc->cn = (const struct lysc_node *)tc->cmod->rpcs; @@ -3486,7 +3486,8 @@ trb_gap_to_opts(const struct trt_node *node) } if (node->name.module_prefix) { - len += strlen(node->name.module_prefix); + /* prefix_name and ':' */ + len += strlen(node->name.module_prefix) + 1; } if (node->name.str) { len += strlen(node->name.str); @@ -3753,7 +3754,10 @@ trb_print_parents(const struct lysc_node *node, struct trt_wrapper *wr_in, struc /* print node */ ly_print_(pc->out, "\n"); print_node = pc->fp.read.node(TRP_EMPTY_PARENT_CACHE, tc); + /* siblings do not print, so the node is always considered the last */ + print_node.last_one = 1; max_gap_before_type = trb_max_gap_to_type(TRP_EMPTY_PARENT_CACHE, pc, tc); + tc->cn = node; trb_print_entire_node(&print_node, max_gap_before_type, wr, pc, tc); } @@ -3856,22 +3860,36 @@ trb_ext_iter(const struct trt_tree_ctx *tc, uint64_t *i) * @param[in] compiled if @p ext is lysc structure. * @param[in] ext current processed extension. * @param[out] plug_ctx is plugin context which will be initialized. + * @param[out] ignore plugin callback is NULL. * @return LY_ERR value. */ static LY_ERR -tro_ext_printer_tree(ly_bool compiled, void *ext, const struct lyspr_tree_ctx *plug_ctx) +tro_ext_printer_tree(ly_bool compiled, void *ext, const struct lyspr_tree_ctx *plug_ctx, ly_bool *ignore) { struct lysc_ext_instance *ext_comp; struct lysp_ext_instance *ext_pars; + const struct lyplg_ext *plugin; const char *flags = NULL, *add_opts = NULL; if (compiled) { ext_comp = ext; - return ext_comp->def->plugin->printer_ctree(ext, plug_ctx, &flags, &add_opts); + plugin = ext_comp->def->plugin; + if (!plugin->printer_ctree) { + *ignore = 1; + return LY_SUCCESS; + } + return plugin->printer_ctree(ext, plug_ctx, &flags, &add_opts); } else { ext_pars = ext; - return ext_pars->record->plugin.printer_ptree(ext, plug_ctx, &flags, &add_opts); + plugin = &ext_pars->record->plugin; + if (!plugin->printer_ptree) { + *ignore = 1; + return LY_SUCCESS; + } + return plugin->printer_ptree(ext, plug_ctx, &flags, &add_opts); } + + return LY_SUCCESS; } /** @@ -3986,7 +4004,7 @@ trb_ext_print_instances(struct trt_wrapper wr, struct trt_parent_cache ca, struc LY_ARRAY_COUNT_TYPE i; uint64_t last_instance = UINT64_MAX; void *ext; - ly_bool child_exists; + ly_bool child_exists, ignore = 0; uint32_t max, max_gap_before_type = 0; ca = tro_parent_cache_for_child(ca, tc); @@ -4004,8 +4022,12 @@ trb_ext_print_instances(struct trt_wrapper wr, struct trt_parent_cache ca, struc while ((ext = trb_ext_iter(tc, &i))) { struct lyspr_tree_ctx plug_ctx = {0}; - rc = tro_ext_printer_tree(tc->lysc_tree, ext, &plug_ctx); + rc = tro_ext_printer_tree(tc->lysc_tree, ext, &plug_ctx, &ignore); LY_CHECK_ERR_GOTO(rc, tc->last_error = rc, end); + if (ignore) { + ignore = 0; + continue; + } trb_ext_try_unified_indent(&plug_ctx, ca, &max_gap_before_type, pc, tc); if (plug_ctx.schemas) { last_instance = i; @@ -4024,8 +4046,12 @@ trb_ext_print_instances(struct trt_wrapper wr, struct trt_parent_cache ca, struc while ((ext = trb_ext_iter(tc, &i))) { struct lyspr_tree_ctx plug_ctx = {0}; - rc = tro_ext_printer_tree(tc->lysc_tree, ext, &plug_ctx); + rc = tro_ext_printer_tree(tc->lysc_tree, ext, &plug_ctx, &ignore); LY_CHECK_ERR_GOTO(rc, tc->last_error = rc, end); + if (ignore) { + ignore = 0; + continue; + } if (!child_exists && (last_instance == i)) { trb_ext_print_schemas(&plug_ctx, 1, max_gap_before_type, wr, ca, pc, tc); } else { @@ -4491,6 +4517,7 @@ trm_print_plugin_ext(struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) struct trt_printer_ctx pc_dupl; struct trt_tree_ctx tc_dupl; struct trt_node node; + ly_bool ignore = 0; uint32_t max_gap_before_type; void *ext; @@ -4504,9 +4531,10 @@ trm_print_plugin_ext(struct trt_printer_ctx *pc, struct trt_tree_ctx *tc) while ((ext = trb_mod_ext_iter(tc, &i))) { struct lyspr_tree_ctx plug_ctx = {0}; - rc = tro_ext_printer_tree(tc->lysc_tree, ext, &plug_ctx); + rc = tro_ext_printer_tree(tc->lysc_tree, ext, &plug_ctx, &ignore); LY_CHECK_ERR_GOTO(rc, tc->last_error = rc, end); - if (!plug_ctx.schemas) { + if (!plug_ctx.schemas || ignore) { + ignore = 0; continue; } diff --git a/src/printer_xml.c b/src/printer_xml.c index a7f4c73..9cd6774 100644 --- a/src/printer_xml.c +++ b/src/printer_xml.c @@ -110,7 +110,9 @@ xml_print_ns_opaq(struct xmlpr_ctx *pctx, LY_VALUE_FORMAT format, const struct l { switch (format) { case LY_VALUE_XML: - return xml_print_ns(pctx, name->module_ns, (prefix_opts & LYXML_PREFIX_DEFAULT) ? NULL : name->prefix, prefix_opts); + if (name->module_ns) { + return xml_print_ns(pctx, name->module_ns, (prefix_opts & LYXML_PREFIX_DEFAULT) ? NULL : name->prefix, prefix_opts); + } break; case LY_VALUE_JSON: if (name->module_name) { @@ -178,6 +180,8 @@ xml_print_meta(struct xmlpr_ctx *pctx, const struct lyd_node *node) struct ly_set ns_list = {0}; LY_ARRAY_COUNT_TYPE u; ly_bool dynamic, filter_attrs = 0; + const char *value; + uint32_t i; /* with-defaults */ if (node->schema->nodetype & LYD_NODE_TERM) { @@ -205,11 +209,14 @@ xml_print_meta(struct xmlpr_ctx *pctx, const struct lyd_node *node) } for (meta = node->meta; meta; meta = meta->next) { - const char *value = meta->value.realtype->plugin->print(LYD_CTX(node), &meta->value, LY_VALUE_XML, &ns_list, - &dynamic, NULL); + /* store the module of the default namespace, NULL because there is none */ + ly_set_add(&ns_list, NULL, 0, NULL); + + /* print the value */ + value = meta->value.realtype->plugin->print(LYD_CTX(node), &meta->value, LY_VALUE_XML, &ns_list, &dynamic, NULL); /* print namespaces connected with the value's prefixes */ - for (uint32_t i = 0; i < ns_list.count; ++i) { + for (i = 1; i < ns_list.count; ++i) { mod = ns_list.objs[i]; xml_print_ns(pctx, mod->ns, mod->prefix, 1); } @@ -314,22 +321,32 @@ static LY_ERR xml_print_node(struct xmlpr_ctx *pctx, const struct lyd_node *node static LY_ERR xml_print_term(struct xmlpr_ctx *pctx, const struct lyd_node_term *node) { + LY_ERR rc = LY_SUCCESS; struct ly_set ns_list = {0}; - ly_bool dynamic; - const char *value; + ly_bool dynamic = 0; + const char *value = NULL; + const struct lys_module *mod; + uint32_t i; - xml_print_node_open(pctx, &node->node); + /* store the module of the default namespace */ + if ((rc = ly_set_add(&ns_list, node->schema->module, 0, NULL))) { + LOGMEM(pctx->ctx); + goto cleanup; + } + + /* print the value */ value = ((struct lysc_node_leaf *)node->schema)->type->plugin->print(LYD_CTX(node), &node->value, LY_VALUE_XML, &ns_list, &dynamic, NULL); - LY_CHECK_RET(!value, LY_EINVAL); + LY_CHECK_ERR_GOTO(!value, rc = LY_EINVAL, cleanup); - /* print namespaces connected with the values's prefixes */ - for (uint32_t u = 0; u < ns_list.count; ++u) { - const struct lys_module *mod = (const struct lys_module *)ns_list.objs[u]; + /* print node opening */ + xml_print_node_open(pctx, &node->node); + /* print namespaces connected with the values's prefixes */ + for (i = 1; i < ns_list.count; ++i) { + mod = ns_list.objs[i]; ly_print_(pctx->out, " xmlns:%s=\"%s\"", mod->prefix, mod->ns); } - ly_set_erase(&ns_list, NULL); if (!value[0]) { ly_print_(pctx->out, "/>%s", DO_FORMAT ? "\n" : ""); @@ -338,11 +355,13 @@ xml_print_term(struct xmlpr_ctx *pctx, const struct lyd_node_term *node) lyxml_dump_text(pctx->out, value, 0); ly_print_(pctx->out, "</%s>%s", node->schema->name, DO_FORMAT ? "\n" : ""); } + +cleanup: + ly_set_erase(&ns_list, NULL); if (dynamic) { free((void *)value); } - - return LY_SUCCESS; + return rc; } /** diff --git a/src/printer_yang.c b/src/printer_yang.c index ea643ac..8e0af18 100644 --- a/src/printer_yang.c +++ b/src/printer_yang.c @@ -628,9 +628,7 @@ yprc_must(struct lys_ypr_ctx *pctx, const struct lysc_must *must, ly_bool *flag) ly_bool inner_flag = 0; ypr_open(pctx->out, flag); - ly_print_(pctx->out, "%*smust \"", INDENT); - ypr_encode(pctx->out, must->cond->expr, -1); - ly_print_(pctx->out, "\""); + ypr_text(pctx, "must", must->cond->expr, 1, 0); LEVEL++; yprc_extension_instances(pctx, LY_STMT_MUST, 0, must->exts, &inner_flag); @@ -773,9 +771,7 @@ yprp_when(struct lys_ypr_ctx *pctx, const struct lysp_when *when, ly_bool *flag) } ypr_open(pctx->out, flag); - ly_print_(pctx->out, "%*swhen \"", INDENT); - ypr_encode(pctx->out, when->cond, -1); - ly_print_(pctx->out, "\""); + ypr_text(pctx, "when", when->cond, 1, 0); LEVEL++; yprp_extension_instances(pctx, LY_STMT_WHEN, 0, when->exts, &inner_flag); @@ -795,9 +791,7 @@ yprc_when(struct lys_ypr_ctx *pctx, const struct lysc_when *when, ly_bool *flag) } ypr_open(pctx->out, flag); - ly_print_(pctx->out, "%*swhen \"", INDENT); - ypr_encode(pctx->out, when->cond->expr, -1); - ly_print_(pctx->out, "\""); + ypr_text(pctx, "when", when->cond->expr, 1, 0); LEVEL++; yprc_extension_instances(pctx, LY_STMT_WHEN, 0, when->exts, &inner_flag); diff --git a/src/schema_compile.c b/src/schema_compile.c index ed768ba..1232228 100644 --- a/src/schema_compile.c +++ b/src/schema_compile.c @@ -437,6 +437,10 @@ lys_compile_expr_implement(const struct ly_ctx *ctx, const struct lyxp_expr *exp assert(implement || mod_p); + if (mod_p) { + *mod_p = NULL; + } + for (i = 0; i < expr->used; ++i) { if ((expr->tokens[i] != LYXP_TOKEN_NAMETEST) && (expr->tokens[i] != LYXP_TOKEN_LITERAL)) { /* token cannot have a prefix */ @@ -476,38 +480,6 @@ lys_compile_expr_implement(const struct ly_ctx *ctx, const struct lyxp_expr *exp } /** - * @brief Check and optionally implement modules referenced by a when expression. - * - * @param[in] ctx Compile context. - * @param[in] when When to check. - * @param[in,out] unres Global unres structure. - * @return LY_ERECOMPILE if the whole dep set needs to be recompiled for these whens to evaluate. - * @return LY_ENOT if full check of this when should be skipped. - * @return LY_ERR value on error. - */ -static LY_ERR -lys_compile_unres_when_implement(struct lysc_ctx *ctx, const struct lysc_when *when, struct lys_glob_unres *unres) -{ - LY_ERR rc = LY_SUCCESS; - const struct lys_module *mod = NULL; - - /* check whether all the referenced modules are implemented */ - rc = lys_compile_expr_implement(ctx->ctx, when->cond, LY_VALUE_SCHEMA_RESOLVED, when->prefixes, - ctx->ctx->flags & LY_CTX_REF_IMPLEMENTED, unres, &mod); - if (rc) { - goto cleanup; - } else if (mod) { - LOGWRN(ctx->ctx, "When condition \"%s\" check skipped because referenced module \"%s\" is not implemented.", - when->cond->expr, mod->name); - rc = LY_ENOT; - goto cleanup; - } - -cleanup: - return rc; -} - -/** * @brief Check when for cyclic dependencies. * * @param[in] set Set with all the referenced nodes. @@ -519,7 +491,7 @@ lys_compile_unres_when_cyclic(struct lyxp_set *set, const struct lysc_node *node { struct lyxp_set tmp_set; struct lyxp_set_scnode *xp_scnode; - uint32_t i, j; + uint32_t i, j, idx; LY_ARRAY_COUNT_TYPE u; LY_ERR ret = LY_SUCCESS; @@ -565,36 +537,46 @@ lys_compile_unres_when_cyclic(struct lyxp_set *set, const struct lysc_node *node } for (j = 0; j < tmp_set.used; ++j) { - /* skip roots'n'stuff */ - if (tmp_set.val.scnodes[j].type == LYXP_NODE_ELEM) { - /* try to find this node in our set */ - uint32_t idx; - - if (lyxp_set_scnode_contains(set, tmp_set.val.scnodes[j].scnode, LYXP_NODE_ELEM, -1, &idx) && - (set->val.scnodes[idx].in_ctx == LYXP_SET_SCNODE_START_USED)) { - LOGVAL(set->ctx, LYVE_SEMANTICS, "When condition cyclic dependency on the node \"%s\".", - tmp_set.val.scnodes[j].scnode->name); - ret = LY_EVALID; - LOG_LOCBACK(1, 0, 0, 0); - goto cleanup; - } - - /* needs to be checked, if in both sets, will be ignored */ - tmp_set.val.scnodes[j].in_ctx = LYXP_SET_SCNODE_ATOM_CTX; - } else { - /* no when, nothing to check */ + if (tmp_set.val.scnodes[j].type != LYXP_NODE_ELEM) { + /* skip roots'n'stuff, no when, nothing to check */ tmp_set.val.scnodes[j].in_ctx = LYXP_SET_SCNODE_ATOM_NODE; + continue; + } + + /* try to find this node in our set */ + if (lyxp_set_scnode_contains(set, tmp_set.val.scnodes[j].scnode, LYXP_NODE_ELEM, -1, &idx) && + (set->val.scnodes[idx].in_ctx == LYXP_SET_SCNODE_START_USED)) { + LOGVAL(set->ctx, LYVE_SEMANTICS, "When condition cyclic dependency on the node \"%s\".", + tmp_set.val.scnodes[j].scnode->name); + ret = LY_EVALID; + LOG_LOCBACK(1, 0, 0, 0); + goto cleanup; + } + + /* needs to be checked, if in both sets, will be ignored */ + tmp_set.val.scnodes[j].in_ctx = LYXP_SET_SCNODE_ATOM_CTX; + } + + if (when->context != node) { + /* node actually depends on this "when", not the context node */ + assert(tmp_set.val.scnodes[0].scnode == when->context); + if (tmp_set.val.scnodes[0].in_ctx == LYXP_SET_SCNODE_START_USED) { + /* replace the non-traversed context node with the dependent node */ + tmp_set.val.scnodes[0].scnode = (struct lysc_node *)node; + } else { + /* context node was traversed, so just add the dependent node */ + ret = lyxp_set_scnode_insert_node(&tmp_set, node, LYXP_SET_SCNODE_START_USED, LYXP_AXIS_CHILD, NULL); + LY_CHECK_ERR_GOTO(ret, LOG_LOCBACK(1, 0, 0, 0), cleanup); } } /* merge this set into the global when set */ lyxp_set_scnode_merge(set, &tmp_set); } + LOG_LOCBACK(1, 0, 0, 0); /* check when of non-data parents as well */ node = node->parent; - - LOG_LOCBACK(1, 0, 0, 0); } while (node && (node->nodetype & (LYS_CASE | LYS_CHOICE))); /* this node when was checked (xp_scnode could have been reallocd) */ @@ -640,6 +622,7 @@ lys_compile_unres_when(struct lysc_ctx *ctx, const struct lysc_when *when, const { struct lyxp_set tmp_set = {0}; uint32_t i, opts; + struct lysc_node *schema; LY_ERR ret = LY_SUCCESS; opts = LYXP_SCNODE_SCHEMA | ((node->flags & LYS_IS_OUTPUT) ? LYXP_SCNODE_OUTPUT : 0); @@ -655,28 +638,45 @@ lys_compile_unres_when(struct lysc_ctx *ctx, const struct lysc_when *when, const ctx->path[0] = '\0'; lysc_path(node, LYSC_PATH_LOG, ctx->path, LYSC_CTX_BUFSIZE); for (i = 0; i < tmp_set.used; ++i) { - /* skip roots'n'stuff */ - if ((tmp_set.val.scnodes[i].type == LYXP_NODE_ELEM) && - (tmp_set.val.scnodes[i].in_ctx != LYXP_SET_SCNODE_START_USED)) { - struct lysc_node *schema = tmp_set.val.scnodes[i].scnode; - - /* XPath expression cannot reference "lower" status than the node that has the definition */ - if (lysc_check_status(NULL, when->flags, node->module, node->name, schema->flags, schema->module, - schema->name)) { - LOGWRN(ctx->ctx, "When condition \"%s\" may be referencing %s node \"%s\".", when->cond->expr, - (schema->flags == LYS_STATUS_OBSLT) ? "obsolete" : "deprecated", schema->name); - } + if (tmp_set.val.scnodes[i].type != LYXP_NODE_ELEM) { + /* skip roots'n'stuff */ + continue; + } else if (tmp_set.val.scnodes[i].in_ctx == LYXP_SET_SCNODE_START_USED) { + /* context node not actually traversed */ + continue; + } - /* check dummy node children/value accessing */ - if (lysc_data_parent(schema) == node) { - LOGVAL(ctx->ctx, LYVE_SEMANTICS, "When condition is accessing its own conditional node children."); - ret = LY_EVALID; - goto cleanup; - } else if ((schema == node) && (tmp_set.val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_VAL)) { - LOGVAL(ctx->ctx, LYVE_SEMANTICS, "When condition is accessing its own conditional node value."); - ret = LY_EVALID; - goto cleanup; - } + schema = tmp_set.val.scnodes[i].scnode; + + /* XPath expression cannot reference "lower" status than the node that has the definition */ + if (lysc_check_status(NULL, when->flags, node->module, node->name, schema->flags, schema->module, + schema->name)) { + LOGWRN(ctx->ctx, "When condition \"%s\" may be referencing %s node \"%s\".", when->cond->expr, + (schema->flags == LYS_STATUS_OBSLT) ? "obsolete" : "deprecated", schema->name); + } + + /* check dummy node children/value accessing */ + if (lysc_data_parent(schema) == node) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "When condition is accessing its own conditional node children."); + ret = LY_EVALID; + goto cleanup; + } else if ((schema == node) && (tmp_set.val.scnodes[i].in_ctx == LYXP_SET_SCNODE_ATOM_VAL)) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "When condition is accessing its own conditional node value."); + ret = LY_EVALID; + goto cleanup; + } + } + + if (when->context != node) { + /* node actually depends on this "when", not the context node */ + assert(tmp_set.val.scnodes[0].scnode == when->context); + if (tmp_set.val.scnodes[0].in_ctx == LYXP_SET_SCNODE_START_USED) { + /* replace the non-traversed context node with the dependent node */ + tmp_set.val.scnodes[0].scnode = (struct lysc_node *)node; + } else { + /* context node was traversed, so just add the dependent node */ + ret = lyxp_set_scnode_insert_node(&tmp_set, node, LYXP_SET_SCNODE_START_USED, LYXP_AXIS_CHILD, NULL); + LY_CHECK_GOTO(ret, cleanup); } } @@ -695,20 +695,16 @@ cleanup: * @param[in] ctx Compile context. * @param[in] node Node to check. * @param[in] local_mods Sized array of local modules for musts of @p node at the same index. - * @param[in,out] unres Global unres structure. - * @return LY_ERECOMPILE - * @return LY_ERR value + * @return LY_ERR value. */ static LY_ERR -lys_compile_unres_must(struct lysc_ctx *ctx, const struct lysc_node *node, const struct lysp_module **local_mods, - struct lys_glob_unres *unres) +lys_compile_unres_must(struct lysc_ctx *ctx, const struct lysc_node *node, const struct lysp_module **local_mods) { struct lyxp_set tmp_set; uint32_t i, opts; LY_ARRAY_COUNT_TYPE u; - struct lysc_must *musts = NULL; + struct lysc_must *musts; LY_ERR ret = LY_SUCCESS; - const struct lys_module *mod; uint16_t flg; LOG_LOCSET(node, NULL, NULL, NULL); @@ -718,18 +714,6 @@ lys_compile_unres_must(struct lysc_ctx *ctx, const struct lysc_node *node, const musts = lysc_node_musts(node); LY_ARRAY_FOR(musts, u) { - /* first check whether all the referenced modules are implemented */ - mod = NULL; - ret = lys_compile_expr_implement(ctx->ctx, musts[u].cond, LY_VALUE_SCHEMA_RESOLVED, musts[u].prefixes, - ctx->ctx->flags & LY_CTX_REF_IMPLEMENTED, unres, &mod); - if (ret) { - goto cleanup; - } else if (mod) { - LOGWRN(ctx->ctx, "Must condition \"%s\" check skipped because referenced module \"%s\" is not implemented.", - musts[u].cond->expr, mod->name); - continue; - } - /* check "must" */ ret = lyxp_atomize(ctx->ctx, musts[u].cond, node->module, LY_VALUE_SCHEMA_RESOLVED, musts[u].prefixes, node, node, &tmp_set, opts); @@ -815,6 +799,7 @@ lys_compile_unres_disabled_bitenum(struct lysc_ctx *ctx, struct lysc_node_leaf * struct lysc_type **t; LY_ARRAY_COUNT_TYPE u, count; struct lysc_type_enum *ent; + ly_bool has_value = 0; if (leaf->type->basetype == LY_TYPE_UNION) { t = ((struct lysc_type_union *)leaf->type)->types; @@ -829,14 +814,19 @@ lys_compile_unres_disabled_bitenum(struct lysc_ctx *ctx, struct lysc_node_leaf * ent = (struct lysc_type_enum *)(t[u]); lys_compile_unres_disabled_bitenum_remove(&ctx->free_ctx, ent->enums); - if (!LY_ARRAY_COUNT(ent->enums)) { - LOGVAL(ctx->ctx, LYVE_SEMANTICS, "%s type of node \"%s\" without any (or all disabled) valid values.", - (ent->basetype == LY_TYPE_BITS) ? "Bits" : "Enumeration", leaf->name); - return LY_EVALID; + if (LY_ARRAY_COUNT(ent->enums)) { + has_value = 1; } + } else { + has_value = 1; } } + if (!has_value) { + LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Node \"%s\" without any (or all disabled) valid values.", leaf->name); + return LY_EVALID; + } + return LY_SUCCESS; } @@ -847,13 +837,11 @@ lys_compile_unres_disabled_bitenum(struct lysc_ctx *ctx, struct lysc_node_leaf * * @param[in] node Context node for the leafref. * @param[in] lref Leafref to check/resolve. * @param[in] local_mod Local module for the leafref type. - * @param[in,out] unres Global unres structure. - * @return LY_ERECOMPILE if context recompilation is needed, * @return LY_ERR value. */ static LY_ERR lys_compile_unres_leafref(struct lysc_ctx *ctx, const struct lysc_node *node, struct lysc_type_leafref *lref, - const struct lysp_module *local_mod, struct lys_glob_unres *unres) + const struct lysp_module *local_mod) { const struct lysc_node *target = NULL; struct ly_path *p; @@ -862,8 +850,10 @@ lys_compile_unres_leafref(struct lysc_ctx *ctx, const struct lysc_node *node, st assert(node->nodetype & (LYS_LEAF | LYS_LEAFLIST)); - /* first implement all the modules in the path */ - LY_CHECK_RET(lys_compile_expr_implement(ctx->ctx, lref->path, LY_VALUE_SCHEMA_RESOLVED, lref->prefixes, 1, unres, NULL)); + if (lref->realtype) { + /* already resolved, may happen (shared union typedef with a leafref) */ + return LY_SUCCESS; + } /* try to find the target, current module is that of the context node (RFC 7950 6.4.1 second bullet) */ LY_CHECK_RET(ly_path_compile_leafref(ctx->ctx, node, ctx->ext, lref->path, @@ -934,6 +924,7 @@ lys_compile_unres_leafref(struct lysc_ctx *ctx, const struct lysc_node *node, st * @param[in] dflt_pmod Parsed module of the @p dflt to resolve possible prefixes. * @param[in,out] storage Storage for the compiled default value. * @param[in,out] unres Global unres structure for newly implemented modules. + * @return LY_ERECOMPILE if the whole dep set needs to be recompiled for the value to be checked. * @return LY_ERR value. */ static LY_ERR @@ -1057,7 +1048,8 @@ lys_compile_unres_llist_dflts(struct lysc_ctx *ctx, struct lysc_node_leaflist *l if (!llist->dflts[u]->realtype->plugin->compare(llist->dflts[u], llist->dflts[v])) { lysc_update_path(ctx, llist->parent ? llist->parent->module : NULL, llist->name); LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Configuration leaf-list has multiple defaults of the same value \"%s\".", - llist->dflts[u]->realtype->plugin->print(ctx->ctx, llist->dflts[u], LY_VALUE_CANON, NULL, NULL, NULL)); + (char *)llist->dflts[u]->realtype->plugin->print(ctx->ctx, llist->dflts[u], LY_VALUE_CANON, + NULL, NULL, NULL)); lysc_update_path(ctx, NULL, NULL); return LY_EVALID; } @@ -1114,6 +1106,108 @@ lys_type_leafref_next(const struct lysc_node *node, uint64_t *index) } /** + * @brief Implement all referenced modules by leafrefs, when and must conditions. + * + * @param[in] ctx libyang context. + * @param[in] unres Global unres structure with the sets to resolve. + * @return LY_SUCCESS on success. + * @return LY_ERECOMPILE if the whole dep set needs to be recompiled with the new implemented modules. + * @return LY_ERR value on error. + */ +static LY_ERR +lys_compile_unres_depset_implement(struct ly_ctx *ctx, struct lys_glob_unres *unres) +{ + struct lys_depset_unres *ds_unres = &unres->ds_unres; + struct lysc_type_leafref *lref; + const struct lys_module *mod; + LY_ARRAY_COUNT_TYPE u; + struct lysc_unres_leafref *l; + struct lysc_unres_when *w; + struct lysc_unres_must *m; + struct lysc_must *musts; + ly_bool not_implemented; + uint32_t di = 0, li = 0, wi = 0, mi = 0; + +implement_all: + /* disabled leafrefs - even those because we need to check their target exists */ + while (di < ds_unres->disabled_leafrefs.count) { + l = ds_unres->disabled_leafrefs.objs[di]; + + u = 0; + while ((lref = lys_type_leafref_next(l->node, &u))) { + LY_CHECK_RET(lys_compile_expr_implement(ctx, lref->path, LY_VALUE_SCHEMA_RESOLVED, lref->prefixes, 1, unres, NULL)); + } + + ++di; + } + + /* leafrefs */ + while (li < ds_unres->leafrefs.count) { + l = ds_unres->leafrefs.objs[li]; + + u = 0; + while ((lref = lys_type_leafref_next(l->node, &u))) { + LY_CHECK_RET(lys_compile_expr_implement(ctx, lref->path, LY_VALUE_SCHEMA_RESOLVED, lref->prefixes, 1, unres, NULL)); + } + + ++li; + } + + /* when conditions */ + while (wi < ds_unres->whens.count) { + w = ds_unres->whens.objs[wi]; + + LY_CHECK_RET(lys_compile_expr_implement(ctx, w->when->cond, LY_VALUE_SCHEMA_RESOLVED, w->when->prefixes, + ctx->flags & LY_CTX_REF_IMPLEMENTED, unres, &mod)); + if (mod) { + LOGWRN(ctx, "When condition \"%s\" check skipped because referenced module \"%s\" is not implemented.", + w->when->cond->expr, mod->name); + + /* remove from the set to skip the check */ + ly_set_rm_index(&ds_unres->whens, wi, free); + continue; + } + + ++wi; + } + + /* must conditions */ + while (mi < ds_unres->musts.count) { + m = ds_unres->musts.objs[mi]; + + not_implemented = 0; + musts = lysc_node_musts(m->node); + LY_ARRAY_FOR(musts, u) { + LY_CHECK_RET(lys_compile_expr_implement(ctx, musts[u].cond, LY_VALUE_SCHEMA_RESOLVED, musts[u].prefixes, + ctx->flags & LY_CTX_REF_IMPLEMENTED, unres, &mod)); + if (mod) { + LOGWRN(ctx, "Must condition \"%s\" check skipped because referenced module \"%s\" is not implemented.", + musts[u].cond->expr, mod->name); + + /* need to implement modules from all the expressions */ + not_implemented = 1; + } + } + + if (not_implemented) { + /* remove from the set to skip the check */ + lysc_unres_must_free(m); + ly_set_rm_index(&ds_unres->musts, mi, NULL); + continue; + } + + ++mi; + } + + if ((di < ds_unres->disabled_leafrefs.count) || (li < ds_unres->leafrefs.count) || (wi < ds_unres->whens.count)) { + /* new items in the sets */ + goto implement_all; + } + + return LY_SUCCESS; +} + +/** * @brief Finish dependency set compilation by resolving all the unres sets. * * @param[in] ctx libyang context. @@ -1125,7 +1219,7 @@ lys_type_leafref_next(const struct lysc_node *node, uint64_t *index) static LY_ERR lys_compile_unres_depset(struct ly_ctx *ctx, struct lys_glob_unres *unres) { - LY_ERR ret = LY_SUCCESS, r; + LY_ERR ret = LY_SUCCESS; struct lysc_node *node; struct lysc_type *typeiter; struct lysc_type_leafref *lref; @@ -1140,7 +1234,12 @@ lys_compile_unres_depset(struct ly_ctx *ctx, struct lys_glob_unres *unres) uint32_t i, processed_leafrefs = 0; resolve_all: - /* check disabled leafrefs first */ + /* implement all referenced modules to get final ds_unres set */ + if ((ret = lys_compile_unres_depset_implement(ctx, unres))) { + goto cleanup; + } + + /* check disabled leafrefs */ while (ds_unres->disabled_leafrefs.count) { /* remember index, it can change before we get to free this item */ i = ds_unres->disabled_leafrefs.count - 1; @@ -1150,7 +1249,7 @@ resolve_all: LOG_LOCSET(l->node, NULL, NULL, NULL); v = 0; while ((ret == LY_SUCCESS) && (lref = lys_type_leafref_next(l->node, &v))) { - ret = lys_compile_unres_leafref(&cctx, l->node, lref, l->local_mod, unres); + ret = lys_compile_unres_leafref(&cctx, l->node, lref, l->local_mod); } LOG_LOCBACK(1, 0, 0, 0); LY_CHECK_GOTO(ret, cleanup); @@ -1169,7 +1268,7 @@ resolve_all: LOG_LOCSET(l->node, NULL, NULL, NULL); v = 0; while ((ret == LY_SUCCESS) && (lref = lys_type_leafref_next(l->node, &v))) { - ret = lys_compile_unres_leafref(&cctx, l->node, lref, l->local_mod, unres); + ret = lys_compile_unres_leafref(&cctx, l->node, lref, l->local_mod); } LOG_LOCBACK(1, 0, 0, 0); LY_CHECK_GOTO(ret, cleanup); @@ -1193,29 +1292,7 @@ resolve_all: processed_leafrefs++; } - /* check when, first implement all the referenced modules (for the cyclic check in the next loop to work) */ - i = 0; - while (i < ds_unres->whens.count) { - w = ds_unres->whens.objs[i]; - LYSC_CTX_INIT_PMOD(cctx, w->node->module->parsed, NULL); - - LOG_LOCSET(w->node, NULL, NULL, NULL); - r = lys_compile_unres_when_implement(&cctx, w->when, unres); - LOG_LOCBACK(w->node ? 1 : 0, 0, 0, 0); - - if (r == LY_ENOT) { - /* skip full when check, remove from the set */ - free(w); - ly_set_rm_index(&ds_unres->whens, i, NULL); - continue; - } else if (r) { - /* error */ - ret = r; - goto cleanup; - } - - ++i; - } + /* check when, the referenced modules must be implemented now */ while (ds_unres->whens.count) { i = ds_unres->whens.count - 1; w = ds_unres->whens.objs[i]; @@ -1237,7 +1314,7 @@ resolve_all: LYSC_CTX_INIT_PMOD(cctx, m->node->module->parsed, m->ext); LOG_LOCSET(m->node, NULL, NULL, NULL); - ret = lys_compile_unres_must(&cctx, m->node, m->local_mods, unres); + ret = lys_compile_unres_must(&cctx, m->node, m->local_mods); LOG_LOCBACK(1, 0, 0, 0); LY_CHECK_GOTO(ret, cleanup); @@ -1278,7 +1355,7 @@ resolve_all: ly_set_rm_index(&ds_unres->dflts, i, NULL); } - /* some unres items may have been added */ + /* some unres items may have been added by the default values */ if ((processed_leafrefs != ds_unres->leafrefs.count) || ds_unres->disabled_leafrefs.count || ds_unres->whens.count || ds_unres->musts.count || ds_unres->dflts.count) { goto resolve_all; @@ -1748,7 +1825,7 @@ lys_has_compiled_import_r(struct lys_module *mod) LY_ERR lys_implement(struct lys_module *mod, const char **features, struct lys_glob_unres *unres) { - LY_ERR ret; + LY_ERR r; struct lys_module *m; assert(!mod->implemented); @@ -1757,21 +1834,15 @@ lys_implement(struct lys_module *mod, const char **features, struct lys_glob_unr m = ly_ctx_get_module_implemented(mod->ctx, mod->name); if (m) { assert(m != mod); - if (!strcmp(mod->name, "yang") && (strcmp(m->revision, mod->revision) > 0)) { - /* special case for newer internal module, continue */ - LOGVRB("Internal module \"%s@%s\" is already implemented in revision \"%s\", using it instead.", - mod->name, mod->revision ? mod->revision : "<none>", m->revision ? m->revision : "<none>"); - } else { - LOGERR(mod->ctx, LY_EDENIED, "Module \"%s@%s\" is already implemented in revision \"%s\".", - mod->name, mod->revision ? mod->revision : "<none>", m->revision ? m->revision : "<none>"); - return LY_EDENIED; - } + LOGERR(mod->ctx, LY_EDENIED, "Module \"%s@%s\" is already implemented in revision \"%s\".", + mod->name, mod->revision ? mod->revision : "<none>", m->revision ? m->revision : "<none>"); + return LY_EDENIED; } /* set features */ - ret = lys_set_features(mod->parsed, features); - if (ret && (ret != LY_EEXIST)) { - return ret; + r = lys_set_features(mod->parsed, features); + if (r && (r != LY_EEXIST)) { + return r; } /* diff --git a/src/schema_compile_amend.c b/src/schema_compile_amend.c index 9ca4e2e..8b570f6 100644 --- a/src/schema_compile_amend.c +++ b/src/schema_compile_amend.c @@ -1351,7 +1351,7 @@ lys_apply_deviate_replace(struct lysc_ctx *ctx, struct lysp_deviate_rpl *d, stru LY_CHECK_GOTO(ret = lysp_qname_dup(ctx->ctx, &d->dflt, &((struct lysp_node_leaf *)target)->dflt), cleanup); break; case LYS_CHOICE: - DEV_CHECK_PRESENCE(struct lysp_node_choice *, dflt.str, "replacing", "default", d->dflt); + DEV_CHECK_PRESENCE(struct lysp_node_choice *, dflt.str, "replacing", "default", d->dflt.str); lydict_remove(ctx->ctx, ((struct lysp_node_choice *)target)->dflt.str); LY_CHECK_GOTO(ret = lysp_qname_dup(ctx->ctx, &d->dflt, &((struct lysp_node_choice *)target)->dflt), cleanup); @@ -1376,13 +1376,6 @@ lys_apply_deviate_replace(struct lysc_ctx *ctx, struct lysp_deviate_rpl *d, stru AMEND_WRONG_NODETYPE("deviation", "replace", "config"); } - if (!(target->flags & LYS_CONFIG_MASK)) { - LOGVAL(ctx->ctx, LY_VCODE_DEV_NOT_PRESENT, "replacing", "config", - d->flags & LYS_CONFIG_W ? "config true" : "config false"); - ret = LY_EVALID; - goto cleanup; - } - target->flags &= ~LYS_CONFIG_MASK; target->flags |= d->flags & LYS_CONFIG_MASK; } diff --git a/src/schema_compile_node.c b/src/schema_compile_node.c index 0b64dcb..2723716 100644 --- a/src/schema_compile_node.c +++ b/src/schema_compile_node.c @@ -4,7 +4,7 @@ * @author Michal Vasko <mvasko@cesnet.cz> * @brief Schema compilation of common nodes. * - * Copyright (c) 2015 - 2022 CESNET, z.s.p.o. + * Copyright (c) 2015 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -43,14 +43,13 @@ #include "tree_schema_internal.h" #include "xpath.h" -static struct lysc_ext_instance * -lysc_ext_instance_dup(struct ly_ctx *ctx, struct lysc_ext_instance *orig) -{ - /* TODO - extensions, increase refcount */ - (void) ctx; - (void) orig; - return NULL; -} +/** + * @brief Item for storing typedef chain item. + */ +struct lys_type_item { + const struct lysp_tpdf *tpdf; + struct lysp_node *node; +}; /** * @brief Add a node with a when to unres. @@ -346,31 +345,48 @@ lysc_patterns_dup(struct ly_ctx *ctx, struct lysc_pattern **orig) /** * @brief Duplicate compiled range structure. * - * @param[in] ctx Libyang context for logging. + * @param[in] ctx Compile context. * @param[in] orig The range structure to be duplicated. + * @param[in] tpdf_chain Chain of the used typedefs, traversed backwards. + * @param[in] tpdf_chain_last Index of the last (backwards) typedef in @p tpdf_chain to use. * @return New compiled range structure as a copy of @p orig. * @return NULL in case of memory allocation error. */ static struct lysc_range * -lysc_range_dup(struct ly_ctx *ctx, const struct lysc_range *orig) +lysc_range_dup(struct lysc_ctx *ctx, const struct lysc_range *orig, struct ly_set *tpdf_chain, uint32_t tpdf_chain_last) { struct lysc_range *dup; LY_ERR ret; + struct lys_type_item *tpdf_item; + uint32_t i; assert(orig); dup = calloc(1, sizeof *dup); - LY_CHECK_ERR_RET(!dup, LOGMEM(ctx), NULL); + LY_CHECK_ERR_RET(!dup, LOGMEM(ctx->ctx), NULL); if (orig->parts) { - LY_ARRAY_CREATE_GOTO(ctx, dup->parts, LY_ARRAY_COUNT(orig->parts), ret, cleanup); + LY_ARRAY_CREATE_GOTO(ctx->ctx, dup->parts, LY_ARRAY_COUNT(orig->parts), ret, cleanup); (*((LY_ARRAY_COUNT_TYPE *)(dup->parts) - 1)) = LY_ARRAY_COUNT(orig->parts); memcpy(dup->parts, orig->parts, LY_ARRAY_COUNT(dup->parts) * sizeof *dup->parts); } - DUP_STRING_GOTO(ctx, orig->eapptag, dup->eapptag, ret, cleanup); - DUP_STRING_GOTO(ctx, orig->emsg, dup->emsg, ret, cleanup); - dup->exts = lysc_ext_instance_dup(ctx, orig->exts); + DUP_STRING_GOTO(ctx->ctx, orig->eapptag, dup->eapptag, ret, cleanup); + DUP_STRING_GOTO(ctx->ctx, orig->emsg, dup->emsg, ret, cleanup); + + /* collect all range extensions */ + if (tpdf_chain->count > tpdf_chain_last) { + i = tpdf_chain->count; + do { + --i; + tpdf_item = tpdf_chain->objs[i]; + if (!tpdf_item->tpdf->type.range) { + continue; + } + COMPILE_EXTS_GOTO(ctx, tpdf_item->tpdf->type.range->exts, dup->exts, dup, ret, cleanup); + } while (i > tpdf_chain_last); + } return dup; + cleanup: free(dup); (void) ret; /* set but not used due to the return type */ @@ -1595,6 +1611,17 @@ done: return ret; } +/** + * @brief Compile union type. + * + * @param[in] ctx Compile context. + * @param[in] ptypes Parsed union types. + * @param[in] context_pnode Schema node where the type/typedef is placed to correctly find the base types. + * @param[in] context_flags Flags of the context node or the referencing typedef to correctly check status of referencing and referenced objects. + * @param[in] context_name Name of the context node or referencing typedef for logging. + * @param[out] utypes_p Array of compiled union types. + * @return LY_ERR value. + */ static LY_ERR lys_compile_type_union(struct lysc_ctx *ctx, struct lysp_type *ptypes, struct lysp_node *context_pnode, uint16_t context_flags, const char *context_name, struct lysc_type ***utypes_p) @@ -1608,6 +1635,8 @@ lys_compile_type_union(struct lysc_ctx *ctx, struct lysp_type *ptypes, struct ly ret = lys_compile_type(ctx, context_pnode, context_flags, context_name, &ptypes[u], &utypes[u + additional], NULL, NULL); LY_CHECK_GOTO(ret, error); + LY_ATOMIC_INC_BARRIER(utypes[u + additional]->refcount); + if (utypes[u + additional]->basetype == LY_TYPE_UNION) { /* add space for additional types from the union subtype */ un_aux = (struct lysc_type_union *)utypes[u + additional]; @@ -1616,28 +1645,8 @@ lys_compile_type_union(struct lysc_ctx *ctx, struct lysp_type *ptypes, struct ly /* copy subtypes of the subtype union */ for (LY_ARRAY_COUNT_TYPE v = 0; v < LY_ARRAY_COUNT(un_aux->types); ++v) { - if (un_aux->types[v]->basetype == LY_TYPE_LEAFREF) { - struct lysc_type_leafref *lref; - - /* duplicate the whole structure because of the instance-specific path resolving for realtype */ - utypes[u + additional] = calloc(1, sizeof(struct lysc_type_leafref)); - LY_CHECK_ERR_GOTO(!utypes[u + additional], LOGMEM(ctx->ctx); ret = LY_EMEM, error); - lref = (struct lysc_type_leafref *)utypes[u + additional]; - - lref->basetype = LY_TYPE_LEAFREF; - ret = lyxp_expr_dup(ctx->ctx, ((struct lysc_type_leafref *)un_aux->types[v])->path, 0, 0, &lref->path); - LY_CHECK_GOTO(ret, error); - lref->refcount = 1; - lref->require_instance = ((struct lysc_type_leafref *)un_aux->types[v])->require_instance; - ret = lyplg_type_prefix_data_dup(ctx->ctx, LY_VALUE_SCHEMA_RESOLVED, - ((struct lysc_type_leafref *)un_aux->types[v])->prefixes, (void **)&lref->prefixes); - LY_CHECK_GOTO(ret, error); - /* TODO extensions */ - - } else { - utypes[u + additional] = un_aux->types[v]; - LY_ATOMIC_INC_BARRIER(un_aux->types[v]->refcount); - } + utypes[u + additional] = un_aux->types[v]; + LY_ATOMIC_INC_BARRIER(un_aux->types[v]->refcount); ++additional; LY_ARRAY_INCREMENT(utypes); } @@ -1664,7 +1673,68 @@ error: } /** + * @brief Allocate a new specific type structure according to the basetype. + * + * @param[in] basetype Base type of the new type. + * @return Specific type structure. + */ +static struct lysc_type * +lys_new_type(LY_DATA_TYPE basetype) +{ + struct lysc_type *type = NULL; + + switch (basetype) { + case LY_TYPE_BINARY: + type = calloc(1, sizeof(struct lysc_type_bin)); + break; + case LY_TYPE_BITS: + type = calloc(1, sizeof(struct lysc_type_bits)); + break; + case LY_TYPE_DEC64: + type = calloc(1, sizeof(struct lysc_type_dec)); + break; + case LY_TYPE_STRING: + type = calloc(1, sizeof(struct lysc_type_str)); + break; + case LY_TYPE_ENUM: + type = calloc(1, sizeof(struct lysc_type_enum)); + break; + case LY_TYPE_INT8: + case LY_TYPE_UINT8: + case LY_TYPE_INT16: + case LY_TYPE_UINT16: + case LY_TYPE_INT32: + case LY_TYPE_UINT32: + case LY_TYPE_INT64: + case LY_TYPE_UINT64: + type = calloc(1, sizeof(struct lysc_type_num)); + break; + case LY_TYPE_IDENT: + type = calloc(1, sizeof(struct lysc_type_identityref)); + break; + case LY_TYPE_LEAFREF: + type = calloc(1, sizeof(struct lysc_type_leafref)); + break; + case LY_TYPE_INST: + type = calloc(1, sizeof(struct lysc_type_instanceid)); + break; + case LY_TYPE_UNION: + type = calloc(1, sizeof(struct lysc_type_union)); + break; + case LY_TYPE_BOOL: + case LY_TYPE_EMPTY: + type = calloc(1, sizeof(struct lysc_type)); + break; + case LY_TYPE_UNKNOWN: + break; + } + + return type; +} + +/** * @brief The core of the lys_compile_type() - compile information about the given type (from typedef or leaf/leaf-list). + * * @param[in] ctx Compile context. * @param[in] context_pnode Schema node where the type/typedef is placed to correctly find the base types. * @param[in] context_flags Flags of the context node or the referencing typedef to correctly check status of referencing and referenced objects. @@ -1672,16 +1742,19 @@ error: * @param[in] type_p Parsed type to compile. * @param[in] basetype Base YANG built-in type of the type to compile. * @param[in] tpdfname Name of the type's typedef, serves as a flag - if it is leaf/leaf-list's type, it is NULL. - * @param[in] base The latest base (compiled) type from which the current type is being derived. - * @param[out] type Newly created type structure with the filled information about the type. + * @param[in] base Latest base (compiled) type from which the current type is being derived. + * @param[in] plugin Type plugin to use. + * @param[in] tpdf_chain Chain of the used typedefs, traversed backwards. + * @param[in] tpdf_chain_last Index of the last (backwards) typedef in @p tpdf_chain to use. + * @param[out] type Compiled type. * @return LY_ERR value. */ static LY_ERR lys_compile_type_(struct lysc_ctx *ctx, struct lysp_node *context_pnode, uint16_t context_flags, const char *context_name, - struct lysp_type *type_p, LY_DATA_TYPE basetype, const char *tpdfname, const struct lysc_type *base, - struct lysc_type **type) + const struct lysp_type *type_p, LY_DATA_TYPE basetype, const char *tpdfname, const struct lysc_type *base, + struct lyplg_type *plugin, struct ly_set *tpdf_chain, uint32_t tpdf_chain_last, struct lysc_type **type) { - LY_ERR ret = LY_SUCCESS; + LY_ERR rc = LY_SUCCESS; struct lysc_type_bin *bin; struct lysc_type_num *num; struct lysc_type_str *str; @@ -1691,41 +1764,69 @@ lys_compile_type_(struct lysc_ctx *ctx, struct lysp_node *context_pnode, uint16_ struct lysc_type_identityref *idref; struct lysc_type_leafref *lref; struct lysc_type_union *un; + struct lys_type_item *tpdf_item; + const struct lysp_type *base_type_p; + uint32_t i; + + /* alloc and init */ + *type = lys_new_type(basetype); + LY_CHECK_ERR_GOTO(!(*type), LOGMEM(ctx->ctx), cleanup); + + (*type)->basetype = basetype; + (*type)->plugin = plugin; switch (basetype) { case LY_TYPE_BINARY: - bin = (struct lysc_type_bin *)(*type); + bin = (struct lysc_type_bin *)*type; /* RFC 7950 9.8.1, 9.4.4 - length, number of octets it contains */ if (type_p->length) { - LY_CHECK_RET(lys_compile_type_range(ctx, type_p->length, basetype, 1, 0, - base ? ((struct lysc_type_bin *)base)->length : NULL, &bin->length)); + LY_CHECK_GOTO(rc = lys_compile_type_range(ctx, type_p->length, basetype, 1, 0, + base ? ((struct lysc_type_bin *)base)->length : NULL, &bin->length), cleanup); if (!tpdfname) { - COMPILE_EXTS_GOTO(ctx, type_p->length->exts, bin->length->exts, bin->length, ret, cleanup); + COMPILE_EXTS_GOTO(ctx, type_p->length->exts, bin->length->exts, bin->length, rc, cleanup); } } break; case LY_TYPE_BITS: /* RFC 7950 9.7 - bits */ - bits = (struct lysc_type_bits *)(*type); + bits = (struct lysc_type_bits *)*type; if (type_p->bits) { - LY_CHECK_RET(lys_compile_type_enums(ctx, type_p->bits, basetype, + /* compile bits from this type */ + LY_CHECK_GOTO(rc = lys_compile_type_enums(ctx, type_p->bits, basetype, base ? (struct lysc_type_bitenum_item *)((struct lysc_type_bits *)base)->bits : NULL, - (struct lysc_type_bitenum_item **)&bits->bits)); - } + (struct lysc_type_bitenum_item **)&bits->bits), cleanup); + } else if (base) { + /* recompile bits from the first superior type with bits */ + assert(tpdf_chain->count > tpdf_chain_last); + base_type_p = NULL; + i = tpdf_chain->count; + do { + --i; + tpdf_item = tpdf_chain->objs[i]; + + if (tpdf_item->tpdf->type.bits) { + base_type_p = &tpdf_item->tpdf->type; + break; + } + } while (i > tpdf_chain_last); + assert(base_type_p); - if (!base && !type_p->flags) { + LY_CHECK_GOTO(rc = lys_compile_type_enums(ctx, base_type_p->bits, basetype, NULL, + (struct lysc_type_bitenum_item **)&bits->bits), cleanup); + } else { /* type derived from bits built-in type must contain at least one bit */ if (tpdfname) { LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "bit", "bits type ", tpdfname); } else { LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "bit", "bits type", ""); } - return LY_EVALID; + rc = LY_EVALID; + goto cleanup; } break; case LY_TYPE_DEC64: - dec = (struct lysc_type_dec *)(*type); + dec = (struct lysc_type_dec *)*type; /* RFC 7950 9.3.4 - fraction-digits */ if (!base) { @@ -1735,7 +1836,8 @@ lys_compile_type_(struct lysc_ctx *ctx, struct lysp_node *context_pnode, uint16_ } else { LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "fraction-digits", "decimal64 type", ""); } - return LY_EVALID; + rc = LY_EVALID; + goto cleanup; } dec->fraction_digits = type_p->fraction_digits; } else { @@ -1749,59 +1851,76 @@ lys_compile_type_(struct lysc_ctx *ctx, struct lysp_node *context_pnode, uint16_ LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, "Invalid fraction-digits substatement for type not directly derived from decimal64 built-in type."); } - return LY_EVALID; + rc = LY_EVALID; + goto cleanup; } dec->fraction_digits = ((struct lysc_type_dec *)base)->fraction_digits; } /* RFC 7950 9.2.4 - range */ if (type_p->range) { - LY_CHECK_RET(lys_compile_type_range(ctx, type_p->range, basetype, 0, dec->fraction_digits, - base ? ((struct lysc_type_dec *)base)->range : NULL, &dec->range)); + LY_CHECK_GOTO(rc = lys_compile_type_range(ctx, type_p->range, basetype, 0, dec->fraction_digits, + base ? ((struct lysc_type_dec *)base)->range : NULL, &dec->range), cleanup); if (!tpdfname) { - COMPILE_EXTS_GOTO(ctx, type_p->range->exts, dec->range->exts, dec->range, ret, cleanup); + COMPILE_EXTS_GOTO(ctx, type_p->range->exts, dec->range->exts, dec->range, rc, cleanup); } } break; case LY_TYPE_STRING: - str = (struct lysc_type_str *)(*type); + str = (struct lysc_type_str *)*type; /* RFC 7950 9.4.4 - length */ if (type_p->length) { - LY_CHECK_RET(lys_compile_type_range(ctx, type_p->length, basetype, 1, 0, - base ? ((struct lysc_type_str *)base)->length : NULL, &str->length)); + LY_CHECK_GOTO(rc = lys_compile_type_range(ctx, type_p->length, basetype, 1, 0, + base ? ((struct lysc_type_str *)base)->length : NULL, &str->length), cleanup); if (!tpdfname) { - COMPILE_EXTS_GOTO(ctx, type_p->length->exts, str->length->exts, str->length, ret, cleanup); + COMPILE_EXTS_GOTO(ctx, type_p->length->exts, str->length->exts, str->length, rc, cleanup); } } else if (base && ((struct lysc_type_str *)base)->length) { - str->length = lysc_range_dup(ctx->ctx, ((struct lysc_type_str *)base)->length); + str->length = lysc_range_dup(ctx, ((struct lysc_type_str *)base)->length, tpdf_chain, tpdf_chain_last); } /* RFC 7950 9.4.5 - pattern */ if (type_p->patterns) { - LY_CHECK_RET(lys_compile_type_patterns(ctx, type_p->patterns, - base ? ((struct lysc_type_str *)base)->patterns : NULL, &str->patterns)); + LY_CHECK_GOTO(rc = lys_compile_type_patterns(ctx, type_p->patterns, + base ? ((struct lysc_type_str *)base)->patterns : NULL, &str->patterns), cleanup); } else if (base && ((struct lysc_type_str *)base)->patterns) { str->patterns = lysc_patterns_dup(ctx->ctx, ((struct lysc_type_str *)base)->patterns); } break; case LY_TYPE_ENUM: - enumeration = (struct lysc_type_enum *)(*type); + enumeration = (struct lysc_type_enum *)*type; /* RFC 7950 9.6 - enum */ if (type_p->enums) { - LY_CHECK_RET(lys_compile_type_enums(ctx, type_p->enums, basetype, - base ? ((struct lysc_type_enum *)base)->enums : NULL, &enumeration->enums)); - } + LY_CHECK_GOTO(rc = lys_compile_type_enums(ctx, type_p->enums, basetype, + base ? ((struct lysc_type_enum *)base)->enums : NULL, &enumeration->enums), cleanup); + } else if (base) { + /* recompile enums from the first superior type with enums */ + assert(tpdf_chain->count > tpdf_chain_last); + base_type_p = NULL; + i = tpdf_chain->count; + do { + --i; + tpdf_item = tpdf_chain->objs[i]; + + if (tpdf_item->tpdf->type.enums) { + base_type_p = &tpdf_item->tpdf->type; + break; + } + } while (i > tpdf_chain_last); + assert(base_type_p); - if (!base && !type_p->flags) { + LY_CHECK_GOTO(rc = lys_compile_type_enums(ctx, base_type_p->enums, basetype, NULL, &enumeration->enums), cleanup); + } else { /* type derived from enumerations built-in type must contain at least one enum */ if (tpdfname) { LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "enum", "enumeration type ", tpdfname); } else { LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "enum", "enumeration type", ""); } - return LY_EVALID; + rc = LY_EVALID; + goto cleanup; } break; case LY_TYPE_INT8: @@ -1812,19 +1931,19 @@ lys_compile_type_(struct lysc_ctx *ctx, struct lysp_node *context_pnode, uint16_ case LY_TYPE_UINT32: case LY_TYPE_INT64: case LY_TYPE_UINT64: - num = (struct lysc_type_num *)(*type); + num = (struct lysc_type_num *)*type; /* RFC 6020 9.2.4 - range */ if (type_p->range) { - LY_CHECK_RET(lys_compile_type_range(ctx, type_p->range, basetype, 0, 0, - base ? ((struct lysc_type_num *)base)->range : NULL, &num->range)); + LY_CHECK_GOTO(rc = lys_compile_type_range(ctx, type_p->range, basetype, 0, 0, + base ? ((struct lysc_type_num *)base)->range : NULL, &num->range), cleanup); if (!tpdfname) { - COMPILE_EXTS_GOTO(ctx, type_p->range->exts, num->range->exts, num->range, ret, cleanup); + COMPILE_EXTS_GOTO(ctx, type_p->range->exts, num->range->exts, num->range, rc, cleanup); } } break; case LY_TYPE_IDENT: - idref = (struct lysc_type_identityref *)(*type); + idref = (struct lysc_type_identityref *)*type; /* RFC 7950 9.10.2 - base */ if (type_p->bases) { @@ -1838,19 +1957,29 @@ lys_compile_type_(struct lysc_ctx *ctx, struct lysp_node *context_pnode, uint16_ LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, "Invalid base substatement for the type not directly derived from identityref built-in type."); } - return LY_EVALID; + rc = LY_EVALID; + goto cleanup; } - LY_CHECK_RET(lys_compile_identity_bases(ctx, type_p->pmod, type_p->bases, NULL, &idref->bases)); - } - - if (!base && !type_p->flags) { + LY_CHECK_GOTO(rc = lys_compile_identity_bases(ctx, type_p->pmod, type_p->bases, NULL, &idref->bases), cleanup); + } else if (base) { + /* copy all the bases */ + const struct lysc_type_identityref *idref_base = (struct lysc_type_identityref *)base; + LY_ARRAY_COUNT_TYPE u; + + LY_ARRAY_CREATE_GOTO(ctx->ctx, idref->bases, LY_ARRAY_COUNT(idref_base->bases), rc, cleanup); + LY_ARRAY_FOR(idref_base->bases, u) { + idref->bases[u] = idref_base->bases[u]; + LY_ARRAY_INCREMENT(idref->bases); + } + } else { /* type derived from identityref built-in type must contain at least one base */ if (tpdfname) { LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "base", "identityref type ", tpdfname); } else { LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "base", "identityref type", ""); } - return LY_EVALID; + rc = LY_EVALID; + goto cleanup; } break; case LY_TYPE_LEAFREF: @@ -1866,7 +1995,8 @@ lys_compile_type_(struct lysc_ctx *ctx, struct lysp_node *context_pnode, uint16_ LOGVAL(ctx->ctx, LYVE_SEMANTICS, "Leafref type can be restricted by require-instance statement only in YANG 1.1 modules."); } - return LY_EVALID; + rc = LY_EVALID; + goto cleanup; } lref->require_instance = type_p->require_instance; } else if (base) { @@ -1879,32 +2009,35 @@ lys_compile_type_(struct lysc_ctx *ctx, struct lysp_node *context_pnode, uint16_ if (type_p->path) { LY_VALUE_FORMAT format; - LY_CHECK_RET(lyxp_expr_dup(ctx->ctx, type_p->path, 0, 0, &lref->path)); - LY_CHECK_RET(lyplg_type_prefix_data_new(ctx->ctx, type_p->path->expr, strlen(type_p->path->expr), - LY_VALUE_SCHEMA, type_p->pmod, &format, (void **)&lref->prefixes)); + LY_CHECK_GOTO(rc = lyxp_expr_dup(ctx->ctx, type_p->path, 0, 0, &lref->path), cleanup); + LY_CHECK_GOTO(lyplg_type_prefix_data_new(ctx->ctx, type_p->path->expr, strlen(type_p->path->expr), + LY_VALUE_SCHEMA, type_p->pmod, &format, (void **)&lref->prefixes), cleanup); } else if (base) { - LY_CHECK_RET(lyxp_expr_dup(ctx->ctx, ((struct lysc_type_leafref *)base)->path, 0, 0, &lref->path)); - LY_CHECK_RET(lyplg_type_prefix_data_dup(ctx->ctx, LY_VALUE_SCHEMA_RESOLVED, - ((struct lysc_type_leafref *)base)->prefixes, (void **)&lref->prefixes)); - } else if (tpdfname) { - LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "path", "leafref type ", tpdfname); - return LY_EVALID; + LY_CHECK_GOTO(rc = lyxp_expr_dup(ctx->ctx, ((struct lysc_type_leafref *)base)->path, 0, 0, &lref->path), cleanup); + LY_CHECK_GOTO(rc = lyplg_type_prefix_data_dup(ctx->ctx, LY_VALUE_SCHEMA_RESOLVED, + ((struct lysc_type_leafref *)base)->prefixes, (void **)&lref->prefixes), cleanup); } else { - LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "path", "leafref type", ""); - return LY_EVALID; + /* type derived from leafref built-in type must contain path */ + if (tpdfname) { + LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "path", "leafref type ", tpdfname); + } else { + LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "path", "leafref type", ""); + } + rc = LY_EVALID; + goto cleanup; } break; case LY_TYPE_INST: /* RFC 7950 9.9.3 - require-instance */ if (type_p->flags & LYS_SET_REQINST) { - ((struct lysc_type_instanceid *)(*type))->require_instance = type_p->require_instance; + ((struct lysc_type_instanceid *)*type)->require_instance = type_p->require_instance; } else { /* default is true */ - ((struct lysc_type_instanceid *)(*type))->require_instance = 1; + ((struct lysc_type_instanceid *)*type)->require_instance = 1; } break; case LY_TYPE_UNION: - un = (struct lysc_type_union *)(*type); + un = (struct lysc_type_union *)*type; /* RFC 7950 7.4 - type */ if (type_p->types) { @@ -1918,20 +2051,32 @@ lys_compile_type_(struct lysc_ctx *ctx, struct lysp_node *context_pnode, uint16_ LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, "Invalid type substatement for the type not directly derived from union built-in type."); } - return LY_EVALID; + rc = LY_EVALID; + goto cleanup; } /* compile the type */ - LY_CHECK_RET(lys_compile_type_union(ctx, type_p->types, context_pnode, context_flags, context_name, &un->types)); - } - - if (!base && !type_p->flags) { + LY_CHECK_GOTO(rc = lys_compile_type_union(ctx, type_p->types, context_pnode, context_flags, context_name, + &un->types), cleanup); + } else if (base) { + /* copy all the types */ + const struct lysc_type_union *un_base = (struct lysc_type_union *)base; + LY_ARRAY_COUNT_TYPE u; + + LY_ARRAY_CREATE_GOTO(ctx->ctx, un->types, LY_ARRAY_COUNT(un_base->types), rc, cleanup); + LY_ARRAY_FOR(un_base->types, u) { + un->types[u] = un_base->types[u]; + LY_ATOMIC_INC_BARRIER(un->types[u]->refcount); + LY_ARRAY_INCREMENT(un->types); + } + } else { /* type derived from union built-in type must contain at least one type */ if (tpdfname) { LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "type", "union type ", tpdfname); } else { LOGVAL(ctx->ctx, LY_VCODE_MISSCHILDSTMT, "type", "union type", ""); } - return LY_EVALID; + rc = LY_EVALID; + goto cleanup; } break; case LY_TYPE_BOOL: @@ -1940,65 +2085,27 @@ lys_compile_type_(struct lysc_ctx *ctx, struct lysp_node *context_pnode, uint16_ break; } - if (tpdfname) { - switch (basetype) { - case LY_TYPE_BINARY: - type_p->compiled = *type; - *type = calloc(1, sizeof(struct lysc_type_bin)); - break; - case LY_TYPE_BITS: - type_p->compiled = *type; - *type = calloc(1, sizeof(struct lysc_type_bits)); - break; - case LY_TYPE_DEC64: - type_p->compiled = *type; - *type = calloc(1, sizeof(struct lysc_type_dec)); - break; - case LY_TYPE_STRING: - type_p->compiled = *type; - *type = calloc(1, sizeof(struct lysc_type_str)); - break; - case LY_TYPE_ENUM: - type_p->compiled = *type; - *type = calloc(1, sizeof(struct lysc_type_enum)); - break; - case LY_TYPE_INT8: - case LY_TYPE_UINT8: - case LY_TYPE_INT16: - case LY_TYPE_UINT16: - case LY_TYPE_INT32: - case LY_TYPE_UINT32: - case LY_TYPE_INT64: - case LY_TYPE_UINT64: - type_p->compiled = *type; - *type = calloc(1, sizeof(struct lysc_type_num)); - break; - case LY_TYPE_IDENT: - type_p->compiled = *type; - *type = calloc(1, sizeof(struct lysc_type_identityref)); - break; - case LY_TYPE_LEAFREF: - type_p->compiled = *type; - *type = calloc(1, sizeof(struct lysc_type_leafref)); - break; - case LY_TYPE_INST: - type_p->compiled = *type; - *type = calloc(1, sizeof(struct lysc_type_instanceid)); - break; - case LY_TYPE_UNION: - type_p->compiled = *type; - *type = calloc(1, sizeof(struct lysc_type_union)); - break; - case LY_TYPE_BOOL: - case LY_TYPE_EMPTY: - case LY_TYPE_UNKNOWN: /* just to complete switch */ - break; - } + if (tpdf_chain->count > tpdf_chain_last) { + i = tpdf_chain->count; + do { + --i; + tpdf_item = tpdf_chain->objs[i]; + + /* compile previous typedefs extensions */ + COMPILE_EXTS_GOTO(ctx, tpdf_item->tpdf->type.exts, (*type)->exts, *type, rc, cleanup); + } while (i > tpdf_chain_last); } - LY_CHECK_ERR_RET(!(*type), LOGMEM(ctx->ctx), LY_EMEM); + + /* compile new parsed extensions */ + COMPILE_EXTS_GOTO(ctx, type_p->exts, (*type)->exts, *type, rc, cleanup); cleanup: - return ret; + if (rc) { + LY_ATOMIC_INC_BARRIER((*type)->refcount); + lysc_type_free(&ctx->free_ctx, *type); + *type = NULL; + } + return rc; } LY_ERR @@ -2007,16 +2114,13 @@ lys_compile_type(struct lysc_ctx *ctx, struct lysp_node *context_pnode, uint16_t { LY_ERR ret = LY_SUCCESS; ly_bool dummyloops = 0; - struct type_context { - const struct lysp_tpdf *tpdf; - struct lysp_node *node; - } *tctx, *tctx_prev = NULL, *tctx_iter; + struct lys_type_item *tctx, *tctx_prev = NULL, *tctx_iter; LY_DATA_TYPE basetype = LY_TYPE_UNKNOWN; - struct lysc_type *base = NULL, *prev_type; + struct lysc_type *base = NULL; struct ly_set tpdf_chain = {0}; struct lyplg_type *plugin; - (*type) = NULL; + *type = NULL; if (dflt) { *dflt = NULL; } @@ -2046,7 +2150,7 @@ lys_compile_type(struct lysc_ctx *ctx, struct lysp_node *context_pnode, uint16_t *dflt = (struct lysp_qname *)&tctx->tpdf->dflt; } if (dummyloops && (!units || *units) && dflt && *dflt) { - basetype = ((struct type_context *)tpdf_chain.objs[tpdf_chain.count - 1])->tpdf->type.compiled->basetype; + basetype = ((struct lys_type_item *)tpdf_chain.objs[tpdf_chain.count - 1])->tpdf->type.compiled->basetype; break; } @@ -2076,7 +2180,7 @@ lys_compile_type(struct lysc_ctx *ctx, struct lysp_node *context_pnode, uint16_t /* circular typedef reference detection */ for (uint32_t u = 0; u < tpdf_chain.count; u++) { /* local part */ - tctx_iter = (struct type_context *)tpdf_chain.objs[u]; + tctx_iter = (struct lys_type_item *)tpdf_chain.objs[u]; if (tctx_iter->tpdf == tctx->tpdf) { LOGVAL(ctx->ctx, LYVE_REFERENCE, "Invalid \"%s\" type reference - circular chain of types detected.", tctx->tpdf->name); @@ -2087,7 +2191,7 @@ lys_compile_type(struct lysc_ctx *ctx, struct lysp_node *context_pnode, uint16_t } for (uint32_t u = 0; u < ctx->tpdf_chain.count; u++) { /* global part for unions corner case */ - tctx_iter = (struct type_context *)ctx->tpdf_chain.objs[u]; + tctx_iter = (struct lys_type_item *)ctx->tpdf_chain.objs[u]; if (tctx_iter->tpdf == tctx->tpdf) { LOGVAL(ctx->ctx, LYVE_REFERENCE, "Invalid \"%s\" type reference - circular chain of types detected.", tctx->tpdf->name); @@ -2109,68 +2213,23 @@ preparenext: } free(tctx); - /* allocate type according to the basetype */ - switch (basetype) { - case LY_TYPE_BINARY: - *type = calloc(1, sizeof(struct lysc_type_bin)); - break; - case LY_TYPE_BITS: - *type = calloc(1, sizeof(struct lysc_type_bits)); - break; - case LY_TYPE_BOOL: - case LY_TYPE_EMPTY: - *type = calloc(1, sizeof(struct lysc_type)); - break; - case LY_TYPE_DEC64: - *type = calloc(1, sizeof(struct lysc_type_dec)); - break; - case LY_TYPE_ENUM: - *type = calloc(1, sizeof(struct lysc_type_enum)); - break; - case LY_TYPE_IDENT: - *type = calloc(1, sizeof(struct lysc_type_identityref)); - break; - case LY_TYPE_INST: - *type = calloc(1, sizeof(struct lysc_type_instanceid)); - break; - case LY_TYPE_LEAFREF: - *type = calloc(1, sizeof(struct lysc_type_leafref)); - break; - case LY_TYPE_STRING: - *type = calloc(1, sizeof(struct lysc_type_str)); - break; - case LY_TYPE_UNION: - *type = calloc(1, sizeof(struct lysc_type_union)); - break; - case LY_TYPE_INT8: - case LY_TYPE_UINT8: - case LY_TYPE_INT16: - case LY_TYPE_UINT16: - case LY_TYPE_INT32: - case LY_TYPE_UINT32: - case LY_TYPE_INT64: - case LY_TYPE_UINT64: - *type = calloc(1, sizeof(struct lysc_type_num)); - break; - case LY_TYPE_UNKNOWN: + /* basic checks */ + if (basetype == LY_TYPE_UNKNOWN) { LOGVAL(ctx->ctx, LYVE_REFERENCE, "Referenced type \"%s\" not found.", tctx_prev ? tctx_prev->tpdf->type.name : type_p->name); ret = LY_EVALID; goto cleanup; } - LY_CHECK_ERR_GOTO(!(*type), LOGMEM(ctx->ctx), cleanup); if (~type_substmt_map[basetype] & type_p->flags) { LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, "Invalid type restrictions for %s type.", ly_data_type2str[basetype]); - free(*type); - (*type) = NULL; ret = LY_EVALID; goto cleanup; } /* get restrictions from the referred typedefs */ for (uint32_t u = tpdf_chain.count - 1; u + 1 > 0; --u) { - tctx = (struct type_context *)tpdf_chain.objs[u]; + tctx = (struct lys_type_item *)tpdf_chain.objs[u]; /* remember the typedef context for circular check */ ret = ly_set_add(&ctx->tpdf_chain, tctx, 1, NULL); @@ -2195,15 +2254,14 @@ preparenext: } assert(plugin); - if ((basetype != LY_TYPE_LEAFREF) && (u != tpdf_chain.count - 1) && !(tctx->tpdf->type.flags) && - (plugin == base->plugin)) { + if ((basetype != LY_TYPE_LEAFREF) && (u != tpdf_chain.count - 1) && !tctx->tpdf->type.flags && + !tctx->tpdf->type.exts && (plugin == base->plugin)) { /* no change, reuse the compiled base */ ((struct lysp_tpdf *)tctx->tpdf)->type.compiled = base; LY_ATOMIC_INC_BARRIER(base->refcount); continue; } - LY_ATOMIC_INC_BARRIER((*type)->refcount); if (~type_substmt_map[basetype] & tctx->tpdf->type.flags) { LOGVAL(ctx->ctx, LYVE_SYNTAX_YANG, "Invalid type \"%s\" restriction(s) for %s type.", tctx->tpdf->name, ly_data_type2str[basetype]); @@ -2217,40 +2275,31 @@ preparenext: goto cleanup; } - (*type)->basetype = basetype; - (*type)->plugin = plugin; - - /* collect extensions */ - COMPILE_EXTS_GOTO(ctx, tctx->tpdf->type.exts, (*type)->exts, (*type), ret, cleanup); - - /* compile the new typedef */ - prev_type = *type; - ret = lys_compile_type_(ctx, tctx->node, tctx->tpdf->flags, tctx->tpdf->name, - &((struct lysp_tpdf *)tctx->tpdf)->type, basetype, tctx->tpdf->name, base, type); + /* compile the typedef type */ + ret = lys_compile_type_(ctx, tctx->node, tctx->tpdf->flags, tctx->tpdf->name, &tctx->tpdf->type, basetype, + tctx->tpdf->name, base, plugin, &tpdf_chain, u + 1, &base); LY_CHECK_GOTO(ret, cleanup); - base = prev_type; + + /* store separately compiled typedef type to be reused */ + ((struct lysp_tpdf *)tctx->tpdf)->type.compiled = base; + LY_ATOMIC_INC_BARRIER(base->refcount); } + /* remove the processed typedef contexts from the stack for circular check */ ctx->tpdf_chain.count = ctx->tpdf_chain.count - tpdf_chain.count; /* process the type definition in leaf */ - if (type_p->flags || !base || (basetype == LY_TYPE_LEAFREF)) { - /* get restrictions from the node itself */ - (*type)->basetype = basetype; - (*type)->plugin = base ? base->plugin : lyplg_type_plugin_find("", NULL, ly_data_type2str[basetype]); - LY_ATOMIC_INC_BARRIER((*type)->refcount); + if (type_p->flags || type_p->exts || !base || (basetype == LY_TYPE_LEAFREF)) { + /* leaf type has changes that need to be compiled into the type */ + plugin = base ? base->plugin : lyplg_type_plugin_find("", NULL, ly_data_type2str[basetype]); ret = lys_compile_type_(ctx, context_pnode, context_flags, context_name, (struct lysp_type *)type_p, basetype, - NULL, base, type); + NULL, base, plugin, &tpdf_chain, 0, type); LY_CHECK_GOTO(ret, cleanup); - } else if ((basetype != LY_TYPE_BOOL) && (basetype != LY_TYPE_EMPTY)) { - /* no specific restriction in leaf's type definition, copy from the base */ - free(*type); - (*type) = base; - LY_ATOMIC_INC_BARRIER((*type)->refcount); + } else { + /* no changes of the type in the leaf, just use the base compiled type */ + *type = base; } - COMPILE_EXTS_GOTO(ctx, type_p->exts, (*type)->exts, (*type), ret, cleanup); - cleanup: ly_set_erase(&tpdf_chain, free); return ret; @@ -2874,6 +2923,7 @@ lys_compile_node_type(struct lysc_ctx *ctx, struct lysp_node *context_node, stru LY_CHECK_RET(lys_compile_type(ctx, context_node, leaf->flags, leaf->name, type_p, &leaf->type, leaf->units ? NULL : &leaf->units, &dflt)); + LY_ATOMIC_INC_BARRIER(leaf->type->refcount); /* store default value, if any */ if (dflt && !(leaf->flags & LYS_SET_DFLT)) { @@ -3367,7 +3417,7 @@ lys_compile_node_list(struct lysc_ctx *ctx, struct lysp_node *pnode, struct lysc /* YANG 1.0 denies key to be of empty type */ if (key->type->basetype == LY_TYPE_EMPTY) { LOGVAL(ctx->ctx, LYVE_SEMANTICS, - "List's key cannot be of \"empty\" type until it is in YANG 1.1 module."); + "List key of the \"empty\" type is allowed only in YANG 1.1 modules."); return LY_EVALID; } } else { diff --git a/src/schema_features.c b/src/schema_features.c index dca998e..b4273df 100644 --- a/src/schema_features.c +++ b/src/schema_features.c @@ -411,7 +411,7 @@ lys_compile_iffeature(const struct ly_ctx *ctx, const struct lysp_qname *qname, LY_ARRAY_CREATE_RET(ctx, iff->features, f_size, LY_EMEM); iff->expr = calloc((j = (expr_size / IFF_RECORDS_IN_BYTE) + ((expr_size % IFF_RECORDS_IN_BYTE) ? 1 : 0)), sizeof *iff->expr); stack.stack = malloc(expr_size * sizeof *stack.stack); - LY_CHECK_ERR_GOTO(!stack.stack || !iff->expr, LOGMEM(ctx); rc = LY_EMEM, error); + LY_CHECK_ERR_GOTO(!stack.stack || !iff->expr, LOGMEM(ctx); rc = LY_EMEM, cleanup); stack.size = expr_size; f_size--; expr_size--; /* used as indexes from now */ @@ -473,7 +473,7 @@ lys_compile_iffeature(const struct ly_ctx *ctx, const struct lysp_qname *qname, LOGVAL(ctx, LYVE_SYNTAX_YANG, "Invalid value \"%s\" of if-feature - unable to find feature \"%.*s\".", qname->str, (int)(j - i), &c[i]); rc = LY_EVALID; - goto error; + goto cleanup; } iff->features[f_size] = f; LY_ARRAY_INCREMENT(iff->features); @@ -489,14 +489,16 @@ lys_compile_iffeature(const struct ly_ctx *ctx, const struct lysp_qname *qname, /* not all expected operators and operands found */ LOGVAL(ctx, LYVE_SYNTAX_YANG, "Invalid value \"%s\" of if-feature - processing error.", qname->str); rc = LY_EINT; - } else { - rc = LY_SUCCESS; } -error: - /* cleanup */ +cleanup: + if (rc) { + LY_ARRAY_FREE(iff->features); + iff->features = NULL; + free(iff->expr); + iff->expr = NULL; + } iff_stack_clean(&stack); - return rc; } @@ -227,9 +227,11 @@ ly_set_rm(struct ly_set *set, void *object, void (*destructor)(void *obj)) return ly_set_rm_index(set, i, destructor); } -LY_ERR +LIBYANG_API_DEF LY_ERR ly_set_rm_index_ordered(struct ly_set *set, uint32_t index, void (*destructor)(void *obj)) { + LY_CHECK_ARG_RET(NULL, set, set->count, LY_EINVAL); + if (destructor) { destructor(set->objs[index]); } @@ -1,9 +1,10 @@ /** * @file set.h * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Michal Vasko <mvasko@cesnet.cz> * @brief Generic set structure and manipulation routines. * - * Copyright (c) 2015 - 2018 CESNET, z.s.p.o. + * Copyright (c) 2015 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -81,8 +82,8 @@ LIBYANG_API_DECL LY_ERR ly_set_dup(const struct ly_set *set, void *(*duplicator) /** * @brief Add an object into the set * - * @param[in] set Set where the \p object will be added. - * @param[in] object Object to be added into the \p set; + * @param[in] set Set where the @p object will be added. + * @param[in] object Object to be added into the @p set; * @param[in] list flag to handle set as a list (without checking for (ignoring) duplicit items) * @param[out] index_p Optional pointer to return index of the added @p object. Usually it is the last index (::ly_set::count - 1), * but in case the duplicities are checked and the object is already in the set, the @p object is not added and index of the @@ -94,7 +95,7 @@ LIBYANG_API_DECL LY_ERR ly_set_dup(const struct ly_set *set, void *(*duplicator) LIBYANG_API_DECL LY_ERR ly_set_add(struct ly_set *set, const void *object, ly_bool list, uint32_t *index_p); /** - * @brief Add all objects from \p src to \p trg. + * @brief Add all objects from @p src to @p trg. * * Since it is a set, the function checks for duplicities. * @@ -102,8 +103,8 @@ LIBYANG_API_DECL LY_ERR ly_set_add(struct ly_set *set, const void *object, ly_bo * @param[in] src Source set. * @param[in] list flag to handle set as a list (without checking for (ignoring) duplicit items) * @param[in] duplicator Optional pointer to function that duplicates the objects being added - * from \p src into \p trg set. If not provided, the \p trg set will point to the exact same - * objects as the \p src set. + * from @p src into @p trg set. If not provided, the @p trg set will point to the exact same + * objects as the @p src set. * @return LY_SUCCESS in case of success * @return LY_EINVAL in case of invalid input parameters. * @return LY_EMEM in case of memory allocation failure. @@ -134,8 +135,8 @@ LIBYANG_API_DECL void ly_set_clean(struct ly_set *set, void (*destructor)(void * * Note that after removing the object from a set, indexes of other objects in the set can change * (the last object is placed instead of the removed object). * - * @param[in] set Set from which the \p node will be removed. - * @param[in] object The object to be removed from the \p set. + * @param[in] set Set from which to remove. + * @param[in] object The object to be removed from the @p set. * @param[in] destructor Optional function to free the objects being removed. * @return LY_ERR return value. */ @@ -147,14 +148,26 @@ LIBYANG_API_DECL LY_ERR ly_set_rm(struct ly_set *set, void *object, void (*destr * Note that after removing the object from a set, indexes of other nodes in the set can change * (the last object is placed instead of the removed object). * - * @param[in] set Set from which a node will be removed. - * @param[in] index Index of the object to remove in the \p set. + * @param[in] set Set from which to remove. + * @param[in] index Index of the object to remove in the @p set. * @param[in] destructor Optional function to free the objects being removed. * @return LY_ERR return value. */ LIBYANG_API_DECL LY_ERR ly_set_rm_index(struct ly_set *set, uint32_t index, void (*destructor)(void *obj)); /** + * @brief Remove an object on the specific set index. + * + * Unlike ::ly_set_rm_indes(), this function moves all the items following the removed one. + * + * @param[in] set Set from which to remove. + * @param[in] index Index of the object to remove in the @p set. + * @param[in] destructor Optional function to free the objects being removed. + * @return LY_ERR return value. + */ +LIBYANG_API_DECL LY_ERR ly_set_rm_index_ordered(struct ly_set *set, uint32_t index, void (*destructor)(void *obj)); + +/** * @brief Free the ::ly_set data. If the destructor is not provided, it frees only the set structure * content, not the referred data. * diff --git a/src/tree_data.c b/src/tree_data.c index d6a04ff..82a1ae8 100644 --- a/src/tree_data.c +++ b/src/tree_data.c @@ -52,6 +52,9 @@ #include "xml.h" #include "xpath.h" +static LY_ERR lyd_compare_siblings_(const struct lyd_node *node1, const struct lyd_node *node2, uint32_t options, + ly_bool parental_schemas_checked); + static LYD_FORMAT lyd_parse_get_format(const struct ly_in *in, LYD_FORMAT format) { @@ -96,10 +99,9 @@ static LY_ERR lyd_parse(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct lyd_node *parent, struct lyd_node **first_p, struct ly_in *in, LYD_FORMAT format, uint32_t parse_opts, uint32_t val_opts, struct lyd_node **op) { - LY_ERR rc = LY_SUCCESS; + LY_ERR r = LY_SUCCESS, rc = LY_SUCCESS; struct lyd_ctx *lydctx = NULL; struct ly_set parsed = {0}; - struct lyd_node *first; uint32_t i, int_opts = 0; ly_bool subtree_sibling = 0; @@ -121,36 +123,40 @@ lyd_parse(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, struct /* parse the data */ switch (format) { case LYD_XML: - rc = lyd_parse_xml(ctx, ext, parent, first_p, in, parse_opts, val_opts, int_opts, &parsed, + r = lyd_parse_xml(ctx, ext, parent, first_p, in, parse_opts, val_opts, int_opts, &parsed, &subtree_sibling, &lydctx); break; case LYD_JSON: - rc = lyd_parse_json(ctx, ext, parent, first_p, in, parse_opts, val_opts, int_opts, &parsed, + r = lyd_parse_json(ctx, ext, parent, first_p, in, parse_opts, val_opts, int_opts, &parsed, &subtree_sibling, &lydctx); break; case LYD_LYB: - rc = lyd_parse_lyb(ctx, ext, parent, first_p, in, parse_opts, val_opts, int_opts, &parsed, + r = lyd_parse_lyb(ctx, ext, parent, first_p, in, parse_opts, val_opts, int_opts, &parsed, &subtree_sibling, &lydctx); break; case LYD_UNKNOWN: LOGARG(ctx, format); - rc = LY_EINVAL; + r = LY_EINVAL; break; } - LY_CHECK_GOTO(rc, cleanup); + if (r) { + rc = r; + if ((r != LY_EVALID) || !lydctx || !(lydctx->val_opts & LYD_VALIDATE_MULTI_ERROR) || + (ly_vecode(ctx) == LYVE_SYNTAX)) { + goto cleanup; + } + } - if (parent) { - /* get first top-level sibling */ - for (first = parent; first->parent; first = lyd_parent(first)) {} - first = lyd_first_sibling(first); - first_p = &first; + if (parent && parsed.count) { + /* use the first parsed node */ + first_p = &parsed.dnodes[0]; } if (!(parse_opts & LYD_PARSE_ONLY)) { /* validate data */ - rc = lyd_validate(first_p, NULL, ctx, val_opts, 0, &lydctx->node_when, &lydctx->node_types, &lydctx->meta_types, + r = lyd_validate(first_p, NULL, ctx, val_opts, 0, &lydctx->node_when, &lydctx->node_types, &lydctx->meta_types, &lydctx->ext_node, &lydctx->ext_val, NULL); - LY_CHECK_GOTO(rc, cleanup); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); } /* set the operation node */ @@ -252,28 +258,7 @@ lyd_parse_data_path(const struct ly_ctx *ctx, const char *path, LYD_FORMAT forma * * At least one of @p parent, @p tree, or @p op must always be set. * - * Specific @p data_type values have different parameter meaning as follows: - * - ::LYD_TYPE_RPC_NETCONF: - * - @p parent - must be NULL, the whole RPC is expected; - * - @p format - must be ::LYD_XML, NETCONF supports only this format; - * - @p tree - must be provided, all the NETCONF-specific XML envelopes will be returned here as - * a separate opaque data tree, even if the function fails, this may be returned; - * - @p op - must be provided, the RPC/action data tree itself will be returned here, pointing to the operation; - * - * - ::LYD_TYPE_NOTIF_NETCONF: - * - @p parent - must be NULL, the whole notification is expected; - * - @p format - must be ::LYD_XML, NETCONF supports only this format; - * - @p tree - must be provided, all the NETCONF-specific XML envelopes will be returned here as - * a separate opaque data tree, even if the function fails, this may be returned; - * - @p op - must be provided, the notification data tree itself will be returned here, pointing to the operation; - * - * - ::LYD_TYPE_REPLY_NETCONF: - * - @p parent - must be set, pointing to the invoked RPC operation (RPC or action) node; - * - @p format - must be ::LYD_XML, NETCONF supports only this format; - * - @p tree - must be provided, all the NETCONF-specific XML envelopes will be returned here as - * a separate opaque data tree, even if the function fails, this may be returned; - * - @p op - must be NULL, the reply is appended to the RPC; - * Note that there are 3 kinds of NETCONF replies - ok, error, and data. Only data reply appends any nodes to the RPC. + * Specific @p data_type values have different parameter meaning as mentioned for ::lyd_parse_op(). * * @param[in] ctx libyang context. * @param[in] ext Extension instance providing the specific schema tree to match with the data being parsed. @@ -295,6 +280,7 @@ lyd_parse_op_(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, str struct ly_set parsed = {0}; struct lyd_node *first = NULL, *envp = NULL; uint32_t i, parse_opts, val_opts, int_opts = 0; + ly_bool proto_msg = 0; if (!ctx) { ctx = LYD_CTX(parent); @@ -316,20 +302,51 @@ lyd_parse_op_(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, str val_opts = 0; switch (data_type) { - - /* special XML NETCONF data types */ case LYD_TYPE_RPC_NETCONF: case LYD_TYPE_NOTIF_NETCONF: LY_CHECK_ARG_RET(ctx, format == LYD_XML, !parent, tree, op, LY_EINVAL); - /* fallthrough */ + proto_msg = 1; + break; case LYD_TYPE_REPLY_NETCONF: - if (data_type == LYD_TYPE_REPLY_NETCONF) { - LY_CHECK_ARG_RET(ctx, format == LYD_XML, parent, parent->schema->nodetype & (LYS_RPC | LYS_ACTION), tree, !op, - LY_EINVAL); - } + LY_CHECK_ARG_RET(ctx, format == LYD_XML, parent, parent->schema, parent->schema->nodetype & (LYS_RPC | LYS_ACTION), + tree, !op, LY_EINVAL); + proto_msg = 1; + break; + case LYD_TYPE_RPC_RESTCONF: + case LYD_TYPE_REPLY_RESTCONF: + LY_CHECK_ARG_RET(ctx, parent, parent->schema, parent->schema->nodetype & (LYS_RPC | LYS_ACTION), tree, !op, LY_EINVAL); + proto_msg = 1; + break; + case LYD_TYPE_NOTIF_RESTCONF: + LY_CHECK_ARG_RET(ctx, format == LYD_JSON, !parent, tree, op, LY_EINVAL); + proto_msg = 1; + break; - /* parse the NETCONF message */ - rc = lyd_parse_xml_netconf(ctx, ext, parent, &first, in, parse_opts, val_opts, data_type, &envp, &parsed, &lydctx); + /* set internal opts */ + case LYD_TYPE_RPC_YANG: + int_opts = LYD_INTOPT_RPC | LYD_INTOPT_ACTION | (parent ? LYD_INTOPT_WITH_SIBLINGS : LYD_INTOPT_NO_SIBLINGS); + break; + case LYD_TYPE_NOTIF_YANG: + int_opts = LYD_INTOPT_NOTIF | (parent ? LYD_INTOPT_WITH_SIBLINGS : LYD_INTOPT_NO_SIBLINGS); + break; + case LYD_TYPE_REPLY_YANG: + int_opts = LYD_INTOPT_REPLY | (parent ? LYD_INTOPT_WITH_SIBLINGS : LYD_INTOPT_NO_SIBLINGS); + break; + case LYD_TYPE_DATA_YANG: + LOGINT(ctx); + rc = LY_EINT; + goto cleanup; + } + + /* parse a full protocol message */ + if (proto_msg) { + if (format == LYD_XML) { + /* parse the NETCONF (or RESTCONF XML) message */ + rc = lyd_parse_xml_netconf(ctx, ext, parent, &first, in, parse_opts, val_opts, data_type, &envp, &parsed, &lydctx); + } else { + /* parse the RESTCONF message */ + rc = lyd_parse_json_restconf(ctx, ext, parent, &first, in, parse_opts, val_opts, data_type, &envp, &parsed, &lydctx); + } if (rc) { if (envp) { /* special situation when the envelopes were parsed successfully */ @@ -349,21 +366,6 @@ lyd_parse_op_(const struct ly_ctx *ctx, const struct lysc_ext_instance *ext, str *op = lydctx->op_node; } goto cleanup; - - /* set internal opts */ - case LYD_TYPE_RPC_YANG: - int_opts = LYD_INTOPT_RPC | LYD_INTOPT_ACTION | LYD_INTOPT_NO_SIBLINGS; - break; - case LYD_TYPE_NOTIF_YANG: - int_opts = LYD_INTOPT_NOTIF | LYD_INTOPT_NO_SIBLINGS; - break; - case LYD_TYPE_REPLY_YANG: - int_opts = LYD_INTOPT_REPLY | LYD_INTOPT_NO_SIBLINGS; - break; - case LYD_TYPE_DATA_YANG: - LOGINT(ctx); - rc = LY_EINT; - goto cleanup; } /* parse the data */ @@ -405,7 +407,7 @@ cleanup: lyd_free_tree(parsed.dnodes[i]); } while (i); } - if (tree && ((format != LYD_XML) || !envp)) { + if (tree && !envp) { *tree = NULL; } if (op) { @@ -484,6 +486,10 @@ lyd_insert_get_next_anchor(const struct lyd_node *first_sibling, const struct ly /* get the first schema sibling */ schema = lys_getnext(NULL, sparent, new_node->schema->module->compiled, getnext_opts); + if (!schema) { + /* must be a top-level extension instance data, no anchor */ + return NULL; + } found = 0; LY_LIST_FOR(match, match) { @@ -506,8 +512,12 @@ lyd_insert_get_next_anchor(const struct lyd_node *first_sibling, const struct ly /* current node (match) is a data node still before the new node, continue search in data */ break; } + schema = lys_getnext(schema, sparent, new_node->schema->module->compiled, getnext_opts); - assert(schema); + if (!schema) { + /* must be a top-level extension instance data, no anchor */ + return NULL; + } } if (found && (match->schema != new_node->schema)) { @@ -660,6 +670,14 @@ lyd_insert_node(struct lyd_node *parent, struct lyd_node **first_sibling_p, stru } else { /* find the anchor, our next node, so we can insert before it */ anchor = lyd_insert_get_next_anchor(first_sibling, node); + + /* cannot insert data node after opaque nodes */ + if (!anchor && node->schema && first_sibling && !first_sibling->prev->schema) { + anchor = first_sibling->prev; + while ((anchor != first_sibling) && !anchor->prev->schema) { + anchor = anchor->prev; + } + } } if (anchor) { @@ -684,7 +702,7 @@ lyd_insert_node(struct lyd_node *parent, struct lyd_node **first_sibling_p, stru lyd_insert_hash(node); /* finish hashes for our parent, if needed and possible */ - if (node->schema && (node->schema->flags & LYS_KEY) && parent && lyd_insert_has_keys(parent)) { + if (node->schema && (node->schema->flags & LYS_KEY) && parent && parent->schema && lyd_insert_has_keys(parent)) { lyd_hash(parent); /* now we can insert even the list into its parent HT */ @@ -756,12 +774,12 @@ lyd_insert_child(struct lyd_node *parent, struct lyd_node *node) } if (node->parent || node->prev->next) { - lyd_unlink_tree(node); + lyd_unlink(node); } while (node) { iter = node->next; - lyd_unlink_tree(node); + lyd_unlink(node); lyd_insert_node(parent, NULL, node, 0); node = iter; } @@ -783,7 +801,7 @@ lyplg_ext_insert(struct lyd_node *parent, struct lyd_node *first) while (first) { iter = first->next; - lyd_unlink_tree(first); + lyd_unlink(first); lyd_insert_node(parent, NULL, first, 1); first = iter; } @@ -807,7 +825,7 @@ lyd_insert_sibling(struct lyd_node *sibling, struct lyd_node *node, struct lyd_n } if (node->parent || node->prev->next) { - lyd_unlink_tree(node); + lyd_unlink(node); } while (node) { @@ -817,7 +835,7 @@ lyd_insert_sibling(struct lyd_node *sibling, struct lyd_node *node, struct lyd_n } iter = node->next; - lyd_unlink_tree(node); + lyd_unlink(node); lyd_insert_node(NULL, &sibling, node, 0); node = iter; } @@ -850,7 +868,7 @@ lyd_insert_before(struct lyd_node *sibling, struct lyd_node *node) return LY_EINVAL; } - lyd_unlink_tree(node); + lyd_unlink(node); lyd_insert_before_node(sibling, node); lyd_insert_hash(node); @@ -874,26 +892,15 @@ lyd_insert_after(struct lyd_node *sibling, struct lyd_node *node) return LY_EINVAL; } - lyd_unlink_tree(node); + lyd_unlink(node); lyd_insert_after_node(sibling, node); lyd_insert_hash(node); return LY_SUCCESS; } -LIBYANG_API_DEF void -lyd_unlink_siblings(struct lyd_node *node) -{ - struct lyd_node *next, *elem, *first = NULL; - - LY_LIST_FOR_SAFE(node, next, elem) { - lyd_unlink_tree(elem); - lyd_insert_node(NULL, &first, elem, 1); - } -} - -LIBYANG_API_DEF void -lyd_unlink_tree(struct lyd_node *node) +void +lyd_unlink(struct lyd_node *node) { struct lyd_node *iter; @@ -941,6 +948,35 @@ lyd_unlink_tree(struct lyd_node *node) node->prev = node; } +LIBYANG_API_DEF void +lyd_unlink_siblings(struct lyd_node *node) +{ + struct lyd_node *next, *elem, *first = NULL; + + LY_LIST_FOR_SAFE(node, next, elem) { + if (lysc_is_key(elem->schema) && elem->parent) { + LOGERR(LYD_CTX(elem), LY_EINVAL, "Cannot unlink a list key \"%s\", unlink the list instance instead.", + LYD_NAME(elem)); + return; + } + + lyd_unlink(elem); + lyd_insert_node(NULL, &first, elem, 1); + } +} + +LIBYANG_API_DEF void +lyd_unlink_tree(struct lyd_node *node) +{ + if (node && lysc_is_key(node->schema) && node->parent) { + LOGERR(LYD_CTX(node), LY_EINVAL, "Cannot unlink a list key \"%s\", unlink the list instance instead.", + LYD_NAME(node)); + return; + } + + lyd_unlink(node); +} + void lyd_insert_meta(struct lyd_node *parent, struct lyd_meta *meta, ly_bool clear_dflt) { @@ -973,7 +1009,7 @@ lyd_insert_meta(struct lyd_node *parent, struct lyd_meta *meta, ly_bool clear_df LY_ERR lyd_create_meta(struct lyd_node *parent, struct lyd_meta **meta, const struct lys_module *mod, const char *name, - size_t name_len, const char *value, size_t value_len, ly_bool *dynamic, LY_VALUE_FORMAT format, + size_t name_len, const char *value, size_t value_len, ly_bool is_utf8, ly_bool *dynamic, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, const struct lysc_node *ctx_node, ly_bool clear_dflt, ly_bool *incomplete) { LY_ERR ret = LY_SUCCESS; @@ -1005,7 +1041,7 @@ lyd_create_meta(struct lyd_node *parent, struct lyd_meta **meta, const struct ly mt->parent = parent; mt->annotation = ant; lyplg_ext_get_storage(ant, LY_STMT_TYPE, sizeof ant_type, (const void **)&ant_type); - ret = lyd_value_store(mod->ctx, &mt->value, ant_type, value, value_len, dynamic, format, prefix_data, hints, + ret = lyd_value_store(mod->ctx, &mt->value, ant_type, value, value_len, is_utf8, dynamic, format, prefix_data, hints, ctx_node, incomplete); LY_CHECK_ERR_GOTO(ret, free(mt), cleanup); ret = lydict_insert(mod->ctx, name, name_len, &mt->name); @@ -1110,11 +1146,9 @@ finish: LIBYANG_API_DEF const struct lyd_node_term * lyd_target(const struct ly_path *path, const struct lyd_node *tree) { - struct lyd_node *target; + struct lyd_node *target = NULL; - if (ly_path_eval(path, tree, &target)) { - return NULL; - } + lyd_find_target(path, tree, &target); return (struct lyd_node_term *)target; } @@ -1149,15 +1183,6 @@ lyd_compare_schema_equal(const struct lysc_node *schema1, const struct lysc_node return 0; } - if (schema1->module->revision || schema2->module->revision) { - if (!schema1->module->revision || !schema2->module->revision) { - return 0; - } - if (strcmp(schema1->module->revision, schema2->module->revision)) { - return 0; - } - } - return 1; } @@ -1269,30 +1294,21 @@ lyd_compare_single_value(const struct lyd_node *node1, const struct lyd_node *no } /** - * @brief Internal implementation of @ref lyd_compare_single. - * @copydoc lyd_compare_single - * @param[in] parental_schemas_checked Flag used for optimization. - * When this function is called for the first time, the flag must be set to 0. - * The @ref lyd_compare_schema_parents_equal should be called only once during - * recursive calls, and this is accomplished by setting to 1 in the lyd_compare_single_ body. + * @brief Compare 2 data nodes if they are equivalent regarding the schema tree. + * + * Works correctly even if @p node1 and @p node2 have different contexts. + * + * @param[in] node1 The first node to compare. + * @param[in] node2 The second node to compare. + * @param[in] options Various @ref datacompareoptions. + * @param[in] parental_schemas_checked Flag set if parent schemas were checked for match. + * @return LY_SUCCESS if the nodes are equivalent. + * @return LY_ENOT if the nodes are not equivalent. */ static LY_ERR -lyd_compare_single_(const struct lyd_node *node1, const struct lyd_node *node2, uint32_t options, +lyd_compare_single_schema(const struct lyd_node *node1, const struct lyd_node *node2, uint32_t options, ly_bool parental_schemas_checked) { - const struct lyd_node *iter1, *iter2; - struct lyd_node_any *any1, *any2; - int len1, len2; - LY_ERR r; - - if (!node1 || !node2) { - if (node1 == node2) { - return LY_SUCCESS; - } else { - return LY_ENOT; - } - } - if (LYD_CTX(node1) == LYD_CTX(node2)) { /* same contexts */ if (options & LYD_COMPARE_OPAQ) { @@ -1317,6 +1333,28 @@ lyd_compare_single_(const struct lyd_node *node1, const struct lyd_node *node2, } } + return LY_SUCCESS; +} + +/** + * @brief Compare 2 data nodes if they are equivalent regarding the data they contain. + * + * Works correctly even if @p node1 and @p node2 have different contexts. + * + * @param[in] node1 The first node to compare. + * @param[in] node2 The second node to compare. + * @param[in] options Various @ref datacompareoptions. + * @return LY_SUCCESS if the nodes are equivalent. + * @return LY_ENOT if the nodes are not equivalent. + */ +static LY_ERR +lyd_compare_single_data(const struct lyd_node *node1, const struct lyd_node *node2, uint32_t options) +{ + const struct lyd_node *iter1, *iter2; + struct lyd_node_any *any1, *any2; + int len1, len2; + LY_ERR r; + if (!(options & LYD_COMPARE_OPAQ) && (node1->hash != node2->hash)) { return LY_ENOT; } @@ -1326,14 +1364,16 @@ lyd_compare_single_(const struct lyd_node *node1, const struct lyd_node *node2, if (!(options & LYD_COMPARE_OPAQ) && ((node1->schema && !node2->schema) || (!node1->schema && node2->schema))) { return LY_ENOT; } - if ((r = lyd_compare_single_value(node1, node2))) { - return r; + if ((!node1->schema && !node2->schema) || (node1->schema && (node1->schema->nodetype & LYD_NODE_TERM)) || + (node2->schema && (node2->schema->nodetype & LYD_NODE_TERM))) { + /* compare values only if there are any to compare */ + if ((r = lyd_compare_single_value(node1, node2))) { + return r; + } } if (options & LYD_COMPARE_FULL_RECURSION) { - iter1 = lyd_child(node1); - iter2 = lyd_child(node2); - goto all_children_compare; + return lyd_compare_siblings_(lyd_child(node1), lyd_child(node2), options, 1); } return LY_SUCCESS; } else { @@ -1354,50 +1394,38 @@ lyd_compare_single_(const struct lyd_node *node1, const struct lyd_node *node2, case LYS_RPC: case LYS_ACTION: case LYS_NOTIF: - if (options & LYD_COMPARE_DEFAULTS) { - if ((node1->flags & LYD_DEFAULT) != (node2->flags & LYD_DEFAULT)) { - return LY_ENOT; - } - } + /* implicit container is always equal to a container with non-default descendants */ if (options & LYD_COMPARE_FULL_RECURSION) { - iter1 = lyd_child(node1); - iter2 = lyd_child(node2); - goto all_children_compare; + return lyd_compare_siblings_(lyd_child(node1), lyd_child(node2), options, 1); } return LY_SUCCESS; case LYS_LIST: iter1 = lyd_child(node1); iter2 = lyd_child(node2); - if (!(node1->schema->flags & LYS_KEYLESS) && !(options & LYD_COMPARE_FULL_RECURSION)) { - /* lists with keys, their equivalence is based on their keys */ - for (const struct lysc_node *key = lysc_node_child(node1->schema); - key && (key->flags & LYS_KEY); - key = key->next) { - if (lyd_compare_single_(iter1, iter2, options, parental_schemas_checked)) { - return LY_ENOT; - } - iter1 = iter1->next; - iter2 = iter2->next; - } - } else { - /* lists without keys, their equivalence is based on equivalence of all the children (both direct and indirect) */ + if (options & LYD_COMPARE_FULL_RECURSION) { + return lyd_compare_siblings_(iter1, iter2, options, 1); + } else if (node1->schema->flags & LYS_KEYLESS) { + /* always equal */ + return LY_SUCCESS; + } -all_children_compare: - if (!iter1 && !iter2) { - /* no children, nothing to compare */ - return LY_SUCCESS; + /* lists with keys, their equivalence is based on their keys */ + for (const struct lysc_node *key = lysc_node_child(node1->schema); + key && (key->flags & LYS_KEY); + key = key->next) { + if (!iter1 || !iter2) { + return (iter1 == iter2) ? LY_SUCCESS : LY_ENOT; } + r = lyd_compare_single_schema(iter1, iter2, options, 1); + LY_CHECK_RET(r); + r = lyd_compare_single_data(iter1, iter2, options); + LY_CHECK_RET(r); - for ( ; iter1 && iter2; iter1 = iter1->next, iter2 = iter2->next) { - if (lyd_compare_single_(iter1, iter2, options | LYD_COMPARE_FULL_RECURSION, parental_schemas_checked)) { - return LY_ENOT; - } - } - if (iter1 || iter2) { - return LY_ENOT; - } + iter1 = iter1->next; + iter2 = iter2->next; } + return LY_SUCCESS; case LYS_ANYXML: case LYS_ANYDATA: @@ -1409,9 +1437,7 @@ all_children_compare: } switch (any1->value_type) { case LYD_ANYDATA_DATATREE: - iter1 = any1->value.tree; - iter2 = any2->value.tree; - goto all_children_compare; + return lyd_compare_siblings_(any1->value.tree, any2->value.tree, options, 1); case LYD_ANYDATA_STRING: case LYD_ANYDATA_XML: case LYD_ANYDATA_JSON: @@ -1441,23 +1467,77 @@ all_children_compare: return LY_EINT; } -LIBYANG_API_DEF LY_ERR -lyd_compare_single(const struct lyd_node *node1, const struct lyd_node *node2, uint32_t options) +/** + * @brief Compare all siblings at a node level. + * + * @param[in] node1 First sibling list. + * @param[in] node2 Second sibling list. + * @param[in] options Various @ref datacompareoptions. + * @param[in] parental_schemas_checked Flag set if parent schemas were checked for match. + * @return LY_SUCCESS if equal. + * @return LY_ENOT if not equal. + * @return LY_ERR on error. + */ +static LY_ERR +lyd_compare_siblings_(const struct lyd_node *node1, const struct lyd_node *node2, uint32_t options, + ly_bool parental_schemas_checked) { - return lyd_compare_single_(node1, node2, options, 0); + LY_ERR r; + const struct lyd_node *iter2; + + while (node1 && node2) { + /* schema match */ + r = lyd_compare_single_schema(node1, node2, options, parental_schemas_checked); + LY_CHECK_RET(r); + + if (node1->schema && (((node1->schema->nodetype == LYS_LIST) && !(node1->schema->flags & LYS_KEYLESS)) || + ((node1->schema->nodetype == LYS_LEAFLIST) && (node1->schema->flags & LYS_CONFIG_W))) && + (node1->schema->flags & LYS_ORDBY_SYSTEM)) { + /* find a matching instance in case they are ordered differently */ + r = lyd_find_sibling_first(node2, node1, (struct lyd_node **)&iter2); + if (r == LY_ENOTFOUND) { + /* no matching instance, data not equal */ + r = LY_ENOT; + } + LY_CHECK_RET(r); + } else { + /* compare with the current node */ + iter2 = node2; + } + + /* data match */ + r = lyd_compare_single_data(node1, iter2, options | LYD_COMPARE_FULL_RECURSION); + LY_CHECK_RET(r); + + node1 = node1->next; + node2 = node2->next; + } + + return (node1 || node2) ? LY_ENOT : LY_SUCCESS; } LIBYANG_API_DEF LY_ERR -lyd_compare_siblings(const struct lyd_node *node1, const struct lyd_node *node2, uint32_t options) +lyd_compare_single(const struct lyd_node *node1, const struct lyd_node *node2, uint32_t options) { - for ( ; node1 && node2; node1 = node1->next, node2 = node2->next) { - LY_CHECK_RET(lyd_compare_single(node1, node2, options)); + LY_ERR r; + + if (!node1 || !node2) { + return (node1 == node2) ? LY_SUCCESS : LY_ENOT; } - if (node1 == node2) { - return LY_SUCCESS; + /* schema match */ + if ((r = lyd_compare_single_schema(node1, node2, options, 0))) { + return r; } - return LY_ENOT; + + /* data match */ + return lyd_compare_single_data(node1, node2, options); +} + +LIBYANG_API_DEF LY_ERR +lyd_compare_siblings(const struct lyd_node *node1, const struct lyd_node *node2, uint32_t options) +{ + return lyd_compare_siblings_(node1, node2, options, 0); } LIBYANG_API_DEF LY_ERR @@ -1676,6 +1756,9 @@ lyd_dup_r(const struct lyd_node *node, const struct ly_ctx *trg_ctx, struct lyd_ } else { dup->flags = (node->flags & (LYD_DEFAULT | LYD_EXT)) | LYD_NEW; } + if (options & LYD_DUP_WITH_PRIV) { + dup->priv = node->priv; + } if (trg_ctx == LYD_CTX(node)) { dup->schema = node->schema; } else { @@ -1736,7 +1819,7 @@ lyd_dup_r(const struct lyd_node *node, const struct ly_ctx *trg_ctx, struct lyd_ /* store canonical value in the target context */ val_can = lyd_get_value(node); type = ((struct lysc_node_leaf *)term->schema)->type; - ret = lyd_value_store(trg_ctx, &term->value, type, val_can, strlen(val_can), NULL, LY_VALUE_CANON, NULL, + ret = lyd_value_store(trg_ctx, &term->value, type, val_can, strlen(val_can), 1, NULL, LY_VALUE_CANON, NULL, LYD_HINT_DATA, term->schema, NULL); LY_CHECK_GOTO(ret, error); } @@ -1787,10 +1870,11 @@ error: * @return LY_ERR value. */ static LY_ERR -lyd_dup_get_local_parent(const struct lyd_node *node, const struct ly_ctx *trg_ctx, const struct lyd_node_inner *parent, - uint32_t options, struct lyd_node **dup_parent, struct lyd_node_inner **local_parent) +lyd_dup_get_local_parent(const struct lyd_node *node, const struct ly_ctx *trg_ctx, struct lyd_node *parent, + uint32_t options, struct lyd_node **dup_parent, struct lyd_node **local_parent) { - const struct lyd_node_inner *orig_parent, *iter; + const struct lyd_node *orig_parent; + struct lyd_node *iter = NULL; ly_bool repeat = 1, ext_parent = 0; *dup_parent = NULL; @@ -1799,38 +1883,38 @@ lyd_dup_get_local_parent(const struct lyd_node *node, const struct ly_ctx *trg_c if (node->flags & LYD_EXT) { ext_parent = 1; } - for (orig_parent = node->parent; repeat && orig_parent; orig_parent = orig_parent->parent) { + for (orig_parent = lyd_parent(node); repeat && orig_parent; orig_parent = lyd_parent(orig_parent)) { if (ext_parent) { /* use the standard context */ trg_ctx = LYD_CTX(orig_parent); } - if (parent && (parent->schema == orig_parent->schema)) { + if (parent && (LYD_CTX(parent) == LYD_CTX(orig_parent)) && (parent->schema == orig_parent->schema)) { /* stop creating parents, connect what we have into the provided parent */ iter = parent; repeat = 0; + } else if (parent && (LYD_CTX(parent) != LYD_CTX(orig_parent)) && + lyd_compare_schema_equal(parent->schema, orig_parent->schema) && + lyd_compare_schema_parents_equal(parent, orig_parent)) { + iter = parent; + repeat = 0; } else { iter = NULL; - LY_CHECK_RET(lyd_dup_r((struct lyd_node *)orig_parent, trg_ctx, NULL, 0, (struct lyd_node **)&iter, options, - (struct lyd_node **)&iter)); - } - if (!*local_parent) { - *local_parent = (struct lyd_node_inner *)iter; - } - if (iter->child) { - /* 1) list - add after keys - * 2) provided parent with some children */ - iter->child->prev->next = *dup_parent; + LY_CHECK_RET(lyd_dup_r(orig_parent, trg_ctx, NULL, 0, &iter, options, &iter)); + + /* insert into the previous duplicated parent */ if (*dup_parent) { - (*dup_parent)->prev = iter->child->prev; - iter->child->prev = *dup_parent; + lyd_insert_node(iter, NULL, *dup_parent, 0); } - } else { - ((struct lyd_node_inner *)iter)->child = *dup_parent; + + /* update the last duplicated parent */ + *dup_parent = iter; } - if (*dup_parent) { - (*dup_parent)->parent = (struct lyd_node_inner *)iter; + + /* set the first parent */ + if (!*local_parent) { + *local_parent = iter; } - *dup_parent = (struct lyd_node *)iter; + if (orig_parent->flags & LYD_EXT) { ext_parent = 1; } @@ -1838,23 +1922,27 @@ lyd_dup_get_local_parent(const struct lyd_node *node, const struct ly_ctx *trg_c if (repeat && parent) { /* given parent and created parents chain actually do not interconnect */ - LOGERR(trg_ctx, LY_EINVAL, - "Invalid argument parent (%s()) - does not interconnect with the created node's parents chain.", __func__); + LOGERR(trg_ctx, LY_EINVAL, "None of the duplicated node \"%s\" schema parents match the provided parent \"%s\".", + LYD_NAME(node), LYD_NAME(parent)); return LY_EINVAL; } + if (*dup_parent && parent) { + /* last insert into a prevously-existing parent */ + lyd_insert_node(parent, NULL, *dup_parent, 0); + } return LY_SUCCESS; } static LY_ERR -lyd_dup(const struct lyd_node *node, const struct ly_ctx *trg_ctx, struct lyd_node_inner *parent, uint32_t options, +lyd_dup(const struct lyd_node *node, const struct ly_ctx *trg_ctx, struct lyd_node *parent, uint32_t options, ly_bool nosiblings, struct lyd_node **dup) { LY_ERR rc; const struct lyd_node *orig; /* original node to be duplicated */ struct lyd_node *first = NULL; /* the first duplicated node, this is returned */ struct lyd_node *top = NULL; /* the most higher created node */ - struct lyd_node_inner *local_parent = NULL; /* the direct parent node for the duplicated node(s) */ + struct lyd_node *local_parent = NULL; /* the direct parent node for the duplicated node(s) */ assert(node && trg_ctx); @@ -1869,7 +1957,7 @@ lyd_dup(const struct lyd_node *node, const struct ly_ctx *trg_ctx, struct lyd_no if (lysc_is_key(orig->schema)) { if (local_parent) { /* the key must already exist in the parent */ - rc = lyd_find_sibling_schema(local_parent->child, orig->schema, first ? NULL : &first); + rc = lyd_find_sibling_schema(lyd_child(local_parent), orig->schema, first ? NULL : &first); LY_CHECK_ERR_GOTO(rc, LOGINT(trg_ctx), error); } else { assert(!(options & LYD_DUP_WITH_PARENTS)); @@ -1879,8 +1967,7 @@ lyd_dup(const struct lyd_node *node, const struct ly_ctx *trg_ctx, struct lyd_no } } else { /* if there is no local parent, it will be inserted into first */ - rc = lyd_dup_r(orig, trg_ctx, local_parent ? &local_parent->node : NULL, 0, &first, options, - first ? NULL : &first); + rc = lyd_dup_r(orig, trg_ctx, local_parent, 0, &first, options, first ? NULL : &first); LY_CHECK_GOTO(rc, error); } if (nosiblings) { @@ -1923,7 +2010,7 @@ lyd_dup_ctx_check(const struct lyd_node *node, const struct lyd_node_inner *pare for (iter = node; iter && !(iter->flags & LYD_EXT); iter = lyd_parent(iter)) {} if (!iter || !lyd_parent(iter) || (LYD_CTX(lyd_parent(iter)) != LYD_CTX(parent))) { - LOGERR(NULL, LY_EINVAL, "Different contexts used in node duplication."); + LOGERR(LYD_CTX(node), LY_EINVAL, "Different contexts used in node duplication."); return LY_EINVAL; } } @@ -1937,7 +2024,7 @@ lyd_dup_single(const struct lyd_node *node, struct lyd_node_inner *parent, uint3 LY_CHECK_ARG_RET(NULL, node, LY_EINVAL); LY_CHECK_RET(lyd_dup_ctx_check(node, parent)); - return lyd_dup(node, LYD_CTX(node), parent, options, 1, dup); + return lyd_dup(node, LYD_CTX(node), (struct lyd_node *)parent, options, 1, dup); } LIBYANG_API_DEF LY_ERR @@ -1946,7 +2033,7 @@ lyd_dup_single_to_ctx(const struct lyd_node *node, const struct ly_ctx *trg_ctx, { LY_CHECK_ARG_RET(trg_ctx, node, trg_ctx, LY_EINVAL); - return lyd_dup(node, trg_ctx, parent, options, 1, dup); + return lyd_dup(node, trg_ctx, (struct lyd_node *)parent, options, 1, dup); } LIBYANG_API_DEF LY_ERR @@ -1955,7 +2042,7 @@ lyd_dup_siblings(const struct lyd_node *node, struct lyd_node_inner *parent, uin LY_CHECK_ARG_RET(NULL, node, LY_EINVAL); LY_CHECK_RET(lyd_dup_ctx_check(node, parent)); - return lyd_dup(node, LYD_CTX(node), parent, options, 0, dup); + return lyd_dup(node, LYD_CTX(node), (struct lyd_node *)parent, options, 0, dup); } LIBYANG_API_DEF LY_ERR @@ -1964,7 +2051,7 @@ lyd_dup_siblings_to_ctx(const struct lyd_node *node, const struct ly_ctx *trg_ct { LY_CHECK_ARG_RET(trg_ctx, node, trg_ctx, LY_EINVAL); - return lyd_dup(node, trg_ctx, parent, options, 0, dup); + return lyd_dup(node, trg_ctx, (struct lyd_node *)parent, options, 0, dup); } LIBYANG_API_DEF LY_ERR @@ -2019,13 +2106,13 @@ finish: */ static LY_ERR lyd_merge_sibling_r(struct lyd_node **first_trg, struct lyd_node *parent_trg, const struct lyd_node **sibling_src_p, - lyd_merge_cb merge_cb, void *cb_data, uint16_t options, struct lyd_dup_inst **dup_inst) + lyd_merge_cb merge_cb, void *cb_data, uint16_t options, struct ly_ht **dup_inst) { const struct lyd_node *child_src, *tmp, *sibling_src; struct lyd_node *match_trg, *dup_src, *elem; struct lyd_node_opaq *opaq_trg, *opaq_src; struct lysc_type *type; - struct lyd_dup_inst *child_dup_inst = NULL; + struct ly_ht *child_dup_inst = NULL; LY_ERR ret; ly_bool first_inst = 0; @@ -2110,7 +2197,7 @@ lyd_merge_sibling_r(struct lyd_node **first_trg, struct lyd_node *parent_trg, co /* node not found, merge it */ if (options & LYD_MERGE_DESTRUCT) { dup_src = (struct lyd_node *)sibling_src; - lyd_unlink_tree(dup_src); + lyd_unlink(dup_src); /* spend it */ *sibling_src_p = NULL; } else { @@ -2147,7 +2234,7 @@ lyd_merge(struct lyd_node **target, const struct lyd_node *source, const struct lyd_merge_cb merge_cb, void *cb_data, uint16_t options, ly_bool nosiblings) { const struct lyd_node *sibling_src, *tmp; - struct lyd_dup_inst *dup_inst = NULL; + struct ly_ht *dup_inst = NULL; ly_bool first; LY_ERR ret = LY_SUCCESS; @@ -2357,9 +2444,9 @@ lyd_path(const struct lyd_node *node, LYD_PATH_TYPE pathtype, char *buffer, size for (iter = node, i = 1; i < depth; iter = lyd_parent(iter), ++i) {} iter_print: /* get the module */ - mod = iter->schema ? iter->schema->module : lyd_owner_module(iter); + mod = lyd_node_module(iter); parent = lyd_parent(iter); - prev_mod = (parent && parent->schema) ? parent->schema->module : lyd_owner_module(parent); + prev_mod = lyd_node_module(parent); if (prev_mod == mod) { mod = NULL; } @@ -2429,15 +2516,17 @@ lyd_path_set(const struct ly_set *dnodes, LYD_PATH_TYPE pathtype) for (depth = 1; depth <= dnodes->count; ++depth) { /* current node */ iter = dnodes->dnodes[depth - 1]; - mod = iter->schema ? iter->schema->module : lyd_owner_module(iter); + mod = lyd_node_module(iter); /* parent */ parent = (depth > 1) ? dnodes->dnodes[depth - 2] : NULL; - assert(!parent || !iter->schema || !parent->schema || (lysc_data_parent(iter->schema) == parent->schema) || - (!lysc_data_parent(iter->schema) && (LYD_CTX(iter) != LYD_CTX(parent)))); + assert(!parent || !iter->schema || !parent->schema || (parent->schema->nodetype & LYD_NODE_ANY) || + (lysc_data_parent(iter->schema) == parent->schema) || + (!lysc_data_parent(iter->schema) && (LYD_CTX(iter) != LYD_CTX(parent))) || + (parent->schema->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF))); /* get module to print, if any */ - prev_mod = (parent && parent->schema) ? parent->schema->module : lyd_owner_module(parent); + prev_mod = lyd_node_module(parent); if (prev_mod == mod) { mod = NULL; } @@ -2567,14 +2656,14 @@ lyd_find_sibling_first(const struct lyd_node *siblings, const struct lyd_node *t siblings = lyd_first_sibling(siblings); parent = siblings->parent; - if (parent && parent->schema && parent->children_ht) { + if (target->schema && parent && parent->schema && parent->children_ht) { assert(target->hash); if (lysc_is_dup_inst_list(target->schema)) { /* we must search the instances from beginning to find the first matching one */ found = 0; LYD_LIST_FOR_INST(siblings, target->schema, iter) { - if (!lyd_compare_single(target, iter, 0)) { + if (!lyd_compare_single(target, iter, LYD_COMPARE_FULL_RECURSION)) { found = 1; break; } @@ -2594,10 +2683,16 @@ lyd_find_sibling_first(const struct lyd_node *siblings, const struct lyd_node *t } } } else { - /* no children hash table */ + /* no children hash table or cannot be used */ for ( ; siblings; siblings = siblings->next) { - if (!lyd_compare_single(siblings, target, LYD_COMPARE_OPAQ)) { - break; + if (lysc_is_dup_inst_list(target->schema)) { + if (!lyd_compare_single(siblings, target, LYD_COMPARE_FULL_RECURSION)) { + break; + } + } else { + if (!lyd_compare_single(siblings, target, 0)) { + break; + } } } } @@ -2661,7 +2756,7 @@ lyd_find_sibling_val(const struct lyd_node *siblings, const struct lysc_node *sc /* create a data node and find the instance */ if (schema->nodetype == LYS_LEAFLIST) { /* target used attributes: schema, hash, value */ - rc = lyd_create_term(schema, key_or_value, val_len, NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, NULL, &target); + rc = lyd_create_term(schema, key_or_value, val_len, 0, NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, NULL, &target); LY_CHECK_RET(rc); } else { /* target used attributes: schema, hash, child (all keys) */ @@ -2684,6 +2779,7 @@ lyd_find_sibling_dup_inst_set(const struct lyd_node *siblings, const struct lyd_ { struct lyd_node **match_p, *first, *iter; struct lyd_node_inner *parent; + uint32_t comp_opts; LY_CHECK_ARG_RET(NULL, target, set, LY_EINVAL); LY_CHECK_CTX_EQUAL_RET(siblings ? LYD_CTX(siblings) : NULL, LYD_CTX(target), LY_EINVAL); @@ -2695,6 +2791,9 @@ lyd_find_sibling_dup_inst_set(const struct lyd_node *siblings, const struct lyd_ return LY_ENOTFOUND; } + /* set options */ + comp_opts = (lysc_is_dup_inst_list(target->schema) ? LYD_COMPARE_FULL_RECURSION : 0); + /* get first sibling */ siblings = lyd_first_sibling(siblings); @@ -2719,7 +2818,7 @@ lyd_find_sibling_dup_inst_set(const struct lyd_node *siblings, const struct lyd_ } while (iter) { /* add all found nodes into the set */ - if ((iter != first) && !lyd_compare_single(iter, target, 0) && ly_set_add(*set, iter, 1, NULL)) { + if ((iter != first) && !lyd_compare_single(iter, target, comp_opts) && ly_set_add(*set, iter, 1, NULL)) { goto error; } @@ -2734,7 +2833,7 @@ lyd_find_sibling_dup_inst_set(const struct lyd_node *siblings, const struct lyd_ } else { /* no children hash table */ LY_LIST_FOR(siblings, siblings) { - if (!lyd_compare_single(target, siblings, LYD_COMPARE_OPAQ)) { + if (!lyd_compare_single(target, siblings, comp_opts)) { ly_set_add(*set, (void *)siblings, 1, NULL); } } @@ -2756,8 +2855,22 @@ lyd_find_sibling_opaq_next(const struct lyd_node *first, const char *name, struc { LY_CHECK_ARG_RET(NULL, name, LY_EINVAL); + if (first && first->schema) { + first = first->prev; + if (first->schema) { + /* no opaque nodes */ + first = NULL; + } else { + /* opaque nodes are at the end, find quickly the first */ + while (!first->prev->schema) { + first = first->prev; + } + } + } + for ( ; first; first = first->next) { - if (!first->schema && !strcmp(LYD_NAME(first), name)) { + assert(!first->schema); + if (!strcmp(LYD_NAME(first), name)) { break; } } @@ -2769,58 +2882,19 @@ lyd_find_sibling_opaq_next(const struct lyd_node *first, const char *name, struc } LIBYANG_API_DEF LY_ERR -lyd_find_xpath4(const struct lyd_node *ctx_node, const struct lyd_node *tree, const char *xpath, LY_VALUE_FORMAT format, - void *prefix_data, const struct lyxp_var *vars, struct ly_set **set) +lyd_find_xpath(const struct lyd_node *ctx_node, const char *xpath, struct ly_set **set) { - LY_ERR ret = LY_SUCCESS; - struct lyxp_set xp_set = {0}; - struct lyxp_expr *exp = NULL; - uint32_t i; - - LY_CHECK_ARG_RET(NULL, tree, xpath, format, set, LY_EINVAL); - - *set = NULL; - - /* parse expression */ - ret = lyxp_expr_parse((struct ly_ctx *)LYD_CTX(tree), xpath, 0, 1, &exp); - LY_CHECK_GOTO(ret, cleanup); - - /* evaluate expression */ - ret = lyxp_eval(LYD_CTX(tree), exp, NULL, format, prefix_data, ctx_node, ctx_node, tree, vars, &xp_set, - LYXP_IGNORE_WHEN); - LY_CHECK_GOTO(ret, cleanup); - - if (xp_set.type != LYXP_SET_NODE_SET) { - LOGERR(LYD_CTX(tree), LY_EINVAL, "XPath \"%s\" result is not a node set.", xpath); - ret = LY_EINVAL; - goto cleanup; - } - - /* allocate return set */ - ret = ly_set_new(set); - LY_CHECK_GOTO(ret, cleanup); + LY_CHECK_ARG_RET(NULL, ctx_node, xpath, set, LY_EINVAL); - /* transform into ly_set, allocate memory for all the elements once (even though not all items must be - * elements but most likely will be) */ - (*set)->objs = malloc(xp_set.used * sizeof *(*set)->objs); - LY_CHECK_ERR_GOTO(!(*set)->objs, LOGMEM(LYD_CTX(tree)); ret = LY_EMEM, cleanup); - (*set)->size = xp_set.used; + return lyd_find_xpath4(ctx_node, ctx_node, xpath, LY_VALUE_JSON, NULL, NULL, set); +} - for (i = 0; i < xp_set.used; ++i) { - if (xp_set.val.nodes[i].type == LYXP_NODE_ELEM) { - ret = ly_set_add(*set, xp_set.val.nodes[i].node, 1, NULL); - LY_CHECK_GOTO(ret, cleanup); - } - } +LIBYANG_API_DEF LY_ERR +lyd_find_xpath2(const struct lyd_node *ctx_node, const char *xpath, const struct lyxp_var *vars, struct ly_set **set) +{ + LY_CHECK_ARG_RET(NULL, ctx_node, xpath, set, LY_EINVAL); -cleanup: - lyxp_set_free_content(&xp_set); - lyxp_expr_free((struct ly_ctx *)LYD_CTX(tree), exp); - if (ret) { - ly_set_free(*set, NULL); - *set = NULL; - } - return ret; + return lyd_find_xpath4(ctx_node, ctx_node, xpath, LY_VALUE_JSON, NULL, vars, set); } LIBYANG_API_DEF LY_ERR @@ -2833,63 +2907,263 @@ lyd_find_xpath3(const struct lyd_node *ctx_node, const struct lyd_node *tree, co } LIBYANG_API_DEF LY_ERR -lyd_find_xpath2(const struct lyd_node *ctx_node, const char *xpath, const struct lyxp_var *vars, struct ly_set **set) +lyd_find_xpath4(const struct lyd_node *ctx_node, const struct lyd_node *tree, const char *xpath, LY_VALUE_FORMAT format, + void *prefix_data, const struct lyxp_var *vars, struct ly_set **set) { - LY_CHECK_ARG_RET(NULL, ctx_node, xpath, set, LY_EINVAL); + LY_CHECK_ARG_RET(NULL, tree, xpath, set, LY_EINVAL); - return lyd_find_xpath4(ctx_node, ctx_node, xpath, LY_VALUE_JSON, NULL, vars, set); + *set = NULL; + + return lyd_eval_xpath4(ctx_node, tree, NULL, xpath, format, prefix_data, vars, NULL, set, NULL, NULL, NULL); } LIBYANG_API_DEF LY_ERR -lyd_find_xpath(const struct lyd_node *ctx_node, const char *xpath, struct ly_set **set) +lyd_eval_xpath(const struct lyd_node *ctx_node, const char *xpath, ly_bool *result) { - LY_CHECK_ARG_RET(NULL, ctx_node, xpath, set, LY_EINVAL); + return lyd_eval_xpath3(ctx_node, NULL, xpath, LY_VALUE_JSON, NULL, NULL, result); +} - return lyd_find_xpath4(ctx_node, ctx_node, xpath, LY_VALUE_JSON, NULL, NULL, set); +LIBYANG_API_DEF LY_ERR +lyd_eval_xpath2(const struct lyd_node *ctx_node, const char *xpath, const struct lyxp_var *vars, ly_bool *result) +{ + return lyd_eval_xpath3(ctx_node, NULL, xpath, LY_VALUE_JSON, NULL, vars, result); } LIBYANG_API_DEF LY_ERR lyd_eval_xpath3(const struct lyd_node *ctx_node, const struct lys_module *cur_mod, const char *xpath, LY_VALUE_FORMAT format, void *prefix_data, const struct lyxp_var *vars, ly_bool *result) { + return lyd_eval_xpath4(ctx_node, ctx_node, cur_mod, xpath, format, prefix_data, vars, NULL, NULL, NULL, NULL, result); +} + +LIBYANG_API_DEF LY_ERR +lyd_eval_xpath4(const struct lyd_node *ctx_node, const struct lyd_node *tree, const struct lys_module *cur_mod, + const char *xpath, LY_VALUE_FORMAT format, void *prefix_data, const struct lyxp_var *vars, LY_XPATH_TYPE *ret_type, + struct ly_set **node_set, char **string, long double *number, ly_bool *boolean) +{ LY_ERR ret = LY_SUCCESS; struct lyxp_set xp_set = {0}; struct lyxp_expr *exp = NULL; + uint32_t i; - LY_CHECK_ARG_RET(NULL, ctx_node, xpath, result, LY_EINVAL); + LY_CHECK_ARG_RET(NULL, tree, xpath, ((ret_type && node_set && string && number && boolean) || + (node_set && !string && !number && !boolean) || (!node_set && string && !number && !boolean) || + (!node_set && !string && number && !boolean) || (!node_set && !string && !number && boolean)), LY_EINVAL); - /* compile expression */ - ret = lyxp_expr_parse((struct ly_ctx *)LYD_CTX(ctx_node), xpath, 0, 1, &exp); + /* parse expression */ + ret = lyxp_expr_parse((struct ly_ctx *)LYD_CTX(tree), xpath, 0, 1, &exp); LY_CHECK_GOTO(ret, cleanup); /* evaluate expression */ - ret = lyxp_eval(LYD_CTX(ctx_node), exp, cur_mod, format, prefix_data, ctx_node, ctx_node, ctx_node, vars, &xp_set, + ret = lyxp_eval(LYD_CTX(tree), exp, cur_mod, format, prefix_data, ctx_node, ctx_node, tree, vars, &xp_set, LYXP_IGNORE_WHEN); LY_CHECK_GOTO(ret, cleanup); - /* transform into boolean */ - ret = lyxp_set_cast(&xp_set, LYXP_SET_BOOLEAN); - LY_CHECK_GOTO(ret, cleanup); + /* return expected result type without or with casting */ + if (node_set) { + /* node set */ + if (xp_set.type == LYXP_SET_NODE_SET) { + /* transform into a set */ + LY_CHECK_GOTO(ret = ly_set_new(node_set), cleanup); + (*node_set)->objs = malloc(xp_set.used * sizeof *(*node_set)->objs); + LY_CHECK_ERR_GOTO(!(*node_set)->objs, LOGMEM(LYD_CTX(tree)); ret = LY_EMEM, cleanup); + (*node_set)->size = xp_set.used; + for (i = 0; i < xp_set.used; ++i) { + if (xp_set.val.nodes[i].type == LYXP_NODE_ELEM) { + ret = ly_set_add(*node_set, xp_set.val.nodes[i].node, 1, NULL); + LY_CHECK_GOTO(ret, cleanup); + } + } + if (ret_type) { + *ret_type = LY_XPATH_NODE_SET; + } + } else if (!string && !number && !boolean) { + LOGERR(LYD_CTX(tree), LY_EINVAL, "XPath \"%s\" result is not a node set.", xpath); + ret = LY_EINVAL; + goto cleanup; + } + } - /* set result */ - *result = xp_set.val.bln; + if (string) { + if ((xp_set.type != LYXP_SET_STRING) && !node_set) { + /* cast into string */ + LY_CHECK_GOTO(ret = lyxp_set_cast(&xp_set, LYXP_SET_STRING), cleanup); + } + if (xp_set.type == LYXP_SET_STRING) { + /* string */ + *string = xp_set.val.str; + xp_set.val.str = NULL; + if (ret_type) { + *ret_type = LY_XPATH_STRING; + } + } + } + + if (number) { + if ((xp_set.type != LYXP_SET_NUMBER) && !node_set) { + /* cast into number */ + LY_CHECK_GOTO(ret = lyxp_set_cast(&xp_set, LYXP_SET_NUMBER), cleanup); + } + if (xp_set.type == LYXP_SET_NUMBER) { + /* number */ + *number = xp_set.val.num; + if (ret_type) { + *ret_type = LY_XPATH_NUMBER; + } + } + } + + if (boolean) { + if ((xp_set.type != LYXP_SET_BOOLEAN) && !node_set) { + /* cast into boolean */ + LY_CHECK_GOTO(ret = lyxp_set_cast(&xp_set, LYXP_SET_BOOLEAN), cleanup); + } + if (xp_set.type == LYXP_SET_BOOLEAN) { + /* boolean */ + *boolean = xp_set.val.bln; + if (ret_type) { + *ret_type = LY_XPATH_BOOLEAN; + } + } + } cleanup: lyxp_set_free_content(&xp_set); - lyxp_expr_free((struct ly_ctx *)LYD_CTX(ctx_node), exp); + lyxp_expr_free((struct ly_ctx *)LYD_CTX(tree), exp); return ret; } -LIBYANG_API_DEF LY_ERR -lyd_eval_xpath2(const struct lyd_node *ctx_node, const char *xpath, const struct lyxp_var *vars, ly_bool *result) +/** + * @brief Hash table node equal callback. + */ +static ly_bool +lyd_trim_equal_cb(void *val1_p, void *val2_p, ly_bool UNUSED(mod), void *UNUSED(cb_data)) { - return lyd_eval_xpath3(ctx_node, NULL, xpath, LY_VALUE_JSON, NULL, vars, result); + struct lyd_node *node1, *node2; + + node1 = *(struct lyd_node **)val1_p; + node2 = *(struct lyd_node **)val2_p; + + return node1 == node2; } LIBYANG_API_DEF LY_ERR -lyd_eval_xpath(const struct lyd_node *ctx_node, const char *xpath, ly_bool *result) +lyd_trim_xpath(struct lyd_node **tree, const char *xpath, const struct lyxp_var *vars) { - return lyd_eval_xpath3(ctx_node, NULL, xpath, LY_VALUE_JSON, NULL, NULL, result); + LY_ERR ret = LY_SUCCESS; + struct ly_ctx *ctx; + struct lyxp_set xp_set = {0}; + struct lyxp_expr *exp = NULL; + struct lyd_node *node, *parent; + struct lyxp_set_hash_node hnode; + struct ly_ht *parent_ht = NULL; + struct ly_set free_set = {0}; + uint32_t i, hash; + ly_bool is_result; + + LY_CHECK_ARG_RET(NULL, tree, xpath, LY_EINVAL); + + if (!*tree) { + /* nothing to do */ + goto cleanup; + } + + *tree = lyd_first_sibling(*tree); + ctx = (struct ly_ctx *)LYD_CTX(*tree); + + /* parse expression */ + ret = lyxp_expr_parse(ctx, xpath, 0, 1, &exp); + LY_CHECK_GOTO(ret, cleanup); + + /* evaluate expression */ + ret = lyxp_eval(ctx, exp, NULL, LY_VALUE_JSON, NULL, *tree, *tree, *tree, vars, &xp_set, LYXP_IGNORE_WHEN); + LY_CHECK_GOTO(ret, cleanup); + + /* create hash table for all the parents of results */ + parent_ht = lyht_new(32, sizeof node, lyd_trim_equal_cb, NULL, 1); + LY_CHECK_GOTO(!parent_ht, cleanup); + + for (i = 0; i < xp_set.used; ++i) { + if (xp_set.val.nodes[i].type != LYXP_NODE_ELEM) { + /* ignore */ + continue; + } + + for (parent = lyd_parent(xp_set.val.nodes[i].node); parent; parent = lyd_parent(parent)) { + /* add the parent into parent_ht */ + ret = lyht_insert(parent_ht, &parent, parent->hash, NULL); + if (ret == LY_EEXIST) { + /* shared parent, we are done */ + break; + } + LY_CHECK_GOTO(ret, cleanup); + } + } + + hnode.type = LYXP_NODE_ELEM; + LY_LIST_FOR(*tree, parent) { + LYD_TREE_DFS_BEGIN(parent, node) { + if (lysc_is_key(node->schema)) { + /* ignore */ + goto next_iter; + } + + /* check the results */ + is_result = 0; + if (xp_set.ht) { + hnode.node = node; + hash = lyht_hash_multi(0, (const char *)&hnode.node, sizeof hnode.node); + hash = lyht_hash_multi(hash, (const char *)&hnode.type, sizeof hnode.type); + hash = lyht_hash_multi(hash, NULL, 0); + + if (!lyht_find(xp_set.ht, &hnode, hash, NULL)) { + is_result = 1; + } + } else { + /* not enough elements for a hash table */ + for (i = 0; i < xp_set.used; ++i) { + if (xp_set.val.nodes[i].type != LYXP_NODE_ELEM) { + /* ignore */ + continue; + } + + if (xp_set.val.nodes[i].node == node) { + is_result = 1; + break; + } + } + } + + if (is_result) { + /* keep the whole subtree if the node is in the results */ + LYD_TREE_DFS_continue = 1; + } else if (lyht_find(parent_ht, &node, node->hash, NULL)) { + /* free the whole subtree if the node is not even among the selected parents */ + ret = ly_set_add(&free_set, node, 1, NULL); + LY_CHECK_GOTO(ret, cleanup); + LYD_TREE_DFS_continue = 1; + } /* else keep the parent node because a subtree is in the results */ + +next_iter: + LYD_TREE_DFS_END(parent, node); + } + } + + /* free */ + for (i = 0; i < free_set.count; ++i) { + node = free_set.dnodes[i]; + if (*tree == node) { + *tree = (*tree)->next; + } + lyd_free_tree(node); + } + +cleanup: + lyxp_set_free_content(&xp_set); + lyxp_expr_free(ctx, exp); + lyht_free(parent_ht, NULL); + ly_set_erase(&free_set, NULL); + return ret; } LIBYANG_API_DEF LY_ERR @@ -2903,7 +3177,7 @@ lyd_find_path(const struct lyd_node *ctx_node, const char *path, ly_bool output, /* parse the path */ ret = ly_path_parse(LYD_CTX(ctx_node), ctx_node->schema, path, strlen(path), 0, LY_PATH_BEGIN_EITHER, - LY_PATH_PREFIX_OPTIONAL, LY_PATH_PRED_SIMPLE, &expr); + LY_PATH_PREFIX_FIRST, LY_PATH_PRED_SIMPLE, &expr); LY_CHECK_GOTO(ret, cleanup); /* compile the path */ @@ -2912,7 +3186,7 @@ lyd_find_path(const struct lyd_node *ctx_node, const char *path, ly_bool output, LY_CHECK_GOTO(ret, cleanup); /* evaluate the path */ - ret = ly_path_eval_partial(lypath, ctx_node, NULL, match); + ret = ly_path_eval_partial(lypath, ctx_node, NULL, 0, NULL, match); cleanup: lyxp_expr_free(LYD_CTX(ctx_node), expr); @@ -2928,7 +3202,7 @@ lyd_find_target(const struct ly_path *path, const struct lyd_node *tree, struct LY_CHECK_ARG_RET(NULL, path, LY_EINVAL); - ret = ly_path_eval(path, tree, &m); + ret = ly_path_eval(path, tree, NULL, &m); if (ret) { if (match) { *match = NULL; @@ -2941,3 +3215,55 @@ lyd_find_target(const struct ly_path *path, const struct lyd_node *tree, struct } return LY_SUCCESS; } + +LIBYANG_API_DEF struct lyd_node * +lyd_parent(const struct lyd_node *node) +{ + if (!node || !node->parent) { + return NULL; + } + + return &node->parent->node; +} + +LIBYANG_API_DEF struct lyd_node * +lyd_child(const struct lyd_node *node) +{ + if (!node) { + return NULL; + } + + if (!node->schema) { + /* opaq node */ + return ((const struct lyd_node_opaq *)node)->child; + } + + switch (node->schema->nodetype) { + case LYS_CONTAINER: + case LYS_LIST: + case LYS_RPC: + case LYS_ACTION: + case LYS_NOTIF: + return ((const struct lyd_node_inner *)node)->child; + default: + return NULL; + } +} + +LIBYANG_API_DEF const char * +lyd_get_value(const struct lyd_node *node) +{ + if (!node) { + return NULL; + } + + if (!node->schema) { + return ((const struct lyd_node_opaq *)node)->value; + } else if (node->schema->nodetype & LYD_NODE_TERM) { + const struct lyd_value *value = &((const struct lyd_node_term *)node)->value; + + return value->_canonical ? value->_canonical : lyd_value_get_canonical(LYD_CTX(node), value); + } + + return NULL; +} diff --git a/src/tree_data.h b/src/tree_data.h index ff98f60..4d87fba 100644 --- a/src/tree_data.h +++ b/src/tree_data.h @@ -822,7 +822,7 @@ struct lyd_node_inner { }; /**< common part corresponding to ::lyd_node */ struct lyd_node *child; /**< pointer to the first child node. */ - struct hash_table *children_ht; /**< hash table with all the direct children (except keys for a list, lists without keys) */ + struct ly_ht *children_ht; /**< hash table with all the direct children (except keys for a list, lists without keys) */ #define LYD_HT_MIN_ITEMS 4 /**< minimal number of children to create ::lyd_node_inner.children_ht hash table. */ }; @@ -1004,15 +1004,7 @@ struct lyd_node_opaq { * @return Pointer to the parent node of the @p node. * @return NULL in case of the top-level node or if the @p node is NULL itself. */ -static inline struct lyd_node * -lyd_parent(const struct lyd_node *node) -{ - if (!node || !node->parent) { - return NULL; - } - - return &node->parent->node; -} +LIBYANG_API_DECL struct lyd_node *lyd_parent(const struct lyd_node *node); /** * @brief Get the child pointer of a generic data node. @@ -1024,29 +1016,7 @@ lyd_parent(const struct lyd_node *node) * @param[in] node Node to use. * @return Pointer to the first child node (if any) of the @p node. */ -static inline struct lyd_node * -lyd_child(const struct lyd_node *node) -{ - if (!node) { - return NULL; - } - - if (!node->schema) { - /* opaq node */ - return ((const struct lyd_node_opaq *)node)->child; - } - - switch (node->schema->nodetype) { - case LYS_CONTAINER: - case LYS_LIST: - case LYS_RPC: - case LYS_ACTION: - case LYS_NOTIF: - return ((const struct lyd_node_inner *)node)->child; - default: - return NULL; - } -} +LIBYANG_API_DECL struct lyd_node *lyd_child(const struct lyd_node *node); /** * @brief Get the child pointer of a generic data node but skip its keys in case it is ::LYS_LIST. @@ -1072,6 +1042,14 @@ LIBYANG_API_DECL struct lyd_node *lyd_child_no_keys(const struct lyd_node *node) LIBYANG_API_DECL const struct lys_module *lyd_owner_module(const struct lyd_node *node); /** + * @brief Get the module of a node. Useful mainly for opaque nodes. + * + * @param[in] node Node to examine. + * @return Module of the node. + */ +LIBYANG_API_DECL const struct lys_module *lyd_node_module(const struct lyd_node *node); + +/** * @brief Check whether a node value equals to its default one. * * @param[in] node Term node to test. @@ -1133,23 +1111,7 @@ LIBYANG_API_DECL const char *lyd_value_get_canonical(const struct ly_ctx *ctx, c * @param[in] node Data node to use. * @return Canonical value. */ -static inline const char * -lyd_get_value(const struct lyd_node *node) -{ - if (!node) { - return NULL; - } - - if (!node->schema) { - return ((const struct lyd_node_opaq *)node)->value; - } else if (node->schema->nodetype & LYD_NODE_TERM) { - const struct lyd_value *value = &((const struct lyd_node_term *)node)->value; - - return value->_canonical ? value->_canonical : lyd_value_get_canonical(LYD_CTX(node), value); - } - - return NULL; -} +LIBYANG_API_DECL const char *lyd_get_value(const struct lyd_node *node); /** * @brief Get anydata string value. @@ -1172,6 +1134,14 @@ LIBYANG_API_DECL LY_ERR lyd_any_copy_value(struct lyd_node *trg, const union lyd LYD_ANYDATA_VALUETYPE value_type); /** + * @brief Get schema node of a data node. Useful especially for opaque nodes. + * + * @param[in] node Data node to use. + * @return Schema node represented by data @p node, NULL if there is none. + */ +LIBYANG_API_DECL const struct lysc_node *lyd_node_schema(const struct lyd_node *node); + +/** * @brief Create a new inner node in the data tree. * * To create list, use ::lyd_new_list() or ::lyd_new_list2(). @@ -1287,6 +1257,57 @@ LIBYANG_API_DECL LY_ERR lyd_new_list2(struct lyd_node *parent, const struct lys_ const char *keys, ly_bool output, struct lyd_node **node); /** + * @brief Create a new list node in the data tree. + * + * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element. + * @param[in] module Module of the node being created. If NULL, @p parent module will be used. + * @param[in] name Schema node name of the new data node. The node must be #LYS_LIST. + * @param[in] format Format of key values. + * @param[in] key_values Ordered key string values of the new list instance, all must be set. + * @param[in] value_lengths Array of lengths of each @p key_values, may be NULL if @p key_values are 0-terminated strings. + * @param[in] output Flag in case the @p parent is RPC/Action. If value is 0, the input's data nodes of the RPC/Action are + * taken into consideration. Otherwise, the output's data node is going to be created. + * @param[out] node Optional created node. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_list3(struct lyd_node *parent, const struct lys_module *module, const char *name, + const char **key_values, uint32_t *value_lengths, ly_bool output, struct lyd_node **node); + +/** + * @brief Create a new list node in the data tree. + * + * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element. + * @param[in] module Module of the node being created. If NULL, @p parent module will be used. + * @param[in] name Schema node name of the new data node. The node must be #LYS_LIST. + * @param[in] format Format of key values. + * @param[in] key_values Ordered key binary (LYB) values of the new list instance, all must be set. + * @param[in] value_lengths Array of lengths of each @p key_values. + * @param[in] output Flag in case the @p parent is RPC/Action. If value is 0, the input's data nodes of the RPC/Action are + * taken into consideration. Otherwise, the output's data node is going to be created. + * @param[out] node Optional created node. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_list3_bin(struct lyd_node *parent, const struct lys_module *module, const char *name, + const void **key_values, uint32_t *value_lengths, ly_bool output, struct lyd_node **node); + +/** + * @brief Create a new list node in the data tree. + * + * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element. + * @param[in] module Module of the node being created. If NULL, @p parent module will be used. + * @param[in] name Schema node name of the new data node. The node must be #LYS_LIST. + * @param[in] format Format of key values. + * @param[in] key_values Ordered key canonical values of the new list instance, all must be set. + * @param[in] value_lengths Array of lengths of each @p key_values, may be NULL if @p key_values are 0-terminated strings. + * @param[in] output Flag in case the @p parent is RPC/Action. If value is 0, the input's data nodes of the RPC/Action are + * taken into consideration. Otherwise, the output's data node is going to be created. + * @param[out] node Optional created node. + * @return LY_ERR value. + */ +LIBYANG_API_DECL LY_ERR lyd_new_list3_canon(struct lyd_node *parent, const struct lys_module *module, const char *name, + const char **key_values, uint32_t *value_lengths, ly_bool output, struct lyd_node **node); + +/** * @brief Create a new term node in the data tree. * * To create a top-level term node defined in an extension instance, use ::lyd_new_ext_term(). @@ -1506,6 +1527,7 @@ LIBYANG_API_DECL LY_ERR lyd_new_attr2(struct lyd_node *parent, const char *modul #define LYD_NEW_PATH_CANON_VALUE 0x10 /**< Interpret the provided leaf/leaf-list @p value as being in the canonical (or JSON if no defined) ::LY_VALUE_CANON format. If it is not, it may lead to unexpected behavior. */ +#define LYD_NEW_PATH_WITH_OPAQ 0x20 /**< Consider opaque nodes normally when searching for existing nodes. */ /** @} pathoptions */ @@ -1519,7 +1541,8 @@ LIBYANG_API_DECL LY_ERR lyd_new_attr2(struct lyd_node *parent, const char *modul * Also, if a leaf-list is being created and both a predicate is defined in @p path * and @p value is set, the predicate is preferred. * - * For key-less lists and non-configuration leaf-lists, positional predicates should be used (indices starting from 1). + * For key-less lists, positional predicates must be used (indices starting from 1). For non-configuration leaf-lists + * either positional predicate can be used or leaf-list predicate, when an instance is always created at the end. * If no predicate is used for these nodes, they are always created. * * @param[in] parent Data parent to add to/modify, can be NULL. Note that in case a first top-level sibling is used, @@ -1531,7 +1554,11 @@ LIBYANG_API_DECL LY_ERR lyd_new_attr2(struct lyd_node *parent, const char *modul * For other node types, it should be NULL. * @param[in] options Bitmask of options, see @ref pathoptions. * @param[out] node Optional first created node. - * @return LY_ERR value. + * @return LY_SUCCESS on success. + * @return LY_EEXIST if the final node to create exists (unless ::LYD_NEW_PATH_UPDATE is used). + * @return LY_EINVAL on invalid arguments including invalid @p path. + * @return LY_EVALID on invalid @p value. + * @return LY_ERR on other errors. */ LIBYANG_API_DECL LY_ERR lyd_new_path(struct lyd_node *parent, const struct ly_ctx *ctx, const char *path, const char *value, uint32_t options, struct lyd_node **node); @@ -1554,7 +1581,11 @@ LIBYANG_API_DECL LY_ERR lyd_new_path(struct lyd_node *parent, const struct ly_ct * @param[in] options Bitmask of options, see @ref pathoptions. * @param[out] new_parent Optional first parent node created. If only one node was created, equals to @p new_node. * @param[out] new_node Optional last node created. - * @return LY_ERR value. + * @return LY_SUCCESS on success. + * @return LY_EEXIST if the final node to create exists (unless ::LYD_NEW_PATH_UPDATE is used). + * @return LY_EINVAL on invalid arguments including invalid @p path. + * @return LY_EVALID on invalid @p value. + * @return LY_ERR on other errors. */ LIBYANG_API_DECL LY_ERR lyd_new_path2(struct lyd_node *parent, const struct ly_ctx *ctx, const char *path, const void *value, size_t value_len, LYD_ANYDATA_VALUETYPE value_type, uint32_t options, struct lyd_node **new_parent, @@ -1576,7 +1607,11 @@ LIBYANG_API_DECL LY_ERR lyd_new_path2(struct lyd_node *parent, const struct ly_c * @param[in] value Value of the new leaf/leaf-list. For other node types, it should be NULL. * @param[in] options Bitmask of options, see @ref pathoptions. * @param[out] node Optional first created node. - * @return LY_ERR value. + * @return LY_SUCCESS on success. + * @return LY_EEXIST if the final node to create exists (unless ::LYD_NEW_PATH_UPDATE is used). + * @return LY_EINVAL on invalid arguments including invalid @p path. + * @return LY_EVALID on invalid @p value. + * @return LY_ERR on other errors. */ LIBYANG_API_DECL LY_ERR lyd_new_ext_path(struct lyd_node *parent, const struct lysc_ext_instance *ext, const char *path, const void *value, uint32_t options, struct lyd_node **node); @@ -1662,7 +1697,7 @@ LIBYANG_API_DECL LY_ERR lyd_change_term(struct lyd_node *term, const char *val_s * is always cleared. * * @param[in] term Term node to change. - * @param[in] value New value to set in binary format, see @ref howtoDataLYB. + * @param[in] value New value to set in binary format (usually a pointer), see @ref howtoDataLYB. * @param[in] value_len Length of @p value. * @return LY_SUCCESS if value was changed, * @return LY_EEXIST if value was the same and only the default flag was cleared, @@ -1927,6 +1962,8 @@ LIBYANG_API_DECL LY_ERR lyd_compare_meta(const struct lyd_meta *meta1, const str #define LYD_DUP_WITH_FLAGS 0x08 /**< Also copy any data node flags. That will cause the duplicated data to preserve its validation/default node state. */ #define LYD_DUP_NO_EXT 0x10 /**< Do not duplicate nodes with the ::LYD_EXT flag (nested extension instance data). */ +#define LYD_DUP_WITH_PRIV 0x20 /**< Also copy data node private pointer. Only the pointer is copied, it still points + to the same data. */ /** @} dupoptions */ @@ -2131,7 +2168,7 @@ LIBYANG_API_DECL LY_ERR lyd_merge_module(struct lyd_node **target, const struct * @param[in] first First data tree. * @param[in] second Second data tree. * @param[in] options Bitmask of options flags, see @ref diffoptions. - * @param[out] diff Generated diff. + * @param[out] diff Generated diff, NULL if there are no differences. * @return LY_SUCCESS on success, * @return LY_ERR on error. */ @@ -2146,7 +2183,7 @@ LIBYANG_API_DECL LY_ERR lyd_diff_tree(const struct lyd_node *first, const struct * @param[in] first First data tree. * @param[in] second Second data tree. * @param[in] options Bitmask of options flags, see @ref diffoptions. - * @param[out] diff Generated diff. + * @param[out] diff Generated diff, NULL if there are no differences. * @return LY_SUCCESS on success, * @return LY_ERR on error. */ @@ -2302,6 +2339,10 @@ typedef enum { /** * @brief Generate path of the given node in the requested format. * + * The path is constructed based on the parent node(s) of this node. When run on a node which is disconnected + * from its parent(s), this function might yield unexpected results such as `/example:b` instead of the expected + * `/example:a/b`. + * * @param[in] node Data path of this node will be generated. * @param[in] pathtype Format of the path to generate. * @param[in,out] buffer Prepared buffer of the @p buflen length to store the generated path. @@ -2419,6 +2460,8 @@ LIBYANG_API_DECL void lyxp_vars_free(struct lyxp_var *vars); * `leaf-list[.=...]`, these instances are found using hashes with constant (*O(1)*) complexity * (unless they are defined in top-level). Other predicates can still follow the aforementioned ones. * + * Opaque nodes are part of the evaluation. + * * @param[in] ctx_node XPath context node. * @param[in] xpath [XPath](@ref howtoXPath) to select in JSON format. It must evaluate into a node set. * @param[out] set Set of found data nodes. In case the result is a number, a string, or a boolean, @@ -2527,11 +2570,61 @@ LIBYANG_API_DECL LY_ERR lyd_eval_xpath3(const struct lyd_node *ctx_node, const s const char *xpath, LY_VALUE_FORMAT format, void *prefix_data, const struct lyxp_var *vars, ly_bool *result); /** + * @brief XPath result type. + */ +typedef enum { + LY_XPATH_NODE_SET, /**< XPath node set */ + LY_XPATH_STRING, /**< XPath string */ + LY_XPATH_NUMBER, /**< XPath number */ + LY_XPATH_BOOLEAN /**< XPath boolean */ +} LY_XPATH_TYPE; + +/** + * @brief Evaluate an XPath on data and return the result or convert it first to an expected result type. + * + * Either all return type parameters @p node_set, @p string, @p number, and @p boolean with @p ret_type + * are provided or exactly one of @p node_set, @p string, @p number, and @p boolean is provided with @p ret_type + * being obvious and hence optional. + * + * @param[in] ctx_node XPath context node, NULL for the root node. + * @param[in] tree Data tree to evaluate on. + * @param[in] cur_mod Current module of @p xpath, needed for some kinds of @p format. + * @param[in] xpath [XPath](@ref howtoXPath) to select. + * @param[in] format Format of any prefixes in @p xpath. + * @param[in] prefix_data Format-specific prefix data. + * @param[in] vars Optional [sized array](@ref sizedarrays) of XPath variables. + * @param[out] ret_type XPath type of the result selecting which of @p node_set, @p string, @p number, and @p boolean to use. + * @param[out] node_set XPath node set result. + * @param[out] string XPath string result. + * @param[out] number XPath number result. + * @param[out] boolean XPath boolean result. + * @return LY_SUCCESS on success. + * @return LY_ERR value on error. + */ +LIBYANG_API_DECL LY_ERR lyd_eval_xpath4(const struct lyd_node *ctx_node, const struct lyd_node *tree, + const struct lys_module *cur_mod, const char *xpath, LY_VALUE_FORMAT format, void *prefix_data, + const struct lyxp_var *vars, LY_XPATH_TYPE *ret_type, struct ly_set **node_set, char **string, + long double *number, ly_bool *boolean); + +/** + * @brief Evaluate an XPath on data and free all the nodes except the subtrees selected by the expression. + * + * @param[in,out] tree Data tree to evaluate on and trim. + * @param[in] xpath [XPath](@ref howtoXPath) to select in JSON format. + * @param[in] vars Optional [sized array](@ref sizedarrays) of XPath variables. + * @return LY_SUCCESS on success. + * @return LY_ERR value on error. + */ +LIBYANG_API_DEF LY_ERR lyd_trim_xpath(struct lyd_node **tree, const char *xpath, const struct lyxp_var *vars); + +/** * @brief Search in given data for a node uniquely identified by a path. * * Always works in constant (*O(1)*) complexity. To be exact, it is *O(n)* where *n* is the depth * of the path used. * + * Opaque nodes are NEVER found/traversed. + * * @param[in] ctx_node Path context node. * @param[in] path [Path](@ref howtoXPath) to find. * @param[in] output Whether to search in RPC/action output nodes or in input nodes. @@ -2557,6 +2650,21 @@ LIBYANG_API_DECL LY_ERR lyd_find_path(const struct lyd_node *ctx_node, const cha LIBYANG_API_DECL LY_ERR lyd_find_target(const struct ly_path *path, const struct lyd_node *tree, struct lyd_node **match); /** + * @brief Get current timezone (including DST setting) UTC (GMT) time offset in seconds. + * + * @return Timezone shift in seconds. + */ +LIBYANG_API_DECL int ly_time_tz_offset(void); + +/** + * @brief Get UTC (GMT) timezone offset in seconds at a specific timestamp (including DST setting). + * + * @param[in] time Timestamp to get the offset at. + * @return Timezone shift in seconds. + */ +LIBYANG_API_DECL int ly_time_tz_offset_at(time_t time); + +/** * @brief Convert date-and-time from string to UNIX timestamp and fractions of a second. * * @param[in] value Valid string date-and-time value. diff --git a/src/tree_data_common.c b/src/tree_data_common.c index f35f8f5..672f720 100644 --- a/src/tree_data_common.c +++ b/src/tree_data_common.c @@ -44,32 +44,65 @@ #include "xpath.h" /** + * @brief Callback for checking first instance hash table values equivalence. + * + * @param[in] val1_p If not @p mod, pointer to the first instance. + * @param[in] val2_p If not @p mod, pointer to the found dup inst item. + */ +static ly_bool +lyht_dup_inst_ht_equal_cb(void *val1_p, void *val2_p, ly_bool mod, void *UNUSED(cb_data)) +{ + if (mod) { + struct lyd_dup_inst **item1 = val1_p, **item2 = val2_p; + + /* equal on 2 dup inst items */ + return *item1 == *item2 ? 1 : 0; + } else { + struct lyd_node **first_inst = val1_p; + struct lyd_dup_inst **item = val2_p; + + /* equal on dup inst item and a first instance */ + return (*item)->set->dnodes[0] == *first_inst ? 1 : 0; + } +} + +/** * @brief Find an entry in duplicate instance cache for an instance. Create it if it does not exist. * - * @param[in] first_inst Instance of the cache entry. - * @param[in,out] dup_inst_cache Duplicate instance cache. + * @param[in] first_inst First instance of the cache entry. + * @param[in] dup_inst_ht Duplicate instance cache hash table. * @return Instance cache entry. */ static struct lyd_dup_inst * -lyd_dup_inst_get(const struct lyd_node *first_inst, struct lyd_dup_inst **dup_inst_cache) +lyd_dup_inst_get(const struct lyd_node *first_inst, struct ly_ht **dup_inst_ht) { - struct lyd_dup_inst *item; - LY_ARRAY_COUNT_TYPE u; + struct lyd_dup_inst **item_p, *item; - LY_ARRAY_FOR(*dup_inst_cache, u) { - if ((*dup_inst_cache)[u].inst_set->dnodes[0] == first_inst) { - return &(*dup_inst_cache)[u]; + if (*dup_inst_ht) { + /* find the item of the first instance */ + if (!lyht_find(*dup_inst_ht, &first_inst, first_inst->hash, (void **)&item_p)) { + return *item_p; } + } else { + /* create the hash table */ + *dup_inst_ht = lyht_new(2, sizeof item, lyht_dup_inst_ht_equal_cb, NULL, 1); + LY_CHECK_RET(!*dup_inst_ht, NULL); } - /* it was not added yet, add it now */ - LY_ARRAY_NEW_RET(LYD_CTX(first_inst), *dup_inst_cache, item, NULL); + /* first instance has no dup inst item, create it */ + item = calloc(1, sizeof *item); + LY_CHECK_RET(!item, NULL); + + /* add into the hash table */ + if (lyht_insert(*dup_inst_ht, &item, first_inst->hash, NULL)) { + return NULL; + } return item; } LY_ERR -lyd_dup_inst_next(struct lyd_node **inst, const struct lyd_node *siblings, struct lyd_dup_inst **dup_inst_cache) +lyd_dup_inst_next(struct lyd_node **inst, const struct lyd_node *siblings, struct ly_ht **dup_inst_ht) { struct lyd_dup_inst *dup_inst; @@ -80,40 +113,47 @@ lyd_dup_inst_next(struct lyd_node **inst, const struct lyd_node *siblings, struc /* there can be more exact same instances (even if not allowed in invalid data) and we must make sure we do not * match a single node more times */ - dup_inst = lyd_dup_inst_get(*inst, dup_inst_cache); + dup_inst = lyd_dup_inst_get(*inst, dup_inst_ht); LY_CHECK_ERR_RET(!dup_inst, LOGMEM(LYD_CTX(siblings)), LY_EMEM); if (!dup_inst->used) { /* we did not cache these instances yet, do so */ - lyd_find_sibling_dup_inst_set(siblings, *inst, &dup_inst->inst_set); - assert(dup_inst->inst_set->count && (dup_inst->inst_set->dnodes[0] == *inst)); + lyd_find_sibling_dup_inst_set(siblings, *inst, &dup_inst->set); + assert(dup_inst->set->count && (dup_inst->set->dnodes[0] == *inst)); } - if (dup_inst->used == dup_inst->inst_set->count) { + if (dup_inst->used == dup_inst->set->count) { if (lysc_is_dup_inst_list((*inst)->schema)) { /* we have used all the instances */ *inst = NULL; } /* else just keep using the last (ideally only) instance */ } else { - assert(dup_inst->used < dup_inst->inst_set->count); + assert(dup_inst->used < dup_inst->set->count); /* use another instance */ - *inst = dup_inst->inst_set->dnodes[dup_inst->used]; + *inst = dup_inst->set->dnodes[dup_inst->used]; ++dup_inst->used; } return LY_SUCCESS; } -void -lyd_dup_inst_free(struct lyd_dup_inst *dup_inst) +/** + * @brief Callback for freeing first instance hash table values. + */ +static void +lyht_dup_inst_ht_free_cb(void *val_p) { - LY_ARRAY_COUNT_TYPE u; + struct lyd_dup_inst **item = val_p; - LY_ARRAY_FOR(dup_inst, u) { - ly_set_free(dup_inst[u].inst_set, NULL); - } - LY_ARRAY_FREE(dup_inst); + ly_set_free((*item)->set, NULL); + free(*item); +} + +void +lyd_dup_inst_free(struct ly_ht *dup_inst_ht) +{ + lyht_free(dup_inst_ht, lyht_dup_inst_ht_free_cb); } struct lyd_node * @@ -180,12 +220,12 @@ lyxp_vars_set(struct lyxp_var **vars, const char *name, const char *value) return LY_EINVAL; } - /* If variable is already defined then change its value. */ - if (*vars && !lyxp_vars_find(*vars, name, 0, &item)) { + /* if variable is already defined then change its value */ + if (*vars && !lyxp_vars_find(NULL, *vars, name, 0, &item)) { var_value = strdup(value); LY_CHECK_RET(!var_value, LY_EMEM); - /* Set new value. */ + /* update value */ free(item->value); item->value = var_value; } else { @@ -193,7 +233,7 @@ lyxp_vars_set(struct lyxp_var **vars, const char *name, const char *value) var_value = strdup(value); LY_CHECK_ERR_GOTO(!var_name || !var_value, ret = LY_EMEM, error); - /* Add new variable. */ + /* add new variable */ LY_ARRAY_NEW_GOTO(NULL, *vars, item, ret, error); item->name = var_name; item->value = var_value; @@ -260,21 +300,68 @@ lyd_owner_module(const struct lyd_node *node) return NULL; } + while (!node->schema && node->parent) { + node = lyd_parent(node); + } + if (!node->schema) { + /* top-level opaque node */ opaq = (struct lyd_node_opaq *)node; switch (opaq->format) { case LY_VALUE_XML: - return opaq->name.module_ns ? ly_ctx_get_module_implemented_ns(LYD_CTX(node), opaq->name.module_ns) : NULL; + if (opaq->name.module_ns) { + return ly_ctx_get_module_implemented_ns(LYD_CTX(node), opaq->name.module_ns); + } + break; case LY_VALUE_JSON: - return opaq->name.module_name ? ly_ctx_get_module_implemented(LYD_CTX(node), opaq->name.module_name) : NULL; + if (opaq->name.module_name) { + return ly_ctx_get_module_implemented(LYD_CTX(node), opaq->name.module_name); + } + break; default: return NULL; } + + return NULL; } return lysc_owner_module(node->schema); } +LIBYANG_API_DEF const struct lys_module * +lyd_node_module(const struct lyd_node *node) +{ + const struct lyd_node_opaq *opaq; + + while (node) { + /* data node */ + if (node->schema) { + return node->schema->module; + } + + /* opaque node */ + opaq = (struct lyd_node_opaq *)node; + switch (opaq->format) { + case LY_VALUE_XML: + if (opaq->name.module_ns) { + return ly_ctx_get_module_implemented_ns(LYD_CTX(node), opaq->name.module_ns); + } + break; + case LY_VALUE_JSON: + if (opaq->name.module_name) { + return ly_ctx_get_module_implemented(LYD_CTX(node), opaq->name.module_name); + } + break; + default: + break; + } + + node = lyd_parent(node); + } + + return NULL; +} + void lyd_first_module_sibling(struct lyd_node **node, const struct lys_module *mod) { @@ -389,12 +476,12 @@ lyd_data_next_module(struct lyd_node **next, struct lyd_node **first) LY_ERR lyd_value_store(const struct ly_ctx *ctx, struct lyd_value *val, const struct lysc_type *type, const void *value, - size_t value_len, ly_bool *dynamic, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, + size_t value_len, ly_bool is_utf8, ly_bool *dynamic, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, const struct lysc_node *ctx_node, ly_bool *incomplete) { LY_ERR ret; struct ly_err_item *err = NULL; - uint32_t options = (dynamic && *dynamic ? LYPLG_TYPE_STORE_DYNAMIC : 0); + uint32_t options = 0; if (!value) { value = ""; @@ -403,6 +490,13 @@ lyd_value_store(const struct ly_ctx *ctx, struct lyd_value *val, const struct ly *incomplete = 0; } + if (dynamic && *dynamic) { + options |= LYPLG_TYPE_STORE_DYNAMIC; + } + if (is_utf8) { + options |= LYPLG_TYPE_STORE_IS_UTF8; + } + ret = type->plugin->store(ctx, type, value, value_len, options, format, prefix_data, hints, ctx_node, val, NULL, &err); if (dynamic) { *dynamic = 0; @@ -440,8 +534,8 @@ lyd_value_validate_incomplete(const struct ly_ctx *ctx, const struct lysc_type * LOGVAL_ERRITEM(ctx, err); ly_err_free(err); } else { - LOGVAL(ctx, LYVE_OTHER, "Resolving value \"%s\" failed.", type->plugin->print(ctx, val, LY_VALUE_CANON, - NULL, NULL, NULL)); + LOGVAL(ctx, LYVE_OTHER, "Resolving value \"%s\" failed.", + (char *)type->plugin->print(ctx, val, LY_VALUE_CANON, NULL, NULL, NULL)); } return ret; } @@ -450,15 +544,15 @@ lyd_value_validate_incomplete(const struct ly_ctx *ctx, const struct lysc_type * } LY_ERR -lys_value_validate(const struct ly_ctx *ctx, const struct lysc_node *node, const char *value, size_t value_len, - LY_VALUE_FORMAT format, void *prefix_data) +ly_value_validate(const struct ly_ctx *ctx, const struct lysc_node *node, const char *value, size_t value_len, + LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints) { LY_ERR rc = LY_SUCCESS; struct ly_err_item *err = NULL; struct lyd_value storage; struct lysc_type *type; - LY_CHECK_ARG_RET(ctx, node, value, LY_EINVAL); + LY_CHECK_ARG_RET(ctx, node, LY_EINVAL); if (!(node->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { LOGARG(ctx, node); @@ -466,8 +560,8 @@ lys_value_validate(const struct ly_ctx *ctx, const struct lysc_node *node, const } type = ((struct lysc_node_leaf *)node)->type; - rc = type->plugin->store(ctx ? ctx : node->module->ctx, type, value, value_len, 0, format, prefix_data, - LYD_HINT_SCHEMA, node, &storage, NULL, &err); + rc = type->plugin->store(ctx ? ctx : node->module->ctx, type, value, value_len, 0, format, prefix_data, hints, node, + &storage, NULL, &err); if (rc == LY_EINCOMPLETE) { /* actually success since we do not provide the context tree and call validation with * LY_TYPE_OPTS_INCOMPLETE_DATA */ @@ -478,14 +572,13 @@ lys_value_validate(const struct ly_ctx *ctx, const struct lysc_node *node, const if (err->path) { LOG_LOCSET(NULL, NULL, err->path, NULL); } else { - /* use at least the schema path */ LOG_LOCSET(node, NULL, NULL, NULL); } LOGVAL_ERRITEM(ctx, err); if (err->path) { LOG_LOCBACK(0, 0, 1, 0); } else { - LOG_LOCBACK(1, 0, 0, 0); + LOG_LOCBACK(1, 0, 1, 0); } } ly_err_free(err); @@ -590,7 +683,7 @@ lyd_value_compare(const struct lyd_node_term *node, const char *value, size_t va /* store the value */ LOG_LOCSET(node->schema, &node->node, NULL, NULL); - ret = lyd_value_store(ctx, &val, type, value, value_len, NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, node->schema, NULL); + ret = lyd_value_store(ctx, &val, type, value, value_len, 0, NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, node->schema, NULL); LOG_LOCBACK(1, 1, 0, 0); LY_CHECK_RET(ret); @@ -704,24 +797,15 @@ lyd_parse_opaq_list_error(const struct lyd_node *node, const struct lysc_node *s assert(!node->schema); /* get all keys into a set */ - while ((key = lys_getnext(key, snode, NULL, 0)) && (snode->flags & LYS_KEY)) { - LY_CHECK_GOTO(ret = ly_set_add(&key_set, (void *)snode, 1, NULL), cleanup); + while ((key = lys_getnext(key, snode, NULL, 0)) && (key->flags & LYS_KEY)) { + LY_CHECK_GOTO(ret = ly_set_add(&key_set, (void *)key, 1, NULL), cleanup); } LY_LIST_FOR(lyd_child(node), child) { - if (child->schema) { - LOGERR(LYD_CTX(node), LY_EINVAL, "Unexpected node %s \"%s\".", lys_nodetype2str(child->schema->nodetype), - LYD_NAME(child)); - ret = LY_EINVAL; - goto cleanup; - } - - opaq_k = (struct lyd_node_opaq *)child; - /* find the key schema node */ for (i = 0; i < key_set.count; ++i) { key = key_set.snodes[i]; - if (!strcmp(key->name, opaq_k->name.name)) { + if (!strcmp(key->name, LYD_NAME(child))) { break; } } @@ -733,9 +817,15 @@ lyd_parse_opaq_list_error(const struct lyd_node *node, const struct lysc_node *s /* key found */ ly_set_rm_index(&key_set, i, NULL); + if (child->schema) { + /* valid key */ + continue; + } + /* check value */ - ret = lys_value_validate(LYD_CTX(node), key, opaq_k->value, strlen(opaq_k->value), opaq_k->format, - opaq_k->val_prefix_data); + opaq_k = (struct lyd_node_opaq *)child; + ret = ly_value_validate(LYD_CTX(node), key, opaq_k->value, strlen(opaq_k->value), opaq_k->format, + opaq_k->val_prefix_data, opaq_k->hints); LY_CHECK_GOTO(ret, cleanup); } @@ -754,93 +844,124 @@ cleanup: LIBYANG_API_DEF LY_ERR lyd_parse_opaq_error(const struct lyd_node *node) { + LY_ERR rc = LY_SUCCESS; const struct ly_ctx *ctx; const struct lyd_node_opaq *opaq; const struct lyd_node *parent; const struct lys_module *mod; - const struct lysc_node *snode; + const struct lysc_node *sparent, *snode; + uint32_t loc_node = 0, loc_path = 0; - LY_CHECK_ARG_RET(LYD_CTX(node), node, !node->schema, !lyd_parent(node) || lyd_parent(node)->schema, LY_EINVAL); + LY_CHECK_ARG_RET(LYD_CTX(node), node, !node->schema, LY_EINVAL); ctx = LYD_CTX(node); opaq = (struct lyd_node_opaq *)node; parent = lyd_parent(node); + sparent = lyd_node_schema(parent); + + if (parent) { + LOG_LOCSET(NULL, parent, NULL, NULL); + ++loc_node; + } else { + LOG_LOCSET(NULL, NULL, "/", NULL); + ++loc_path; + } if (!opaq->name.module_ns) { LOGVAL(ctx, LYVE_REFERENCE, "Unknown module of node \"%s\".", opaq->name.name); - return LY_EVALID; + rc = LY_EVALID; + goto cleanup; } /* module */ switch (opaq->format) { case LY_VALUE_XML: - if (!parent || strcmp(opaq->name.module_ns, parent->schema->module->ns)) { + if (!sparent || strcmp(opaq->name.module_ns, sparent->module->ns)) { mod = ly_ctx_get_module_implemented_ns(ctx, opaq->name.module_ns); if (!mod) { LOGVAL(ctx, LYVE_REFERENCE, "No (implemented) module with namespace \"%s\" of node \"%s\" in the context.", opaq->name.module_ns, opaq->name.name); - return LY_EVALID; + rc = LY_EVALID; + goto cleanup; } } else { /* inherit */ - mod = parent->schema->module; + mod = sparent->module; } break; case LY_VALUE_JSON: case LY_VALUE_LYB: - if (!parent || strcmp(opaq->name.module_name, parent->schema->module->name)) { + if (!sparent || strcmp(opaq->name.module_name, sparent->module->name)) { mod = ly_ctx_get_module_implemented(ctx, opaq->name.module_name); if (!mod) { LOGVAL(ctx, LYVE_REFERENCE, "No (implemented) module named \"%s\" of node \"%s\" in the context.", opaq->name.module_name, opaq->name.name); - return LY_EVALID; + rc = LY_EVALID; + goto cleanup; } } else { /* inherit */ - mod = parent->schema->module; + mod = sparent->module; } break; default: LOGERR(ctx, LY_EINVAL, "Unsupported value format."); - return LY_EINVAL; + rc = LY_EINVAL; + goto cleanup; } /* schema */ - snode = lys_find_child(parent ? parent->schema : NULL, mod, opaq->name.name, 0, 0, 0); - if (!snode && parent && parent->schema && (parent->schema->nodetype & (LYS_RPC | LYS_ACTION))) { + snode = lys_find_child(sparent, mod, opaq->name.name, 0, 0, 0); + if (!snode && sparent && (sparent->nodetype & (LYS_RPC | LYS_ACTION))) { /* maybe output node */ - snode = lys_find_child(parent->schema, mod, opaq->name.name, 0, 0, LYS_GETNEXT_OUTPUT); + snode = lys_find_child(sparent, mod, opaq->name.name, 0, 0, LYS_GETNEXT_OUTPUT); } if (!snode) { - if (parent) { + if (sparent) { LOGVAL(ctx, LYVE_REFERENCE, "Node \"%s\" not found as a child of \"%s\" node.", opaq->name.name, - LYD_NAME(parent)); + sparent->name); } else { LOGVAL(ctx, LYVE_REFERENCE, "Node \"%s\" not found in the \"%s\" module.", opaq->name.name, mod->name); } - return LY_EVALID; + rc = LY_EVALID; + goto cleanup; } + /* schema node exists */ + LOG_LOCBACK(0, loc_node, loc_path, 0); + loc_node = 0; + loc_path = 0; + LOG_LOCSET(NULL, node, NULL, NULL); + ++loc_node; + if (snode->nodetype & LYD_NODE_TERM) { /* leaf / leaf-list */ - LY_CHECK_RET(lys_value_validate(ctx, snode, opaq->value, strlen(opaq->value), opaq->format, opaq->val_prefix_data)); + rc = ly_value_validate(ctx, snode, opaq->value, strlen(opaq->value), opaq->format, opaq->val_prefix_data, opaq->hints); + LY_CHECK_GOTO(rc, cleanup); } else if (snode->nodetype == LYS_LIST) { /* list */ - LY_CHECK_RET(lyd_parse_opaq_list_error(node, snode)); + rc = lyd_parse_opaq_list_error(node, snode); + LY_CHECK_GOTO(rc, cleanup); } else if (snode->nodetype & LYD_NODE_INNER) { /* inner node */ if (opaq->value) { LOGVAL(ctx, LYVE_DATA, "Invalid value \"%s\" for %s \"%s\".", opaq->value, lys_nodetype2str(snode->nodetype), snode->name); - return LY_EVALID; + rc = LY_EVALID; + goto cleanup; } } else { LOGERR(ctx, LY_EINVAL, "Unexpected opaque schema node %s \"%s\".", lys_nodetype2str(snode->nodetype), snode->name); - return LY_EINVAL; + rc = LY_EINVAL; + goto cleanup; } LOGERR(ctx, LY_EINVAL, "Unexpected valid opaque node %s \"%s\".", lys_nodetype2str(snode->nodetype), snode->name); - return LY_EINVAL; + rc = LY_EINVAL; + +cleanup: + LOG_LOCBACK(0, loc_node, loc_path, 0); + return rc; } LIBYANG_API_DEF const char * @@ -970,7 +1091,7 @@ lyd_any_copy_value(struct lyd_node *trg, const union lyd_any_value *value, LYD_A return LY_SUCCESS; } -const struct lysc_node * +LIBYANG_API_DEF const struct lysc_node * lyd_node_schema(const struct lyd_node *node) { const struct lysc_node *schema = NULL; @@ -983,27 +1104,30 @@ lyd_node_schema(const struct lyd_node *node) return node->schema; } + /* find the first schema node in the parents */ + for (iter = lyd_parent(node); iter && !iter->schema; iter = lyd_parent(iter)) {} + if (iter) { + prev_iter = iter; + schema = prev_iter->schema; + } + /* get schema node of an opaque node */ do { /* get next data node */ for (iter = node; lyd_parent(iter) != prev_iter; iter = lyd_parent(iter)) {} - /* get equivalent schema node */ - if (iter->schema) { - schema = iter->schema; - } else { - /* get module */ - mod = lyd_owner_module(iter); - if (!mod && !schema) { - /* top-level opaque node has unknown module */ - break; - } - - /* get schema node */ - schema = lys_find_child(schema, mod ? mod : schema->module, LYD_NAME(iter), 0, 0, 0); + /* get module */ + mod = lyd_node_module(iter); + if (!mod) { + /* unknown module, no schema node */ + schema = NULL; + break; } - /* remember to move to the descendant */ + /* get schema node */ + schema = lys_find_child(schema, mod, LYD_NAME(iter), 0, 0, 0); + + /* move to the descendant */ prev_iter = iter; } while (schema && (iter != node)); @@ -1068,9 +1192,7 @@ lyd_find_sibling_schema(const struct lyd_node *siblings, const struct lysc_node { struct lyd_node **match_p; struct lyd_node_inner *parent; - const struct lysc_node *cur_schema; uint32_t hash; - lyht_value_equal_cb ht_cb; assert(schema); if (!siblings) { @@ -1084,23 +1206,17 @@ lyd_find_sibling_schema(const struct lyd_node *siblings, const struct lysc_node parent = siblings->parent; if (parent && parent->schema && parent->children_ht) { /* calculate our hash */ - hash = dict_hash_multi(0, schema->module->name, strlen(schema->module->name)); - hash = dict_hash_multi(hash, schema->name, strlen(schema->name)); - hash = dict_hash_multi(hash, NULL, 0); - - /* use special hash table function */ - ht_cb = lyht_set_cb(parent->children_ht, lyd_hash_table_schema_val_equal); + hash = lyht_hash_multi(0, schema->module->name, strlen(schema->module->name)); + hash = lyht_hash_multi(hash, schema->name, strlen(schema->name)); + hash = lyht_hash_multi(hash, NULL, 0); - /* find by hash */ - if (!lyht_find(parent->children_ht, &schema, hash, (void **)&match_p)) { + /* find by hash but use special hash table function (and stay thread-safe) */ + if (!lyht_find_with_val_cb(parent->children_ht, &schema, hash, lyd_hash_table_schema_val_equal, (void **)&match_p)) { siblings = *match_p; } else { /* not found */ siblings = NULL; } - - /* set the original hash table compare function back */ - lyht_set_cb(parent->children_ht, ht_cb); } else { /* find first sibling */ if (siblings->parent) { @@ -1111,25 +1227,22 @@ lyd_find_sibling_schema(const struct lyd_node *siblings, const struct lysc_node } } - /* search manually without hashes */ - for ( ; siblings; siblings = siblings->next) { - cur_schema = lyd_node_schema(siblings); - if (!cur_schema) { - /* some unknown opaque node */ - continue; - } - + /* search manually without hashes and ignore opaque nodes (cannot be found by hashes) */ + for ( ; siblings && siblings->schema; siblings = siblings->next) { /* schema match is enough */ - if (cur_schema->module->ctx == schema->module->ctx) { - if (cur_schema == schema) { + if (LYD_CTX(siblings) == schema->module->ctx) { + if (siblings->schema == schema) { break; } } else { - if (!strcmp(cur_schema->name, schema->name) && !strcmp(cur_schema->module->name, schema->module->name)) { + if (!strcmp(LYD_NAME(siblings), schema->name) && !strcmp(siblings->schema->module->name, schema->module->name)) { break; } } } + if (siblings && !siblings->schema) { + siblings = NULL; + } } if (!siblings) { @@ -1468,6 +1581,52 @@ ly_format2str(LY_VALUE_FORMAT format) return NULL; } +LIBYANG_API_DEF int +ly_time_tz_offset(void) +{ + return ly_time_tz_offset_at(time(NULL)); +} + +LIBYANG_API_DEF int +ly_time_tz_offset_at(time_t time) +{ + struct tm tm_local, tm_utc; + int result = 0; + + /* init timezone */ + tzset(); + + /* get local and UTC time */ + localtime_r(&time, &tm_local); + gmtime_r(&time, &tm_utc); + + /* account for year/month/day change by adding/subtracting from the hours, the change cannot be more than 1 day */ + if (tm_local.tm_year < tm_utc.tm_year) { + tm_utc.tm_hour += 24; + } else if (tm_local.tm_year > tm_utc.tm_year) { + tm_local.tm_hour += 24; + } else if (tm_local.tm_mon < tm_utc.tm_mon) { + tm_utc.tm_hour += 24; + } else if (tm_local.tm_mon > tm_utc.tm_mon) { + tm_local.tm_hour += 24; + } else if (tm_local.tm_mday < tm_utc.tm_mday) { + tm_utc.tm_hour += 24; + } else if (tm_local.tm_mday > tm_utc.tm_mday) { + tm_local.tm_hour += 24; + } + + /* hours shift in seconds */ + result += (tm_local.tm_hour - tm_utc.tm_hour) * 3600; + + /* minutes shift in seconds */ + result += (tm_local.tm_min - tm_utc.tm_min) * 60; + + /* seconds shift */ + result += tm_local.tm_sec - tm_utc.tm_sec; + + return result; +} + LIBYANG_API_DEF LY_ERR ly_time_str2time(const char *value, time_t *time, char **fractions_s) { @@ -1486,6 +1645,28 @@ ly_time_str2time(const char *value, time_t *time, char **fractions_s) tm.tm_min = atoi(&value[14]); tm.tm_sec = atoi(&value[17]); + /* explicit checks for some gross errors */ + if (tm.tm_mon > 11) { + LOGERR(NULL, LY_EINVAL, "Invalid date-and-time month \"%d\".", tm.tm_mon); + return LY_EINVAL; + } + if ((tm.tm_mday < 1) || (tm.tm_mday > 31)) { + LOGERR(NULL, LY_EINVAL, "Invalid date-and-time day of month \"%d\".", tm.tm_mday); + return LY_EINVAL; + } + if (tm.tm_hour > 23) { + LOGERR(NULL, LY_EINVAL, "Invalid date-and-time hours \"%d\".", tm.tm_hour); + return LY_EINVAL; + } + if (tm.tm_min > 59) { + LOGERR(NULL, LY_EINVAL, "Invalid date-and-time minutes \"%d\".", tm.tm_min); + return LY_EINVAL; + } + if (tm.tm_sec > 60) { + LOGERR(NULL, LY_EINVAL, "Invalid date-and-time seconds \"%d\".", tm.tm_sec); + return LY_EINVAL; + } + t = timegm(&tm); i = 19; @@ -1506,12 +1687,24 @@ ly_time_str2time(const char *value, time_t *time, char **fractions_s) shift = 0; } else { shift = strtol(&value[i], NULL, 10); + if (shift > 23) { + LOGERR(NULL, LY_EINVAL, "Invalid date-and-time timezone hour \"%" PRIi64 "\".", shift); + return LY_EINVAL; + } shift = shift * 60 * 60; /* convert from hours to seconds */ - shift_m = strtol(&value[i + 4], NULL, 10) * 60; /* includes conversion from minutes to seconds */ + + shift_m = strtol(&value[i + 4], NULL, 10); + if (shift_m > 59) { + LOGERR(NULL, LY_EINVAL, "Invalid date-and-time timezone minutes \"%" PRIi64 "\".", shift_m); + return LY_EINVAL; + } + shift_m *= 60; /* convert from minutes to seconds */ + /* correct sign */ if (shift < 0) { shift_m *= -1; } + /* connect hours and minutes of the shift */ shift = shift + shift_m; } @@ -1535,41 +1728,24 @@ LIBYANG_API_DEF LY_ERR ly_time_time2str(time_t time, const char *fractions_s, char **str) { struct tm tm; - char zoneshift[8]; - int32_t zonediff_h, zonediff_m; + char zoneshift[12]; + int zonediff_s, zonediff_h, zonediff_m; LY_CHECK_ARG_RET(NULL, str, LY_EINVAL); - /* initialize the local timezone */ + /* init timezone */ tzset(); -#ifdef HAVE_TM_GMTOFF /* convert */ if (!localtime_r(&time, &tm)) { return LY_ESYS; } - /* get timezone offset */ - if (tm.tm_gmtoff == 0) { - /* time is Zulu (UTC) */ - zonediff_h = 0; - zonediff_m = 0; - } else { - /* timezone offset */ - zonediff_h = tm.tm_gmtoff / 60 / 60; - zonediff_m = tm.tm_gmtoff / 60 % 60; - } + /* get timezone offset (do not use tm_gmtoff to avoid portability problems) */ + zonediff_s = ly_time_tz_offset_at(time); + zonediff_h = zonediff_s / 60 / 60; + zonediff_m = zonediff_s / 60 % 60; sprintf(zoneshift, "%+03d:%02d", zonediff_h, zonediff_m); -#else - /* convert */ - if (!gmtime_r(&time, &tm)) { - return LY_ESYS; - } - - (void)zonediff_h; - (void)zonediff_m; - sprintf(zoneshift, "-00:00"); -#endif /* print */ if (asprintf(str, "%04d-%02d-%02dT%02d:%02d:%02d%s%s%s", diff --git a/src/tree_data_free.c b/src/tree_data_free.c index bf17a91..0281ae5 100644 --- a/src/tree_data_free.c +++ b/src/tree_data_free.c @@ -166,7 +166,7 @@ lyd_free_subtree(struct lyd_node *node, ly_bool top) ly_free_prefix_data(opaq->format, opaq->val_prefix_data); } else if (node->schema->nodetype & LYD_NODE_INNER) { /* remove children hash table in case of inner data node */ - lyht_free(((struct lyd_node_inner *)node)->children_ht); + lyht_free(((struct lyd_node_inner *)node)->children_ht, NULL); ((struct lyd_node_inner *)node)->children_ht = NULL; /* free the children */ @@ -189,7 +189,7 @@ lyd_free_subtree(struct lyd_node *node, ly_bool top) /* unlink only the nodes from the first level, nodes in subtree are freed all, so no unlink is needed */ if (top) { - lyd_unlink_tree(node); + lyd_unlink(node); } free(node); @@ -202,6 +202,11 @@ lyd_free_tree(struct lyd_node *node) return; } + if (lysc_is_key(node->schema) && node->parent) { + LOGERR(LYD_CTX(node), LY_EINVAL, "Cannot free a list key \"%s\", free the list instance instead.", LYD_NAME(node)); + return; + } + lyd_free_subtree(node, 1); } @@ -223,6 +228,11 @@ lyd_free_(struct lyd_node *node, ly_bool top) } LY_LIST_FOR_SAFE(node, next, iter) { + if (lysc_is_key(iter->schema) && iter->parent) { + LOGERR(LYD_CTX(iter), LY_EINVAL, "Cannot free a list key \"%s\", free the list instance instead.", LYD_NAME(iter)); + return; + } + /* in case of the top-level nodes (node->parent is NULL), no unlinking needed */ lyd_free_subtree(iter, iter->parent ? 1 : 0); } diff --git a/src/tree_data_hash.c b/src/tree_data_hash.c index 52f99a8..7235c27 100644 --- a/src/tree_data_hash.c +++ b/src/tree_data_hash.c @@ -39,15 +39,15 @@ lyd_hash(struct lyd_node *node) } /* hash always starts with the module and schema name */ - node->hash = dict_hash_multi(0, node->schema->module->name, strlen(node->schema->module->name)); - node->hash = dict_hash_multi(node->hash, node->schema->name, strlen(node->schema->name)); + node->hash = lyht_hash_multi(0, node->schema->module->name, strlen(node->schema->module->name)); + node->hash = lyht_hash_multi(node->hash, node->schema->name, strlen(node->schema->name)); if (node->schema->nodetype == LYS_LIST) { if (node->schema->flags & LYS_KEYLESS) { /* key-less list simply calls hash function again with empty key, * just so that it differs from the first-instance hash */ - node->hash = dict_hash_multi(node->hash, NULL, 0); + node->hash = lyht_hash_multi(node->hash, NULL, 0); } else { struct lyd_node_inner *list = (struct lyd_node_inner *)node; @@ -56,7 +56,7 @@ lyd_hash(struct lyd_node *node) struct lyd_node_term *key = (struct lyd_node_term *)iter; hash_key = key->value.realtype->plugin->print(NULL, &key->value, LY_VALUE_LYB, NULL, &dyn, &key_len); - node->hash = dict_hash_multi(node->hash, hash_key, key_len); + node->hash = lyht_hash_multi(node->hash, hash_key, key_len); if (dyn) { free((void *)hash_key); } @@ -67,14 +67,14 @@ lyd_hash(struct lyd_node *node) struct lyd_node_term *llist = (struct lyd_node_term *)node; hash_key = llist->value.realtype->plugin->print(NULL, &llist->value, LY_VALUE_LYB, NULL, &dyn, &key_len); - node->hash = dict_hash_multi(node->hash, hash_key, key_len); + node->hash = lyht_hash_multi(node->hash, hash_key, key_len); if (dyn) { free((void *)hash_key); } } /* finish the hash */ - node->hash = dict_hash_multi(node->hash, NULL, 0); + node->hash = lyht_hash_multi(node->hash, NULL, 0); return LY_SUCCESS; } @@ -121,14 +121,14 @@ lyd_hash_table_val_equal(void *val1_p, void *val2_p, ly_bool mod, void *UNUSED(c * @return LY_ERR value. */ static LY_ERR -lyd_insert_hash_add(struct hash_table *ht, struct lyd_node *node, ly_bool empty_ht) +lyd_insert_hash_add(struct ly_ht *ht, struct lyd_node *node, ly_bool empty_ht) { uint32_t hash; assert(ht && node && node->schema); /* add node itself */ - if (lyht_insert(ht, &node, node->hash, NULL)) { + if (lyht_insert_no_check(ht, &node, node->hash, NULL)) { LOGINT_RET(LYD_CTX(node)); } @@ -136,9 +136,9 @@ lyd_insert_hash_add(struct hash_table *ht, struct lyd_node *node, ly_bool empty_ if ((node->schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) && (!node->prev->next || (node->prev->schema != node->schema))) { /* get the simple hash */ - hash = dict_hash_multi(0, node->schema->module->name, strlen(node->schema->module->name)); - hash = dict_hash_multi(hash, node->schema->name, strlen(node->schema->name)); - hash = dict_hash_multi(hash, NULL, 0); + hash = lyht_hash_multi(0, node->schema->module->name, strlen(node->schema->module->name)); + hash = lyht_hash_multi(hash, node->schema->name, strlen(node->schema->name)); + hash = lyht_hash_multi(hash, NULL, 0); /* remove any previous stored instance, only if we did not start with an empty HT */ if (!empty_ht && node->next && (node->next->schema == node->schema)) { @@ -182,8 +182,7 @@ lyd_insert_hash(struct lyd_node *node) } } if (u >= LYD_HT_MIN_ITEMS) { - /* create hash table, insert all the children */ - node->parent->children_ht = lyht_new(1, sizeof(struct lyd_node *), lyd_hash_table_val_equal, NULL, 1); + node->parent->children_ht = lyht_new(lyht_get_fixed_size(u), sizeof(struct lyd_node *), lyd_hash_table_val_equal, NULL, 1); LY_LIST_FOR(node->parent->child, iter) { if (iter->schema) { LY_CHECK_RET(lyd_insert_hash_add(node->parent->children_ht, iter, 1)); @@ -216,9 +215,9 @@ lyd_unlink_hash(struct lyd_node *node) /* first instance of the (leaf-)list, needs to be removed from HT */ if ((node->schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) && (!node->prev->next || (node->prev->schema != node->schema))) { /* get the simple hash */ - hash = dict_hash_multi(0, node->schema->module->name, strlen(node->schema->module->name)); - hash = dict_hash_multi(hash, node->schema->name, strlen(node->schema->name)); - hash = dict_hash_multi(hash, NULL, 0); + hash = lyht_hash_multi(0, node->schema->module->name, strlen(node->schema->module->name)); + hash = lyht_hash_multi(hash, node->schema->name, strlen(node->schema->name)); + hash = lyht_hash_multi(hash, NULL, 0); /* remove the instance */ if (lyht_remove(node->parent->children_ht, &node, hash)) { diff --git a/src/tree_data_internal.h b/src/tree_data_internal.h index fd0792d..cfb93f1 100644 --- a/src/tree_data_internal.h +++ b/src/tree_data_internal.h @@ -4,7 +4,7 @@ * @author Michal Vasko <mvasko@cesnet.cz> * @brief internal functions for YANG schema trees. * - * Copyright (c) 2015 - 2022 CESNET, z.s.p.o. + * Copyright (c) 2015 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -34,31 +34,30 @@ struct lysc_module; #define LY_LYB_SUFFIX_LEN 4 /** - * @brief Internal structure for remembering "used" instances of lists with duplicate instances allowed. + * @brief Internal item structure for remembering "used" instances of duplicate node instances. */ struct lyd_dup_inst { - struct ly_set *inst_set; + struct ly_set *set; uint32_t used; }; /** - * @brief Update a found inst using a duplicate instance cache. Needs to be called for every "used" + * @brief Update a found inst using a duplicate instance cache hash table. Needs to be called for every "used" * (that should not be considered next time) instance. * * @param[in,out] inst Found instance, is updated so that the same instance is not returned twice. * @param[in] siblings Siblings where @p inst was found. - * @param[in,out] dup_inst_cache Duplicate instance cache. + * @param[in] dup_inst_ht Duplicate instance cache hash table. * @return LY_ERR value. */ -LY_ERR lyd_dup_inst_next(struct lyd_node **inst, const struct lyd_node *siblings, - struct lyd_dup_inst **dup_inst_cache); +LY_ERR lyd_dup_inst_next(struct lyd_node **inst, const struct lyd_node *siblings, struct ly_ht **dup_inst_ht); /** * @brief Free duplicate instance cache. * - * @param[in] dup_inst Duplicate instance cache to free. + * @param[in] dup_inst Duplicate instance cache hash table to free. */ -void lyd_dup_inst_free(struct lyd_dup_inst *dup_inst); +void lyd_dup_inst_free(struct ly_ht *dup_inst_ht); /** * @brief Just like ::lys_getnext() but iterates over all data instances of the schema nodes. @@ -118,14 +117,6 @@ const struct lys_module *lyd_mod_next_module(struct lyd_node *tree, const struct const struct lys_module *lyd_data_next_module(struct lyd_node **next, struct lyd_node **first); /** - * @brief Get schema node of a data node. Useful especially for opaque nodes. - * - * @param[in] node Data node to use. - * @return Schema node represented by data @p node, NULL if there is none. - */ -const struct lysc_node *lyd_node_schema(const struct lyd_node *node); - -/** * @brief Set dflt flag for a NP container if applicable, recursively for parents. * * @param[in] node Node whose criteria for the dflt flag has changed. @@ -233,6 +224,7 @@ const char *ly_format2str(LY_VALUE_FORMAT format); * @param[in] schema Schema node of the new data node. * @param[in] value String value to be parsed. * @param[in] value_len Length of @p value, must be set correctly. + * @param[in] is_utf8 Whether @p value is a valid UTF-8 string, if applicable. * @param[in,out] dynamic Flag if @p value is dynamically allocated, is adjusted when @p value is consumed. * @param[in] format Input format of @p value. * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). @@ -243,8 +235,9 @@ const char *ly_format2str(LY_VALUE_FORMAT format); * @return LY_EINCOMPLETE in case data tree is needed to finish the validation. * @return LY_ERR value if an error occurred. */ -LY_ERR lyd_create_term(const struct lysc_node *schema, const char *value, size_t value_len, ly_bool *dynamic, - LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, ly_bool *incomplete, struct lyd_node **node); +LY_ERR lyd_create_term(const struct lysc_node *schema, const char *value, size_t value_len, ly_bool is_utf8, + ly_bool *dynamic, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, ly_bool *incomplete, + struct lyd_node **node); /** * @brief Create a term (leaf/leaf-list) node from a parsed value by duplicating it. @@ -280,11 +273,13 @@ LY_ERR lyd_create_inner(const struct lysc_node *schema, struct lyd_node **node); * * @param[in] schema Schema node of the new data node. * @param[in] predicates Compiled key list predicates. + * @param[in] vars Array of defined variables to use in predicates, may be NULL. * @param[out] node Created node. * @return LY_SUCCESS on success. * @return LY_ERR value if an error occurred. */ -LY_ERR lyd_create_list(const struct lysc_node *schema, const struct ly_path_predicate *predicates, struct lyd_node **node); +LY_ERR lyd_create_list(const struct lysc_node *schema, const struct ly_path_predicate *predicates, + const struct lyxp_var *vars, struct lyd_node **node); /** * @brief Create a list with all its keys (cannot be used for key-less list). @@ -403,6 +398,13 @@ void lyd_insert_before_node(struct lyd_node *sibling, struct lyd_node *node); void lyd_insert_node(struct lyd_node *parent, struct lyd_node **first_sibling, struct lyd_node *node, ly_bool last); /** + * @brief Unlink the specified data subtree. + * + * @param[in] node Data tree node to be unlinked (together with all the children). + */ +void lyd_unlink(struct lyd_node *node); + +/** * @brief Insert a metadata (last) into a parent * * @param[in] parent Parent of the metadata. @@ -421,6 +423,7 @@ void lyd_insert_meta(struct lyd_node *parent, struct lyd_meta *meta, ly_bool cle * @param[in] name_len Length of @p name, must be set correctly. * @param[in] value String value to be parsed. * @param[in] value_len Length of @p value, must be set correctly. + * @param[in] is_utf8 Whether @p value is a valid UTF-8 string, if applicable. * @param[in,out] dynamic Flag if @p value is dynamically allocated, is adjusted when @p value is consumed. * @param[in] format Input format of @p value. * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). @@ -433,7 +436,7 @@ void lyd_insert_meta(struct lyd_node *parent, struct lyd_meta *meta, ly_bool cle * @return LY_ERR value if an error occurred. */ LY_ERR lyd_create_meta(struct lyd_node *parent, struct lyd_meta **meta, const struct lys_module *mod, const char *name, - size_t name_len, const char *value, size_t value_len, ly_bool *dynamic, LY_VALUE_FORMAT format, + size_t name_len, const char *value, size_t value_len, ly_bool is_utf8, ly_bool *dynamic, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, const struct lysc_node *ctx_node, ly_bool clear_dflt, ly_bool *incomplete); /** @@ -477,6 +480,7 @@ LY_ERR lyd_create_attr(struct lyd_node *parent, struct lyd_attr **attr, const st * @param[in] type Type of the value. * @param[in] value Value to be parsed, must not be NULL. * @param[in] value_len Length of the give @p value, must be set correctly. + * @param[in] is_utf8 Whether @p value is a valid UTF-8 string, if applicable. * @param[in,out] dynamic Flag if @p value is dynamically allocated, is adjusted when @p value is consumed. * @param[in] format Input format of @p value. * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). @@ -487,7 +491,7 @@ LY_ERR lyd_create_attr(struct lyd_node *parent, struct lyd_attr **attr, const st * @return LY_ERR value on error. */ LY_ERR lyd_value_store(const struct ly_ctx *ctx, struct lyd_value *val, const struct lysc_type *type, const void *value, - size_t value_len, ly_bool *dynamic, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, + size_t value_len, ly_bool is_utf8, ly_bool *dynamic, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, const struct lysc_node *ctx_node, ly_bool *incomplete); /** @@ -505,8 +509,7 @@ LY_ERR lyd_value_validate_incomplete(const struct ly_ctx *ctx, const struct lysc const struct lyd_node *ctx_node, const struct lyd_node *tree); /** - * @brief Check type restrictions applicable to the particular leaf/leaf-list with the given string @p value coming - * from a schema. + * @brief Check type restrictions applicable to the particular leaf/leaf-list with the given string @p value. * * This function check just the type's restriction, if you want to check also the data tree context (e.g. in case of * require-instance restriction), use ::lyd_value_validate(). @@ -517,11 +520,12 @@ LY_ERR lyd_value_validate_incomplete(const struct ly_ctx *ctx, const struct lysc * @param[in] value_len Length of the given @p value (mandatory). * @param[in] format Value prefix format. * @param[in] prefix_data Format-specific data for resolving any prefixes (see ::ly_resolve_prefix). + * @param[in] hints Value encoding hints. * @return LY_SUCCESS on success * @return LY_ERR value if an error occurred. */ -LY_ERR lys_value_validate(const struct ly_ctx *ctx, const struct lysc_node *node, const char *value, size_t value_len, - LY_VALUE_FORMAT format, void *prefix_data); +LY_ERR ly_value_validate(const struct ly_ctx *ctx, const struct lysc_node *node, const char *value, size_t value_len, + LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints); /** * @defgroup datahash Data nodes hash manipulation diff --git a/src/tree_data_new.c b/src/tree_data_new.c index 752b181..5d9f429 100644 --- a/src/tree_data_new.c +++ b/src/tree_data_new.c @@ -51,7 +51,7 @@ #include "xpath.h" LY_ERR -lyd_create_term(const struct lysc_node *schema, const char *value, size_t value_len, ly_bool *dynamic, +lyd_create_term(const struct lysc_node *schema, const char *value, size_t value_len, ly_bool is_utf8, ly_bool *dynamic, LY_VALUE_FORMAT format, void *prefix_data, uint32_t hints, ly_bool *incomplete, struct lyd_node **node) { LY_ERR ret; @@ -68,7 +68,7 @@ lyd_create_term(const struct lysc_node *schema, const char *value, size_t value_ LOG_LOCSET(schema, NULL, NULL, NULL); ret = lyd_value_store(schema->module->ctx, &term->value, ((struct lysc_node_leaf *)term->schema)->type, value, - value_len, dynamic, format, prefix_data, hints, schema, incomplete); + value_len, is_utf8, dynamic, format, prefix_data, hints, schema, incomplete); LOG_LOCBACK(1, 0, 0, 0); LY_CHECK_ERR_RET(ret, free(term), ret); lyd_hash(&term->node); @@ -134,10 +134,14 @@ lyd_create_inner(const struct lysc_node *schema, struct lyd_node **node) } LY_ERR -lyd_create_list(const struct lysc_node *schema, const struct ly_path_predicate *predicates, struct lyd_node **node) +lyd_create_list(const struct lysc_node *schema, const struct ly_path_predicate *predicates, const struct lyxp_var *vars, + struct lyd_node **node) { LY_ERR ret = LY_SUCCESS; struct lyd_node *list = NULL, *key; + const struct lyd_value *value; + struct lyd_value val = {0}; + struct lyxp_var *var; LY_ARRAY_COUNT_TYPE u; assert((schema->nodetype == LYS_LIST) && !(schema->flags & LYS_KEYLESS)); @@ -149,7 +153,31 @@ lyd_create_list(const struct lysc_node *schema, const struct ly_path_predicate * /* create and insert all the keys */ LY_ARRAY_FOR(predicates, u) { - LY_CHECK_GOTO(ret = lyd_create_term2(predicates[u].key, &predicates[u].value, &key), cleanup); + if (predicates[u].type == LY_PATH_PREDTYPE_LIST_VAR) { + /* find the var */ + if ((ret = lyxp_vars_find(schema->module->ctx, vars, predicates[u].variable, 0, &var))) { + goto cleanup; + } + + /* store the value */ + LOG_LOCSET(predicates[u].key, NULL, NULL, NULL); + ret = lyd_value_store(schema->module->ctx, &val, ((struct lysc_node_leaf *)predicates[u].key)->type, + var->value, strlen(var->value), 0, NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, predicates[u].key, NULL); + LOG_LOCBACK(1, 0, 0, 0); + LY_CHECK_GOTO(ret, cleanup); + + value = &val; + } else { + assert(predicates[u].type == LY_PATH_PREDTYPE_LIST); + value = &predicates[u].value; + } + + ret = lyd_create_term2(predicates[u].key, value, &key); + if (val.realtype) { + val.realtype->plugin->free(schema->module->ctx, &val); + memset(&val, 0, sizeof val); + } + LY_CHECK_GOTO(ret, cleanup); lyd_insert_node(list, NULL, key, 0); } @@ -172,7 +200,6 @@ lyd_create_list2(const struct lysc_node *schema, const char *keys, size_t keys_l LY_ERR ret = LY_SUCCESS; struct lyxp_expr *expr = NULL; uint32_t exp_idx = 0; - enum ly_path_pred_type pred_type = 0; struct ly_path_predicate *predicates = NULL; LOG_LOCSET(schema, NULL, NULL, NULL); @@ -183,100 +210,111 @@ lyd_create_list2(const struct lysc_node *schema, const char *keys, size_t keys_l /* compile them */ LY_CHECK_GOTO(ret = ly_path_compile_predicate(schema->module->ctx, NULL, NULL, schema, expr, &exp_idx, LY_VALUE_JSON, - NULL, &predicates, &pred_type), cleanup); + NULL, &predicates), cleanup); /* create the list node */ - LY_CHECK_GOTO(ret = lyd_create_list(schema, predicates, node), cleanup); + LY_CHECK_GOTO(ret = lyd_create_list(schema, predicates, NULL, node), cleanup); cleanup: LOG_LOCBACK(1, 0, 0, 0); lyxp_expr_free(schema->module->ctx, expr); - ly_path_predicates_free(schema->module->ctx, pred_type, predicates); + ly_path_predicates_free(schema->module->ctx, predicates); return ret; } /** - * @brief Convert an anydata value into a datatree. + * @brief Learn actual any value type in case it is currently ::LYD_ANYDATA_STRING. + * + * @param[in] value Any value. + * @param[out] value_type Detected value type. + */ +static void +lyd_create_any_string_valtype(const void *value, LYD_ANYDATA_VALUETYPE *value_type) +{ + /* detect format */ + if (!value) { + /* interpret it as an empty data tree */ + *value_type = LYD_ANYDATA_DATATREE; + } else if (((char *)value)[0] == '<') { + *value_type = LYD_ANYDATA_XML; + } else if (((char *)value)[0] == '{') { + *value_type = LYD_ANYDATA_JSON; + } else if (!strncmp(value, "lyb", 3)) { + *value_type = LYD_ANYDATA_LYB; + } else { + /* really just some string */ + *value_type = LYD_ANYDATA_STRING; + } +} + +/** + * @brief Convert an any value into a datatree. * * @param[in] ctx libyang context. - * @param[in] value Anydata value. - * @param[in] value_type Anydata @p value type. + * @param[in] value_in Any value as an input. + * @param[in] value_type Any @p value type. + * @param[in] log Whether parsing errors should be logged. * @param[out] tree Parsed data tree. * @return LY_ERR value. */ static LY_ERR -lyd_create_anydata_datatree(const struct ly_ctx *ctx, const void *value, LYD_ANYDATA_VALUETYPE value_type, +lyd_create_any_datatree(const struct ly_ctx *ctx, struct ly_in *value_in, LYD_ANYDATA_VALUETYPE value_type, ly_bool log, struct lyd_node **tree) { - LY_ERR r; - struct ly_in *in = NULL; + LY_ERR rc = LY_SUCCESS; struct lyd_ctx *lydctx = NULL; - uint32_t parse_opts, int_opts; + uint32_t parse_opts, int_opts, log_opts = 0; *tree = NULL; - if (!value) { - /* empty data tree no matter the value type */ - return LY_SUCCESS; - } - - if (value_type == LYD_ANYDATA_STRING) { - /* detect format */ - if (((char *)value)[0] == '<') { - value_type = LYD_ANYDATA_XML; - } else if (((char *)value)[0] == '{') { - value_type = LYD_ANYDATA_JSON; - } else if (!strncmp(value, "lyb", 3)) { - value_type = LYD_ANYDATA_LYB; - } else { - LOGERR(ctx, LY_EINVAL, "Invalid string value of an anydata node."); - return LY_EINVAL; - } - } - - /* create input */ - LY_CHECK_RET(ly_in_new_memory(value, &in)); - /* set options */ parse_opts = LYD_PARSE_ONLY | LYD_PARSE_OPAQ; int_opts = LYD_INTOPT_ANY | LYD_INTOPT_WITH_SIBLINGS; + if (!log) { + /* no logging */ + ly_temp_log_options(&log_opts); + } + switch (value_type) { case LYD_ANYDATA_DATATREE: case LYD_ANYDATA_STRING: /* unreachable */ - ly_in_free(in, 0); LOGINT_RET(ctx); case LYD_ANYDATA_XML: - r = lyd_parse_xml(ctx, NULL, NULL, tree, in, parse_opts, 0, int_opts, NULL, NULL, &lydctx); + rc = lyd_parse_xml(ctx, NULL, NULL, tree, value_in, parse_opts, 0, int_opts, NULL, NULL, &lydctx); break; case LYD_ANYDATA_JSON: - r = lyd_parse_json(ctx, NULL, NULL, tree, in, parse_opts, 0, int_opts, NULL, NULL, &lydctx); + rc = lyd_parse_json(ctx, NULL, NULL, tree, value_in, parse_opts, 0, int_opts, NULL, NULL, &lydctx); break; case LYD_ANYDATA_LYB: - r = lyd_parse_lyb(ctx, NULL, NULL, tree, in, parse_opts | LYD_PARSE_STRICT, 0, int_opts, NULL, NULL, &lydctx); + rc = lyd_parse_lyb(ctx, NULL, NULL, tree, value_in, parse_opts | LYD_PARSE_STRICT, 0, int_opts, NULL, NULL, &lydctx); break; } if (lydctx) { lydctx->free(lydctx); } - ly_in_free(in, 0); - if (r) { - LOGERR(ctx, LY_EINVAL, "Failed to parse anydata content into a data tree."); - return LY_EINVAL; + if (!log) { + /* restore logging */ + ly_temp_log_options(NULL); } - return LY_SUCCESS; + if (rc && *tree) { + lyd_free_siblings(*tree); + *tree = NULL; + } + return rc; } LY_ERR lyd_create_any(const struct lysc_node *schema, const void *value, LYD_ANYDATA_VALUETYPE value_type, ly_bool use_value, struct lyd_node **node) { - LY_ERR ret; + LY_ERR rc = LY_SUCCESS, r; struct lyd_node *tree; struct lyd_node_any *any = NULL; union lyd_any_value any_val; + struct ly_in *in = NULL; assert(schema->nodetype & LYD_NODE_ANY); @@ -287,15 +325,70 @@ lyd_create_any(const struct lysc_node *schema, const void *value, LYD_ANYDATA_VA any->prev = &any->node; any->flags = LYD_NEW; - if ((schema->nodetype == LYS_ANYDATA) && (value_type != LYD_ANYDATA_DATATREE)) { - /* only a data tree can be stored */ - LY_CHECK_GOTO(ret = lyd_create_anydata_datatree(schema->module->ctx, value, value_type, &tree), error); - if (use_value) { - free((void *)value); + if (schema->nodetype == LYS_ANYDATA) { + /* anydata */ + if (value_type == LYD_ANYDATA_STRING) { + /* detect value type */ + lyd_create_any_string_valtype(value, &value_type); + } + + if (value_type != LYD_ANYDATA_DATATREE) { + /* create input */ + assert(value); + LY_CHECK_GOTO(rc = ly_in_new_memory(value, &in), cleanup); + + /* parse as a data tree */ + if ((r = lyd_create_any_datatree(schema->module->ctx, in, value_type, 1, &tree))) { + LOGERR(schema->module->ctx, rc, "Failed to parse any content into a data tree."); + rc = r; + goto cleanup; + } + + /* use the parsed data tree */ + if (use_value) { + free((void *)value); + } + use_value = 1; + value = tree; + value_type = LYD_ANYDATA_DATATREE; + } + } else { + /* anyxml */ + switch (value_type) { + case LYD_ANYDATA_DATATREE: + /* fine, just use the value */ + break; + case LYD_ANYDATA_STRING: + /* detect value type */ + lyd_create_any_string_valtype(value, &value_type); + if ((value_type == LYD_ANYDATA_DATATREE) || (value_type == LYD_ANYDATA_STRING)) { + break; + } + /* fallthrough */ + case LYD_ANYDATA_XML: + case LYD_ANYDATA_JSON: + case LYD_ANYDATA_LYB: + if (!value) { + /* nothing to parse */ + break; + } + + /* create input */ + LY_CHECK_GOTO(rc = ly_in_new_memory(value, &in), cleanup); + + /* try to parse as a data tree */ + r = lyd_create_any_datatree(schema->module->ctx, in, value_type, 0, &tree); + if (!r) { + /* use the parsed data tree */ + if (use_value) { + free((void *)value); + } + use_value = 1; + value = tree; + value_type = LYD_ANYDATA_DATATREE; + } + break; } - use_value = 1; - value = tree; - value_type = LYD_ANYDATA_DATATREE; } if (use_value) { @@ -306,7 +399,7 @@ lyd_create_any(const struct lysc_node *schema, const void *value, LYD_ANYDATA_VA case LYD_ANYDATA_STRING: case LYD_ANYDATA_XML: case LYD_ANYDATA_JSON: - LY_CHECK_GOTO(ret = lydict_insert_zc(schema->module->ctx, (void *)value, &any->value.str), error); + LY_CHECK_GOTO(rc = lydict_insert_zc(schema->module->ctx, (void *)value, &any->value.str), cleanup); break; case LYD_ANYDATA_LYB: any->value.mem = (void *)value; @@ -315,16 +408,18 @@ lyd_create_any(const struct lysc_node *schema, const void *value, LYD_ANYDATA_VA any->value_type = value_type; } else { any_val.str = value; - LY_CHECK_GOTO(ret = lyd_any_copy_value(&any->node, &any_val, value_type), error); + LY_CHECK_GOTO(rc = lyd_any_copy_value(&any->node, &any_val, value_type), cleanup); } lyd_hash(&any->node); - *node = &any->node; - return LY_SUCCESS; - -error: - free(any); - return ret; +cleanup: + if (rc) { + lyd_free_tree(&any->node); + } else { + *node = &any->node; + } + ly_in_free(in, 0); + return rc; } LY_ERR @@ -444,33 +539,25 @@ lyd_new_ext_inner(const struct lysc_ext_instance *ext, const char *name, struct } /** - * @brief Create a new list node in the data tree. + * @brief Create a new lits node instance without its keys. * + * @param[in] ctx Context to use for logging. * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element. * @param[in] module Module of the node being created. If NULL, @p parent module will be used. * @param[in] name Schema node name of the new data node. The node must be #LYS_LIST. - * @param[in] format Format of key values. * @param[in] output Flag in case the @p parent is RPC/Action. If value is 0, the input's data nodes of the RPC/Action are * taken into consideration. Otherwise, the output's data node is going to be created. - * @param[out] node Optional created node. - * @param[in] ap Ordered key values of the new list instance, all must be set. For ::LY_VALUE_LYB, every value must - * be followed by the value length. + * @param[out] node Created node. * @return LY_ERR value. */ static LY_ERR -_lyd_new_list(struct lyd_node *parent, const struct lys_module *module, const char *name, LY_VALUE_FORMAT format, - ly_bool output, struct lyd_node **node, va_list ap) +_lyd_new_list_node(const struct ly_ctx *ctx, const struct lyd_node *parent, const struct lys_module *module, + const char *name, ly_bool output, struct lyd_node **node) { - struct lyd_node *ret = NULL, *key; - const struct lysc_node *schema, *key_s; + struct lyd_node *ret = NULL; + const struct lysc_node *schema; struct lysc_ext_instance *ext = NULL; - const struct ly_ctx *ctx = parent ? LYD_CTX(parent) : (module ? module->ctx : NULL); - const void *key_val; - uint32_t key_len; - LY_ERR r, rc = LY_SUCCESS; - - LY_CHECK_ARG_RET(ctx, parent || module, parent || node, name, LY_EINVAL); - LY_CHECK_CTX_EQUAL_RET(parent ? LYD_CTX(parent) : NULL, module ? module->ctx : NULL, LY_EINVAL); + LY_ERR r; if (!module) { module = parent->schema->module; @@ -487,8 +574,47 @@ _lyd_new_list(struct lyd_node *parent, const struct lys_module *module, const ch /* create list inner node */ LY_CHECK_RET(lyd_create_inner(schema, &ret)); + if (ext) { + ret->flags |= LYD_EXT; + } + + *node = ret; + return LY_SUCCESS; +} + +/** + * @brief Create a new list node in the data tree. + * + * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element. + * @param[in] module Module of the node being created. If NULL, @p parent module will be used. + * @param[in] name Schema node name of the new data node. The node must be #LYS_LIST. + * @param[in] format Format of key values. + * @param[in] output Flag in case the @p parent is RPC/Action. If value is 0, the input's data nodes of the RPC/Action are + * taken into consideration. Otherwise, the output's data node is going to be created. + * @param[out] node Optional created node. + * @param[in] ap Ordered key values of the new list instance, all must be set. For ::LY_VALUE_LYB, every value must + * be followed by the value length. + * @return LY_ERR value. + */ +static LY_ERR +_lyd_new_list(struct lyd_node *parent, const struct lys_module *module, const char *name, LY_VALUE_FORMAT format, + ly_bool output, struct lyd_node **node, va_list ap) +{ + struct lyd_node *ret = NULL, *key; + const struct lysc_node *key_s; + const struct ly_ctx *ctx = parent ? LYD_CTX(parent) : (module ? module->ctx : NULL); + const void *key_val; + uint32_t key_len; + LY_ERR rc = LY_SUCCESS; + + LY_CHECK_ARG_RET(ctx, parent || module, parent || node, name, LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(parent ? LYD_CTX(parent) : NULL, module ? module->ctx : NULL, LY_EINVAL); + + /* create the list node */ + LY_CHECK_RET(_lyd_new_list_node(ctx, parent, module, name, output, &ret)); + /* create and insert all the keys */ - for (key_s = lysc_node_child(schema); key_s && (key_s->flags & LYS_KEY); key_s = key_s->next) { + for (key_s = lysc_node_child(ret->schema); key_s && (key_s->flags & LYS_KEY); key_s = key_s->next) { if (format == LY_VALUE_LYB) { key_val = va_arg(ap, const void *); key_len = va_arg(ap, uint32_t); @@ -497,14 +623,11 @@ _lyd_new_list(struct lyd_node *parent, const struct lys_module *module, const ch key_len = key_val ? strlen((char *)key_val) : 0; } - rc = lyd_create_term(key_s, key_val, key_len, NULL, format, NULL, LYD_HINT_DATA, NULL, &key); + rc = lyd_create_term(key_s, key_val, key_len, 0, NULL, format, NULL, LYD_HINT_DATA, NULL, &key); LY_CHECK_GOTO(rc, cleanup); lyd_insert_node(ret, NULL, key, 1); } - if (ext) { - ret->flags |= LYD_EXT; - } if (parent) { lyd_insert_node(parent, NULL, ret, 0); } @@ -595,7 +718,7 @@ lyd_new_ext_list(const struct lysc_ext_instance *ext, const char *name, struct l for (key_s = lysc_node_child(schema); key_s && (key_s->flags & LYS_KEY); key_s = key_s->next) { key_val = va_arg(ap, const char *); - rc = lyd_create_term(key_s, key_val, key_val ? strlen(key_val) : 0, NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, + rc = lyd_create_term(key_s, key_val, key_val ? strlen(key_val) : 0, 0, NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, NULL, &key); LY_CHECK_GOTO(rc, cleanup); lyd_insert_node(ret, NULL, key, 1); @@ -661,6 +784,84 @@ lyd_new_list2(struct lyd_node *parent, const struct lys_module *module, const ch } /** + * @brief Create a new list node in the data tree. + * + * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element. + * @param[in] module Module of the node being created. If NULL, @p parent module will be used. + * @param[in] name Schema node name of the new data node. The node must be #LYS_LIST. + * @param[in] format Format of key values. + * @param[in] key_values Ordered key values of the new list instance ended with NULL, all must be set. + * @param[in] value_lengths Lengths of @p key_values, required for ::LY_VALUE_LYB, optional otherwise. + * @param[in] output Flag in case the @p parent is RPC/Action. If value is 0, the input's data nodes of the RPC/Action are + * taken into consideration. Otherwise, the output's data node is going to be created. + * @param[out] node Optional created node. + * @return LY_ERR value. + */ +static LY_ERR +_lyd_new_list3(struct lyd_node *parent, const struct lys_module *module, const char *name, LY_VALUE_FORMAT format, + const void **key_values, uint32_t *value_lengths, ly_bool output, struct lyd_node **node) +{ + struct lyd_node *ret = NULL, *key; + const struct lysc_node *key_s; + const struct ly_ctx *ctx = parent ? LYD_CTX(parent) : (module ? module->ctx : NULL); + const void *key_val; + uint32_t key_len, i; + LY_ERR rc = LY_SUCCESS; + + LY_CHECK_ARG_RET(ctx, parent || module, parent || node, name, key_values, (format != LY_VALUE_LYB) || value_lengths, + LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(parent ? LYD_CTX(parent) : NULL, module ? module->ctx : NULL, LY_EINVAL); + + /* create the list node */ + LY_CHECK_RET(_lyd_new_list_node(ctx, parent, module, name, output, &ret)); + + /* create and insert all the keys */ + i = 0; + for (key_s = lysc_node_child(ret->schema); key_s && (key_s->flags & LYS_KEY); key_s = key_s->next) { + key_val = key_values[i] ? key_values[i] : ""; + key_len = value_lengths ? value_lengths[i] : strlen(key_val); + + rc = lyd_create_term(key_s, key_val, key_len, 0, NULL, format, NULL, LYD_HINT_DATA, NULL, &key); + LY_CHECK_GOTO(rc, cleanup); + lyd_insert_node(ret, NULL, key, 1); + } + + if (parent) { + lyd_insert_node(parent, NULL, ret, 0); + } + +cleanup: + if (rc) { + lyd_free_tree(ret); + ret = NULL; + } else if (node) { + *node = ret; + } + return rc; +} + +LIBYANG_API_DEF LY_ERR +lyd_new_list3(struct lyd_node *parent, const struct lys_module *module, const char *name, const char **key_values, + uint32_t *value_lengths, ly_bool output, struct lyd_node **node) +{ + return _lyd_new_list3(parent, module, name, LY_VALUE_JSON, (const void **)key_values, value_lengths, output, node); +} + +LIBYANG_API_DEF LY_ERR +lyd_new_list3_bin(struct lyd_node *parent, const struct lys_module *module, const char *name, const void **key_values, + uint32_t *value_lengths, ly_bool output, struct lyd_node **node) +{ + return _lyd_new_list3(parent, module, name, LY_VALUE_LYB, key_values, value_lengths, output, node); +} + +LIBYANG_API_DEF LY_ERR +lyd_new_list3_canon(struct lyd_node *parent, const struct lys_module *module, const char *name, const char **key_values, + uint32_t *value_lengths, ly_bool output, struct lyd_node **node) +{ + return _lyd_new_list3(parent, module, name, LY_VALUE_CANON, (const void **)key_values, value_lengths, output, node); +} + +/** * @brief Create a new term node in the data tree. * * @param[in] parent Parent node for the node being created. NULL in case of creating a top level element. @@ -699,7 +900,7 @@ _lyd_new_term(struct lyd_node *parent, const struct lys_module *module, const ch } LY_CHECK_ERR_RET(!schema, LOGERR(ctx, LY_EINVAL, "Term node \"%s\" not found.", name), LY_ENOTFOUND); - LY_CHECK_RET(lyd_create_term(schema, value, value_len, NULL, format, NULL, LYD_HINT_DATA, NULL, &ret)); + LY_CHECK_RET(lyd_create_term(schema, value, value_len, 0, NULL, format, NULL, LYD_HINT_DATA, NULL, &ret)); if (ext) { ret->flags |= LYD_EXT; } @@ -754,7 +955,8 @@ lyd_new_ext_term(const struct lysc_ext_instance *ext, const char *name, const ch } return LY_ENOTFOUND; } - rc = lyd_create_term(schema, val_str, val_str ? strlen(val_str) : 0, NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, NULL, &ret); + rc = lyd_create_term(schema, val_str, val_str ? strlen(val_str) : 0, 0, NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, + NULL, &ret); LY_CHECK_RET(rc); *node = ret; @@ -772,7 +974,8 @@ lyd_new_any(struct lyd_node *parent, const struct lys_module *module, const char struct lysc_ext_instance *ext = NULL; const struct ly_ctx *ctx = parent ? LYD_CTX(parent) : (module ? module->ctx : NULL); - LY_CHECK_ARG_RET(ctx, parent || module, parent || node, name, LY_EINVAL); + LY_CHECK_ARG_RET(ctx, parent || module, parent || node, name, + (value_type == LYD_ANYDATA_DATATREE) || (value_type == LYD_ANYDATA_STRING) || value, LY_EINVAL); LY_CHECK_CTX_EQUAL_RET(parent ? LYD_CTX(parent) : NULL, module ? module->ctx : NULL, LY_EINVAL); if (!module) { @@ -867,7 +1070,7 @@ lyd_new_meta(const struct ly_ctx *ctx, struct lyd_node *parent, const struct lys val_str = ""; } - return lyd_create_meta(parent, meta, module, name, name_len, val_str, strlen(val_str), NULL, LY_VALUE_JSON, + return lyd_create_meta(parent, meta, module, name, name_len, val_str, strlen(val_str), 0, NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, parent ? parent->schema : NULL, clear_dflt, NULL); } @@ -881,7 +1084,8 @@ lyd_new_meta2(const struct ly_ctx *ctx, struct lyd_node *parent, ly_bool clear_d LY_CHECK_CTX_EQUAL_RET(ctx, parent ? LYD_CTX(parent) : NULL, LY_EINVAL); if (parent && !parent->schema) { - LOGERR(ctx, LY_EINVAL, "Cannot add metadata to an opaque node \"%s\".", ((struct lyd_node_opaq *)parent)->name); + LOGERR(ctx, LY_EINVAL, "Cannot add metadata to an opaque node \"%s\".", + ((struct lyd_node_opaq *)parent)->name.name); return LY_EINVAL; } if (meta) { @@ -908,7 +1112,7 @@ lyd_new_meta2(const struct ly_ctx *ctx, struct lyd_node *parent, ly_bool clear_d } return lyd_create_meta(parent, meta, mod, attr->name.name, strlen(attr->name.name), attr->value, strlen(attr->value), - NULL, attr->format, attr->val_prefix_data, attr->hints, parent ? parent->schema : NULL, clear_dflt, NULL); + 0, NULL, attr->format, attr->val_prefix_data, attr->hints, parent ? parent->schema : NULL, clear_dflt, NULL); } LIBYANG_API_DEF LY_ERR @@ -1094,7 +1298,8 @@ _lyd_change_term(struct lyd_node *term, const void *value, size_t value_len, LY_ /* parse the new value */ LOG_LOCSET(term->schema, term, NULL, NULL); - ret = lyd_value_store(LYD_CTX(term), &val, type, value, value_len, NULL, format, NULL, LYD_HINT_DATA, term->schema, NULL); + ret = lyd_value_store(LYD_CTX(term), &val, type, value, value_len, 0, NULL, format, NULL, LYD_HINT_DATA, + term->schema, NULL); LOG_LOCBACK(term->schema ? 1 : 0, 1, 0, 0); LY_CHECK_GOTO(ret, cleanup); @@ -1195,7 +1400,7 @@ lyd_change_meta(struct lyd_meta *meta, const char *val_str) /* parse the new value into a new meta structure */ ret = lyd_create_meta(NULL, &m2, meta->annotation->module, meta->name, strlen(meta->name), val_str, strlen(val_str), - NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, meta->parent ? meta->parent->schema : NULL, 0, NULL); + 0, NULL, LY_VALUE_JSON, NULL, LYD_HINT_DATA, meta->parent ? meta->parent->schema : NULL, 0, NULL); LY_CHECK_GOTO(ret, cleanup); /* compare original and new value */ @@ -1320,18 +1525,20 @@ lyd_new_path_check_find_lypath(struct ly_path *path, const char *str_path, const schema = path[u].node; if (lysc_is_dup_inst_list(schema)) { - if (path[u].pred_type == LY_PATH_PREDTYPE_NONE) { + if (!path[u].predicates || + ((schema->nodetype == LYS_LEAFLIST) && (path[u].predicates[0].type == LY_PATH_PREDTYPE_LEAFLIST))) { /* creating a new key-less list or state leaf-list instance */ create = 1; new_count = u; - } else if (path[u].pred_type != LY_PATH_PREDTYPE_POSITION) { + } else if (path[u].predicates[0].type != LY_PATH_PREDTYPE_POSITION) { LOG_LOCSET(schema, NULL, NULL, NULL); - LOGVAL(schema->module->ctx, LYVE_XPATH, "Invalid predicate for %s \"%s\" in path \"%s\".", + LOGVAL(schema->module->ctx, LYVE_XPATH, "Invalid predicate for state %s \"%s\" in path \"%s\".", lys_nodetype2str(schema->nodetype), schema->name, str_path); LOG_LOCBACK(1, 0, 0, 0); return LY_EINVAL; } - } else if ((schema->nodetype == LYS_LIST) && (path[u].pred_type != LY_PATH_PREDTYPE_LIST)) { + } else if ((schema->nodetype == LYS_LIST) && + (!path[u].predicates || (path[u].predicates[0].type != LY_PATH_PREDTYPE_LIST))) { if ((u < LY_ARRAY_COUNT(path) - 1) || !(options & LYD_NEW_PATH_OPAQ)) { LOG_LOCSET(schema, NULL, NULL, NULL); LOGVAL(schema->module->ctx, LYVE_XPATH, "Predicate missing for %s \"%s\" in path \"%s\".", @@ -1339,7 +1546,8 @@ lyd_new_path_check_find_lypath(struct ly_path *path, const char *str_path, const LOG_LOCBACK(1, 0, 0, 0); return LY_EINVAL; } /* else creating an opaque list */ - } else if ((schema->nodetype == LYS_LEAFLIST) && (path[u].pred_type != LY_PATH_PREDTYPE_LEAFLIST)) { + } else if ((schema->nodetype == LYS_LEAFLIST) && + (!path[u].predicates || (path[u].predicates[0].type != LY_PATH_PREDTYPE_LEAFLIST))) { r = LY_SUCCESS; if (options & LYD_NEW_PATH_OPAQ) { r = lyd_value_validate(NULL, schema, value, value_len, NULL, NULL, NULL); @@ -1347,12 +1555,12 @@ lyd_new_path_check_find_lypath(struct ly_path *path, const char *str_path, const if (!r) { /* try to store the value */ LY_CHECK_RET(lyd_value_store(schema->module->ctx, &val, ((struct lysc_node_leaflist *)schema)->type, - value, value_len, NULL, format, NULL, LYD_HINT_DATA, schema, NULL)); + value, value_len, 0, NULL, format, NULL, LYD_HINT_DATA, schema, NULL)); ++((struct lysc_type *)val.realtype)->refcount; /* store the new predicate so that it is used when searching for this instance */ - path[u].pred_type = LY_PATH_PREDTYPE_LEAFLIST; LY_ARRAY_NEW_RET(schema->module->ctx, path[u].predicates, pred, LY_EMEM); + pred->type = LY_PATH_PREDTYPE_LEAFLIST; pred->value = val; } /* else we have opaq flag and the value is not valid, leave no predicate and then create an opaque node */ } @@ -1429,7 +1637,7 @@ lyd_new_path_(struct lyd_node *parent, const struct ly_ctx *ctx, const struct ly } /* parse path */ - LY_CHECK_GOTO(ret = ly_path_parse(ctx, NULL, path, strlen(path), 0, LY_PATH_BEGIN_EITHER, LY_PATH_PREFIX_OPTIONAL, + LY_CHECK_GOTO(ret = ly_path_parse(ctx, NULL, path, strlen(path), 0, LY_PATH_BEGIN_EITHER, LY_PATH_PREFIX_FIRST, LY_PATH_PRED_SIMPLE, &exp), cleanup); /* compile path */ @@ -1442,32 +1650,36 @@ lyd_new_path_(struct lyd_node *parent, const struct ly_ctx *ctx, const struct ly /* try to find any existing nodes in the path */ if (parent) { - ret = ly_path_eval_partial(p, parent, &path_idx, &node); - if (ret == LY_SUCCESS) { + r = ly_path_eval_partial(p, parent, NULL, options & LYD_NEW_PATH_WITH_OPAQ, &path_idx, &node); + if (r == LY_SUCCESS) { if (orig_count == LY_ARRAY_COUNT(p)) { /* the node exists, are we supposed to update it or is it just a default? */ if (!(options & LYD_NEW_PATH_UPDATE) && !(node->flags & LYD_DEFAULT)) { LOG_LOCSET(NULL, node, NULL, NULL); - LOGVAL(ctx, LYVE_REFERENCE, "Path \"%s\" already exists", path); + LOGVAL(ctx, LYVE_REFERENCE, "Path \"%s\" already exists.", path); LOG_LOCBACK(0, 1, 0, 0); ret = LY_EEXIST; goto cleanup; + } else if ((options & LYD_NEW_PATH_UPDATE) && lysc_is_key(node->schema)) { + /* fine, the key value must not be changed and has to be in the predicate to be found */ + goto cleanup; } /* update the existing node */ ret = lyd_new_path_update(node, value, value_len, value_type, format, &nparent, &nnode); goto cleanup; } /* else we were not searching for the whole path */ - } else if (ret == LY_EINCOMPLETE) { + } else if (r == LY_EINCOMPLETE) { /* some nodes were found, adjust the iterator to the next segment */ ++path_idx; - } else if (ret == LY_ENOTFOUND) { + } else if (r == LY_ENOTFOUND) { /* we will create the nodes from top-level, default behavior (absolute path), or from the parent (relative path) */ if (lysc_data_parent(p[0].node)) { node = parent; } } else { /* error */ + ret = r; goto cleanup; } } @@ -1487,15 +1699,14 @@ lyd_new_path_(struct lyd_node *parent, const struct ly_ctx *ctx, const struct ly if (lysc_is_dup_inst_list(schema)) { /* create key-less list instance */ LY_CHECK_GOTO(ret = lyd_create_inner(schema, &node), cleanup); - } else if ((options & LYD_NEW_PATH_OPAQ) && (p[path_idx].pred_type == LY_PATH_PREDTYPE_NONE)) { + } else if ((options & LYD_NEW_PATH_OPAQ) && !p[path_idx].predicates) { /* creating opaque list without keys */ LY_CHECK_GOTO(ret = lyd_create_opaq(ctx, schema->name, strlen(schema->name), NULL, 0, schema->module->name, strlen(schema->module->name), NULL, 0, NULL, LY_VALUE_JSON, NULL, LYD_NODEHINT_LIST, &node), cleanup); } else { /* create standard list instance */ - assert(p[path_idx].pred_type == LY_PATH_PREDTYPE_LIST); - LY_CHECK_GOTO(ret = lyd_create_list(schema, p[path_idx].predicates, &node), cleanup); + LY_CHECK_GOTO(ret = lyd_create_list(schema, p[path_idx].predicates, NULL, &node), cleanup); } break; case LYS_CONTAINER: @@ -1505,7 +1716,8 @@ lyd_new_path_(struct lyd_node *parent, const struct ly_ctx *ctx, const struct ly LY_CHECK_GOTO(ret = lyd_create_inner(schema, &node), cleanup); break; case LYS_LEAFLIST: - if ((options & LYD_NEW_PATH_OPAQ) && (p[path_idx].pred_type != LY_PATH_PREDTYPE_LEAFLIST)) { + if ((options & LYD_NEW_PATH_OPAQ) && + (!p[path_idx].predicates || (p[path_idx].predicates[0].type != LY_PATH_PREDTYPE_LEAFLIST))) { /* we have not checked this only for dup-inst lists, otherwise it must be opaque */ r = LY_EVALID; if (lysc_is_dup_inst_list(schema)) { @@ -1522,15 +1734,15 @@ lyd_new_path_(struct lyd_node *parent, const struct ly_ctx *ctx, const struct ly } /* get value to set */ - if (p[path_idx].pred_type == LY_PATH_PREDTYPE_LEAFLIST) { + if (p[path_idx].predicates && (p[path_idx].predicates[0].type == LY_PATH_PREDTYPE_LEAFLIST)) { val = &p[path_idx].predicates[0].value; } /* create a leaf-list instance */ if (val) { - LY_CHECK_GOTO(ret = lyd_create_term2(schema, &p[path_idx].predicates[0].value, &node), cleanup); + LY_CHECK_GOTO(ret = lyd_create_term2(schema, val, &node), cleanup); } else { - LY_CHECK_GOTO(ret = lyd_create_term(schema, value, value_len, NULL, format, NULL, LYD_HINT_DATA, + LY_CHECK_GOTO(ret = lyd_create_term(schema, value, value_len, 0, NULL, format, NULL, LYD_HINT_DATA, NULL, &node), cleanup); } break; @@ -1560,7 +1772,7 @@ lyd_new_path_(struct lyd_node *parent, const struct ly_ctx *ctx, const struct ly } /* create a leaf instance */ - LY_CHECK_GOTO(ret = lyd_create_term(schema, value, value_len, NULL, format, NULL, LYD_HINT_DATA, NULL, + LY_CHECK_GOTO(ret = lyd_create_term(schema, value, value_len, 0, NULL, format, NULL, LYD_HINT_DATA, NULL, &node), cleanup); break; case LYS_ANYDATA: @@ -1804,9 +2016,7 @@ lyd_new_implicit_tree(struct lyd_node *tree, uint32_t implicit_options, struct l } LYD_TREE_DFS_BEGIN(tree, node) { - /* skip added default nodes */ - if (((node->flags & (LYD_DEFAULT | LYD_NEW)) != (LYD_DEFAULT | LYD_NEW)) && - (node->schema->nodetype & LYD_NODE_INNER)) { + if (node->schema->nodetype & LYD_NODE_INNER) { LY_CHECK_GOTO(ret = lyd_new_implicit_r(node, lyd_node_child_p(node), NULL, NULL, &node_when, NULL, NULL, implicit_options, diff), cleanup); } @@ -1891,16 +2101,12 @@ lyd_new_implicit_module(struct lyd_node **tree, const struct lys_module *module, /* process nested nodes */ LY_LIST_FOR(*tree, root) { - /* skip added default nodes */ - if ((root->flags & (LYD_DEFAULT | LYD_NEW)) != (LYD_DEFAULT | LYD_NEW)) { - LY_CHECK_GOTO(ret = lyd_new_implicit_tree(root, implicit_options, diff ? &d : NULL), cleanup); - - if (d) { - /* merge into one diff */ - lyd_insert_sibling(*diff, d, diff); + LY_CHECK_GOTO(ret = lyd_new_implicit_tree(root, implicit_options, diff ? &d : NULL), cleanup); - d = NULL; - } + if (d) { + /* merge into one diff */ + lyd_insert_sibling(*diff, d, diff); + d = NULL; } } diff --git a/src/tree_schema.c b/src/tree_schema.c index 5c897bf..baf2c46 100644 --- a/src/tree_schema.c +++ b/src/tree_schema.c @@ -37,6 +37,7 @@ #include "parser_internal.h" #include "parser_schema.h" #include "path.h" +#include "plugins_exts.h" #include "plugins_internal.h" #include "schema_compile.h" #include "schema_compile_amend.h" @@ -164,7 +165,11 @@ lys_getnext_(const struct lysc_node *last, const struct lysc_node *parent, const const struct lysc_ext_instance *ext, uint32_t options) { const struct lysc_node *next = NULL; - ly_bool action_flag = 0, notif_flag = 0; + ly_bool action_flag = 0, notif_flag = 0, sm_flag = options & LYS_GETNEXT_WITHSCHEMAMOUNT ? 0 : 1; + LY_ARRAY_COUNT_TYPE u; + struct ly_ctx *sm_ctx = NULL; + const struct lys_module *mod; + uint32_t idx; LY_CHECK_ARG_RET(NULL, parent || module || ext, NULL); @@ -172,7 +177,7 @@ next: if (!last) { /* first call */ - /* get know where to start */ + /* learn where to start */ if (parent) { /* schema subtree */ next = last = lysc_node_child(parent); @@ -204,8 +209,29 @@ next: repeat: if (!next) { - /* possibly go back to parent */ - if (last && (last->parent != parent)) { + if (last && !sm_flag && parent && (last->module->ctx != parent->module->ctx)) { + sm_flag = 1; + + /* find the module of last */ + sm_ctx = last->module->ctx; + idx = 0; + while ((mod = ly_ctx_get_module_iter(sm_ctx, &idx))) { + if (mod == last->module) { + break; + } + } + assert(mod); + + /* get node from the next mounted module */ + while (!next && (mod = ly_ctx_get_module_iter(sm_ctx, &idx))) { + if (!mod->implemented) { + continue; + } + + next = lys_getnext(NULL, NULL, mod->compiled, options & ~LYS_GETNEXT_WITHSCHEMAMOUNT); + } + } else if (last && (last->parent != parent)) { + /* go back to parent */ last = last->parent; goto next; } else if (!action_flag) { @@ -226,6 +252,35 @@ repeat: } else { next = (struct lysc_node *)module->notifs; } + } else if (!sm_flag) { + sm_flag = 1; + if (parent) { + LY_ARRAY_FOR(parent->exts, u) { + if (!strcmp(parent->exts[u].def->name, "mount-point") && + !strcmp(parent->exts[u].def->module->name, "ietf-yang-schema-mount")) { + lyplg_ext_schema_mount_create_context(&parent->exts[u], &sm_ctx); + if (sm_ctx) { + /* some usable context created */ + break; + } + } + } + if (sm_ctx) { + /* get the first node from the first usable module */ + idx = 0; + while (!next && (mod = ly_ctx_get_module_iter(sm_ctx, &idx))) { + if (!mod->implemented) { + continue; + } + + next = lys_getnext(NULL, NULL, mod->compiled, options & ~LYS_GETNEXT_WITHSCHEMAMOUNT); + } + if (!next) { + /* no nodes found */ + ly_ctx_destroy(sm_ctx); + } + } + } } else { return NULL; } @@ -387,7 +442,7 @@ lys_find_xpath_atoms(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, struct ly_set **set) { LY_ERR ret = LY_SUCCESS; - struct lyxp_set xp_set; + struct lyxp_set xp_set = {0}; struct lyxp_expr *exp = NULL; uint32_t i; @@ -400,7 +455,9 @@ lys_find_xpath_atoms(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, ctx = ctx_node->module->ctx; } - memset(&xp_set, 0, sizeof xp_set); + /* allocate return set */ + ret = ly_set_new(set); + LY_CHECK_GOTO(ret, cleanup); /* compile expression */ ret = lyxp_expr_parse(ctx, xpath, 0, 1, &exp); @@ -410,10 +467,6 @@ lys_find_xpath_atoms(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, ret = lyxp_atomize(ctx, exp, NULL, LY_VALUE_JSON, NULL, ctx_node, ctx_node, &xp_set, options); LY_CHECK_GOTO(ret, cleanup); - /* allocate return set */ - ret = ly_set_new(set); - LY_CHECK_GOTO(ret, cleanup); - /* transform into ly_set */ (*set)->objs = malloc(xp_set.used * sizeof *(*set)->objs); LY_CHECK_ERR_GOTO(!(*set)->objs, LOGMEM(ctx); ret = LY_EMEM, cleanup); @@ -446,15 +499,15 @@ lys_find_expr_atoms(const struct lysc_node *ctx_node, const struct lys_module *c options = LYXP_SCNODE; } + /* allocate return set */ + ret = ly_set_new(set); + LY_CHECK_GOTO(ret, cleanup); + /* atomize expression */ ret = lyxp_atomize(cur_mod->ctx, expr, cur_mod, LY_VALUE_SCHEMA_RESOLVED, (void *)prefixes, ctx_node, ctx_node, &xp_set, options); LY_CHECK_GOTO(ret, cleanup); - /* allocate return set */ - ret = ly_set_new(set); - LY_CHECK_GOTO(ret, cleanup); - /* transform into ly_set */ (*set)->objs = malloc(xp_set.used * sizeof *(*set)->objs); LY_CHECK_ERR_GOTO(!(*set)->objs, LOGMEM(cur_mod->ctx); ret = LY_EMEM, cleanup); @@ -497,6 +550,10 @@ lys_find_xpath(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const ctx = ctx_node->module->ctx; } + /* allocate return set */ + ret = ly_set_new(set); + LY_CHECK_GOTO(ret, cleanup); + /* compile expression */ ret = lyxp_expr_parse(ctx, xpath, 0, 1, &exp); LY_CHECK_GOTO(ret, cleanup); @@ -505,10 +562,6 @@ lys_find_xpath(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const ret = lyxp_atomize(ctx, exp, NULL, LY_VALUE_JSON, NULL, ctx_node, ctx_node, &xp_set, options); LY_CHECK_GOTO(ret, cleanup); - /* allocate return set */ - ret = ly_set_new(set); - LY_CHECK_GOTO(ret, cleanup); - /* transform into ly_set */ (*set)->objs = malloc(xp_set.used * sizeof *(*set)->objs); LY_CHECK_ERR_GOTO(!(*set)->objs, LOGMEM(ctx); ret = LY_EMEM, cleanup); @@ -545,8 +598,8 @@ lys_find_lypath_atoms(const struct ly_path *path, struct ly_set **set) LY_ARRAY_FOR(path, u) { /* add nodes from the path */ LY_CHECK_GOTO(ret = ly_set_add(*set, (void *)path[u].node, 0, NULL), cleanup); - if (path[u].pred_type == LY_PATH_PREDTYPE_LIST) { - LY_ARRAY_FOR(path[u].predicates, v) { + LY_ARRAY_FOR(path[u].predicates, v) { + if ((path[u].predicates[v].type == LY_PATH_PREDTYPE_LIST) || (path[u].predicates[v].type == LY_PATH_PREDTYPE_LIST_VAR)) { /* add all the keys in a predicate */ LY_CHECK_GOTO(ret = ly_set_add(*set, (void *)path[u].predicates[v].key, 0, NULL), cleanup); } @@ -578,7 +631,8 @@ lys_find_path_atoms(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, } /* parse */ - ret = lyxp_expr_parse(ctx, path, strlen(path), 0, &expr); + ret = ly_path_parse(ctx, ctx_node, path, strlen(path), 0, LY_PATH_BEGIN_EITHER, LY_PATH_PREFIX_FIRST, + LY_PATH_PRED_SIMPLE, &expr); LY_CHECK_GOTO(ret, cleanup); /* compile */ @@ -599,7 +653,7 @@ LIBYANG_API_DEF const struct lysc_node * lys_find_path(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const char *path, ly_bool output) { const struct lysc_node *snode = NULL; - struct lyxp_expr *exp = NULL; + struct lyxp_expr *expr = NULL; struct ly_path *p = NULL; LY_ERR ret; uint8_t oper; @@ -612,12 +666,13 @@ lys_find_path(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const } /* parse */ - ret = lyxp_expr_parse(ctx, path, strlen(path), 0, &exp); + ret = ly_path_parse(ctx, ctx_node, path, strlen(path), 0, LY_PATH_BEGIN_EITHER, LY_PATH_PREFIX_FIRST, + LY_PATH_PRED_SIMPLE, &expr); LY_CHECK_GOTO(ret, cleanup); /* compile */ oper = output ? LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT; - ret = ly_path_compile(ctx, NULL, ctx_node, NULL, exp, oper, LY_PATH_TARGET_MANY, 0, LY_VALUE_JSON, NULL, &p); + ret = ly_path_compile(ctx, NULL, ctx_node, NULL, expr, oper, LY_PATH_TARGET_MANY, 0, LY_VALUE_JSON, NULL, &p); LY_CHECK_GOTO(ret, cleanup); /* get last node */ @@ -625,7 +680,7 @@ lys_find_path(const struct ly_ctx *ctx, const struct lysc_node *ctx_node, const cleanup: ly_path_free(ctx, p); - lyxp_expr_free(ctx, exp); + lyxp_expr_free(ctx, expr); return snode; } @@ -1457,9 +1512,10 @@ error: static LY_ERR lysp_add_internal_ietf_netconf(struct lysp_ctx *pctx, struct lysp_module *mod) { - struct lysp_ext_instance *extp; + struct lysp_ext_instance *extp, *prev_exts = mod->exts; struct lysp_stmt *stmt; struct lysp_import *imp; + uint32_t idx; /* * 1) edit-config's operation @@ -1599,10 +1655,16 @@ lysp_add_internal_ietf_netconf(struct lysp_ctx *pctx, struct lysp_module *mod) stmt->prefix_data = mod; stmt->kw = LY_STMT_TYPE; - if (LY_ARRAY_COUNT(mod->exts) == 3) { + if (!prev_exts) { /* first extension instances */ assert(pctx->main_ctx == pctx); LY_CHECK_RET(ly_set_add(&pctx->ext_inst, mod->exts, 1, NULL)); + } else { + /* replace previously stored extension instances */ + if (!ly_set_contains(&pctx->ext_inst, prev_exts, &idx)) { + LOGINT_RET(mod->mod->ctx); + } + pctx->ext_inst.objs[idx] = mod->exts; } /* create new imports for the used prefixes */ @@ -1630,9 +1692,10 @@ lysp_add_internal_ietf_netconf(struct lysp_ctx *pctx, struct lysp_module *mod) static LY_ERR lysp_add_internal_ietf_netconf_with_defaults(struct lysp_ctx *pctx, struct lysp_module *mod) { - struct lysp_ext_instance *extp; + struct lysp_ext_instance *extp, *prev_exts = mod->exts; struct lysp_stmt *stmt; struct lysp_import *imp; + uint32_t idx; /* add new extension instance */ LY_ARRAY_NEW_RET(mod->mod->ctx, mod->exts, extp, LY_EMEM); @@ -1654,10 +1717,16 @@ lysp_add_internal_ietf_netconf_with_defaults(struct lysp_ctx *pctx, struct lysp_ stmt->prefix_data = mod; stmt->kw = LY_STMT_TYPE; - if (LY_ARRAY_COUNT(mod->exts) == 1) { - /* first extension instance */ + if (!prev_exts) { + /* first extension instances */ assert(pctx->main_ctx == pctx); LY_CHECK_RET(ly_set_add(&pctx->ext_inst, mod->exts, 1, NULL)); + } else { + /* replace previously stored extension instances */ + if (!ly_set_contains(&pctx->ext_inst, prev_exts, &idx)) { + LOGINT_RET(mod->mod->ctx); + } + pctx->ext_inst.objs[idx] = mod->exts; } /* create new import for the used prefix */ @@ -1680,8 +1749,6 @@ lys_parse_in(struct ly_ctx *ctx, struct ly_in *in, LYS_INFORMAT format, struct lysp_yin_ctx *yinctx = NULL; struct lysp_ctx *pctx = NULL; struct lysf_ctx fctx = {.ctx = ctx}; - char *filename, *rev, *dot; - size_t len; ly_bool module_created = 0; assert(ctx && in && new_mods); @@ -1762,30 +1829,7 @@ lys_parse_in(struct ly_ctx *ctx, struct ly_in *in, LYS_INFORMAT format, switch (in->type) { case LY_IN_FILEPATH: - /* check that name and revision match filename */ - filename = strrchr(in->method.fpath.filepath, '/'); - if (!filename) { - filename = in->method.fpath.filepath; - } else { - filename++; - } - rev = strchr(filename, '@'); - dot = strrchr(filename, '.'); - - /* name */ - len = strlen(mod->name); - if (strncmp(filename, mod->name, len) || - ((rev && (rev != &filename[len])) || (!rev && (dot != &filename[len])))) { - LOGWRN(ctx, "File name \"%s\" does not match module name \"%s\".", filename, mod->name); - } - if (rev) { - len = dot - ++rev; - if (!mod->parsed->revs || (len != LY_REV_SIZE - 1) || strncmp(mod->parsed->revs[0].date, rev, len)) { - LOGWRN(ctx, "File name \"%s\" does not match module revision \"%s\".", filename, - mod->parsed->revs ? mod->parsed->revs[0].date : "none"); - } - } - + ly_check_module_filename(ctx, mod->name, mod->parsed->revs ? mod->parsed->revs[0].date : NULL, in->method.fpath.filepath); break; case LY_IN_FD: case LY_IN_FILE: diff --git a/src/tree_schema.h b/src/tree_schema.h index c57a0fc..e712239 100644 --- a/src/tree_schema.h +++ b/src/tree_schema.h @@ -1579,9 +1579,7 @@ struct lysc_node_choice { }; }; - struct lysc_node_case *cases; /**< list of the cases (linked list). Note that all the children of all the cases are linked each other - as siblings. Their parent pointers points to the specific case they belongs to, so distinguish the - case is simple. */ + struct lysc_node_case *cases; /**< list of all the cases (linked list) */ struct lysc_when **when; /**< list of pointers to when statements ([sized array](@ref sizedarrays)) */ struct lysc_node_case *dflt; /**< default case of the choice, only a pointer into the cases array. */ }; @@ -1887,6 +1885,14 @@ LIBYANG_API_DECL struct lysc_must *lysc_node_musts(const struct lysc_node *node) LIBYANG_API_DECL struct lysc_when **lysc_node_when(const struct lysc_node *node); /** + * @brief Get the target node of a leafref node. + * + * @param[in] node Leafref node. + * @return Leafref target, NULL on any error. + */ +LIBYANG_API_DECL const struct lysc_node *lysc_node_lref_target(const struct lysc_node *node); + +/** * @brief Callback to be called for every schema node in a DFS traversal. * * @param[in] node Current node. @@ -1971,6 +1977,8 @@ LIBYANG_API_DECL struct lysp_feature *lysp_feature_next(const struct lysp_featur #define LYS_FIND_XP_OUTPUT 0x10 /**< Search RPC/action output nodes instead of input ones. */ #define LYS_FIND_NO_MATCH_ERROR 0x40 /**< Return error if a path segment matches no nodes, otherwise only warning is printed. */ +#define LYS_FIND_SCHEMAMOUNT 0x0200 /**< Traverse also nodes from mounted modules. If any such nodes are returned, + the caller **must free** their context! */ /** @} findxpathoptions */ /** @@ -2189,6 +2197,9 @@ LIBYANG_API_DECL const struct lysc_node *lys_getnext_ext(const struct lysc_node #define LYS_GETNEXT_INTONPCONT 0x08 /**< ::lys_getnext() option to look into non-presence container, instead of returning container itself */ #define LYS_GETNEXT_OUTPUT 0x10 /**< ::lys_getnext() option to provide RPC's/action's output schema nodes instead of input schema nodes provided by default */ +#define LYS_GETNEXT_WITHSCHEMAMOUNT 0x20 /**< ::lys_getnext() option to also traverse top-level nodes of all the mounted modules + on the parent mount point but note that if any such nodes are returned, + the caller **must free** their context */ /** @} sgetnextflags */ /** diff --git a/src/tree_schema_common.c b/src/tree_schema_common.c index bbdd676..6d3b710 100644 --- a/src/tree_schema_common.c +++ b/src/tree_schema_common.c @@ -32,6 +32,7 @@ #include "in_internal.h" #include "log.h" #include "parser_schema.h" +#include "path.h" #include "schema_compile.h" #include "schema_features.h" #include "set.h" @@ -104,7 +105,7 @@ lysp_check_date(struct lysp_ctx *ctx, const char *date, size_t date_len, const c error: if (stmt) { - LOGVAL_PARSER(ctx, LY_VCODE_INVAL, date_len, date, stmt); + LOGVAL_PARSER(ctx, LY_VCODE_INVAL, (int)date_len, date, stmt); } return LY_EINVAL; } @@ -140,10 +141,10 @@ lysp_check_enum_name(struct lysp_ctx *ctx, const char *name, size_t name_len) (int)name_len, name); return LY_EVALID; } else { - for (size_t u = 0; u < name_len; ++u) { + for (uint32_t u = 0; u < name_len; ++u) { if (iscntrl(name[u])) { - LOGWRN(PARSER_CTX(ctx), "Control characters in enum name should be avoided (\"%.*s\", character number %d).", - (int)name_len, name, u + 1); + LOGWRN(PARSER_CTX(ctx), "Control characters in enum name should be avoided " + "(\"%.*s\", character number %" PRIu32 ").", (int)name_len, name, u + 1); break; } } @@ -350,19 +351,18 @@ lysp_type_find(const char *id, struct lysp_node *start_node, const struct lysp_m * @param[in,out] ctx Context to log the error. * @param[in,out] ht Hash table with top-level names. * @param[in] name Inserted top-level identifier. - * @param[in] statement The name of the statement type from which - * @p name originated (eg typedef, feature, ...). + * @param[in] statement The name of the statement type from which @p name originated (eg typedef, feature, ...). * @param[in] err_detail Optional error specification. * @return LY_ERR, but LY_EEXIST is mapped to LY_EVALID. */ static LY_ERR -lysp_check_dup_ht_insert(struct lysp_ctx *ctx, struct hash_table *ht, - const char *name, const char *statement, const char *err_detail) +lysp_check_dup_ht_insert(struct lysp_ctx *ctx, struct ly_ht *ht, const char *name, const char *statement, + const char *err_detail) { LY_ERR ret; uint32_t hash; - hash = dict_hash(name, strlen(name)); + hash = lyht_hash(name, strlen(name)); ret = lyht_insert(ht, &name, hash, NULL); if (ret == LY_EEXIST) { if (err_detail) { @@ -388,7 +388,7 @@ lysp_check_dup_ht_insert(struct lysp_ctx *ctx, struct hash_table *ht, */ static LY_ERR lysp_check_dup_typedef(struct lysp_ctx *ctx, struct lysp_node *node, const struct lysp_tpdf *tpdf, - struct hash_table *tpdfs_global) + struct ly_ht *tpdfs_global) { struct lysp_node *parent; uint32_t hash; @@ -434,7 +434,7 @@ lysp_check_dup_typedef(struct lysp_ctx *ctx, struct lysp_node *node, const struc /* check collision with the top-level typedefs */ if (node) { - hash = dict_hash(name, name_len); + hash = lyht_hash(name, name_len); if (!lyht_find(tpdfs_global, &name, hash, NULL)) { LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG, "Duplicate identifier \"%s\" of typedef statement - scoped type collide with a top-level type.", name); @@ -469,7 +469,7 @@ lysp_id_cmp(void *val1, void *val2, ly_bool UNUSED(mod), void *UNUSED(cb_data)) LY_ERR lysp_check_dup_typedefs(struct lysp_ctx *ctx, struct lysp_module *mod) { - struct hash_table *ids_global; + struct ly_ht *ids_global; const struct lysp_tpdf *typedefs; LY_ARRAY_COUNT_TYPE u, v; uint32_t i; @@ -496,7 +496,7 @@ lysp_check_dup_typedefs(struct lysp_ctx *ctx, struct lysp_module *mod) } cleanup: - lyht_free(ids_global); + lyht_free(ids_global, NULL); return ret; } @@ -528,7 +528,7 @@ lysp_grouping_match(const char *name, struct lysp_node *node) */ static LY_ERR lysp_check_dup_grouping(struct lysp_ctx *ctx, struct lysp_node *node, const struct lysp_node_grp *grp, - struct hash_table *grps_global) + struct ly_ht *grps_global) { struct lysp_node *parent; uint32_t hash; @@ -567,7 +567,7 @@ lysp_check_dup_grouping(struct lysp_ctx *ctx, struct lysp_node *node, const stru /* check collision with the top-level groupings */ if (node) { - hash = dict_hash(name, name_len); + hash = lyht_hash(name, name_len); if (!lyht_find(grps_global, &name, hash, NULL)) { LOGVAL_PARSER(ctx, LYVE_SYNTAX_YANG, "Duplicate identifier \"%s\" of grouping statement - scoped grouping collide with a top-level grouping.", name); @@ -584,7 +584,7 @@ lysp_check_dup_grouping(struct lysp_ctx *ctx, struct lysp_node *node, const stru LY_ERR lysp_check_dup_groupings(struct lysp_ctx *ctx, struct lysp_module *mod) { - struct hash_table *ids_global; + struct ly_ht *ids_global; const struct lysp_node_grp *groupings, *grp_iter; LY_ARRAY_COUNT_TYPE u; uint32_t i; @@ -610,7 +610,7 @@ lysp_check_dup_groupings(struct lysp_ctx *ctx, struct lysp_module *mod) } cleanup: - lyht_free(ids_global); + lyht_free(ids_global, NULL); return ret; } @@ -626,7 +626,7 @@ LY_ERR lysp_check_dup_features(struct lysp_ctx *ctx, struct lysp_module *mod) { LY_ARRAY_COUNT_TYPE u; - struct hash_table *ht; + struct ly_ht *ht; struct lysp_feature *f; LY_ERR ret = LY_SUCCESS; @@ -650,7 +650,7 @@ lysp_check_dup_features(struct lysp_ctx *ctx, struct lysp_module *mod) } cleanup: - lyht_free(ht); + lyht_free(ht, NULL); return ret; } @@ -658,7 +658,7 @@ LY_ERR lysp_check_dup_identities(struct lysp_ctx *ctx, struct lysp_module *mod) { LY_ARRAY_COUNT_TYPE u; - struct hash_table *ht; + struct ly_ht *ht; struct lysp_ident *i; LY_ERR ret = LY_SUCCESS; @@ -682,7 +682,7 @@ lysp_check_dup_identities(struct lysp_ctx *ctx, struct lysp_module *mod) } cleanup: - lyht_free(ht); + lyht_free(ht, NULL); return ret; } @@ -697,9 +697,8 @@ static LY_ERR lysp_load_module_check(const struct ly_ctx *ctx, struct lysp_module *mod, struct lysp_submodule *submod, void *data) { struct lysp_load_module_check_data *info = data; - const char *filename, *dot, *rev, *name; + const char *name; uint8_t latest_revision; - size_t len; struct lysp_revision *revs; name = mod ? mod->mod->name : submod->name; @@ -740,29 +739,7 @@ lysp_load_module_check(const struct ly_ctx *ctx, struct lysp_module *mod, struct } } if (info->path) { - /* check that name and revision match filename */ - filename = strrchr(info->path, '/'); - if (!filename) { - filename = info->path; - } else { - filename++; - } - /* name */ - len = strlen(name); - rev = strchr(filename, '@'); - dot = strrchr(info->path, '.'); - if (strncmp(filename, name, len) || - ((rev && (rev != &filename[len])) || (!rev && (dot != &filename[len])))) { - LOGWRN(ctx, "File name \"%s\" does not match module name \"%s\".", filename, name); - } - /* revision */ - if (rev) { - len = dot - ++rev; - if (!revs || (len != LY_REV_SIZE - 1) || strncmp(revs[0].date, rev, len)) { - LOGWRN(ctx, "File name \"%s\" does not match module revision \"%s\".", filename, - revs ? revs[0].date : "none"); - } - } + ly_check_module_filename(ctx, name, revs ? revs[0].date : NULL, info->path); } return LY_SUCCESS; } @@ -921,7 +898,7 @@ lys_get_module_without_revision(struct ly_ctx *ctx, const char *name) struct lys_module *mod, *mod_impl; uint32_t index; - /* Try to find module with LYS_MOD_IMPORTED_REV flag. */ + /* try to find module with LYS_MOD_IMPORTED_REV flag */ index = 0; while ((mod = ly_ctx_get_module_iter(ctx, &index))) { if (!strcmp(mod->name, name) && (mod->latest_revision & LYS_MOD_IMPORTED_REV)) { @@ -929,17 +906,19 @@ lys_get_module_without_revision(struct ly_ctx *ctx, const char *name) } } - /* Try to find the implemented module. */ + /* try to find the implemented module */ mod_impl = ly_ctx_get_module_implemented(ctx, name); - if (mod && mod_impl) { - LOGVRB("Implemented module \"%s@%s\" is not used for import, revision \"%s\" is imported instead.", - mod_impl->name, mod_impl->revision, mod->revision); + if (mod) { + if (mod_impl && (mod != mod_impl)) { + LOGVRB("Implemented module \"%s@%s\" is not used for import, revision \"%s\" is imported instead.", + mod_impl->name, mod_impl->revision, mod->revision); + } return mod; } else if (mod_impl) { return mod_impl; } - /* Try to find the latest module in the current context. */ + /* try to find the latest module in the current context */ mod = ly_ctx_get_module_latest(ctx, name); return mod; @@ -949,10 +928,8 @@ lys_get_module_without_revision(struct ly_ctx *ctx, const char *name) * @brief Check if a circular dependency exists between modules. * * @param[in] ctx libyang context for log an error. - * @param[in,out] mod Examined module which is set to NULL - * if the circular dependency is detected. - * @return LY_SUCCESS if no circular dependecy is detected, - * otherwise LY_EVALID. + * @param[in,out] mod Examined module which is set to NULL if the circular dependency is detected. + * @return LY_SUCCESS if no circular dependecy is detected, otherwise LY_EVALID. */ static LY_ERR lys_check_circular_dependency(struct ly_ctx *ctx, struct lys_module **mod) @@ -1184,7 +1161,6 @@ lysp_inject_submodule(struct lysp_ctx *pctx, struct lysp_include *inc) DUP_STRING_RET(PARSER_CTX(pctx), inc->name, inc_new->name); DUP_STRING_RET(PARSER_CTX(pctx), inc->dsc, inc_new->dsc); DUP_STRING_RET(PARSER_CTX(pctx), inc->ref, inc_new->ref); - /* TODO duplicate extensions */ memcpy(inc_new->rev, inc->rev, LY_REV_SIZE); inc_new->injected = 1; } @@ -1826,6 +1802,36 @@ lysc_node_when(const struct lysc_node *node) } } +LIBYANG_API_DEF const struct lysc_node * +lysc_node_lref_target(const struct lysc_node *node) +{ + struct lysc_type_leafref *lref; + struct ly_path *p; + const struct lysc_node *target; + + if (!node || !(node->nodetype & LYD_NODE_TERM)) { + return NULL; + } + + lref = (struct lysc_type_leafref *)((struct lysc_node_leaf *)node)->type; + if (lref->basetype != LY_TYPE_LEAFREF) { + return NULL; + } + + /* compile the path */ + if (ly_path_compile_leafref(node->module->ctx, node, NULL, lref->path, + (node->flags & LYS_IS_OUTPUT) ? LY_PATH_OPER_OUTPUT : LY_PATH_OPER_INPUT, LY_PATH_TARGET_MANY, + LY_VALUE_SCHEMA_RESOLVED, lref->prefixes, &p)) { + return NULL; + } + + /* get the target node */ + target = p[LY_ARRAY_COUNT(p) - 1].node; + ly_path_free(node->module->ctx, p); + + return target; +} + enum ly_stmt lysp_match_kw(struct ly_in *in, uint64_t *indent) { @@ -2615,3 +2621,41 @@ lys_stmt_flags(enum ly_stmt stmt) return 0; } + +void +ly_check_module_filename(const struct ly_ctx *ctx, const char *name, const char *revision, const char *filename) +{ + const char *basename, *rev, *dot; + size_t len; + + /* check that name and revision match filename */ + basename = strrchr(filename, '/'); +#ifdef _WIN32 + const char *backslash = strrchr(filename, '\\'); + + if (!basename || (basename && backslash && (backslash > basename))) { + basename = backslash; + } +#endif + if (!basename) { + basename = filename; + } else { + basename++; /* leading slash */ + } + rev = strchr(basename, '@'); + dot = strrchr(basename, '.'); + + /* name */ + len = strlen(name); + if (strncmp(basename, name, len) || + ((rev && (rev != &basename[len])) || (!rev && (dot != &basename[len])))) { + LOGWRN(ctx, "File name \"%s\" does not match module name \"%s\".", basename, name); + } + if (rev) { + len = dot - ++rev; + if (!revision || (len != LY_REV_SIZE - 1) || strncmp(revision, rev, len)) { + LOGWRN(ctx, "File name \"%s\" does not match module revision \"%s\".", basename, + revision ? revision : "none"); + } + } +} diff --git a/src/tree_schema_internal.h b/src/tree_schema_internal.h index 95dee0b..0a2ad3f 100644 --- a/src/tree_schema_internal.h +++ b/src/tree_schema_internal.h @@ -732,4 +732,14 @@ uint8_t lys_stmt_flags(enum ly_stmt stmt); */ LY_ERR lyplg_ext_get_storage_p(const struct lysc_ext_instance *ext, int stmt, const void ***storage_p); +/** + * @brief Warning if the filename does not match the expected module name and version + * + * @param[in] ctx Context for logging + * @param[in] name Expected module name + * @param[in] revision Expected module revision, or NULL if not to be checked + * @param[in] filename File path to be checked + */ +void ly_check_module_filename(const struct ly_ctx *ctx, const char *name, const char *revision, const char *filename); + #endif /* LY_TREE_SCHEMA_INTERNAL_H_ */ diff --git a/src/validation.c b/src/validation.c index 6db020a..35136ad 100644 --- a/src/validation.c +++ b/src/validation.c @@ -3,7 +3,7 @@ * @author Michal Vasko <mvasko@cesnet.cz> * @brief Validation * - * Copyright (c) 2019 - 2022 CESNET, z.s.p.o. + * Copyright (c) 2019 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -40,6 +40,22 @@ #include "tree_schema_internal.h" #include "xpath.h" +/** + * @brief Check validation error taking into account multi-error validation. + * + * @param[in] r Local return value. + * @param[in] err_cmd Command to perform on any error. + * @param[in] val_opts Validation options. + * @param[in] label Label to go to on fatal error. + */ +#define LY_VAL_ERR_GOTO(r, err_cmd, val_opts, label) \ + if (r) { \ + err_cmd; \ + if ((r != LY_EVALID) || !(val_opts & LYD_VALIDATE_MULTI_ERROR)) { \ + goto label; \ + } \ + } + LY_ERR lyd_val_diff_add(const struct lyd_node *node, enum lyd_diff_op op, struct lyd_node **diff) { @@ -126,7 +142,7 @@ static LY_ERR lyd_validate_node_when(const struct lyd_node *tree, const struct lyd_node *node, const struct lysc_node *schema, uint32_t xpath_options, const struct lysc_when **disabled) { - LY_ERR ret; + LY_ERR r; const struct lyd_node *ctx_node; struct lyxp_set xp_set; LY_ARRAY_COUNT_TYPE u; @@ -152,12 +168,12 @@ lyd_validate_node_when(const struct lyd_node *tree, const struct lyd_node *node, /* evaluate when */ memset(&xp_set, 0, sizeof xp_set); - ret = lyxp_eval(LYD_CTX(node), when->cond, schema->module, LY_VALUE_SCHEMA_RESOLVED, when->prefixes, + r = lyxp_eval(LYD_CTX(node), when->cond, schema->module, LY_VALUE_SCHEMA_RESOLVED, when->prefixes, ctx_node, ctx_node, tree, NULL, &xp_set, LYXP_SCHEMA | xpath_options); lyxp_set_cast(&xp_set, LYXP_SET_BOOLEAN); /* return error or LY_EINCOMPLETE for dependant unresolved when */ - LY_CHECK_RET(ret); + LY_CHECK_RET(r); if (!xp_set.val.bln) { /* false when */ @@ -173,6 +189,62 @@ lyd_validate_node_when(const struct lyd_node *tree, const struct lyd_node *node, } /** + * @brief Properly delete a node as part of auto-delete validation tasks. + * + * @param[in,out] first First sibling, is updated if needed. + * @param[in] del Node instance to delete. + * @param[in] mod Module of the siblings, NULL for nested siblings. + * @param[in] np_cont_diff Whether to put NP container into diff or only its children. + * @param[in,out] node Optional current iteration node, update it if it is deleted. + * @param[in,out] node_when Optional set with nodes with "when" conditions, may be removed from. + * @param[in,out] diff Validation diff. + * @return 1 if @p node auto-deleted and updated to its next sibling. + * @return 0 if @p node was not auto-deleted. + */ +static ly_bool +lyd_validate_autodel_node_del(struct lyd_node **first, struct lyd_node *del, const struct lys_module *mod, + int np_cont_diff, struct lyd_node **node, struct ly_set *node_types, struct lyd_node **diff) +{ + struct lyd_node *iter; + ly_bool node_autodel = 0; + uint32_t idx; + + /* update pointers */ + lyd_del_move_root(first, del, mod); + if (node && (del == *node)) { + *node = (*node)->next; + node_autodel = 1; + } + + if (diff) { + /* add into diff */ + if (!np_cont_diff && (del->schema->nodetype == LYS_CONTAINER) && !(del->schema->flags & LYS_PRESENCE)) { + /* we do not want to track NP container changes, but remember any removed children */ + LY_LIST_FOR(lyd_child(del), iter) { + lyd_val_diff_add(iter, LYD_DIFF_OP_DELETE, diff); + } + } else { + lyd_val_diff_add(del, LYD_DIFF_OP_DELETE, diff); + } + } + + if (node_types && node_types->count) { + /* remove from node_types set */ + LYD_TREE_DFS_BEGIN(del, iter) { + if (ly_set_contains(node_types, iter, &idx)) { + ly_set_rm_index(node_types, idx, NULL); + } + LYD_TREE_DFS_END(del, iter); + } + } + + /* free */ + lyd_free_tree(del); + + return node_autodel; +} + +/** * @brief Evaluate when conditions of collected unres nodes. * * @param[in,out] tree Data tree, is updated if some nodes are autodeleted. @@ -180,6 +252,7 @@ lyd_validate_node_when(const struct lyd_node *tree, const struct lyd_node *node, * If set, it is expected @p tree should point to the first node of @p mod. Otherwise it will simply be * the first top-level sibling. * @param[in] node_when Set with nodes with "when" conditions. + * @param[in] val_opts Validation options. * @param[in] xpath_options Additional XPath options to use. * @param[in,out] node_types Set with nodes with unresolved types, remove any with false "when" parents. * @param[in,out] diff Validation diff. @@ -187,13 +260,13 @@ lyd_validate_node_when(const struct lyd_node *tree, const struct lyd_node *node, * @return LY_ERR value on error. */ static LY_ERR -lyd_validate_unres_when(struct lyd_node **tree, const struct lys_module *mod, struct ly_set *node_when, +lyd_validate_unres_when(struct lyd_node **tree, const struct lys_module *mod, struct ly_set *node_when, uint32_t val_opts, uint32_t xpath_options, struct ly_set *node_types, struct lyd_node **diff) { - LY_ERR rc, r; - uint32_t i, idx; + LY_ERR rc = LY_SUCCESS, r; + uint32_t i; const struct lysc_when *disabled; - struct lyd_node *node = NULL, *elem; + struct lyd_node *node = NULL; if (!node_when->count) { return LY_SUCCESS; @@ -212,32 +285,15 @@ lyd_validate_unres_when(struct lyd_node **tree, const struct lys_module *mod, st /* when false */ if (node->flags & LYD_WHEN_TRUE) { /* autodelete */ - lyd_del_move_root(tree, node, mod); - if (diff) { - /* add into diff */ - LY_CHECK_GOTO(rc = lyd_val_diff_add(node, LYD_DIFF_OP_DELETE, diff), error); - } - - /* remove from node types set, if present */ - if (node_types && node_types->count) { - LYD_TREE_DFS_BEGIN(node, elem) { - /* only term nodes with a validation callback can be in node_types */ - if ((elem->schema->nodetype & LYD_NODE_TERM) && - ((struct lysc_node_leaf *)elem->schema)->type->plugin->validate && - ly_set_contains(node_types, elem, &idx)) { - LY_CHECK_GOTO(rc = ly_set_rm_index(node_types, idx, NULL), error); - } - LYD_TREE_DFS_END(node, elem); - } - } - - /* free */ - lyd_free_tree(node); + lyd_validate_autodel_node_del(tree, node, mod, 1, NULL, node_types, diff); + } else if (val_opts & LYD_VALIDATE_OPERATIONAL) { + /* only a warning */ + LOGWRN(LYD_CTX(node), "When condition \"%s\" not satisfied.", disabled->cond->expr); } else { /* invalid data */ LOGVAL(LYD_CTX(node), LY_VCODE_NOWHEN, disabled->cond->expr); - rc = LY_EVALID; - goto error; + r = LY_EVALID; + LY_VAL_ERR_GOTO(r, rc = r, val_opts, error); } } else { /* when true */ @@ -248,14 +304,13 @@ lyd_validate_unres_when(struct lyd_node **tree, const struct lys_module *mod, st ly_set_rm_index_ordered(node_when, i, NULL); } else if (r != LY_EINCOMPLETE) { /* error */ - rc = r; - goto error; + LY_VAL_ERR_GOTO(r, rc = r, val_opts, error); } LOG_LOCBACK(1, 1, 0, 0); } while (i); - return LY_SUCCESS; + return rc; error: LOG_LOCBACK(1, 1, 0, 0); @@ -267,7 +322,7 @@ lyd_validate_unres(struct lyd_node **tree, const struct lys_module *mod, enum ly uint32_t when_xp_opts, struct ly_set *node_types, struct ly_set *meta_types, struct ly_set *ext_node, struct ly_set *ext_val, uint32_t val_opts, struct lyd_node **diff) { - LY_ERR ret = LY_SUCCESS; + LY_ERR r, rc = LY_SUCCESS; uint32_t i; if (ext_val && ext_val->count) { @@ -279,8 +334,8 @@ lyd_validate_unres(struct lyd_node **tree, const struct lys_module *mod, enum ly struct lyd_ctx_ext_val *ext_v = ext_val->objs[i]; /* validate extension data */ - ret = ext_v->ext->def->plugin->validate(ext_v->ext, ext_v->sibling, *tree, data_type, val_opts, diff); - LY_CHECK_RET(ret); + r = ext_v->ext->def->plugin->validate(ext_v->ext, ext_v->sibling, *tree, data_type, val_opts, diff); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); /* remove this item from the set */ ly_set_rm_index(ext_val, i, free); @@ -296,8 +351,8 @@ lyd_validate_unres(struct lyd_node **tree, const struct lys_module *mod, enum ly struct lyd_ctx_ext_node *ext_n = ext_node->objs[i]; /* validate the node */ - ret = ext_n->ext->def->plugin->node(ext_n->ext, ext_n->node, val_opts); - LY_CHECK_RET(ret); + r = ext_n->ext->def->plugin->node(ext_n->ext, ext_n->node, val_opts); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); /* remove this item from the set */ ly_set_rm_index(ext_node, i, free); @@ -310,12 +365,14 @@ lyd_validate_unres(struct lyd_node **tree, const struct lys_module *mod, enum ly do { prev_count = node_when->count; - LY_CHECK_RET(lyd_validate_unres_when(tree, mod, node_when, when_xp_opts, node_types, diff)); + r = lyd_validate_unres_when(tree, mod, node_when, val_opts, when_xp_opts, node_types, diff); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); + /* there must have been some when conditions resolved */ } while (prev_count > node_when->count); /* there could have been no cyclic when dependencies, checked during compilation */ - assert(!node_when->count); + assert(!node_when->count || ((rc == LY_EVALID) && (val_opts & LYD_VALIDATE_MULTI_ERROR))); } if (node_types && node_types->count) { @@ -329,9 +386,9 @@ lyd_validate_unres(struct lyd_node **tree, const struct lys_module *mod, enum ly /* resolve the value of the node */ LOG_LOCSET(NULL, &node->node, NULL, NULL); - ret = lyd_value_validate_incomplete(LYD_CTX(node), type, &node->value, &node->node, *tree); + r = lyd_value_validate_incomplete(LYD_CTX(node), type, &node->value, &node->node, *tree); LOG_LOCBACK(0, 1, 0, 0); - LY_CHECK_RET(ret); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); /* remove this node from the set */ ly_set_rm_index(node_types, i, NULL); @@ -349,15 +406,16 @@ lyd_validate_unres(struct lyd_node **tree, const struct lys_module *mod, enum ly /* validate and store the value of the metadata */ lyplg_ext_get_storage(meta->annotation, LY_STMT_TYPE, sizeof type, (const void **)&type); - ret = lyd_value_validate_incomplete(LYD_CTX(meta->parent), type, &meta->value, meta->parent, *tree); - LY_CHECK_RET(ret); + r = lyd_value_validate_incomplete(LYD_CTX(meta->parent), type, &meta->value, meta->parent, *tree); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); /* remove this attr from the set */ ly_set_rm_index(meta_types, i, NULL); } while (i); } - return ret; +cleanup: + return rc; } /** @@ -365,12 +423,13 @@ lyd_validate_unres(struct lyd_node **tree, const struct lys_module *mod, enum ly * * @param[in] first First sibling to search in. * @param[in] node Data node instance to check. + * @param[in] val_opts Validation options. * @return LY_ERR value. */ static LY_ERR -lyd_validate_duplicates(const struct lyd_node *first, const struct lyd_node *node) +lyd_validate_duplicates(const struct lyd_node *first, const struct lyd_node *node, uint32_t val_opts) { - struct lyd_node **match_p; + struct lyd_node **match_p, *match; ly_bool fail = 0; assert(node->flags & LYD_NEW); @@ -383,7 +442,12 @@ lyd_validate_duplicates(const struct lyd_node *first, const struct lyd_node *nod /* find exactly the same next instance using hashes if possible */ if (node->parent && node->parent->children_ht) { - if (!lyht_find_next(node->parent->children_ht, &node, node->hash, (void **)&match_p)) { + lyd_find_sibling_first(first, node, &match); + assert(match); + + if (match != node) { + fail = 1; + } else if (!lyht_find_next(node->parent->children_ht, &node, node->hash, (void **)&match_p)) { fail = 1; } } else { @@ -405,8 +469,17 @@ lyd_validate_duplicates(const struct lyd_node *first, const struct lyd_node *nod } if (fail) { - LOGVAL(node->schema->module->ctx, LY_VCODE_DUP, node->schema->name); - return LY_EVALID; + if ((node->schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) && (val_opts & LYD_VALIDATE_OPERATIONAL)) { + /* only a warning */ + LOG_LOCSET(NULL, node, NULL, NULL); + LOGWRN(node->schema->module->ctx, "Duplicate instance of \"%s\".", node->schema->name); + LOG_LOCBACK(0, 1, 0, 0); + } else { + LOG_LOCSET(NULL, node, NULL, NULL); + LOGVAL(node->schema->module->ctx, LY_VCODE_DUP, node->schema->name); + LOG_LOCBACK(0, 1, 0, 0); + return LY_EVALID; + } } return LY_SUCCESS; } @@ -530,45 +603,6 @@ lyd_val_has_default(const struct lysc_node *schema) } /** - * @brief Properly delete a node as part of auto-delete validation tasks. - * - * @param[in,out] first First sibling, is updated if needed. - * @param[in] del Node instance to delete. - * @param[in] mod Module of the siblings, NULL for nested siblings. - * @param[in,out] node Current iteration node, update it if it is deleted. - * @param[in,out] diff Validation diff. - * @return 1 if @p node auto-deleted and updated to its next sibling. - * @return 0 if @p node was not auto-deleted. - */ -static ly_bool -lyd_validate_autodel_node_del(struct lyd_node **first, struct lyd_node *del, const struct lys_module *mod, - struct lyd_node **node, struct lyd_node **diff) -{ - struct lyd_node *iter; - ly_bool node_autodel = 0; - - lyd_del_move_root(first, del, mod); - if (del == *node) { - *node = (*node)->next; - node_autodel = 1; - } - if (diff) { - /* add into diff */ - if ((del->schema->nodetype == LYS_CONTAINER) && !(del->schema->flags & LYS_PRESENCE)) { - /* we do not want to track NP container changes, but remember any removed children */ - LY_LIST_FOR(lyd_child(del), iter) { - lyd_val_diff_add(iter, LYD_DIFF_OP_DELETE, diff); - } - } else { - lyd_val_diff_add(del, LYD_DIFF_OP_DELETE, diff); - } - } - lyd_free_tree(del); - - return node_autodel; -} - -/** * @brief Auto-delete leaf-list default instances to prevent validation errors. * * @param[in,out] first First sibling to search in, is updated if needed. @@ -606,7 +640,7 @@ lyd_validate_autodel_leaflist_dflt(struct lyd_node **first, struct lyd_node **no LYD_LIST_FOR_INST_SAFE(*first, schema, next, iter) { if (iter->flags & LYD_DEFAULT) { /* default instance found, remove it */ - if (lyd_validate_autodel_node_del(first, iter, mod, node, diff)) { + if (lyd_validate_autodel_node_del(first, iter, mod, 0, node, NULL, diff)) { node_autodel = 1; } } @@ -651,7 +685,7 @@ lyd_validate_autodel_cont_leaf_dflt(struct lyd_node **first, struct lyd_node **n LYD_LIST_FOR_INST_SAFE(*first, schema, next, iter) { if (iter->flags & LYD_DEFAULT) { /* default instance, remove it */ - if (lyd_validate_autodel_node_del(first, iter, mod, node, diff)) { + if (lyd_validate_autodel_node_del(first, iter, mod, 0, node, NULL, diff)) { node_autodel = 1; } } @@ -661,7 +695,7 @@ lyd_validate_autodel_cont_leaf_dflt(struct lyd_node **first, struct lyd_node **n LYD_LIST_FOR_INST(*first, schema, iter) { if ((iter->flags & LYD_DEFAULT) && !(iter->flags & LYD_NEW)) { /* old default instance, remove it */ - if (lyd_validate_autodel_node_del(first, iter, mod, node, diff)) { + if (lyd_validate_autodel_node_del(first, iter, mod, 0, node, NULL, diff)) { node_autodel = 1; } break; @@ -719,7 +753,7 @@ lyd_validate_autodel_case_dflt(struct lyd_node **first, struct lyd_node **node, if (!iter) { /* there are only default nodes of the case meaning it does not exist and neither should any default nodes * of the case, remove this one default node */ - if (lyd_validate_autodel_node_del(first, *node, mod, node, diff)) { + if (lyd_validate_autodel_node_del(first, *node, mod, 0, node, NULL, diff)) { node_autodel = 1; } } @@ -733,40 +767,46 @@ lyd_validate_autodel_case_dflt(struct lyd_node **first, struct lyd_node **node, * @param[in,out] first First sibling. * @param[in] sparent Schema parent of the siblings, NULL for top-level siblings. * @param[in] mod Module of the siblings, NULL for nested siblings. + * @param[in] val_opts Validation options. * @param[in,out] diff Validation diff. * @return LY_ERR value. */ static LY_ERR lyd_validate_choice_r(struct lyd_node **first, const struct lysc_node *sparent, const struct lys_module *mod, - struct lyd_node **diff) + uint32_t val_opts, struct lyd_node **diff) { + LY_ERR r, rc = LY_SUCCESS; const struct lysc_node *snode = NULL; while (*first && (snode = lys_getnext(snode, sparent, mod ? mod->compiled : NULL, LYS_GETNEXT_WITHCHOICE))) { /* check case duplicites */ if (snode->nodetype == LYS_CHOICE) { - LY_CHECK_RET(lyd_validate_cases(first, mod, (struct lysc_node_choice *)snode, diff)); + r = lyd_validate_cases(first, mod, (struct lysc_node_choice *)snode, diff); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); /* check for nested choice */ - LY_CHECK_RET(lyd_validate_choice_r(first, snode, mod, diff)); + r = lyd_validate_choice_r(first, snode, mod, val_opts, diff); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); } } - return LY_SUCCESS; +cleanup: + return rc; } LY_ERR lyd_validate_new(struct lyd_node **first, const struct lysc_node *sparent, const struct lys_module *mod, - struct lyd_node **diff) + uint32_t val_opts, struct lyd_node **diff) { - LY_ERR r; + LY_ERR r, rc = LY_SUCCESS; struct lyd_node *node; const struct lysc_node *last_dflt_schema = NULL; assert(first && (sparent || mod)); /* validate choices */ - LY_CHECK_RET(lyd_validate_choice_r(first, sparent, mod, diff)); + r = lyd_validate_choice_r(first, sparent, mod, val_opts, diff); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); node = *first; while (node) { @@ -797,10 +837,8 @@ lyd_validate_new(struct lyd_node **first, const struct lysc_node *sparent, const if (node->flags & LYD_NEW) { /* then check new node instance duplicities */ - LOG_LOCSET(NULL, node, NULL, NULL); - r = lyd_validate_duplicates(*first, node); - LOG_LOCBACK(0, 1, 0, 0); - LY_CHECK_RET(r); + r = lyd_validate_duplicates(*first, node, val_opts); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); /* this node is valid */ node->flags &= ~LYD_NEW; @@ -817,7 +855,8 @@ lyd_validate_new(struct lyd_node **first, const struct lysc_node *sparent, const node = node->next; } - return LY_SUCCESS; +cleanup: + return rc; } /** @@ -833,7 +872,7 @@ static LY_ERR lyd_validate_dummy_when(const struct lyd_node *first, const struct lyd_node *parent, const struct lysc_node *snode, const struct lysc_when **disabled) { - LY_ERR ret = LY_SUCCESS; + LY_ERR rc = LY_SUCCESS; struct lyd_node *tree, *dummy = NULL; uint32_t xp_opts; @@ -850,8 +889,8 @@ lyd_validate_dummy_when(const struct lyd_node *first, const struct lyd_node *par } /* create dummy opaque node */ - ret = lyd_new_opaq((struct lyd_node *)parent, snode->module->ctx, snode->name, NULL, NULL, snode->module->name, &dummy); - LY_CHECK_GOTO(ret, cleanup); + rc = lyd_new_opaq((struct lyd_node *)parent, snode->module->ctx, snode->name, NULL, NULL, snode->module->name, &dummy); + LY_CHECK_GOTO(rc, cleanup); /* connect it if needed */ if (!parent) { @@ -871,20 +910,20 @@ lyd_validate_dummy_when(const struct lyd_node *first, const struct lyd_node *par } /* evaluate all when */ - ret = lyd_validate_node_when(tree, dummy, snode, xp_opts, disabled); - if (ret == LY_EINCOMPLETE) { + rc = lyd_validate_node_when(tree, dummy, snode, xp_opts, disabled); + if (rc == LY_EINCOMPLETE) { /* all other when must be resolved by now */ LOGINT(snode->module->ctx); - ret = LY_EINT; + rc = LY_EINT; goto cleanup; - } else if (ret) { + } else if (rc) { /* error */ goto cleanup; } cleanup: lyd_free_tree(dummy); - return ret; + return rc; } /** @@ -893,10 +932,12 @@ cleanup: * @param[in] first First sibling to search in. * @param[in] parent Data parent. * @param[in] snode Schema node to validate. + * @param[in] val_opts Validation options. * @return LY_ERR value. */ static LY_ERR -lyd_validate_mandatory(const struct lyd_node *first, const struct lyd_node *parent, const struct lysc_node *snode) +lyd_validate_mandatory(const struct lyd_node *first, const struct lyd_node *parent, const struct lysc_node *snode, + uint32_t val_opts) { const struct lysc_when *disabled; @@ -921,13 +962,26 @@ lyd_validate_mandatory(const struct lyd_node *first, const struct lyd_node *pare } if (!disabled) { - /* node instance not found */ - if (snode->nodetype == LYS_CHOICE) { - LOGVAL_APPTAG(snode->module->ctx, "missing-choice", LY_VCODE_NOMAND_CHOIC, snode->name); + if (val_opts & LYD_VALIDATE_OPERATIONAL) { + /* only a warning */ + LOG_LOCSET(parent ? NULL : snode, parent, NULL, NULL); + if (snode->nodetype == LYS_CHOICE) { + LOGWRN(snode->module->ctx, "Mandatory choice \"%s\" data do not exist.", snode->name); + } else { + LOGWRN(snode->module->ctx, "Mandatory node \"%s\" instance does not exist.", snode->name); + } + LOG_LOCBACK(parent ? 0 : 1, parent ? 1 : 0, 0, 0); } else { - LOGVAL(snode->module->ctx, LY_VCODE_NOMAND, snode->name); + /* node instance not found */ + LOG_LOCSET(parent ? NULL : snode, parent, NULL, NULL); + if (snode->nodetype == LYS_CHOICE) { + LOGVAL_APPTAG(snode->module->ctx, "missing-choice", LY_VCODE_NOMAND_CHOIC, snode->name); + } else { + LOGVAL(snode->module->ctx, LY_VCODE_NOMAND, snode->name); + } + LOG_LOCBACK(parent ? 0 : 1, parent ? 1 : 0, 0, 0); + return LY_EVALID; } - return LY_EVALID; } return LY_SUCCESS; @@ -941,16 +995,16 @@ lyd_validate_mandatory(const struct lyd_node *first, const struct lyd_node *pare * @param[in] snode Schema node to validate. * @param[in] min Minimum number of elements, 0 for no restriction. * @param[in] max Max number of elements, 0 for no restriction. + * @param[in] val_opts Validation options. * @return LY_ERR value. */ static LY_ERR lyd_validate_minmax(const struct lyd_node *first, const struct lyd_node *parent, const struct lysc_node *snode, - uint32_t min, uint32_t max) + uint32_t min, uint32_t max, uint32_t val_opts) { uint32_t count = 0; struct lyd_node *iter; const struct lysc_when *disabled; - ly_bool invalid_instance = 0; assert(min || max); @@ -967,8 +1021,6 @@ lyd_validate_minmax(const struct lyd_node *first, const struct lyd_node *parent, } if (max && (count > max)) { /* not satisifed */ - LOG_LOCSET(NULL, iter, NULL, NULL); - invalid_instance = 1; break; } } @@ -982,20 +1034,42 @@ lyd_validate_minmax(const struct lyd_node *first, const struct lyd_node *parent, LY_CHECK_RET(lyd_validate_dummy_when(first, parent, snode, &disabled)); } - if (!disabled) { - LOGVAL_APPTAG(snode->module->ctx, "too-few-elements", LY_VCODE_NOMIN, snode->name); - goto failure; + if (disabled) { + /* satisfied */ + min = 0; } - } else if (max && (count > max)) { - LOGVAL_APPTAG(snode->module->ctx, "too-many-elements", LY_VCODE_NOMAX, snode->name); - goto failure; + } + if (max && (count <= max)) { + /* satisfied */ + max = 0; } + if (min) { + if (val_opts & LYD_VALIDATE_OPERATIONAL) { + /* only a warning */ + LOG_LOCSET(snode, NULL, NULL, NULL); + LOGWRN(snode->module->ctx, "Too few \"%s\" instances.", snode->name); + LOG_LOCBACK(1, 0, 0, 0); + } else { + LOG_LOCSET(snode, NULL, NULL, NULL); + LOGVAL_APPTAG(snode->module->ctx, "too-few-elements", LY_VCODE_NOMIN, snode->name); + LOG_LOCBACK(1, 0, 0, 0); + return LY_EVALID; + } + } else if (max) { + if (val_opts & LYD_VALIDATE_OPERATIONAL) { + /* only a warning */ + LOG_LOCSET(NULL, iter, NULL, NULL); + LOGWRN(snode->module->ctx, "Too many \"%s\" instances.", snode->name); + LOG_LOCBACK(0, 1, 0, 0); + } else { + LOG_LOCSET(NULL, iter, NULL, NULL); + LOGVAL_APPTAG(snode->module->ctx, "too-many-elements", LY_VCODE_NOMAX, snode->name); + LOG_LOCBACK(0, 1, 0, 0); + return LY_EVALID; + } + } return LY_SUCCESS; - -failure: - LOG_LOCBACK(0, invalid_instance, 0, 0); - return LY_EVALID; } /** @@ -1035,11 +1109,17 @@ lyd_val_uniq_find_leaf(const struct lysc_node_leaf *uniq_leaf, const struct lyd_ } /** + * @brief Unique list validation callback argument. + */ +struct lyd_val_uniq_arg { + LY_ARRAY_COUNT_TYPE action; /**< Action to perform - 0 to compare all uniques, n to compare only n-th unique. */ + uint32_t val_opts; /**< Validation options. */ +}; + +/** * @brief Callback for comparing 2 list unique leaf values. * * Implementation of ::lyht_value_equal_cb. - * - * @param[in] cb_data 0 to compare all uniques, n to compare only n-th unique. */ static ly_bool lyd_val_uniq_list_equal(void *val1_p, void *val2_p, ly_bool UNUSED(mod), void *cb_data) @@ -1049,13 +1129,14 @@ lyd_val_uniq_list_equal(void *val1_p, void *val2_p, ly_bool UNUSED(mod), void *c struct lyd_node *diter, *first, *second; struct lyd_value *val1, *val2; char *path1, *path2, *uniq_str, *ptr; - LY_ARRAY_COUNT_TYPE u, v, action; + LY_ARRAY_COUNT_TYPE u, v; + struct lyd_val_uniq_arg *arg = cb_data; + const uint32_t uniq_err_msg_size = 1024; assert(val1_p && val2_p); first = *((struct lyd_node **)val1_p); second = *((struct lyd_node **)val2_p); - action = (uintptr_t)cb_data; assert(first && (first->schema->nodetype == LYS_LIST)); assert(second && (second->schema == first->schema)); @@ -1065,8 +1146,8 @@ lyd_val_uniq_list_equal(void *val1_p, void *val2_p, ly_bool UNUSED(mod), void *c slist = (struct lysc_node_list *)first->schema; /* compare unique leaves */ - if (action > 0) { - u = action - 1; + if (arg->action > 0) { + u = arg->action - 1; if (u < LY_ARRAY_COUNT(slist->uniques)) { goto uniquecheck; } @@ -1098,13 +1179,12 @@ uniquecheck: } } if (v && (v == LY_ARRAY_COUNT(slist->uniques[u]))) { - /* all unique leafs are the same in this set, create this nice error */ + /* all unique leaves are the same in this set, create this nice error */ path1 = lyd_path(first, LYD_PATH_STD, NULL, 0); path2 = lyd_path(second, LYD_PATH_STD, NULL, 0); /* use buffer to rebuild the unique string */ -#define UNIQ_BUF_SIZE 1024 - uniq_str = malloc(UNIQ_BUF_SIZE); + uniq_str = malloc(uniq_err_msg_size); uniq_str[0] = '\0'; ptr = uniq_str; LY_ARRAY_FOR(slist->uniques[u], v) { @@ -1113,7 +1193,7 @@ uniquecheck: ++ptr; } ptr = lysc_path_until((struct lysc_node *)slist->uniques[u][v], &slist->node, LYSC_PATH_LOG, - ptr, UNIQ_BUF_SIZE - (ptr - uniq_str)); + ptr, uniq_err_msg_size - (ptr - uniq_str)); if (!ptr) { /* path will be incomplete, whatever */ break; @@ -1122,18 +1202,24 @@ uniquecheck: ptr += strlen(ptr); } LOG_LOCSET(NULL, second, NULL, NULL); - LOGVAL_APPTAG(ctx, "data-not-unique", LY_VCODE_NOUNIQ, uniq_str, path1, path2); + if (arg->val_opts & LYD_VALIDATE_OPERATIONAL) { + /* only a warning */ + LOGWRN(ctx, "Unique data leaf(s) \"%s\" not satisfied in \"%s\" and \"%s\".", uniq_str, path1, path2); + } else { + LOGVAL_APPTAG(ctx, "data-not-unique", LY_VCODE_NOUNIQ, uniq_str, path1, path2); + } LOG_LOCBACK(0, 1, 0, 0); free(path1); free(path2); free(uniq_str); -#undef UNIQ_BUF_SIZE - return 1; + if (!(arg->val_opts & LYD_VALIDATE_OPERATIONAL)) { + return 1; + } } - if (action > 0) { + if (arg->action > 0) { /* done */ return 0; } @@ -1148,10 +1234,12 @@ uniquecheck: * @param[in] first First sibling to search in. * @param[in] snode Schema node to validate. * @param[in] uniques List unique arrays to validate. + * @param[in] val_opts Validation options. * @return LY_ERR value. */ static LY_ERR -lyd_validate_unique(const struct lyd_node *first, const struct lysc_node *snode, const struct lysc_node_leaf ***uniques) +lyd_validate_unique(const struct lyd_node *first, const struct lysc_node *snode, const struct lysc_node_leaf ***uniques, + uint32_t val_opts) { const struct lyd_node *diter; struct ly_set *set; @@ -1161,8 +1249,8 @@ lyd_validate_unique(const struct lyd_node *first, const struct lysc_node *snode, size_t key_len; ly_bool dyn; const void *hash_key; - void *cb_data; - struct hash_table **uniqtables = NULL; + struct lyd_val_uniq_arg arg, *args = NULL; + struct ly_ht **uniqtables = NULL; struct lyd_value *val; struct ly_ctx *ctx = snode->module->ctx; @@ -1179,7 +1267,9 @@ lyd_validate_unique(const struct lyd_node *first, const struct lysc_node *snode, if (set->count == 2) { /* simple comparison */ - if (lyd_val_uniq_list_equal(&set->objs[0], &set->objs[1], 0, (void *)0)) { + arg.action = 0; + arg.val_opts = val_opts; + if (lyd_val_uniq_list_equal(&set->objs[0], &set->objs[1], 0, &arg)) { /* instance duplication */ ret = LY_EVALID; goto cleanup; @@ -1187,12 +1277,14 @@ lyd_validate_unique(const struct lyd_node *first, const struct lysc_node *snode, } else if (set->count > 2) { /* use hashes for comparison */ uniqtables = malloc(LY_ARRAY_COUNT(uniques) * sizeof *uniqtables); - LY_CHECK_ERR_GOTO(!uniqtables, LOGMEM(ctx); ret = LY_EMEM, cleanup); + args = malloc(LY_ARRAY_COUNT(uniques) * sizeof *args); + LY_CHECK_ERR_GOTO(!uniqtables || !args, LOGMEM(ctx); ret = LY_EMEM, cleanup); x = LY_ARRAY_COUNT(uniques); for (v = 0; v < x; v++) { - cb_data = (void *)(uintptr_t)(v + 1L); + args[v].action = v + 1; + args[v].val_opts = val_opts; uniqtables[v] = lyht_new(lyht_get_fixed_size(set->count), sizeof(struct lyd_node *), - lyd_val_uniq_list_equal, cb_data, 0); + lyd_val_uniq_list_equal, &args[v], 0); LY_CHECK_ERR_GOTO(!uniqtables[v], LOGMEM(ctx); ret = LY_EMEM, cleanup); } @@ -1215,7 +1307,7 @@ lyd_validate_unique(const struct lyd_node *first, const struct lysc_node *snode, /* get hash key */ hash_key = val->realtype->plugin->print(NULL, val, LY_VALUE_LYB, NULL, &dyn, &key_len); - hash = dict_hash_multi(hash, hash_key, key_len); + hash = lyht_hash_multi(hash, hash_key, key_len); if (dyn) { free((void *)hash_key); } @@ -1226,7 +1318,7 @@ lyd_validate_unique(const struct lyd_node *first, const struct lysc_node *snode, } /* finish the hash value */ - hash = dict_hash_multi(hash, NULL, 0); + hash = lyht_hash_multi(hash, NULL, 0); /* insert into the hashtable */ ret = lyht_insert(uniqtables[u], &set->objs[i], hash, NULL); @@ -1246,9 +1338,10 @@ cleanup: /* failed when allocating uniquetables[j], following j are not allocated */ break; } - lyht_free(uniqtables[v]); + lyht_free(uniqtables[v], NULL); } free(uniqtables); + free(args); return ret; } @@ -1268,7 +1361,7 @@ static LY_ERR lyd_validate_siblings_schema_r(const struct lyd_node *first, const struct lyd_node *parent, const struct lysc_node *sparent, const struct lysc_module *mod, uint32_t val_opts, uint32_t int_opts) { - LY_ERR ret = LY_SUCCESS; + LY_ERR r, rc = LY_SUCCESS; const struct lysc_node *snode = NULL, *scase; struct lysc_node_list *slist; struct lysc_node_leaflist *sllist; @@ -1282,34 +1375,32 @@ lyd_validate_siblings_schema_r(const struct lyd_node *first, const struct lyd_no continue; } - LOG_LOCSET(snode, NULL, NULL, NULL); - /* check min-elements and max-elements */ if (snode->nodetype == LYS_LIST) { slist = (struct lysc_node_list *)snode; if (slist->min || slist->max) { - ret = lyd_validate_minmax(first, parent, snode, slist->min, slist->max); - LY_CHECK_GOTO(ret, error); + r = lyd_validate_minmax(first, parent, snode, slist->min, slist->max, val_opts); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); } } else if (snode->nodetype == LYS_LEAFLIST) { sllist = (struct lysc_node_leaflist *)snode; if (sllist->min || sllist->max) { - ret = lyd_validate_minmax(first, parent, snode, sllist->min, sllist->max); - LY_CHECK_GOTO(ret, error); + r = lyd_validate_minmax(first, parent, snode, sllist->min, sllist->max, val_opts); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); } } else if (snode->flags & LYS_MAND_TRUE) { /* check generic mandatory existence */ - ret = lyd_validate_mandatory(first, parent, snode); - LY_CHECK_GOTO(ret, error); + r = lyd_validate_mandatory(first, parent, snode, val_opts); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); } /* check unique */ if (snode->nodetype == LYS_LIST) { slist = (struct lysc_node_list *)snode; if (slist->uniques) { - ret = lyd_validate_unique(first, snode, (const struct lysc_node_leaf ***)slist->uniques); - LY_CHECK_GOTO(ret, error); + r = lyd_validate_unique(first, snode, (const struct lysc_node_leaf ***)slist->uniques, val_opts); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); } } @@ -1318,21 +1409,16 @@ lyd_validate_siblings_schema_r(const struct lyd_node *first, const struct lyd_no LY_LIST_FOR(lysc_node_child(snode), scase) { if (lys_getnext_data(NULL, first, NULL, scase, NULL)) { /* validate only this case */ - ret = lyd_validate_siblings_schema_r(first, parent, scase, mod, val_opts, int_opts); - LY_CHECK_GOTO(ret, error); + r = lyd_validate_siblings_schema_r(first, parent, scase, mod, val_opts, int_opts); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); break; } } } - - LOG_LOCBACK(1, 0, 0, 0); } - return LY_SUCCESS; - -error: - LOG_LOCBACK(1, 0, 0, 0); - return ret; +cleanup: + return rc; } /** @@ -1348,7 +1434,9 @@ lyd_validate_obsolete(const struct lyd_node *node) snode = node->schema; do { if (snode->flags & LYS_STATUS_OBSLT) { + LOG_LOCSET(NULL, node, NULL, NULL); LOGWRN(snode->module->ctx, "Obsolete schema node \"%s\" instantiated in data.", snode->name); + LOG_LOCBACK(0, 1, 0, 0); break; } @@ -1360,14 +1448,15 @@ lyd_validate_obsolete(const struct lyd_node *node) * @brief Validate must conditions of a data node. * * @param[in] node Node to validate. + * @param[in] val_opts Validation options. * @param[in] int_opts Internal parser options. * @param[in] xpath_options Additional XPath options to use. * @return LY_ERR value. */ static LY_ERR -lyd_validate_must(const struct lyd_node *node, uint32_t int_opts, uint32_t xpath_options) +lyd_validate_must(const struct lyd_node *node, uint32_t val_opts, uint32_t int_opts, uint32_t xpath_options) { - LY_ERR ret; + LY_ERR r, rc = LY_SUCCESS; struct lyxp_set xp_set; struct lysc_must *musts; const struct lyd_node *tree; @@ -1403,30 +1492,46 @@ lyd_validate_must(const struct lyd_node *node, uint32_t int_opts, uint32_t xpath memset(&xp_set, 0, sizeof xp_set); /* evaluate must */ - ret = lyxp_eval(LYD_CTX(node), musts[u].cond, node->schema->module, LY_VALUE_SCHEMA_RESOLVED, + r = lyxp_eval(LYD_CTX(node), musts[u].cond, node->schema->module, LY_VALUE_SCHEMA_RESOLVED, musts[u].prefixes, node, node, tree, NULL, &xp_set, LYXP_SCHEMA | xpath_options); - if (ret == LY_EINCOMPLETE) { - LOGINT_RET(LYD_CTX(node)); - } else if (ret) { - return ret; + if (r == LY_EINCOMPLETE) { + LOGERR(LYD_CTX(node), LY_EINCOMPLETE, + "Must \"%s\" depends on a node with a when condition, which has not been evaluated.", musts[u].cond->expr); } + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); /* check the result */ lyxp_set_cast(&xp_set, LYXP_SET_BOOLEAN); if (!xp_set.val.bln) { - /* use specific error information */ - emsg = musts[u].emsg; - eapptag = musts[u].eapptag ? musts[u].eapptag : "must-violation"; - if (emsg) { - LOGVAL_APPTAG(LYD_CTX(node), eapptag, LYVE_DATA, "%s", emsg); + if (val_opts & LYD_VALIDATE_OPERATIONAL) { + /* only a warning */ + emsg = musts[u].emsg; + LOG_LOCSET(NULL, node, NULL, NULL); + if (emsg) { + LOGWRN(LYD_CTX(node), "%s", emsg); + } else { + LOGWRN(LYD_CTX(node), "Must condition \"%s\" not satisfied.", musts[u].cond->expr); + } + LOG_LOCBACK(0, 1, 0, 0); } else { - LOGVAL_APPTAG(LYD_CTX(node), eapptag, LY_VCODE_NOMUST, musts[u].cond->expr); + /* use specific error information */ + emsg = musts[u].emsg; + eapptag = musts[u].eapptag ? musts[u].eapptag : "must-violation"; + LOG_LOCSET(NULL, node, NULL, NULL); + if (emsg) { + LOGVAL_APPTAG(LYD_CTX(node), eapptag, LYVE_DATA, "%s", emsg); + } else { + LOGVAL_APPTAG(LYD_CTX(node), eapptag, LY_VCODE_NOMUST, musts[u].cond->expr); + } + LOG_LOCBACK(0, 1, 0, 0); + r = LY_EVALID; + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); } - return LY_EVALID; } } - return LY_SUCCESS; +cleanup: + return rc; } /** @@ -1445,29 +1550,25 @@ static LY_ERR lyd_validate_final_r(struct lyd_node *first, const struct lyd_node *parent, const struct lysc_node *sparent, const struct lys_module *mod, uint32_t val_opts, uint32_t int_opts, uint32_t must_xp_opts) { - LY_ERR r; + LY_ERR r, rc = LY_SUCCESS; const char *innode; - struct lyd_node *next = NULL, *node; + struct lyd_node *node; /* validate all restrictions of nodes themselves */ - LY_LIST_FOR_SAFE(first, next, node) { + LY_LIST_FOR(first, node) { if (node->flags & LYD_EXT) { /* ext instance data should have already been validated */ continue; } - LOG_LOCSET(node->schema, node, NULL, NULL); - /* opaque data */ if (!node->schema) { r = lyd_parse_opaq_error(node); - LOG_LOCBACK(0, 1, 0, 0); - return r; + goto next_iter; } if (!node->parent && mod && (lyd_owner_module(node) != mod)) { /* all top-level data from this module checked */ - LOG_LOCBACK(1, 1, 0, 0); break; } @@ -1487,43 +1588,47 @@ lyd_validate_final_r(struct lyd_node *first, const struct lyd_node *parent, cons innode = "notification"; } if (innode) { + LOG_LOCSET(NULL, node, NULL, NULL); LOGVAL(LYD_CTX(node), LY_VCODE_UNEXPNODE, innode, node->schema->name); - LOG_LOCBACK(1, 1, 0, 0); - return LY_EVALID; + LOG_LOCBACK(0, 1, 0, 0); + r = LY_EVALID; + goto next_iter; } /* obsolete data */ lyd_validate_obsolete(node); /* node's musts */ - if ((r = lyd_validate_must(node, int_opts, must_xp_opts))) { - LOG_LOCBACK(1, 1, 0, 0); - return r; + if ((r = lyd_validate_must(node, val_opts, int_opts, must_xp_opts))) { + goto next_iter; } /* node value was checked by plugins */ - /* next iter */ - LOG_LOCBACK(1, 1, 0, 0); +next_iter: + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); } /* validate schema-based restrictions */ - LY_CHECK_RET(lyd_validate_siblings_schema_r(first, parent, sparent, mod ? mod->compiled : NULL, val_opts, int_opts)); + r = lyd_validate_siblings_schema_r(first, parent, sparent, mod ? mod->compiled : NULL, val_opts, int_opts); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); LY_LIST_FOR(first, node) { - if (!node->parent && mod && (lyd_owner_module(node) != mod)) { - /* all top-level data from this module checked */ + if (!node->schema || (!node->parent && mod && (lyd_owner_module(node) != mod))) { + /* only opaque data following or all top-level data from this module checked */ break; } /* validate all children recursively */ - LY_CHECK_RET(lyd_validate_final_r(lyd_child(node), node, node->schema, NULL, val_opts, int_opts, must_xp_opts)); + r = lyd_validate_final_r(lyd_child(node), node, node->schema, NULL, val_opts, int_opts, must_xp_opts); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); /* set default for containers */ lyd_cont_set_dflt(node); } - return LY_SUCCESS; +cleanup: + return rc; } /** @@ -1609,18 +1714,20 @@ lyd_validate_node_ext(struct lyd_node *node, struct ly_set *ext_node) * @param[in,out] meta_types Set for unres metadata types. * @param[in,out] ext_node Set with nodes with extensions to validate. * @param[in,out] ext_val Set for parsed extension data to validate. - * @param[in] impl_opts Implicit options, see @ref implicitoptions. + * @param[in] val_opts Validation options. * @param[in,out] diff Validation diff. * @return LY_ERR value. */ static LY_ERR lyd_validate_subtree(struct lyd_node *root, struct ly_set *node_when, struct ly_set *node_types, - struct ly_set *meta_types, struct ly_set *ext_node, struct ly_set *ext_val, uint32_t impl_opts, + struct ly_set *meta_types, struct ly_set *ext_node, struct ly_set *ext_val, uint32_t val_opts, struct lyd_node **diff) { + LY_ERR r, rc = LY_SUCCESS; const struct lyd_meta *meta; const struct lysc_type *type; struct lyd_node *node; + uint32_t impl_opts; LYD_TREE_DFS_BEGIN(root, node) { if (node->flags & LYD_EXT) { @@ -1637,34 +1744,48 @@ lyd_validate_subtree(struct lyd_node *root, struct ly_set *node_when, struct ly_ lyplg_ext_get_storage(meta->annotation, LY_STMT_TYPE, sizeof type, (const void **)&type); if (type->plugin->validate) { /* metadata type resolution */ - LY_CHECK_RET(ly_set_add(meta_types, (void *)meta, 1, NULL)); + r = ly_set_add(meta_types, (void *)meta, 1, NULL); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); } } if ((node->schema->nodetype & LYD_NODE_TERM) && ((struct lysc_node_leaf *)node->schema)->type->plugin->validate) { /* node type resolution */ - LY_CHECK_RET(ly_set_add(node_types, (void *)node, 1, NULL)); + r = ly_set_add(node_types, (void *)node, 1, NULL); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); } else if (node->schema->nodetype & LYD_NODE_INNER) { /* new node validation, autodelete */ - LY_CHECK_RET(lyd_validate_new(lyd_node_child_p(node), node->schema, NULL, diff)); + r = lyd_validate_new(lyd_node_child_p(node), node->schema, NULL, val_opts, diff); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); /* add nested defaults */ - LY_CHECK_RET(lyd_new_implicit_r(node, lyd_node_child_p(node), NULL, NULL, NULL, NULL, NULL, impl_opts, diff)); + impl_opts = 0; + if (val_opts & LYD_VALIDATE_NO_STATE) { + impl_opts |= LYD_IMPLICIT_NO_STATE; + } + if (val_opts & LYD_VALIDATE_NO_DEFAULTS) { + impl_opts |= LYD_IMPLICIT_NO_DEFAULTS; + } + r = lyd_new_implicit_r(node, lyd_node_child_p(node), NULL, NULL, NULL, NULL, NULL, impl_opts, diff); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); } if (lysc_has_when(node->schema)) { /* when evaluation */ - LY_CHECK_RET(ly_set_add(node_when, (void *)node, 1, NULL)); + r = ly_set_add(node_when, (void *)node, 1, NULL); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); } /* store for ext instance node validation, if needed */ - LY_CHECK_RET(lyd_validate_node_ext(node, ext_node)); + r = lyd_validate_node_ext(node, ext_node); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); next_node: LYD_TREE_DFS_END(root, node); } - return LY_SUCCESS; +cleanup: + return rc; } LY_ERR @@ -1672,11 +1793,11 @@ lyd_validate(struct lyd_node **tree, const struct lys_module *module, const stru ly_bool validate_subtree, struct ly_set *node_when_p, struct ly_set *node_types_p, struct ly_set *meta_types_p, struct ly_set *ext_node_p, struct ly_set *ext_val_p, struct lyd_node **diff) { - LY_ERR ret = LY_SUCCESS; + LY_ERR r, rc = LY_SUCCESS; struct lyd_node *first, *next, **first2, *iter; const struct lys_module *mod; struct ly_set node_types = {0}, meta_types = {0}, node_when = {0}, ext_node = {0}, ext_val = {0}; - uint32_t i = 0; + uint32_t i = 0, impl_opts; assert(tree && ctx); assert((node_when_p && node_types_p && meta_types_p && ext_node_p && ext_val_p) || @@ -1708,15 +1829,21 @@ lyd_validate(struct lyd_node **tree, const struct lys_module *module, const stru } /* validate new top-level nodes of this module, autodelete */ - ret = lyd_validate_new(first2, NULL, mod, diff); - LY_CHECK_GOTO(ret, cleanup); + r = lyd_validate_new(first2, *first2 ? lysc_data_parent((*first2)->schema) : NULL, mod, val_opts, diff); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); /* add all top-level defaults for this module, if going to validate subtree, do not add into unres sets * (lyd_validate_subtree() adds all the nodes in that case) */ - ret = lyd_new_implicit_r(NULL, first2, NULL, mod, validate_subtree ? NULL : node_when_p, - validate_subtree ? NULL : node_types_p, validate_subtree ? NULL : ext_node_p, - (val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, diff); - LY_CHECK_GOTO(ret, cleanup); + impl_opts = 0; + if (val_opts & LYD_VALIDATE_NO_STATE) { + impl_opts |= LYD_IMPLICIT_NO_STATE; + } + if (val_opts & LYD_VALIDATE_NO_DEFAULTS) { + impl_opts |= LYD_IMPLICIT_NO_DEFAULTS; + } + r = lyd_new_implicit_r(lyd_parent(*first2), first2, NULL, mod, validate_subtree ? NULL : node_when_p, + validate_subtree ? NULL : node_types_p, validate_subtree ? NULL : ext_node_p, impl_opts, diff); + LY_CHECK_ERR_GOTO(r, rc = r, cleanup); /* our first module node pointer may no longer be the first */ first = *first2; @@ -1734,20 +1861,22 @@ lyd_validate(struct lyd_node **tree, const struct lys_module *module, const stru break; } - ret = lyd_validate_subtree(iter, node_when_p, node_types_p, meta_types_p, ext_node_p, ext_val_p, - (val_opts & LYD_VALIDATE_NO_STATE) ? LYD_IMPLICIT_NO_STATE : 0, diff); - LY_CHECK_GOTO(ret, cleanup); + r = lyd_validate_subtree(iter, node_when_p, node_types_p, meta_types_p, ext_node_p, ext_val_p, + val_opts, diff); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); } } /* finish incompletely validated terminal values/attributes and when conditions */ - ret = lyd_validate_unres(first2, mod, LYD_TYPE_DATA_YANG, node_when_p, 0, node_types_p, meta_types_p, + r = lyd_validate_unres(first2, mod, LYD_TYPE_DATA_YANG, node_when_p, 0, node_types_p, meta_types_p, ext_node_p, ext_val_p, val_opts, diff); - LY_CHECK_GOTO(ret, cleanup); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); - /* perform final validation that assumes the data tree is final */ - ret = lyd_validate_final_r(*first2, NULL, NULL, mod, val_opts, 0, 0); - LY_CHECK_GOTO(ret, cleanup); + if (!(val_opts & LYD_VALIDATE_NOT_FINAL)) { + /* perform final validation that assumes the data tree is final */ + r = lyd_validate_final_r(*first2, NULL, NULL, mod, val_opts, 0, 0); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); + } } cleanup: @@ -1756,7 +1885,7 @@ cleanup: ly_set_erase(&meta_types, NULL); ly_set_erase(&ext_node, free); ly_set_erase(&ext_val, free); - return ret; + return rc; } LIBYANG_API_DEF LY_ERR @@ -1777,14 +1906,36 @@ lyd_validate_all(struct lyd_node **tree, const struct ly_ctx *ctx, uint32_t val_ LIBYANG_API_DEF LY_ERR lyd_validate_module(struct lyd_node **tree, const struct lys_module *module, uint32_t val_opts, struct lyd_node **diff) { - LY_CHECK_ARG_RET(NULL, tree, *tree || module, LY_EINVAL); - LY_CHECK_CTX_EQUAL_RET(*tree ? LYD_CTX(*tree) : NULL, module ? module->ctx : NULL, LY_EINVAL); + LY_CHECK_ARG_RET(NULL, tree, module, !(val_opts & LYD_VALIDATE_PRESENT), LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(*tree ? LYD_CTX(*tree) : NULL, module->ctx, LY_EINVAL); if (diff) { *diff = NULL; } - return lyd_validate(tree, module, (*tree) ? LYD_CTX(*tree) : module->ctx, val_opts, 1, NULL, NULL, NULL, NULL, NULL, - diff); + return lyd_validate(tree, module, module->ctx, val_opts, 1, NULL, NULL, NULL, NULL, NULL, diff); +} + +LIBYANG_API_DEF LY_ERR +lyd_validate_module_final(struct lyd_node *tree, const struct lys_module *module, uint32_t val_opts) +{ + LY_ERR r, rc = LY_SUCCESS; + struct lyd_node *first; + const struct lys_module *mod; + uint32_t i = 0; + + LY_CHECK_ARG_RET(NULL, module, !(val_opts & (LYD_VALIDATE_PRESENT | LYD_VALIDATE_NOT_FINAL)), LY_EINVAL); + LY_CHECK_CTX_EQUAL_RET(LYD_CTX(tree), module->ctx, LY_EINVAL); + + /* module is unchanged but we need to get the first module data node */ + mod = lyd_mod_next_module(tree, module, module->ctx, &i, &first); + assert(mod); + + /* perform final validation that assumes the data tree is final */ + r = lyd_validate_final_r(first, NULL, NULL, mod, val_opts, 0, 0); + LY_VAL_ERR_GOTO(r, rc = r, val_opts, cleanup); + +cleanup: + return rc; } /** @@ -1895,14 +2046,12 @@ _lyd_validate_op(struct lyd_node *op_tree, struct lyd_node *op_node, const struc op_sibling_after = op_subtree->next; op_parent = lyd_parent(op_subtree); - lyd_unlink_tree(op_subtree); + lyd_unlink(op_subtree); lyd_insert_node(tree_parent, &tree_sibling, op_subtree, 0); if (!dep_tree) { dep_tree = tree_sibling; } - LOG_LOCSET(NULL, op_node, NULL, NULL); - if (int_opts & LYD_INTOPT_REPLY) { /* add output children defaults */ rc = lyd_new_implicit_r(op_node, lyd_node_child_p(op_node), NULL, NULL, node_when_p, node_types_p, @@ -1931,21 +2080,21 @@ _lyd_validate_op(struct lyd_node *op_tree, struct lyd_node *op_node, const struc /* perform final validation of the operation/notification */ lyd_validate_obsolete(op_node); - LY_CHECK_GOTO(rc = lyd_validate_must(op_node, int_opts, LYXP_IGNORE_WHEN), cleanup); + LY_CHECK_GOTO(rc = lyd_validate_must(op_node, 0, int_opts, LYXP_IGNORE_WHEN), cleanup); /* final validation of all the descendants */ rc = lyd_validate_final_r(lyd_child(op_node), op_node, op_node->schema, NULL, 0, int_opts, LYXP_IGNORE_WHEN); LY_CHECK_GOTO(rc, cleanup); cleanup: - LOG_LOCBACK(0, 1, 0, 0); - /* restore operation tree */ - lyd_unlink_tree(op_subtree); + lyd_unlink(op_subtree); if (op_sibling_before) { lyd_insert_after_node(op_sibling_before, op_subtree); + lyd_insert_hash(op_subtree); } else if (op_sibling_after) { lyd_insert_before_node(op_sibling_after, op_subtree); + lyd_insert_hash(op_subtree); } else if (op_parent) { lyd_insert_node(op_parent, NULL, op_subtree, 0); } diff --git a/src/validation.h b/src/validation.h index c9f5da0..db24ef6 100644 --- a/src/validation.h +++ b/src/validation.h @@ -69,11 +69,12 @@ LY_ERR lyd_validate_unres(struct lyd_node **tree, const struct lys_module *mod, * @param[in,out] first First sibling. * @param[in] sparent Schema parent of the siblings, NULL for top-level siblings. * @param[in] mod Module of the siblings, NULL for nested siblings. + * @param[in] val_opts Validation options. * @param[in,out] diff Validation diff. * @return LY_ERR value. */ LY_ERR lyd_validate_new(struct lyd_node **first, const struct lysc_node *sparent, const struct lys_module *mod, - struct lyd_node **diff); + uint32_t val_opts, struct lyd_node **diff); /** * @brief Validate data node with an extension instance, if any, by storing it in its unres set. @@ -222,17 +222,29 @@ cleanup: void lyxml_ns_rm(struct lyxml_ctx *xmlctx) { - for (uint32_t u = xmlctx->ns.count - 1; u + 1 > 0; --u) { - if (((struct lyxml_ns *)xmlctx->ns.objs[u])->depth != xmlctx->elements.count + 1) { + struct lyxml_ns *ns; + uint32_t u; + + if (!xmlctx->ns.count) { + return; + } + + u = xmlctx->ns.count; + do { + --u; + ns = (struct lyxml_ns *)xmlctx->ns.objs[u]; + + if (ns->depth != xmlctx->elements.count + 1) { /* we are done, the namespaces from a single element are supposed to be together */ break; } + /* remove the ns structure */ - free(((struct lyxml_ns *)xmlctx->ns.objs[u])->prefix); - free(((struct lyxml_ns *)xmlctx->ns.objs[u])->uri); - free(xmlctx->ns.objs[u]); + free(ns->prefix); + free(ns->uri); + free(ns); --xmlctx->ns.count; - } + } while (u); if (!xmlctx->ns.count) { /* cleanup the xmlctx's namespaces storage */ @@ -244,9 +256,17 @@ const struct lyxml_ns * lyxml_ns_get(const struct ly_set *ns_set, const char *prefix, size_t prefix_len) { struct lyxml_ns *ns; + uint32_t u; - for (uint32_t u = ns_set->count - 1; u + 1 > 0; --u) { + if (!ns_set->count) { + return NULL; + } + + u = ns_set->count; + do { + --u; ns = (struct lyxml_ns *)ns_set->objs[u]; + if (prefix && prefix_len) { if (ns->prefix && !ly_strncmp(ns->prefix, prefix, prefix_len)) { return ns; @@ -255,7 +275,7 @@ lyxml_ns_get(const struct ly_set *ns_set, const char *prefix, size_t prefix_len) /* default namespace */ return ns; } - } + } while (u); return NULL; } @@ -415,12 +435,11 @@ static LY_ERR lyxml_parse_value(struct lyxml_ctx *xmlctx, char endchar, char **value, size_t *length, ly_bool *ws_only, ly_bool *dynamic) { const struct ly_ctx *ctx = xmlctx->ctx; /* shortcut */ - const char *in = xmlctx->in->current, *start, *in_aux; + const char *in = xmlctx->in->current, *start, *in_aux, *p; char *buf = NULL; size_t offset; /* read offset in input buffer */ size_t len; /* length of the output string (write offset in output buffer) */ size_t size = 0; /* size of the output buffer */ - void *p; uint32_t n; size_t u; ly_bool ws = 1; @@ -467,7 +486,7 @@ lyxml_parse_value(struct lyxml_ctx *xmlctx, char endchar, char **value, size_t * } offset = 0; } else { - p = (void *)&in[offset - 1]; + p = &in[offset - 1]; /* character reference */ ++offset; if (isdigit(in[offset])) { @@ -486,7 +505,7 @@ lyxml_parse_value(struct lyxml_ctx *xmlctx, char endchar, char **value, size_t * n = (LY_BASE_HEX * n) + u; } } else { - LOGVAL(ctx, LYVE_SYNTAX, "Invalid character reference \"%.*s\".", 12, p); + LOGVAL(ctx, LYVE_SYNTAX, "Invalid character reference \"%.12s\".", p); goto error; } @@ -497,7 +516,7 @@ lyxml_parse_value(struct lyxml_ctx *xmlctx, char endchar, char **value, size_t * } ++offset; if (ly_pututf8(&buf[len], n, &u)) { - LOGVAL(ctx, LYVE_SYNTAX, "Invalid character reference \"%.*s\" (0x%08x).", 12, p, n); + LOGVAL(ctx, LYVE_SYNTAX, "Invalid character reference \"%.12s\" (0x%08x).", p, n); goto error; } len += u; diff --git a/src/xpath.c b/src/xpath.c index ab7921e..bc5f695 100644 --- a/src/xpath.c +++ b/src/xpath.c @@ -43,8 +43,6 @@ #include "tree_schema_internal.h" #include "xml.h" -static LY_ERR set_scnode_insert_node(struct lyxp_set *set, const struct lysc_node *node, enum lyxp_node_type node_type, - enum lyxp_axis axis, uint32_t *index_p); static LY_ERR reparse_or_expr(const struct ly_ctx *ctx, struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t depth); static LY_ERR eval_expr_select(const struct lyxp_expr *exp, uint32_t *tok_idx, enum lyxp_expr_type etype, struct lyxp_set *set, uint32_t options); @@ -123,6 +121,8 @@ lyxp_token2str(enum lyxp_token tok) return "@"; case LYXP_TOKEN_COMMA: return ","; + case LYXP_TOKEN_DCOLON: + return "::"; case LYXP_TOKEN_NAMETEST: return "NameTest"; case LYXP_TOKEN_NODETYPE: @@ -147,6 +147,8 @@ lyxp_token2str(enum lyxp_token tok) return "Operator(Path)"; case LYXP_TOKEN_OPER_RPATH: return "Operator(Recursive Path)"; + case LYXP_TOKEN_AXISNAME: + return "AxisName"; case LYXP_TOKEN_LITERAL: return "Literal"; case LYXP_TOKEN_NUMBER: @@ -217,16 +219,53 @@ str2axis(const char *str, uint32_t str_len) } /** - * @brief Print the whole expression \p exp to debug output. + * @brief Append a string to a dynamic string variable. + * + * @param[in,out] str String to use. + * @param[in,out] size String size. + * @param[in,out] used String used size excluding terminating zero. + * @param[in] format Message format. + * @param[in] ... Message format arguments. + */ +static void +print_expr_str(char **str, size_t *size, size_t *used, const char *format, ...) +{ + int p; + va_list ap; + + va_start(ap, format); + + /* try to append the string */ + p = vsnprintf(*str ? *str + *used : NULL, *size - *used, format, ap); + + if ((unsigned)p >= *size - *used) { + /* realloc */ + *str = ly_realloc(*str, *size + p + 1); + *size += p + 1; + + /* restart ap */ + va_end(ap); + va_start(ap, format); + + /* print */ + p = vsnprintf(*str + *used, *size - *used, format, ap); + } + + *used += p; + va_end(ap); +} + +/** + * @brief Print the whole expression @p exp to debug output. * * @param[in] exp Expression to use. */ static void print_expr_struct_debug(const struct lyxp_expr *exp) { -#define MSG_BUFFER_SIZE 128 - char tmp[MSG_BUFFER_SIZE]; + char *buf = NULL; uint32_t i, j; + size_t size = 0, used = 0; if (!exp || (ly_ll < LY_LLDBG)) { return; @@ -234,18 +273,21 @@ print_expr_struct_debug(const struct lyxp_expr *exp) LOGDBG(LY_LDGXPATH, "expression \"%s\":", exp->expr); for (i = 0; i < exp->used; ++i) { - sprintf(tmp, "\ttoken %s, in expression \"%.*s\"", lyxp_token2str(exp->tokens[i]), exp->tok_len[i], - &exp->expr[exp->tok_pos[i]]); + print_expr_str(&buf, &size, &used, "\ttoken %s, in expression \"%.*s\"", + lyxp_token2str(exp->tokens[i]), exp->tok_len[i], &exp->expr[exp->tok_pos[i]]); + if (exp->repeat && exp->repeat[i]) { - sprintf(tmp + strlen(tmp), " (repeat %d", exp->repeat[i][0]); + print_expr_str(&buf, &size, &used, " (repeat %d", exp->repeat[i][0]); for (j = 1; exp->repeat[i][j]; ++j) { - sprintf(tmp + strlen(tmp), ", %d", exp->repeat[i][j]); + print_expr_str(&buf, &size, &used, ", %d", exp->repeat[i][j]); } - strcat(tmp, ")"); + print_expr_str(&buf, &size, &used, ")"); } - LOGDBG(LY_LDGXPATH, tmp); + LOGDBG(LY_LDGXPATH, buf); + used = 0; } -#undef MSG_BUFFER_SIZE + + free(buf); } #ifndef NDEBUG @@ -284,18 +326,19 @@ print_set_debug(struct lyxp_set *set) LOGDBG(LY_LDGXPATH, "\t%d (pos %u): ROOT CONFIG", i + 1, item->pos); break; case LYXP_NODE_ELEM: - if ((item->node->schema->nodetype == LYS_LIST) && (lyd_child(item->node)->schema->nodetype == LYS_LEAF)) { + if (item->node->schema && (item->node->schema->nodetype == LYS_LIST) && + (lyd_child(item->node)->schema->nodetype == LYS_LEAF)) { LOGDBG(LY_LDGXPATH, "\t%d (pos %u): ELEM %s (1st child val: %s)", i + 1, item->pos, item->node->schema->name, lyd_get_value(lyd_child(item->node))); - } else if (item->node->schema->nodetype == LYS_LEAFLIST) { + } else if ((!item->node->schema && !lyd_child(item->node)) || (item->node->schema->nodetype == LYS_LEAFLIST)) { LOGDBG(LY_LDGXPATH, "\t%d (pos %u): ELEM %s (val: %s)", i + 1, item->pos, - item->node->schema->name, lyd_get_value(item->node)); + LYD_NAME(item->node), lyd_get_value(item->node)); } else { - LOGDBG(LY_LDGXPATH, "\t%d (pos %u): ELEM %s", i + 1, item->pos, item->node->schema->name); + LOGDBG(LY_LDGXPATH, "\t%d (pos %u): ELEM %s", i + 1, item->pos, LYD_NAME(item->node)); } break; case LYXP_NODE_TEXT: - if (item->node->schema->nodetype & LYS_ANYDATA) { + if (item->node->schema && (item->node->schema->nodetype & LYS_ANYDATA)) { LOGDBG(LY_LDGXPATH, "\t%d (pos %u): TEXT <%s>", i + 1, item->pos, item->node->schema->nodetype == LYS_ANYXML ? "anyxml" : "anydata"); } else { @@ -416,13 +459,14 @@ cast_string_recursive(const struct lyd_node *node, struct lyxp_set *set, uint32_ { char *buf, *line, *ptr = NULL; const char *value_str; + uint16_t nodetype; const struct lyd_node *child; enum lyxp_node_type child_type; struct lyd_node *tree; struct lyd_node_any *any; LY_ERR rc; - if ((set->root_type == LYXP_NODE_ROOT_CONFIG) && node && (node->schema->flags & LYS_CONFIG_R)) { + if ((set->root_type == LYXP_NODE_ROOT_CONFIG) && node && node->schema && (node->schema->flags & LYS_CONFIG_R)) { return LY_SUCCESS; } @@ -448,7 +492,15 @@ cast_string_recursive(const struct lyd_node *node, struct lyxp_set *set, uint32_ --indent; } else { - switch (node->schema->nodetype) { + if (node->schema) { + nodetype = node->schema->nodetype; + } else if (lyd_child(node)) { + nodetype = LYS_CONTAINER; + } else { + nodetype = LYS_LEAF; + } + + switch (nodetype) { case LYS_CONTAINER: case LYS_LIST: case LYS_RPC: @@ -691,29 +743,29 @@ set_insert_node_hash(struct lyxp_set *set, struct lyd_node *node, enum lyxp_node hnode.node = set->val.nodes[i].node; hnode.type = set->val.nodes[i].type; - hash = dict_hash_multi(0, (const char *)&hnode.node, sizeof hnode.node); - hash = dict_hash_multi(hash, (const char *)&hnode.type, sizeof hnode.type); - hash = dict_hash_multi(hash, NULL, 0); + hash = lyht_hash_multi(0, (const char *)&hnode.node, sizeof hnode.node); + hash = lyht_hash_multi(hash, (const char *)&hnode.type, sizeof hnode.type); + hash = lyht_hash_multi(hash, NULL, 0); r = lyht_insert(set->ht, &hnode, hash, NULL); assert(!r); (void)r; - if (hnode.node == node) { + if ((hnode.node == node) && (hnode.type == type)) { /* it was just added, do not add it twice */ - node = NULL; + return; } } } - if (set->ht && node) { + if (set->ht) { /* add the new node into hash table */ hnode.node = node; hnode.type = type; - hash = dict_hash_multi(0, (const char *)&hnode.node, sizeof hnode.node); - hash = dict_hash_multi(hash, (const char *)&hnode.type, sizeof hnode.type); - hash = dict_hash_multi(hash, NULL, 0); + hash = lyht_hash_multi(0, (const char *)&hnode.node, sizeof hnode.node); + hash = lyht_hash_multi(hash, (const char *)&hnode.type, sizeof hnode.type); + hash = lyht_hash_multi(hash, NULL, 0); r = lyht_insert(set->ht, &hnode, hash, NULL); assert(!r); @@ -739,16 +791,16 @@ set_remove_node_hash(struct lyxp_set *set, struct lyd_node *node, enum lyxp_node hnode.node = node; hnode.type = type; - hash = dict_hash_multi(0, (const char *)&hnode.node, sizeof hnode.node); - hash = dict_hash_multi(hash, (const char *)&hnode.type, sizeof hnode.type); - hash = dict_hash_multi(hash, NULL, 0); + hash = lyht_hash_multi(0, (const char *)&hnode.node, sizeof hnode.node); + hash = lyht_hash_multi(hash, (const char *)&hnode.type, sizeof hnode.type); + hash = lyht_hash_multi(hash, NULL, 0); r = lyht_remove(set->ht, &hnode, hash); assert(!r); (void)r; if (!set->ht->used) { - lyht_free(set->ht); + lyht_free(set->ht, NULL); set->ht = NULL; } } @@ -772,9 +824,9 @@ set_dup_node_hash_check(const struct lyxp_set *set, struct lyd_node *node, enum hnode.node = node; hnode.type = type; - hash = dict_hash_multi(0, (const char *)&hnode.node, sizeof hnode.node); - hash = dict_hash_multi(hash, (const char *)&hnode.type, sizeof hnode.type); - hash = dict_hash_multi(hash, NULL, 0); + hash = lyht_hash_multi(0, (const char *)&hnode.node, sizeof hnode.node); + hash = lyht_hash_multi(hash, (const char *)&hnode.type, sizeof hnode.type); + hash = lyht_hash_multi(hash, NULL, 0); if (!lyht_find(set->ht, &hnode, hash, (void **)&match_p)) { if ((skip_idx > -1) && (set->val.nodes[skip_idx].node == match_p->node) && (set->val.nodes[skip_idx].type == match_p->type)) { @@ -802,10 +854,10 @@ lyxp_set_free_content(struct lyxp_set *set) if (set->type == LYXP_SET_NODE_SET) { free(set->val.nodes); - lyht_free(set->ht); + lyht_free(set->ht, NULL); } else if (set->type == LYXP_SET_SCNODE_SET) { free(set->val.scnodes); - lyht_free(set->ht); + lyht_free(set->ht, NULL); } else { if (set->type == LYXP_SET_STRING) { free(set->val.str); @@ -847,18 +899,21 @@ static void set_init(struct lyxp_set *new, const struct lyxp_set *set) { memset(new, 0, sizeof *new); - if (set) { - new->non_child_axis = set->non_child_axis; - new->ctx = set->ctx; - new->cur_node = set->cur_node; - new->root_type = set->root_type; - new->context_op = set->context_op; - new->tree = set->tree; - new->cur_mod = set->cur_mod; - new->format = set->format; - new->prefix_data = set->prefix_data; - new->vars = set->vars; + if (!set) { + return; } + + new->non_child_axis = set->non_child_axis; + new->not_found = set->not_found; + new->ctx = set->ctx; + new->cur_node = set->cur_node; + new->root_type = set->root_type; + new->context_op = set->context_op; + new->tree = set->tree; + new->cur_mod = set->cur_mod; + new->format = set->format; + new->prefix_data = set->prefix_data; + new->vars = set->vars; } /** @@ -889,7 +944,7 @@ set_copy(struct lyxp_set *set) (set->val.scnodes[i].in_ctx == LYXP_SET_SCNODE_START)) { uint32_t idx; - LY_CHECK_ERR_RET(set_scnode_insert_node(ret, set->val.scnodes[i].scnode, set->val.scnodes[i].type, + LY_CHECK_ERR_RET(lyxp_set_scnode_insert_node(ret, set->val.scnodes[i].scnode, set->val.scnodes[i].type, set->val.scnodes[i].axis, &idx), lyxp_set_free(ret), NULL); /* coverity seems to think scnodes can be NULL */ if (!ret->val.scnodes) { @@ -989,11 +1044,7 @@ set_fill_set(struct lyxp_set *trg, const struct lyxp_set *src) return; } - if (trg->type == LYXP_SET_NODE_SET) { - free(trg->val.nodes); - } else if (trg->type == LYXP_SET_STRING) { - free(trg->val.str); - } + lyxp_set_free_content(trg); set_init(trg, src); if (src->type == LYXP_SET_SCNODE_SET) { @@ -1314,19 +1365,8 @@ set_insert_node(struct lyxp_set *set, const struct lyd_node *node, uint32_t pos, set_insert_node_hash(set, (struct lyd_node *)node, node_type); } -/** - * @brief Insert schema node into set. - * - * @param[in] set Set to insert into. - * @param[in] node Node to insert. - * @param[in] node_type Node type of @p node. - * @param[in] axis Axis that @p node was reached on. - * @param[out] index_p Optional pointer to store index if the inserted @p node. - * @return LY_SUCCESS on success. - * @return LY_EMEM on memory allocation failure. - */ -static LY_ERR -set_scnode_insert_node(struct lyxp_set *set, const struct lysc_node *node, enum lyxp_node_type node_type, +LY_ERR +lyxp_set_scnode_insert_node(struct lyxp_set *set, const struct lysc_node *node, enum lyxp_node_type node_type, enum lyxp_axis axis, uint32_t *index_p) { uint32_t index; @@ -1346,7 +1386,7 @@ set_scnode_insert_node(struct lyxp_set *set, const struct lysc_node *node, enum } if (lyxp_set_scnode_contains(set, node, node_type, -1, &index)) { - /* BUG if axes differs, this new one is thrown away */ + /* BUG if axes differ, this new one is thrown away */ set->val.scnodes[index].in_ctx = LYXP_SET_SCNODE_ATOM_CTX; } else { if (set->used == set->size) { @@ -1445,7 +1485,7 @@ dfs_search: LYD_TREE_DFS_continue = 0; } - if (!elem->schema || ((root_type == LYXP_NODE_ROOT_CONFIG) && (elem->schema->flags & LYS_CONFIG_R))) { + if ((root_type == LYXP_NODE_ROOT_CONFIG) && elem->schema && (elem->schema->flags & LYS_CONFIG_R)) { /* skip */ LYD_TREE_DFS_continue = 1; } else { @@ -1662,7 +1702,7 @@ set_comp_canonize(struct lyxp_set *set, const struct lyxp_set_node *xp_node) /* is there anything to canonize even? */ if (set->type == LYXP_SET_STRING) { /* do we have a type to use for canonization? */ - if ((xp_node->type == LYXP_NODE_ELEM) && (xp_node->node->schema->nodetype & LYD_NODE_TERM)) { + if ((xp_node->type == LYXP_NODE_ELEM) && xp_node->node->schema && (xp_node->node->schema->nodetype & LYD_NODE_TERM)) { type = ((struct lyd_node_term *)xp_node->node)->value.realtype; } else if (xp_node->type == LYXP_NODE_META) { type = ((struct lyd_meta *)xp_node->node)->value.realtype; @@ -1785,9 +1825,9 @@ set_sort(struct lyxp_set *set) hnode.node = set->val.nodes[i].node; hnode.type = set->val.nodes[i].type; - hash = dict_hash_multi(0, (const char *)&hnode.node, sizeof hnode.node); - hash = dict_hash_multi(hash, (const char *)&hnode.type, sizeof hnode.type); - hash = dict_hash_multi(hash, NULL, 0); + hash = lyht_hash_multi(0, (const char *)&hnode.node, sizeof hnode.node); + hash = lyht_hash_multi(hash, (const char *)&hnode.type, sizeof hnode.type); + hash = lyht_hash_multi(hash, NULL, 0); assert(!lyht_find(set->ht, &hnode, hash, NULL)); } @@ -1993,11 +2033,11 @@ lyxp_next_token2(const struct ly_ctx *ctx, const struct lyxp_expr *exp, uint32_t * @brief Stack operation push on the repeat array. * * @param[in] exp Expression to use. - * @param[in] tok_idx Position in the expresion \p exp. - * @param[in] repeat_op_idx Index from \p exp of the operator token. This value is pushed. + * @param[in] tok_idx Position in the expresion @p exp. + * @param[in] repeat_expr_type Repeated expression type, this value is pushed. */ static void -exp_repeat_push(struct lyxp_expr *exp, uint32_t tok_idx, uint32_t repeat_op_idx) +exp_repeat_push(struct lyxp_expr *exp, uint32_t tok_idx, enum lyxp_expr_type repeat_expr_type) { uint32_t i; @@ -2005,12 +2045,12 @@ exp_repeat_push(struct lyxp_expr *exp, uint32_t tok_idx, uint32_t repeat_op_idx) for (i = 0; exp->repeat[tok_idx][i]; ++i) {} exp->repeat[tok_idx] = realloc(exp->repeat[tok_idx], (i + 2) * sizeof *exp->repeat[tok_idx]); LY_CHECK_ERR_RET(!exp->repeat[tok_idx], LOGMEM(NULL), ); - exp->repeat[tok_idx][i] = repeat_op_idx; + exp->repeat[tok_idx][i] = repeat_expr_type; exp->repeat[tok_idx][i + 1] = 0; } else { exp->repeat[tok_idx] = calloc(2, sizeof *exp->repeat[tok_idx]); LY_CHECK_ERR_RET(!exp->repeat[tok_idx], LOGMEM(NULL), ); - exp->repeat[tok_idx][0] = repeat_op_idx; + exp->repeat[tok_idx][0] = repeat_expr_type; } } @@ -2994,7 +3034,7 @@ lyxp_expr_parse(const struct ly_ctx *ctx, const char *expr_str, size_t expr_len, parsed++; ncname_len = parse_ncname(&expr_str[parsed]); LY_CHECK_ERR_GOTO(ncname_len < 1, LOGVAL(ctx, LY_VCODE_XP_INEXPR, expr_str[parsed - ncname_len], - parsed - ncname_len + 1, expr_str); ret = LY_EVALID, error); + (uint32_t)(parsed - ncname_len + 1), expr_str); ret = LY_EVALID, error); tok_len = ncname_len; LY_CHECK_ERR_GOTO(expr_str[parsed + tok_len] == ':', LOGVAL(ctx, LYVE_XPATH, "Variable with prefix is not supported."); ret = LY_EVALID, @@ -3084,7 +3124,7 @@ lyxp_expr_parse(const struct ly_ctx *ctx, const char *expr_str, size_t expr_len, ret = LY_EVALID; goto error; } else { - LOGVAL(ctx, LY_VCODE_XP_INEXPR, expr_str[parsed], parsed + 1, expr_str); + LOGVAL(ctx, LY_VCODE_XP_INEXPR, expr_str[parsed], (uint32_t)(parsed + 1), expr_str); ret = LY_EVALID; goto error; } @@ -3096,7 +3136,7 @@ lyxp_expr_parse(const struct ly_ctx *ctx, const char *expr_str, size_t expr_len, } else { ncname_len = parse_ncname(&expr_str[parsed]); LY_CHECK_ERR_GOTO(ncname_len < 1, LOGVAL(ctx, LY_VCODE_XP_INEXPR, expr_str[parsed - ncname_len], - parsed - ncname_len + 1, expr_str); ret = LY_EVALID, error); + (uint32_t)(parsed - ncname_len + 1), expr_str); ret = LY_EVALID, error); } tok_len = ncname_len; @@ -3104,7 +3144,8 @@ lyxp_expr_parse(const struct ly_ctx *ctx, const char *expr_str, size_t expr_len, if (!strncmp(&expr_str[parsed + tok_len], "::", 2)) { /* axis */ LY_CHECK_ERR_GOTO(expr_parse_axis(&expr_str[parsed], ncname_len), - LOGVAL(ctx, LY_VCODE_XP_INEXPR, expr_str[parsed], parsed + 1, expr_str); ret = LY_EVALID, error); + LOGVAL(ctx, LY_VCODE_XP_INEXPR, expr_str[parsed], (uint32_t)(parsed + 1), expr_str); ret = LY_EVALID, + error); tok_type = LYXP_TOKEN_AXISNAME; LY_CHECK_GOTO(ret = exp_add_token(ctx, expr, tok_type, parsed, tok_len), error); @@ -3122,7 +3163,7 @@ lyxp_expr_parse(const struct ly_ctx *ctx, const char *expr_str, size_t expr_len, } else { ncname_len = parse_ncname(&expr_str[parsed]); LY_CHECK_ERR_GOTO(ncname_len < 1, LOGVAL(ctx, LY_VCODE_XP_INEXPR, expr_str[parsed - ncname_len], - parsed - ncname_len + 1, expr_str); ret = LY_EVALID, error); + (uint32_t)(parsed - ncname_len + 1), expr_str); ret = LY_EVALID, error); } tok_len = ncname_len; @@ -3136,7 +3177,7 @@ lyxp_expr_parse(const struct ly_ctx *ctx, const char *expr_str, size_t expr_len, } else { ncname_len = parse_ncname(&expr_str[parsed + tok_len]); LY_CHECK_ERR_GOTO(ncname_len < 1, LOGVAL(ctx, LY_VCODE_XP_INEXPR, expr_str[parsed - ncname_len], - parsed - ncname_len + 1, expr_str); ret = LY_EVALID, error); + (uint32_t)(parsed - ncname_len + 1), expr_str); ret = LY_EVALID, error); tok_len += ncname_len; } /* remove old flags to prevent ambiguities */ @@ -3934,10 +3975,10 @@ xpath_current(struct lyxp_set **args, uint32_t arg_count, struct lyxp_set *set, set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE); if (set->cur_scnode) { - LY_CHECK_RET(set_scnode_insert_node(set, set->cur_scnode, LYXP_NODE_ELEM, LYXP_AXIS_SELF, NULL)); + LY_CHECK_RET(lyxp_set_scnode_insert_node(set, set->cur_scnode, LYXP_NODE_ELEM, LYXP_AXIS_SELF, NULL)); } else { /* root node */ - LY_CHECK_RET(set_scnode_insert_node(set, NULL, set->root_type, LYXP_AXIS_SELF, NULL)); + LY_CHECK_RET(lyxp_set_scnode_insert_node(set, NULL, set->root_type, LYXP_AXIS_SELF, NULL)); } } else { lyxp_set_free_content(set); @@ -4003,7 +4044,7 @@ xpath_deref(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set target = p[LY_ARRAY_COUNT(p) - 1].node; ly_path_free(set->ctx, p); - LY_CHECK_RET(set_scnode_insert_node(set, target, LYXP_NODE_ELEM, LYXP_AXIS_SELF, NULL)); + LY_CHECK_RET(lyxp_set_scnode_insert_node(set, target, LYXP_NODE_ELEM, LYXP_AXIS_SELF, NULL)); } /* else the target was found before but is disabled so it was removed */ } @@ -4030,7 +4071,7 @@ xpath_deref(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set } } else { assert(sleaf->type->basetype == LY_TYPE_INST); - if (ly_path_eval(leaf->value.target, set->tree, &node)) { + if (ly_path_eval(leaf->value.target, set->tree, NULL, &node)) { LOGERR(set->ctx, LY_EVALID, "Invalid instance-identifier \"%s\" value - required instance not found.", lyd_get_value(&leaf->node)); return LY_EVALID; @@ -4274,9 +4315,25 @@ xpath_false(struct lyxp_set **UNUSED(args), uint32_t UNUSED(arg_count), struct l * @return LY_ERR */ static LY_ERR -xpath_floor(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t UNUSED(options)) +xpath_floor(struct lyxp_set **args, uint32_t UNUSED(arg_count), struct lyxp_set *set, uint32_t options) { - LY_ERR rc; + struct lysc_node_leaf *sleaf; + LY_ERR rc = LY_SUCCESS; + + if (options & LYXP_SCNODE_ALL) { + if (args[0]->type != LYXP_SET_SCNODE_SET) { + LOGWRN(set->ctx, "Argument #1 of %s not a node-set as expected.", __func__); + } else if ((sleaf = (struct lysc_node_leaf *)warn_get_scnode_in_ctx(args[0]))) { + if (!(sleaf->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { + LOGWRN(set->ctx, "Argument #1 of %s is a %s node \"%s\".", __func__, lys_nodetype2str(sleaf->nodetype), + sleaf->name); + } else if (!warn_is_specific_type(sleaf->type, LY_TYPE_DEC64)) { + LOGWRN(set->ctx, "Argument #1 of %s is node \"%s\", not of type \"decimal64\".", __func__, sleaf->name); + } + } + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_VAL); + return rc; + } rc = lyxp_set_cast(args[0], LYXP_SET_NUMBER); LY_CHECK_RET(rc); @@ -4474,7 +4531,7 @@ xpath_local_name(struct lyxp_set **args, uint32_t arg_count, struct lyxp_set *se set_fill_string(set, "", 0); break; case LYXP_NODE_ELEM: - set_fill_string(set, item->node->schema->name, strlen(item->node->schema->name)); + set_fill_string(set, LYD_NAME(item->node), strlen(LYD_NAME(item->node))); break; case LYXP_NODE_META: set_fill_string(set, ((struct lyd_meta *)item->node)->name, strlen(((struct lyd_meta *)item->node)->name)); @@ -4498,7 +4555,7 @@ static LY_ERR xpath_name(struct lyxp_set **args, uint32_t arg_count, struct lyxp_set *set, uint32_t options) { struct lyxp_set_node *item; - struct lys_module *mod = NULL; + const struct lys_module *mod = NULL; char *str; const char *name = NULL; @@ -4544,8 +4601,8 @@ xpath_name(struct lyxp_set **args, uint32_t arg_count, struct lyxp_set *set, uin /* keep NULL */ break; case LYXP_NODE_ELEM: - mod = item->node->schema->module; - name = item->node->schema->name; + mod = lyd_node_module(item->node); + name = LYD_NAME(item->node); break; case LYXP_NODE_META: mod = ((struct lyd_meta *)item->node)->annotation->module; @@ -4580,7 +4637,7 @@ static LY_ERR xpath_namespace_uri(struct lyxp_set **args, uint32_t arg_count, struct lyxp_set *set, uint32_t options) { struct lyxp_set_node *item; - struct lys_module *mod; + const struct lys_module *mod; /* suppress unused variable warning */ (void)options; @@ -4630,7 +4687,7 @@ xpath_namespace_uri(struct lyxp_set **args, uint32_t arg_count, struct lyxp_set case LYXP_NODE_ELEM: case LYXP_NODE_META: if (item->type == LYXP_NODE_ELEM) { - mod = item->node->schema->module; + mod = lyd_node_module(item->node); } else { /* LYXP_NODE_META */ /* annotations */ mod = ((struct lyd_meta *)item->node)->annotation->module; @@ -5541,7 +5598,7 @@ xpath_pi_text(struct lyxp_set *set, enum lyxp_axis axis, uint32_t options) case LYXP_NODE_NONE: LOGINT_RET(set->ctx); case LYXP_NODE_ELEM: - if (set->val.nodes[i].node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) { + if (!set->val.nodes[i].node->schema || (set->val.nodes[i].node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST))) { set->val.nodes[i].type = LYXP_NODE_TEXT; break; } @@ -5588,7 +5645,7 @@ moveto_resolve_model(const char **qname, uint32_t *qname_len, const struct lyxp_ /* check for errors and non-implemented modules, as they are not valid */ if (!mod || !mod->implemented) { - LOGVAL(set->ctx, LY_VCODE_XP_INMOD, pref_len, *qname); + LOGVAL(set->ctx, LY_VCODE_XP_INMOD, (int)pref_len, *qname); return LY_EVALID; } @@ -5641,10 +5698,9 @@ moveto_root(struct lyxp_set *set, uint32_t options) if (options & LYXP_SCNODE_ALL) { set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE); - LY_CHECK_RET(set_scnode_insert_node(set, NULL, set->root_type, LYXP_AXIS_SELF, NULL)); + LY_CHECK_RET(lyxp_set_scnode_insert_node(set, NULL, set->root_type, LYXP_AXIS_SELF, NULL)); } else { - set->type = LYXP_SET_NODE_SET; - set->used = 0; + lyxp_set_free_content(set); set_insert_node(set, NULL, 0, set->root_type, 0); set->non_child_axis = 0; } @@ -5668,6 +5724,8 @@ static LY_ERR moveto_node_check(const struct lyd_node *node, enum lyxp_node_type node_type, const struct lyxp_set *set, const char *node_name, const struct lys_module *moveto_mod, uint32_t options) { + const struct lysc_node *schema; + if ((node_type == LYXP_NODE_ROOT_CONFIG) || (node_type == LYXP_NODE_ROOT)) { assert(node_type == set->root_type); @@ -5681,39 +5739,40 @@ moveto_node_check(const struct lyd_node *node, enum lyxp_node_type node_type, co return LY_ENOT; } - if (!node->schema) { - /* opaque node never matches */ + /* get schema node even of an opaque node */ + schema = lyd_node_schema(node); + if (!schema) { + /* unknown opaque node never matches */ return LY_ENOT; } /* module check */ if (moveto_mod) { - if ((set->ctx == LYD_CTX(node)) && (node->schema->module != moveto_mod)) { + if ((set->ctx == LYD_CTX(node)) && (schema->module != moveto_mod)) { return LY_ENOT; - } else if ((set->ctx != LYD_CTX(node)) && strcmp(node->schema->module->name, moveto_mod->name)) { + } else if ((set->ctx != LYD_CTX(node)) && strcmp(schema->module->name, moveto_mod->name)) { return LY_ENOT; } } /* context check */ - if ((set->root_type == LYXP_NODE_ROOT_CONFIG) && (node->schema->flags & LYS_CONFIG_R)) { + if ((set->root_type == LYXP_NODE_ROOT_CONFIG) && (schema->flags & LYS_CONFIG_R)) { return LY_EINVAL; - } else if (set->context_op && (node->schema->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) && - (node->schema != set->context_op)) { + } else if (set->context_op && (schema->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) && (schema != set->context_op)) { return LY_EINVAL; } /* name check */ if (node_name) { - if ((set->ctx == LYD_CTX(node)) && (node->schema->name != node_name)) { + if ((set->ctx == LYD_CTX(node)) && (schema->name != node_name)) { return LY_ENOT; - } else if ((set->ctx != LYD_CTX(node)) && strcmp(node->schema->name, node_name)) { + } else if ((set->ctx != LYD_CTX(node)) && strcmp(schema->name, node_name)) { return LY_ENOT; } } /* when check, accept the context node because it should only be the path ".", we have checked the when is valid before */ - if (!(options & LYXP_IGNORE_WHEN) && lysc_has_when(node->schema) && !(node->flags & LYD_WHEN_TRUE) && + if (!(options & LYXP_IGNORE_WHEN) && lysc_has_when(schema) && !(node->flags & LYD_WHEN_TRUE) && (node != set->cur_node)) { return LY_EINCOMPLETE; } @@ -6144,7 +6203,7 @@ moveto_node_hash_child(struct lyxp_set *set, const struct lysc_node *scnode, con /* create specific data instance if needed */ if (scnode->nodetype == LYS_LIST) { - LY_CHECK_GOTO(ret = lyd_create_list(scnode, predicates, &inst), cleanup); + LY_CHECK_GOTO(ret = lyd_create_list(scnode, predicates, NULL, &inst), cleanup); } else if (scnode->nodetype == LYS_LEAFLIST) { LY_CHECK_GOTO(ret = lyd_create_term2(scnode, &predicates[0].value, &inst), cleanup); } @@ -6168,6 +6227,10 @@ moveto_node_hash_child(struct lyxp_set *set, const struct lysc_node *scnode, con } else { r = lyd_find_sibling_val(siblings, scnode, NULL, 0, &sub); } + if (r == LY_ENOTFOUND) { + /* may still be an opaque node */ + r = lyd_find_sibling_opaq_next(siblings, scnode->name, &sub); + } LY_CHECK_ERR_GOTO(r && (r != LY_ENOTFOUND), ret = r, cleanup); /* when check */ @@ -6655,7 +6718,7 @@ moveto_scnode(struct lyxp_set *set, const struct lys_module *moveto_mod, const c { ly_bool temp_ctx = 0; uint32_t getnext_opts, orig_used, i, mod_idx, idx; - const struct lys_module *mod; + const struct lys_module *mod = NULL; const struct lysc_node *iter; enum lyxp_node_type iter_type; @@ -6673,6 +6736,9 @@ moveto_scnode(struct lyxp_set *set, const struct lys_module *moveto_mod, const c if (options & LYXP_SCNODE_OUTPUT) { getnext_opts |= LYS_GETNEXT_OUTPUT; } + if (options & LYXP_SCNODE_SCHEMAMOUNT) { + getnext_opts |= LYS_GETNEXT_WITHSCHEMAMOUNT; + } orig_used = set->used; for (i = 0; i < orig_used; ++i) { @@ -6691,7 +6757,7 @@ moveto_scnode(struct lyxp_set *set, const struct lys_module *moveto_mod, const c } /* insert */ - LY_CHECK_RET(set_scnode_insert_node(set, iter, iter_type, axis, &idx)); + LY_CHECK_RET(lyxp_set_scnode_insert_node(set, iter, iter_type, axis, &idx)); /* we need to prevent these nodes from being considered in this moveto */ if ((idx < orig_used) && (idx > i)) { @@ -6704,7 +6770,7 @@ moveto_scnode(struct lyxp_set *set, const struct lys_module *moveto_mod, const c (set->val.scnodes[i].type == LYXP_NODE_ELEM) && !ly_nested_ext_schema(NULL, set->val.scnodes[i].scnode, moveto_mod->name, strlen(moveto_mod->name), LY_VALUE_JSON, NULL, ncname, strlen(ncname), &iter, NULL)) { /* there is a matching node from an extension, use it */ - LY_CHECK_RET(set_scnode_insert_node(set, iter, LYXP_NODE_ELEM, axis, &idx)); + LY_CHECK_RET(lyxp_set_scnode_insert_node(set, iter, LYXP_NODE_ELEM, axis, &idx)); if ((idx < orig_used) && (idx > i)) { set->val.scnodes[idx].in_ctx = LYXP_SET_SCNODE_ATOM_NEW_CTX; temp_ctx = 1; @@ -6848,7 +6914,7 @@ moveto_scnode_dfs(struct lyxp_set *set, const struct lysc_node *start, uint32_t goto skip_children; } } else { - LY_CHECK_RET(set_scnode_insert_node(set, elem, LYXP_NODE_ELEM, LYXP_AXIS_DESCENDANT, NULL)); + LY_CHECK_RET(lyxp_set_scnode_insert_node(set, elem, LYXP_NODE_ELEM, LYXP_AXIS_DESCENDANT, NULL)); } } else if (rc == LY_EINVAL) { goto skip_children; @@ -7459,7 +7525,11 @@ only_parse: *tok_idx = orig_exp; rc = eval_expr_select(exp, tok_idx, 0, &set2, options); - if (rc != LY_SUCCESS) { + if (!rc && set2.not_found) { + set->not_found = 1; + break; + } + if (rc) { lyxp_set_free_content(&set2); return rc; } @@ -7508,6 +7578,9 @@ only_parse: *tok_idx = orig_exp; rc = eval_expr_select(exp, tok_idx, 0, set, options); + if (!rc && set->not_found) { + break; + } LY_CHECK_RET(rc); set->val.scnodes[i].in_ctx = pred_in_ctx; @@ -7527,7 +7600,7 @@ only_parse: set_fill_set(&set2, set); rc = eval_expr_select(exp, tok_idx, 0, &set2, options); - if (rc != LY_SUCCESS) { + if (rc) { lyxp_set_free_content(&set2); return rc; } @@ -7702,14 +7775,13 @@ cleanup: * @param[in] ctx_scnode Found schema node as the context for the predicate. * @param[in] set Context set. * @param[out] predicates Parsed predicates. - * @param[out] pred_type Type of @p predicates. * @return LY_SUCCESS on success, * @return LY_ENOT if a predicate could not be compiled. * @return LY_ERR on any error. */ static LY_ERR eval_name_test_try_compile_predicates(const struct lyxp_expr *exp, uint32_t *tok_idx, const struct lysc_node *ctx_scnode, - const struct lyxp_set *set, struct ly_path_predicate **predicates, enum ly_path_pred_type *pred_type) + const struct lyxp_set *set, struct ly_path_predicate **predicates) { LY_ERR rc = LY_SUCCESS; uint32_t e_idx, val_start_idx, pred_idx = 0, temp_lo = 0, pred_len = 0, nested_pred; @@ -7848,7 +7920,7 @@ eval_name_test_try_compile_predicates(const struct lyxp_expr *exp, uint32_t *tok /* compile */ rc = ly_path_compile_predicate(set->ctx, set->cur_node ? set->cur_node->schema : NULL, set->cur_mod, ctx_scnode, exp2, - &pred_idx, LY_VALUE_JSON, NULL, predicates, pred_type); + &pred_idx, LY_VALUE_JSON, NULL, predicates); LY_CHECK_GOTO(rc, cleanup); /* success, the predicate must include all the needed information for hash-based search */ @@ -7881,7 +7953,7 @@ eval_name_test_with_predicate_get_scnode(const struct ly_ctx *ctx, const struct uint32_t name_len, const struct lys_module *moveto_mod, enum lyxp_node_type root_type, LY_VALUE_FORMAT format, const struct lysc_node **found) { - const struct lysc_node *scnode; + const struct lysc_node *scnode, *scnode2; const struct lys_module *mod; uint32_t idx = 0; @@ -7919,7 +7991,19 @@ continue_search: } /* search in children, do not repeat the same search */ - scnode = lys_find_child(node->schema, moveto_mod, name, name_len, 0, 0); + if (node->schema->nodetype & (LYS_RPC | LYS_ACTION)) { + /* make sure the node is unique, whether in input or output */ + scnode = lys_find_child(node->schema, moveto_mod, name, name_len, 0, 0); + scnode2 = lys_find_child(node->schema, moveto_mod, name, name_len, 0, LYS_GETNEXT_OUTPUT); + if (scnode && scnode2) { + /* conflict, do not use hashes */ + scnode = NULL; + } else if (scnode2) { + scnode = scnode2; + } + } else { + scnode = lys_find_child(node->schema, moveto_mod, name, name_len, 0, 0); + } } /* else skip redundant search */ /* additional context check */ @@ -7977,14 +8061,14 @@ eval_name_test_scnode_no_match_msg(struct lyxp_set *set, const struct lyxp_set_s if (ppath) { format = "Schema node \"%.*s\" for parent \"%s\" not found; in expr \"%.*s\" with context node \"%s\"."; if (options & LYXP_SCNODE_ERROR) { - LOGERR(set->ctx, LY_EVALID, format, ncname_len, ncname, ppath, (ncname - expr) + ncname_len, expr, path); + LOGERR(set->ctx, LY_ENOTFOUND, format, ncname_len, ncname, ppath, (ncname - expr) + ncname_len, expr, path); } else { LOGWRN(set->ctx, format, ncname_len, ncname, ppath, (ncname - expr) + ncname_len, expr, path); } } else { format = "Schema node \"%.*s\" not found; in expr \"%.*s\" with context node \"%s\"."; if (options & LYXP_SCNODE_ERROR) { - LOGERR(set->ctx, LY_EVALID, format, ncname_len, ncname, (ncname - expr) + ncname_len, expr, path); + LOGERR(set->ctx, LY_ENOTFOUND, format, ncname_len, ncname, (ncname - expr) + ncname_len, expr, path); } else { LOGWRN(set->ctx, format, ncname_len, ncname, (ncname - expr) + ncname_len, expr, path); } @@ -8018,7 +8102,6 @@ eval_name_test_with_predicate(const struct lyxp_expr *exp, uint32_t *tok_idx, en const struct lys_module *moveto_mod = NULL; const struct lysc_node *scnode = NULL; struct ly_path_predicate *predicates = NULL; - enum ly_path_pred_type pred_type = 0; int scnode_skip_pred = 0; LOGDBG(LY_LDGXPATH, "%-27s %s %s[%u]", __func__, (options & LYXP_SKIP_EXPR ? "skipped" : "parsed"), @@ -8060,7 +8143,7 @@ eval_name_test_with_predicate(const struct lyxp_expr *exp, uint32_t *tok_idx, en if (scnode && (scnode->nodetype & (LYS_LIST | LYS_LEAFLIST))) { /* try to create the predicates */ - if (eval_name_test_try_compile_predicates(exp, tok_idx, scnode, set, &predicates, &pred_type)) { + if (eval_name_test_try_compile_predicates(exp, tok_idx, scnode, set, &predicates)) { /* hashes cannot be used */ scnode = NULL; } @@ -8132,8 +8215,7 @@ moveto: if (options & LYXP_SCNODE_ERROR) { /* error */ - rc = LY_EVALID; - goto cleanup; + set->not_found = 1; } /* skip the predicates and the rest of this path to not generate invalid warnings */ @@ -8175,10 +8257,8 @@ cleanup: /* restore options */ options &= ~LYXP_SKIP_EXPR; } - if (!(options & LYXP_SKIP_EXPR)) { - lydict_remove(set->ctx, ncname_dict); - ly_path_predicates_free(set->ctx, pred_type, predicates); - } + lydict_remove(set->ctx, ncname_dict); + ly_path_predicates_free(set->ctx, predicates); return rc; } @@ -8350,8 +8430,9 @@ step: rc = eval_name_test_with_predicate(exp, tok_idx, axis, all_desc, set, options); if (rc == LY_ENOT) { assert(options & LYXP_SCNODE_ALL); - /* skip the rest of this path */ rc = LY_SUCCESS; + + /* skip the rest of this path */ scnode_skip_path = 1; options |= LYXP_SKIP_EXPR; } @@ -8600,8 +8681,9 @@ eval_function_call(const struct lyxp_expr *exp, uint32_t *tok_idx, struct lyxp_s rc = eval_expr_select(exp, tok_idx, 0, args[0], options); LY_CHECK_GOTO(rc, cleanup); + set->not_found = args[0]->not_found; } else { - rc = eval_expr_select(exp, tok_idx, 0, set, options | LYXP_SKIP_EXPR); + rc = eval_expr_select(exp, tok_idx, 0, set, options); LY_CHECK_GOTO(rc, cleanup); } } @@ -8623,8 +8705,11 @@ eval_function_call(const struct lyxp_expr *exp, uint32_t *tok_idx, struct lyxp_s rc = eval_expr_select(exp, tok_idx, 0, args[arg_count - 1], options); LY_CHECK_GOTO(rc, cleanup); + if (args[arg_count - 1]->not_found) { + set->not_found = 1; + } } else { - rc = eval_expr_select(exp, tok_idx, 0, set, options | LYXP_SKIP_EXPR); + rc = eval_expr_select(exp, tok_idx, 0, set, options); LY_CHECK_GOTO(rc, cleanup); } } @@ -8698,27 +8783,30 @@ eval_number(struct ly_ctx *ctx, const struct lyxp_expr *exp, uint32_t *tok_idx, } LY_ERR -lyxp_vars_find(struct lyxp_var *vars, const char *name, size_t name_len, struct lyxp_var **var) +lyxp_vars_find(const struct ly_ctx *ctx, const struct lyxp_var *vars, const char *name, size_t name_len, + struct lyxp_var **var) { - LY_ERR ret = LY_ENOTFOUND; LY_ARRAY_COUNT_TYPE u; - assert(vars && name); + assert(name); - name_len = name_len ? name_len : strlen(name); + if (!name_len) { + name_len = strlen(name); + } LY_ARRAY_FOR(vars, u) { if (!strncmp(vars[u].name, name, name_len)) { - ret = LY_SUCCESS; - break; + if (var) { + *var = (struct lyxp_var *)&vars[u]; + } + return LY_SUCCESS; } } - if (var && !ret) { - *var = &vars[u]; + if (ctx) { + LOGERR(ctx, LY_ENOTFOUND, "Variable \"%.*s\" not defined.", (int)name_len, name); } - - return ret; + return LY_ENOTFOUND; } /** @@ -8737,17 +8825,14 @@ eval_variable_reference(const struct lyxp_expr *exp, uint32_t *tok_idx, struct l LY_ERR ret; const char *name; struct lyxp_var *var; - const struct lyxp_var *vars; struct lyxp_expr *tokens = NULL; uint32_t token_index, name_len; - vars = set->vars; - /* find out the name and value of the variable */ name = &exp->expr[exp->tok_pos[*tok_idx]]; name_len = exp->tok_len[*tok_idx]; - ret = lyxp_vars_find((struct lyxp_var *)vars, name, name_len, &var); - LY_CHECK_ERR_RET(ret, LOGERR(set->ctx, ret, "XPath variable \"%.*s\" not defined.", (int)name_len, name), ret); + ret = lyxp_vars_find(set->ctx, set->vars, name, name_len, &var); + LY_CHECK_RET(ret); /* parse value */ ret = lyxp_expr_parse(set->ctx, var->value, 0, 1, &tokens); @@ -8917,8 +9002,9 @@ static LY_ERR eval_union_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, struct lyxp_set *set, uint32_t options) { LY_ERR rc = LY_SUCCESS; - struct lyxp_set orig_set, set2; uint32_t i; + struct lyxp_set orig_set, set2; + ly_bool found = 0; assert(repeat); @@ -8929,6 +9015,11 @@ eval_union_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_UNION, set, options); LY_CHECK_GOTO(rc, cleanup); + if (set->not_found) { + set->not_found = 0; + } else { + found = 1; + } /* ('|' PathExpr)* */ for (i = 0; i < repeat; ++i) { @@ -8946,6 +9037,9 @@ eval_union_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, set_fill_set(&set2, &orig_set); rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_UNION, &set2, options); LY_CHECK_GOTO(rc, cleanup); + if (!set2.not_found) { + found = 1; + } /* eval */ if (options & LYXP_SCNODE_ALL) { @@ -8959,6 +9053,9 @@ eval_union_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, cleanup: lyxp_set_free_content(&orig_set); lyxp_set_free_content(&set2); + if (!found) { + set->not_found = 1; + } return rc; } @@ -9026,7 +9123,7 @@ static LY_ERR eval_multiplicative_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, struct lyxp_set *set, uint32_t options) { - LY_ERR rc; + LY_ERR rc = LY_SUCCESS; uint32_t i, this_op; struct lyxp_set orig_set, set2; @@ -9058,6 +9155,9 @@ eval_multiplicative_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_ set_fill_set(&set2, &orig_set); rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_MULTIPLICATIVE, &set2, options); LY_CHECK_GOTO(rc, cleanup); + if (set2.not_found) { + set->not_found = 1; + } /* eval */ if (options & LYXP_SCNODE_ALL) { @@ -9093,7 +9193,7 @@ cleanup: static LY_ERR eval_additive_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, struct lyxp_set *set, uint32_t options) { - LY_ERR rc; + LY_ERR rc = LY_SUCCESS; uint32_t i, this_op; struct lyxp_set orig_set, set2; @@ -9125,6 +9225,9 @@ eval_additive_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repe set_fill_set(&set2, &orig_set); rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_ADDITIVE, &set2, options); LY_CHECK_GOTO(rc, cleanup); + if (set2.not_found) { + set->not_found = 1; + } /* eval */ if (options & LYXP_SCNODE_ALL) { @@ -9162,7 +9265,7 @@ cleanup: static LY_ERR eval_relational_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, struct lyxp_set *set, uint32_t options) { - LY_ERR rc; + LY_ERR rc = LY_SUCCESS; uint32_t i, this_op; struct lyxp_set orig_set, set2; @@ -9194,6 +9297,9 @@ eval_relational_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t re set_fill_set(&set2, &orig_set); rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_RELATIONAL, &set2, options); LY_CHECK_GOTO(rc, cleanup); + if (set2.not_found) { + set->not_found = 1; + } /* eval */ if (options & LYXP_SCNODE_ALL) { @@ -9231,7 +9337,7 @@ cleanup: static LY_ERR eval_equality_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, struct lyxp_set *set, uint32_t options) { - LY_ERR rc; + LY_ERR rc = LY_SUCCESS; uint32_t i, this_op; struct lyxp_set orig_set, set2; @@ -9263,6 +9369,9 @@ eval_equality_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repe set_fill_set(&set2, &orig_set); rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_EQUALITY, &set2, options); LY_CHECK_GOTO(rc, cleanup); + if (set2.not_found) { + set->not_found = 1; + } /* eval */ if (options & LYXP_SCNODE_ALL) { @@ -9301,7 +9410,7 @@ cleanup: static LY_ERR eval_and_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, struct lyxp_set *set, uint32_t options) { - LY_ERR rc; + LY_ERR rc = LY_SUCCESS; struct lyxp_set orig_set, set2; uint32_t i; @@ -9315,11 +9424,13 @@ eval_and_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, s rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_AND, set, options); LY_CHECK_GOTO(rc, cleanup); - /* cast to boolean, we know that will be the final result */ - if (!(options & LYXP_SKIP_EXPR) && (options & LYXP_SCNODE_ALL)) { - set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE); - } else { - lyxp_set_cast(set, LYXP_SET_BOOLEAN); + if (!(options & LYXP_SKIP_EXPR)) { + if (options & LYXP_SCNODE_ALL) { + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE); + } else { + /* cast to boolean, we know that will be the final result */ + lyxp_set_cast(set, LYXP_SET_BOOLEAN); + } } /* ('and' EqualityExpr)* */ @@ -9339,6 +9450,9 @@ eval_and_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, s set_fill_set(&set2, &orig_set); rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_AND, &set2, options); LY_CHECK_GOTO(rc, cleanup); + if (set2.not_found) { + set->not_found = 1; + } /* eval - just get boolean value actually */ if (set->type == LYXP_SET_SCNODE_SET) { @@ -9371,7 +9485,7 @@ cleanup: static LY_ERR eval_or_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, struct lyxp_set *set, uint32_t options) { - LY_ERR rc; + LY_ERR rc = LY_SUCCESS; struct lyxp_set orig_set, set2; uint32_t i; @@ -9385,11 +9499,13 @@ eval_or_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, st rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_OR, set, options); LY_CHECK_GOTO(rc, cleanup); - /* cast to boolean, we know that will be the final result */ - if (!(options & LYXP_SKIP_EXPR) && (options & LYXP_SCNODE_ALL)) { - set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE); - } else { - lyxp_set_cast(set, LYXP_SET_BOOLEAN); + if (!(options & LYXP_SKIP_EXPR)) { + if (options & LYXP_SCNODE_ALL) { + set_scnode_clear_ctx(set, LYXP_SET_SCNODE_ATOM_NODE); + } else { + /* cast to boolean, we know that will be the final result */ + lyxp_set_cast(set, LYXP_SET_BOOLEAN); + } } /* ('or' AndExpr)* */ @@ -9411,6 +9527,9 @@ eval_or_expr(const struct lyxp_expr *exp, uint32_t *tok_idx, uint32_t repeat, st * but it does not matter */ rc = eval_expr_select(exp, tok_idx, LYXP_EXPR_OR, &set2, options); LY_CHECK_GOTO(rc, cleanup); + if (set2.not_found) { + set->not_found = 1; + } /* eval - just get boolean value actually */ if (set->type == LYXP_SET_SCNODE_SET) { @@ -9523,7 +9642,7 @@ lyxp_get_root_type(const struct lyd_node *ctx_node, const struct lysc_node *ctx_ /* schema */ for (op = ctx_scnode; op && !(op->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)); op = op->parent) {} - if (op || (options & LYXP_SCNODE)) { + if (op || !(options & LYXP_SCNODE_SCHEMA)) { /* general root that can access everything */ return LYXP_NODE_ROOT; } else if (!ctx_scnode || (ctx_scnode->flags & LYS_CONFIG_W)) { @@ -9597,7 +9716,10 @@ lyxp_eval(const struct ly_ctx *ctx, const struct lyxp_expr *exp, const struct ly /* evaluate */ rc = eval_expr_select(exp, &tok_idx, 0, set, options); - if (rc != LY_SUCCESS) { + if (!rc && set->not_found) { + rc = LY_ENOTFOUND; + } + if (rc) { lyxp_set_free_content(set); } @@ -9838,7 +9960,7 @@ lyxp_atomize(const struct ly_ctx *ctx, const struct lyxp_expr *exp, const struct LY_VALUE_FORMAT format, void *prefix_data, const struct lysc_node *cur_scnode, const struct lysc_node *ctx_scnode, struct lyxp_set *set, uint32_t options) { - LY_ERR ret; + LY_ERR rc; uint32_t tok_idx = 0; LY_CHECK_ARG_RET(ctx, ctx, exp, set, LY_EINVAL); @@ -9851,7 +9973,7 @@ lyxp_atomize(const struct ly_ctx *ctx, const struct lyxp_expr *exp, const struct memset(set, 0, sizeof *set); set->type = LYXP_SET_SCNODE_SET; set->root_type = lyxp_get_root_type(NULL, ctx_scnode, options); - LY_CHECK_RET(set_scnode_insert_node(set, ctx_scnode, ctx_scnode ? LYXP_NODE_ELEM : set->root_type, LYXP_AXIS_SELF, NULL)); + LY_CHECK_RET(lyxp_set_scnode_insert_node(set, ctx_scnode, ctx_scnode ? LYXP_NODE_ELEM : set->root_type, LYXP_AXIS_SELF, NULL)); set->val.scnodes[0].in_ctx = LYXP_SET_SCNODE_START; set->ctx = (struct ly_ctx *)ctx; @@ -9866,10 +9988,13 @@ lyxp_atomize(const struct ly_ctx *ctx, const struct lyxp_expr *exp, const struct LOG_LOCSET(set->cur_scnode, NULL, NULL, NULL); /* evaluate */ - ret = eval_expr_select(exp, &tok_idx, 0, set, options); + rc = eval_expr_select(exp, &tok_idx, 0, set, options); + if (!rc && set->not_found) { + rc = LY_ENOTFOUND; + } LOG_LOCBACK(set->cur_scnode ? 1 : 0, 0, 0, 0); - return ret; + return rc; } LIBYANG_API_DEF const char * diff --git a/src/xpath.h b/src/xpath.h index 3e61bb0..17bda6c 100644 --- a/src/xpath.h +++ b/src/xpath.h @@ -291,13 +291,15 @@ struct lyxp_set { /* this is valid only for type LYXP_SET_NODE_SET and LYXP_SET_SCNODE_SET */ uint32_t used; /**< Number of nodes in the set. */ uint32_t size; /**< Allocated size for the set. */ - struct hash_table *ht; /**< Hash table for quick determination of whether a node is in the set. */ + struct ly_ht *ht; /**< Hash table for quick determination of whether a node is in the set. */ /* XPath context information, this is valid only for type LYXP_SET_NODE_SET */ uint32_t ctx_pos; /**< Position of the current examined node in the set. */ uint32_t ctx_size; /**< Position of the last node at the time the node was examined. */ ly_bool non_child_axis; /**< Whether any node change was performed on a non-child axis. */ + ly_bool not_found; /**< Set if a node is not found and it is considered an error. */ + /* general context */ struct ly_ctx *ctx; /**< General context for logging. */ @@ -327,6 +329,8 @@ const char *lyxp_token2str(enum lyxp_token tok); * @brief Evaluate an XPath expression on data. Be careful when using this function, the result can often * be confusing without thorough understanding of XPath evaluation rules defined in RFC 7950. * + * Traverses (valid) opaque nodes in the evaluation. + * * @param[in] ctx libyang context to use. * @param[in] exp Parsed XPath expression to be evaluated. * @param[in] cur_mod Current module for the expression (where it was "instantiated"). @@ -381,6 +385,7 @@ LY_ERR lyxp_atomize(const struct ly_ctx *ctx, const struct lyxp_expr *exp, const warning is printed. */ #define LYXP_ACCESS_TREE_ALL 0x80 /**< Explicit accessible tree of all the nodes. */ #define LYXP_ACCESS_TREE_CONFIG 0x0100 /**< Explicit accessible tree of only configuration data. */ +#define LYXP_SCNODE_SCHEMAMOUNT LYS_FIND_SCHEMAMOUNT /**< Nodes from mounted modules are also accessible. */ /** * @brief Cast XPath set to another type. @@ -421,6 +426,20 @@ ly_bool lyxp_set_scnode_contains(struct lyxp_set *set, const struct lysc_node *n void lyxp_set_scnode_merge(struct lyxp_set *set1, struct lyxp_set *set2); /** + * @brief Insert schema node into set. + * + * @param[in] set Set to insert into. + * @param[in] node Node to insert. + * @param[in] node_type Node type of @p node. + * @param[in] axis Axis that @p node was reached on. + * @param[out] index_p Optional pointer to store the index of the inserted @p node. + * @return LY_SUCCESS on success. + * @return LY_EMEM on memory allocation failure. + */ +LY_ERR lyxp_set_scnode_insert_node(struct lyxp_set *set, const struct lysc_node *node, enum lyxp_node_type node_type, + enum lyxp_axis axis, uint32_t *index_p); + +/** * @brief Parse an XPath expression into a structure of tokens. * Logs directly. * @@ -496,15 +515,17 @@ LY_ERR lyxp_next_token2(const struct ly_ctx *ctx, const struct lyxp_expr *exp, u enum lyxp_token want_tok1, enum lyxp_token want_tok2); /** - * @brief Find variable named @name in @p vars. + * @brief Find variable named @p name in @p vars. * + * @param[in] ctx Context for logging, not logged if NULL. * @param[in] vars [Sized array](@ref sizedarrays) of XPath variables. * @param[in] name Name of the variable being searched. * @param[in] name_len Name length can be set to 0 if @p name is terminated by null byte. * @param[out] var Variable that was found. The parameter is optional. * @return LY_SUCCESS if the variable was found, otherwise LY_ENOTFOUND. */ -LY_ERR lyxp_vars_find(struct lyxp_var *vars, const char *name, size_t name_len, struct lyxp_var **var); +LY_ERR lyxp_vars_find(const struct ly_ctx *ctx, const struct lyxp_var *vars, const char *name, size_t name_len, + struct lyxp_var **var); /** * @brief Frees a parsed XPath expression. @p expr should not be used afterwards. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6f36f31..259ef34 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -45,6 +45,8 @@ if(ENABLE_TESTS) add_subdirectory(style) add_subdirectory(fuzz) endif() + add_subdirectory(yanglint) + add_subdirectory(yangre) endif() if(ENABLE_PERF_TESTS) add_subdirectory(perf) diff --git a/tests/tool_i.tcl b/tests/tool_i.tcl new file mode 100644 index 0000000..d0f3d4b --- /dev/null +++ b/tests/tool_i.tcl @@ -0,0 +1,156 @@ +# @brief Common functions and variables for Tool Under Test (TUT). +# +# The script requires variables: +# TUT_PATH - Assumed absolute path to the directory in which the TUT is located. +# TUT_NAME - TUT name (without path). +# +# The script sets the variables: +# TUT - The path (including the name) of the executable TUT. +# error_prompt - Delimiter on error. +# error_head - Header on error. + +package require Expect + +# Complete the path for Tool Under Test (TUT). For example, on Windows, TUT can be located in the Debug or Release +# subdirectory. Note that Release build takes precedence over Debug. +set conftypes {{} Release Debug} +foreach i $conftypes { + if { [file executable "$TUT_PATH/$i/$TUT_NAME"] || [file executable "$TUT_PATH/$i/$TUT_NAME.exe"] } { + set TUT "$TUT_PATH/$i/$TUT_NAME" + break + } +} +if {![info exists TUT]} { + error "$TUT_NAME executable not found" +} + +# prompt of error message +set error_prompt ">>>" +# the beginning of error message +set error_head "$error_prompt Check-failed" + +# detection on eof and timeout will be on every expect command +expect_after { + eof { + global error_head + error "$error_head unexpected termination" + } timeout { + global error_head + error "$error_head timeout" + } +} + +# Run commands from command line +tcltest::loadTestedCommands + +# namespace of internal functions +namespace eval ly::private {} + +# Send command 'cmd' to the process, then check output string by 'pattern'. +# Parameter cmd is a string of arguments. +# Parameter pattern is a regex or an exact string to match. If is not specified, only prompt assumed afterwards. +# It must not contain a prompt. There can be an '$' character at the end of the pattern, in which case the regex +# matches the characters before the prompt. +# Parameter 'opt' can contain: +# -ex has a similar meaning to the expect command. The 'pattern' parameter is used as a simple string +# for exact matching of the output. So 'pattern' is not a regular expression but some characters +# must still be escaped, eg ][. +proc ly_cmd {cmd {pattern ""} {opt ""}} { + global prompt + + send -- "${cmd}\r" + expect -- "${cmd}\r\n" + + if { $pattern eq "" } { + # command without output + expect ^$prompt + return + } + + # definition of an expression that matches failure + set failure_pattern "\r\n${prompt}$" + + if { $opt eq "" && [string index $pattern end] eq "$"} { + # check output by regular expression + # It was explicitly specified how the expression should end. + set pattern [string replace $pattern end end] + expect { + -re "${pattern}\r\n${prompt}$" {} + -re $failure_pattern { + error "unexpected output:\n$expect_out(buffer)" + } + } + } elseif { $opt eq "" } { + # check output by regular expression + expect { + -re "${pattern}.*\r\n${prompt}$" {} + -re $failure_pattern { + error "unexpected output:\n$expect_out(buffer)" + } + } + } elseif { $opt eq "-ex" } { + # check output by exact matching + expect { + -ex "${pattern}\r\n${prompt}" {} + -re $failure_pattern { + error "unexpected output:\n$expect_out(buffer)" + } + } + } else { + global error_head + error "$error_head unrecognized value of parameter 'opt'" + } +} + +# Send command 'cmd' to the process, expect some header and then check output string by 'pattern'. +# This function is useful for checking an error that appears in the form of a header. +# Parameter header is the expected header on the output. +# Parameter cmd is a string of arguments. +# Parameter pattern is a regex. It must not contain a prompt. +proc ly_cmd_header {cmd header pattern} { + global prompt + + send -- "${cmd}\r" + expect -- "${cmd}\r\n" + + expect { + -re "$header .*${pattern}.*\r\n${prompt}$" {} + -re "\r\n${prompt}$" { + error "unexpected output:\n$expect_out(buffer)" + } + } +} + +# Whatever is written is sent, output is ignored and then another prompt is expected. +# Parameter cmd is optional and any output is ignored. +proc ly_ignore {{cmd ""}} { + global prompt + + send "${cmd}\r" + expect -re "$prompt$" +} + +# Send a completion request and check if the anchored regex output matches. +proc ly_completion {input output} { + global prompt + + send -- "${input}\t" + # expecting echoing input, output and 10 terminal control characters + expect -re "^${input}\r${prompt}${output}.*\r.*$" +} + +# Send a completion request and check if the anchored regex hint options match. +proc ly_hint {input prev_input hints} { + global prompt + + set output {} + foreach i $hints { + # each element might have some number of spaces and CRLF around it + append output "${i} *(?:\\r\\n)?" + } + + send -- "${input}\t" + # expecting the hints, previous input from which the hints were generated + # and some number of terminal control characters + expect -re "${output}\r${prompt}${prev_input}.*\r.*$" +} diff --git a/tests/tool_ni.tcl b/tests/tool_ni.tcl new file mode 100644 index 0000000..7282d35 --- /dev/null +++ b/tests/tool_ni.tcl @@ -0,0 +1,141 @@ +# @brief Common functions and variables for Tool Under Test (TUT). +# +# The script requires variables: +# TUT_PATH - Assumed absolute path to the directory in which the TUT is located. +# TUT_NAME - TUT name (without path). +# +# The script sets the variables: +# TUT - The path (including the name) of the executable TUT. +# error_prompt - Delimiter on error. +# error_head - Header on error. + +# Complete the path for Tool Under Test (TUT). For example, on Windows, TUT can be located in the Debug or Release +# subdirectory. Note that Release build takes precedence over Debug. +set conftypes {{} Release Debug} +foreach i $conftypes { + if { [file executable "$TUT_PATH/$i/$TUT_NAME"] || [file executable "$TUT_PATH/$i/$TUT_NAME.exe"] } { + set TUT "$TUT_PATH/$i/$TUT_NAME" + break + } +} +if {![info exists TUT]} { + error "$TUT_NAME executable not found" +} + +# prompt of error message +set error_prompt ">>>" +# the beginning of error message +set error_head "$error_prompt Check-failed" + +# Run commands from command line +tcltest::loadTestedCommands + +# namespace of internal functions +namespace eval ly::private { + namespace export * +} + +# Run the process with arguments. +# Parameter cmd is a string with arguments. +# Parameter wrn is a flag. Set to 1 if stderr should be ignored. +# Returns a pair where the first is the return code and the second is the output. +proc ly::private::ly_exec {cmd {wrn ""}} { + global TUT + try { + set results [exec -- $TUT {*}$cmd] + set status 0 + } trap CHILDSTATUS {results options} { + # return code is not 0 + set status [lindex [dict get $options -errorcode] 2] + } trap NONE results { + if { $wrn == 1 } { + set status 0 + } else { + error "return code is 0 but something was written to stderr:\n$results\n" + } + } trap CHILDKILLED {results options} { + set status [lindex [dict get $options -errorcode] 2] + error "process was killed: $status" + } + list $status $results +} + +# Internal function. +# Check the output with pattern. +# Parameter pattern is a regex or an exact string to match. +# Parameter msg is the output to check. +# Parameter 'opt' is optional. If contains '-ex', then the 'pattern' parameter is +# used as a simple string for exact matching of the output. +proc ly::private::output_check {pattern msg {opt ""}} { + if { $opt eq "" } { + expr {![regexp -- $pattern $msg]} + } elseif { $opt eq "-ex" } { + expr {![string equal "$pattern" $msg]} + } else { + global error_head + error "$error_head unrecognized value of parameter 'opt'" + } +} + +# Execute yanglint with arguments and expect success. +# Parameter cmd is a string of arguments. +# Parameter pattern is a regex or an exact string to match. +# Parameter 'opt' is optional. If contains '-ex', then the 'pattern' parameter is +# used as a simple string for exact matching of the output. +proc ly_cmd {cmd {pattern ""} {opt ""}} { + namespace import ly::private::* + lassign [ly_exec $cmd] rc msg + if { $rc != 0 } { + error "unexpected return code $rc:\n$msg\n" + } + if { $pattern ne "" && [output_check $pattern $msg $opt] } { + error "unexpected output:\n$msg\n" + } + return +} + +# Execute yanglint with arguments and expect error. +# Parameter cmd is a string of arguments. +# Parameter pattern is a regex. +proc ly_cmd_err {cmd pattern} { + namespace import ly::private::* + lassign [ly_exec $cmd] rc msg + if { $rc == 0 } { + error "unexpected return code $rc" + } + if { [output_check $pattern $msg] } { + error "unexpected output:\n$msg\n" + } + return +} + +# Execute yanglint with arguments, expect warning in stderr but success. +# Parameter cmd is a string of arguments. +# Parameter pattern is a regex. +proc ly_cmd_wrn {cmd pattern} { + namespace import ly::private::* + lassign [ly_exec $cmd 1] rc msg + if { $rc != 0 } { + error "unexpected return code $rc:\n$msg\n" + } + if { [output_check $pattern $msg] } { + error "unexpected output:\n$msg\n" + } + return +} + +# Check if yanglint supports the specified option. +# Parameter opt is a option to be found. +# Return true if option is found otherwise false. +proc ly_opt_exists {opt} { + namespace import ly::private::* + lassign [ly_exec "--help"] rc msg + if { $rc != 0 } { + error "unexpected return code $rc:\n$msg\n" + } + if { [output_check $opt $msg] } { + return false + } else { + return true + } +} diff --git a/tests/utests/basic/test_common.c b/tests/utests/basic/test_common.c index 75235a2..46b80ab 100644 --- a/tests/utests/basic/test_common.c +++ b/tests/utests/basic/test_common.c @@ -1,4 +1,4 @@ -/* +/** * @file test_common.c * @author: Radek Krejci <rkrejci@cesnet.cz> * @brief unit tests for functions from common.c diff --git a/tests/utests/basic/test_context.c b/tests/utests/basic/test_context.c index 4c4cc3f..cfba1d3 100644 --- a/tests/utests/basic/test_context.c +++ b/tests/utests/basic/test_context.c @@ -1,4 +1,4 @@ -/* +/** * @file test_context.c * @author: Radek Krejci <rkrejci@cesnet.cz> * @brief unit tests for functions from context.c @@ -298,8 +298,8 @@ test_models(void **state) assert_int_equal(LY_EVALID, lys_parse_in(UTEST_LYCTX, in, LYS_IN_YANG, NULL, NULL, &unres.creating, &mod1)); lys_unres_glob_erase(&unres); ly_in_free(in, 0); - CHECK_LOG_CTX("Parsing module \"y\" failed.", NULL, - "Name collision between module and submodule of name \"y\".", "Line number 1."); + CHECK_LOG_CTX("Parsing module \"y\" failed.", NULL); + CHECK_LOG_CTX("Name collision between module and submodule of name \"y\".", "Line number 1."); assert_int_equal(LY_SUCCESS, ly_in_new_memory("module a {namespace urn:a;prefix a;include y;revision 2018-10-30; }", &in)); assert_int_equal(LY_SUCCESS, lys_parse_in(UTEST_LYCTX, in, LYS_IN_YANG, NULL, NULL, &unres.creating, &mod1)); @@ -308,8 +308,8 @@ test_models(void **state) assert_int_equal(LY_EVALID, lys_parse_in(UTEST_LYCTX, in, LYS_IN_YANG, NULL, NULL, &unres.creating, &mod1)); lys_unres_glob_erase(&unres); ly_in_free(in, 0); - CHECK_LOG_CTX("Parsing module \"y\" failed.", NULL, - "Name collision between module and submodule of name \"y\".", "Line number 1."); + CHECK_LOG_CTX("Parsing module \"y\" failed.", NULL); + CHECK_LOG_CTX("Name collision between module and submodule of name \"y\".", "Line number 1."); ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, "submodule y {belongs-to b {prefix b;}}"); assert_int_equal(LY_SUCCESS, ly_in_new_memory("module b {namespace urn:b;prefix b;include y;}", &in)); @@ -317,10 +317,10 @@ test_models(void **state) lys_unres_glob_revert(UTEST_LYCTX, &unres); lys_unres_glob_erase(&unres); ly_in_free(in, 0); - CHECK_LOG_CTX("Parsing module \"b\" failed.", NULL, - "Including \"y\" submodule into \"b\" failed.", NULL, - "Parsing submodule failed.", NULL, - "Name collision between submodules of name \"y\".", "Line number 1."); + CHECK_LOG_CTX("Parsing module \"b\" failed.", NULL); + CHECK_LOG_CTX("Including \"y\" submodule into \"b\" failed.", NULL); + CHECK_LOG_CTX("Parsing submodule failed.", NULL); + CHECK_LOG_CTX("Name collision between submodules of name \"y\".", "Line number 1."); /* selecting correct revision of the submodules */ ly_ctx_reset_latests(UTEST_LYCTX); @@ -338,19 +338,6 @@ test_models(void **state) assert_non_null(mod1->compiled); assert_non_null(mod1->parsed); -#if 0 - /* TODO in case we are able to remove the parsed schema, here we will test how it will handle missing import parsed schema */ - - assert_int_equal(LY_SUCCESS, ly_in_new_memory("module z {namespace urn:z;prefix z;import w {prefix w;revision-date 2018-10-24;}}", &in)); - /* mod1->parsed is necessary to compile mod2 because of possible groupings, typedefs, ... */ - ly_ctx_set_module_imp_clb(UTEST_LYCTX, NULL, NULL); - assert_int_equal(LY_ENOTFOUND, lys_create_module(UTEST_LYCTX, in, LYS_IN_YANG, 1, NULL, NULL, &mod2)); - /*logbuf_assert("Unable to reload \"w\" module to import it into \"z\", source data not found.");*/ - CHECK_LOG_CTX("Recompilation of module \"w\" failed.", NULL); - assert_null(mod2); - ly_in_free(in, 0); -#endif - assert_int_equal(LY_SUCCESS, ly_in_new_memory("module z {namespace urn:z;prefix z;import w {prefix w;revision-date 2018-10-24;}}", &in)); ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, "module w {namespace urn:w;prefix w;revision 2018-10-24;}"); assert_int_equal(LY_SUCCESS, lys_parse(UTEST_LYCTX, in, LYS_IN_YANG, NULL, &mod2)); @@ -425,6 +412,7 @@ test_imports(void **state) "import a {prefix a;}" "}"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL)); + ly_err_clean(UTEST_LYCTX, NULL); } static void diff --git a/tests/utests/basic/test_hash_table.c b/tests/utests/basic/test_hash_table.c index 25b595a..78950cd 100644 --- a/tests/utests/basic/test_hash_table.c +++ b/tests/utests/basic/test_hash_table.c @@ -1,4 +1,4 @@ -/* +/** * @file test_hash_table.c * @author: Radek Krejci <rkrejci@cesnet.cz> * @brief unit tests for functions from hash_table.c @@ -19,8 +19,6 @@ #include "common.h" #include "hash_table.h" -struct ht_rec *lyht_get_rec(unsigned char *recs, uint16_t rec_size, uint32_t idx); - static void test_invalid_arguments(void **state) { @@ -83,7 +81,7 @@ static void test_ht_basic(void **state) { uint32_t i; - struct hash_table *ht; + struct ly_ht *ht; assert_non_null(ht = lyht_new(8, sizeof(int), ht_equal_clb, NULL, 0)); @@ -96,15 +94,14 @@ test_ht_basic(void **state) assert_int_equal(LY_ENOTFOUND, lyht_remove(ht, &i, i)); CHECK_LOG("Invalid argument hash (lyht_remove_with_resize_cb()).", NULL); - lyht_free(ht); + lyht_free(ht, NULL); } static void test_ht_resize(void **state) { uint32_t i; - struct ht_rec *rec; - struct hash_table *ht; + struct ly_ht *ht; assert_non_null(ht = lyht_new(8, sizeof(int), ht_equal_clb, NULL, 1)); assert_int_equal(8, ht->size); @@ -120,13 +117,12 @@ test_ht_resize(void **state) for (i = 0; i < 16; ++i) { if ((i >= 2) && (i < 8)) { /* inserted data on indexes 2-7 */ - rec = lyht_get_rec(ht->recs, ht->rec_size, i); - assert_int_equal(1, rec->hits); - assert_int_equal(i, rec->hash); + assert_int_not_equal(UINT32_MAX, ht->hlists[i].first); + assert_int_equal(LY_SUCCESS, lyht_find(ht, &i, i, NULL)); } else { /* nothing otherwise */ - rec = lyht_get_rec(ht->recs, ht->rec_size, i); - assert_int_equal(0, rec->hits); + assert_int_equal(UINT32_MAX, ht->hlists[i].first); + assert_int_equal(LY_ENOTFOUND, lyht_find(ht, &i, i, NULL)); } } @@ -153,7 +149,7 @@ test_ht_resize(void **state) } /* cleanup */ - lyht_free(ht); + lyht_free(ht, NULL); } static void @@ -162,8 +158,10 @@ test_ht_collisions(void **UNUSED(state)) #define GET_REC_INT(rec) (*((uint32_t *)&(rec)->val)) uint32_t i; - struct ht_rec *rec; - struct hash_table *ht; + struct ly_ht_rec *rec; + struct ly_ht *ht; + uint32_t rec_idx; + int count; assert_non_null(ht = lyht_new(8, sizeof(int), ht_equal_clb, NULL, 1)); @@ -172,66 +170,69 @@ test_ht_collisions(void **UNUSED(state)) } /* check all records */ - for (i = 0; i < 2; ++i) { - rec = lyht_get_rec(ht->recs, ht->rec_size, i); - assert_int_equal(rec->hits, 0); + for (i = 0; i < 8; ++i) { + if (i == 2) { + assert_int_not_equal(UINT32_MAX, ht->hlists[i].first); + } else { + assert_int_equal(UINT32_MAX, ht->hlists[i].first); + } } - rec = lyht_get_rec(ht->recs, ht->rec_size, i); - assert_int_equal(rec->hits, 4); - assert_int_equal(GET_REC_INT(rec), i); - ++i; - for ( ; i < 6; ++i) { - rec = lyht_get_rec(ht->recs, ht->rec_size, i); - assert_int_equal(rec->hits, 1); - assert_int_equal(GET_REC_INT(rec), i); + for (i = 0; i < 8; ++i) { + if ((i >= 2) && (i < 6)) { + assert_int_equal(LY_SUCCESS, lyht_find(ht, &i, 2, NULL)); + } else { + assert_int_equal(LY_ENOTFOUND, lyht_find(ht, &i, 2, NULL)); + } } - for ( ; i < 8; ++i) { - rec = lyht_get_rec(ht->recs, ht->rec_size, i); - assert_int_equal(rec->hits, 0); + rec_idx = ht->hlists[2].first; + count = 0; + while (rec_idx != UINT32_MAX) { + rec = lyht_get_rec(ht->recs, ht->rec_size, rec_idx); + rec_idx = rec->next; + assert_int_equal(rec->hash, 2); + count++; } + assert_int_equal(count, 4); i = 4; assert_int_equal(lyht_remove(ht, &i, 2), 0); rec = lyht_get_rec(ht->recs, ht->rec_size, i); - assert_int_equal(rec->hits, -1); i = 2; assert_int_equal(lyht_remove(ht, &i, 2), 0); /* check all records */ - for (i = 0; i < 2; ++i) { - rec = lyht_get_rec(ht->recs, ht->rec_size, i); - assert_int_equal(rec->hits, 0); + for (i = 0; i < 8; ++i) { + if (i == 2) { + assert_int_not_equal(UINT32_MAX, ht->hlists[i].first); + } else { + assert_int_equal(UINT32_MAX, ht->hlists[i].first); + } } - rec = lyht_get_rec(ht->recs, ht->rec_size, i); - assert_int_equal(rec->hits, 2); - assert_int_equal(GET_REC_INT(rec), 5); - ++i; - rec = lyht_get_rec(ht->recs, ht->rec_size, i); - assert_int_equal(rec->hits, 1); - assert_int_equal(GET_REC_INT(rec), 3); - ++i; - for ( ; i < 6; ++i) { - rec = lyht_get_rec(ht->recs, ht->rec_size, i); - assert_int_equal(rec->hits, -1); + for (i = 0; i < 8; ++i) { + if ((i == 3) || (i == 5)) { + assert_int_equal(LY_SUCCESS, lyht_find(ht, &i, 2, NULL)); + } else { + assert_int_equal(LY_ENOTFOUND, lyht_find(ht, &i, 2, NULL)); + } } - for ( ; i < 8; ++i) { - rec = lyht_get_rec(ht->recs, ht->rec_size, i); - assert_int_equal(rec->hits, 0); + rec_idx = ht->hlists[2].first; + count = 0; + while (rec_idx != UINT32_MAX) { + rec = lyht_get_rec(ht->recs, ht->rec_size, rec_idx); + rec_idx = rec->next; + assert_int_equal(rec->hash, 2); + count++; } + assert_int_equal(count, 2); - for (i = 0; i < 3; ++i) { - assert_int_equal(lyht_find(ht, &i, 2, NULL), LY_ENOTFOUND); - } - assert_int_equal(lyht_find(ht, &i, 2, NULL), LY_SUCCESS); - ++i; - assert_int_equal(lyht_find(ht, &i, 2, NULL), LY_ENOTFOUND); - ++i; - assert_int_equal(lyht_find(ht, &i, 2, NULL), LY_SUCCESS); - ++i; - for ( ; i < 8; ++i) { - assert_int_equal(lyht_find(ht, &i, 2, NULL), LY_ENOTFOUND); + for (i = 0; i < 8; ++i) { + if ((i == 3) || (i == 5)) { + assert_int_equal(lyht_find(ht, &i, 2, NULL), LY_SUCCESS); + } else { + assert_int_equal(lyht_find(ht, &i, 2, NULL), LY_ENOTFOUND); + } } i = 3; @@ -240,20 +241,11 @@ test_ht_collisions(void **UNUSED(state)) assert_int_equal(lyht_remove(ht, &i, 2), 0); /* check all records */ - for (i = 0; i < 2; ++i) { - rec = lyht_get_rec(ht->recs, ht->rec_size, i); - assert_int_equal(rec->hits, 0); - } - for ( ; i < 6; ++i) { - rec = lyht_get_rec(ht->recs, ht->rec_size, i); - assert_int_equal(rec->hits, -1); - } - for ( ; i < 8; ++i) { - rec = lyht_get_rec(ht->recs, ht->rec_size, i); - assert_int_equal(rec->hits, 0); + for (i = 0; i < 8; ++i) { + assert_int_equal(UINT32_MAX, ht->hlists[i].first); } - lyht_free(ht); + lyht_free(ht, NULL); } int diff --git a/tests/utests/basic/test_inout.c b/tests/utests/basic/test_inout.c index be27510..3f83568 100644 --- a/tests/utests/basic/test_inout.c +++ b/tests/utests/basic/test_inout.c @@ -84,8 +84,11 @@ test_input_mem(void **UNUSED(state)) char *str1 = "a", *str2 = "b"; assert_int_equal(LY_EINVAL, ly_in_new_memory(NULL, NULL)); + CHECK_LOG_LASTMSG("Invalid argument str (ly_in_new_memory())."); assert_int_equal(LY_EINVAL, ly_in_new_memory(str1, NULL)); + CHECK_LOG_LASTMSG("Invalid argument in (ly_in_new_memory())."); assert_null(ly_in_memory(NULL, NULL)); + CHECK_LOG_LASTMSG("Invalid argument in (ly_in_memory())."); assert_int_equal(LY_SUCCESS, ly_in_new_memory(str1, &in)); assert_int_equal(LY_IN_MEMORY, ly_in_type(in)); @@ -103,12 +106,15 @@ test_input_fd(void **UNUSED(state)) struct stat statbuf; assert_int_equal(LY_EINVAL, ly_in_new_fd(-1, NULL)); + CHECK_LOG_LASTMSG("Invalid argument fd >= 0 (ly_in_new_fd())."); assert_int_equal(-1, ly_in_fd(NULL, -1)); + CHECK_LOG_LASTMSG("Invalid argument in (ly_in_fd())."); assert_int_not_equal(-1, fd1 = open(TEST_INPUT_FILE, O_RDONLY)); assert_int_not_equal(-1, fd2 = open(TEST_INPUT_FILE, O_RDONLY)); assert_int_equal(LY_EINVAL, ly_in_new_fd(fd1, NULL)); + CHECK_LOG_LASTMSG("Invalid argument in (ly_in_new_fd())."); assert_int_equal(LY_SUCCESS, ly_in_new_fd(fd1, &in)); assert_int_equal(LY_IN_FD, ly_in_type(in)); diff --git a/tests/utests/basic/test_json.c b/tests/utests/basic/test_json.c index 1896b8a..08b7719 100644 --- a/tests/utests/basic/test_json.c +++ b/tests/utests/basic/test_json.c @@ -1,4 +1,4 @@ -/* +/** * @file test_json.c * @author: Radek Krejci <rkrejci@cesnet.cz> * @brief unit tests for a generic JSON parser @@ -27,39 +27,40 @@ test_general(void **state) /* empty */ str = ""; assert_int_equal(LY_SUCCESS, ly_in_new_memory(str, &in)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0)); - lyjson_ctx_free(jsonctx); + assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + CHECK_LOG_CTX("Empty JSON file.", "Line number 1."); str = " \n\t \n"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0)); - lyjson_ctx_free(jsonctx); + assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + CHECK_LOG_CTX("Empty JSON file.", "Line number 3."); /* constant values */ str = "true"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_TRUE, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_TRUE, lyjson_ctx_status(jsonctx)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx)); lyjson_ctx_free(jsonctx); str = "false"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_FALSE, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_FALSE, lyjson_ctx_status(jsonctx)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx)); lyjson_ctx_free(jsonctx); str = "null"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NULL, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NULL, lyjson_ctx_status(jsonctx)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx)); lyjson_ctx_free(jsonctx); ly_in_free(in, 0); @@ -75,8 +76,8 @@ test_number(void **state) /* simple value */ str = "11"; assert_int_equal(LY_SUCCESS, ly_in_new_memory(str, &in)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("11", jsonctx->value); assert_int_equal(2, jsonctx->value_len); assert_int_equal(0, jsonctx->dynamic); @@ -85,8 +86,8 @@ test_number(void **state) /* fraction number */ str = "37.7668"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("37.7668", jsonctx->value); assert_int_equal(7, jsonctx->value_len); assert_int_equal(0, jsonctx->dynamic); @@ -95,8 +96,8 @@ test_number(void **state) /* negative number */ str = "-122.3959"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("-122.3959", jsonctx->value); assert_int_equal(9, jsonctx->value_len); assert_int_equal(0, jsonctx->dynamic); @@ -105,8 +106,8 @@ test_number(void **state) /* integer, positive exponent */ str = "550E3"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("550000", jsonctx->value); assert_int_equal(6, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -114,8 +115,8 @@ test_number(void **state) str = "-550E3"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("-550000", jsonctx->value); assert_int_equal(7, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -124,8 +125,8 @@ test_number(void **state) /* integer, negative exponent */ str = "1E-1"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("0.1", jsonctx->value); assert_int_equal(3, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -133,8 +134,8 @@ test_number(void **state) str = "15E-1"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("1.5", jsonctx->value); assert_int_equal(3, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -142,8 +143,8 @@ test_number(void **state) str = "-15E-1"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("-1.5", jsonctx->value); assert_int_equal(4, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -151,8 +152,8 @@ test_number(void **state) str = "16E-2"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("0.16", jsonctx->value); assert_int_equal(4, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -160,8 +161,8 @@ test_number(void **state) str = "-16E-2"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("-0.16", jsonctx->value); assert_int_equal(5, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -169,8 +170,8 @@ test_number(void **state) str = "17E-3"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("0.017", jsonctx->value); assert_int_equal(5, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -178,8 +179,8 @@ test_number(void **state) str = "-17E-3"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("-0.017", jsonctx->value); assert_int_equal(6, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -187,8 +188,8 @@ test_number(void **state) str = "21000E-2"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("210", jsonctx->value); assert_int_equal(3, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -196,8 +197,8 @@ test_number(void **state) str = "21000E-4"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("2.1", jsonctx->value); assert_int_equal(3, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -205,8 +206,8 @@ test_number(void **state) str = "21000E-7"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("0.0021", jsonctx->value); assert_int_equal(6, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -215,8 +216,8 @@ test_number(void **state) /* decimal number, positive exponent */ str = "5.087E1"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("50.87", jsonctx->value); assert_int_equal(5, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -224,8 +225,8 @@ test_number(void **state) str = "-5.087E1"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("-50.87", jsonctx->value); assert_int_equal(6, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -233,8 +234,8 @@ test_number(void **state) str = "5.087E5"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("508700", jsonctx->value); assert_int_equal(6, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -242,8 +243,8 @@ test_number(void **state) str = "59.1e+1"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("591", jsonctx->value); assert_int_equal(3, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -251,8 +252,8 @@ test_number(void **state) str = "0.005087E1"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("0.05087", jsonctx->value); assert_int_equal(7, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -260,8 +261,8 @@ test_number(void **state) str = "0.005087E2"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("0.5087", jsonctx->value); assert_int_equal(6, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -269,8 +270,8 @@ test_number(void **state) str = "0.005087E6"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("5087", jsonctx->value); assert_int_equal(4, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -278,8 +279,8 @@ test_number(void **state) str = "0.05087E6"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("50870", jsonctx->value); assert_int_equal(5, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -287,8 +288,8 @@ test_number(void **state) str = "0.005087E8"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("508700", jsonctx->value); assert_int_equal(6, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -297,8 +298,8 @@ test_number(void **state) /* decimal number, negative exponent */ str = "35.94e-1"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("3.594", jsonctx->value); assert_int_equal(5, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -306,8 +307,8 @@ test_number(void **state) str = "-35.94e-1"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("-3.594", jsonctx->value); assert_int_equal(6, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -315,8 +316,8 @@ test_number(void **state) str = "35.94e-2"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("0.3594", jsonctx->value); assert_int_equal(6, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -324,8 +325,8 @@ test_number(void **state) str = "35.94e-3"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("0.03594", jsonctx->value); assert_int_equal(7, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -333,8 +334,8 @@ test_number(void **state) str = "0.3594e-1"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("0.03594", jsonctx->value); assert_int_equal(7, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -342,8 +343,8 @@ test_number(void **state) str = "0.03594e-1"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("0.003594", jsonctx->value); assert_int_equal(8, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -351,8 +352,8 @@ test_number(void **state) str = "0.003594e-1"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("0.0003594", jsonctx->value); assert_int_equal(9, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -360,8 +361,8 @@ test_number(void **state) str = "0.3594e-2"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("0.003594", jsonctx->value); assert_int_equal(8, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -369,8 +370,8 @@ test_number(void **state) str = "0.03594e-2"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("0.0003594", jsonctx->value); assert_int_equal(9, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -378,8 +379,8 @@ test_number(void **state) str = "0.003594e-2"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("0.00003594", jsonctx->value); assert_int_equal(10, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -388,8 +389,8 @@ test_number(void **state) /* zero */ str = "0"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_true(jsonctx->value[0] == '0'); assert_int_equal(1, jsonctx->value_len); assert_int_equal(0, jsonctx->dynamic); @@ -397,8 +398,8 @@ test_number(void **state) str = "-0"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_true(jsonctx->value[0] == '-'); assert_true(jsonctx->value[1] == '0'); assert_int_equal(2, jsonctx->value_len); @@ -407,8 +408,8 @@ test_number(void **state) str = "94E0"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_true(jsonctx->value[0] == '9'); assert_true(jsonctx->value[1] == '4'); assert_int_equal(2, jsonctx->value_len); @@ -417,8 +418,8 @@ test_number(void **state) str = "0E2"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_true(jsonctx->value[0] == '0'); assert_int_equal(1, jsonctx->value_len); assert_int_equal(0, jsonctx->dynamic); @@ -426,8 +427,8 @@ test_number(void **state) str = "-0E2"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_true(jsonctx->value[0] == '-'); assert_true(jsonctx->value[1] == '0'); assert_int_equal(2, jsonctx->value_len); @@ -436,8 +437,8 @@ test_number(void **state) str = "5.320e+2"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("532", jsonctx->value); assert_int_equal(3, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -445,8 +446,8 @@ test_number(void **state) str = "5.320e-1"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_NUMBER, lyjson_ctx_status(jsonctx)); assert_string_equal("0.532", jsonctx->value); assert_int_equal(5, jsonctx->value_len); assert_int_equal(1, jsonctx->dynamic); @@ -455,69 +456,64 @@ test_number(void **state) /* various invalid inputs */ str = "-x"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); + assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); CHECK_LOG_CTX("Invalid character in JSON Number value (\"x\").", "Line number 1."); str = " -"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); + assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); CHECK_LOG_CTX("Unexpected end-of-input.", "Line number 1."); str = "--1"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); + assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); CHECK_LOG_CTX("Invalid character in JSON Number value (\"-\").", "Line number 1."); str = "+1"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); + assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); CHECK_LOG_CTX("Invalid character sequence \"+1\", expected a JSON value.", "Line number 1."); str = " 1.x "; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); + assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); CHECK_LOG_CTX("Invalid character in JSON Number value (\"x\").", "Line number 1."); str = "1."; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); + assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); CHECK_LOG_CTX("Unexpected end-of-input.", "Line number 1."); str = " 1eo "; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); + assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); CHECK_LOG_CTX("Invalid character in JSON Number value (\"o\").", "Line number 1."); str = "1e"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); + assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); CHECK_LOG_CTX("Unexpected end-of-input.", "Line number 1."); str = "1E1000"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); + assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); CHECK_LOG_CTX("Number encoded as a string exceeded the LY_NUMBER_MAXLEN limit.", "Line number 1."); str = "1e9999999999999999999"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); + assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); CHECK_LOG_CTX("Exponent out-of-bounds in a JSON Number value (1e9999999999999999999).", "Line number 1."); str = "1.1e66000"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); + assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); CHECK_LOG_CTX("Exponent out-of-bounds in a JSON Number value (1.1e66000).", "Line number 1."); str = "1.1e-66000"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); + assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); CHECK_LOG_CTX("Exponent out-of-bounds in a JSON Number value (1.1e-66000).", "Line number 1."); - str = "-2.1e0."; - assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - CHECK_LOG_CTX("Unexpected character \".\" after JSON number.", "Line number 1."); - ly_in_free(in, 0); } @@ -532,68 +528,12 @@ test_string(void **state) str = ""; assert_int_equal(LY_SUCCESS, ly_in_new_memory(str, &in)); -#if 0 - /* simple string */ - str = "\"hello\""; - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_STRING, lyjson_ctx_status(jsonctx, 0)); - assert_ptr_equal(&str[1], jsonctx->value); - assert_int_equal(5, jsonctx->value_len); - assert_int_equal(0, jsonctx->dynamic); - lyjson_ctx_free(jsonctx); - - /* 4-byte utf8 character */ - str = "\"\\t𠜎\""; - assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_STRING, lyjson_ctx_status(jsonctx, 0)); - assert_string_equal("\t𠜎", jsonctx->value); - assert_int_equal(5, jsonctx->value_len); - assert_int_equal(1, jsonctx->dynamic); - lyjson_ctx_free(jsonctx); - - /* valid escape sequences - note that here it mixes valid JSON string characters (RFC 7159, sec. 7) and - * valid characters in YANG string type (RFC 7950, sec. 9.4). Since the latter is a subset of JSON string, - * the YANG string type's restrictions apply to the JSON escape sequences */ - str = "\"\\\" \\\\ \\r \\/ \\n \\t \\u20ac\""; - assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_STRING, lyjson_ctx_status(jsonctx, 0)); - assert_string_equal("\" \\ \r / \n \t €", jsonctx->value); - assert_int_equal(15, jsonctx->value_len); - assert_int_equal(1, jsonctx->dynamic); - lyjson_ctx_free(jsonctx); - - /* backspace and form feed are valid JSON escape sequences, but the control characters they represents are not allowed values for YANG string type */ - str = "\"\\b\""; - assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - CHECK_LOG_CTX("Invalid character reference \"\\b\" (0x00000008).", "Line number 1."); - - str = "\"\\f\""; - assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - CHECK_LOG_CTX("Invalid character reference \"\\f\" (0x0000000c).", "Line number 1."); -#endif - /* unterminated string */ str = "\"unterminated string"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); + assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); CHECK_LOG_CTX("Missing quotation-mark at the end of a JSON string.", "Line number 1."); -#if 0 - /* invalid escape sequence */ - str = "\"char \\x \""; - assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - CHECK_LOG_CTX("Invalid character escape sequence \\x.", "Line number 1."); - - /* new line is allowed only as escaped character in JSON */ - str = "\"\n\""; - assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - CHECK_LOG_CTX("Invalid character in JSON string \"\n\" (0x0000000a).", "Line number 1."); -#endif + CHECK_LOG_CTX("Unexpected end-of-input.", "Line number 1."); ly_in_free(in, 0); } @@ -608,95 +548,122 @@ test_object(void **state) /* empty */ str = " { } "; assert_int_equal(LY_SUCCESS, ly_in_new_memory(str, &in)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_OBJECT_EMPTY, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx)); + + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); + assert_int_equal(LYJSON_OBJECT_CLOSED, lyjson_ctx_status(jsonctx)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx)); lyjson_ctx_free(jsonctx); /* simple value */ str = "{\"name\" : \"Radek\"}"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx, 0)); + + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx)); + + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); + assert_int_equal(LYJSON_OBJECT_NAME, lyjson_ctx_status(jsonctx)); assert_ptr_equal(&str[2], jsonctx->value); assert_int_equal(4, jsonctx->value_len); assert_int_equal(0, jsonctx->dynamic); - assert_string_equal("\"Radek\"}", jsonctx->in->current); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_STRING, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LYJSON_STRING, lyjson_ctx_status(jsonctx)); assert_string_equal("Radek\"}", jsonctx->value); assert_int_equal(5, jsonctx->value_len); assert_int_equal(0, jsonctx->dynamic); - assert_string_equal("}", jsonctx->in->current); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_OBJECT_CLOSED, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LYJSON_OBJECT_CLOSED, lyjson_ctx_status(jsonctx)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx)); lyjson_ctx_free(jsonctx); /* two values */ str = "{\"smart\" : true,\"handsom\":false}"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx)); + + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); + assert_int_equal(LYJSON_OBJECT_NAME, lyjson_ctx_status(jsonctx)); assert_string_equal("smart\" : true,\"handsom\":false}", jsonctx->value); assert_int_equal(5, jsonctx->value_len); assert_int_equal(0, jsonctx->dynamic); - assert_string_equal("true,\"handsom\":false}", jsonctx->in->current); + + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); + assert_int_equal(LYJSON_TRUE, lyjson_ctx_status(jsonctx)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_TRUE, lyjson_ctx_status(jsonctx, 0)); - assert_string_equal(",\"handsom\":false}", jsonctx->in->current); + assert_int_equal(LYJSON_OBJECT_NEXT, lyjson_ctx_status(jsonctx)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LYJSON_OBJECT_NAME, lyjson_ctx_status(jsonctx)); assert_string_equal("handsom\":false}", jsonctx->value); assert_int_equal(7, jsonctx->value_len); assert_int_equal(0, jsonctx->dynamic); - assert_string_equal("false}", jsonctx->in->current); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_FALSE, lyjson_ctx_status(jsonctx, 0)); - assert_string_equal("}", jsonctx->in->current); + assert_int_equal(LYJSON_FALSE, lyjson_ctx_status(jsonctx)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_OBJECT_CLOSED, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LYJSON_OBJECT_CLOSED, lyjson_ctx_status(jsonctx)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx)); lyjson_ctx_free(jsonctx); /* inherited objects */ str = "{\"person\" : {\"name\":\"Radek\"}}"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx)); + + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); + assert_int_equal(LYJSON_OBJECT_NAME, lyjson_ctx_status(jsonctx)); assert_string_equal("person\" : {\"name\":\"Radek\"}}", jsonctx->value); assert_int_equal(6, jsonctx->value_len); assert_int_equal(0, jsonctx->dynamic); - assert_string_equal("{\"name\":\"Radek\"}}", jsonctx->in->current); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx)); + + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); + assert_int_equal(LYJSON_OBJECT_NAME, lyjson_ctx_status(jsonctx)); assert_string_equal("name\":\"Radek\"}}", jsonctx->value); assert_int_equal(4, jsonctx->value_len); assert_int_equal(0, jsonctx->dynamic); - assert_string_equal("\"Radek\"}}", jsonctx->in->current); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_STRING, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LYJSON_STRING, lyjson_ctx_status(jsonctx)); assert_string_equal("Radek\"}}", jsonctx->value); assert_int_equal(5, jsonctx->value_len); assert_int_equal(0, jsonctx->dynamic); - assert_string_equal("}}", jsonctx->in->current); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_OBJECT_CLOSED, lyjson_ctx_status(jsonctx, 0)); - assert_string_equal("}", jsonctx->in->current); + assert_int_equal(LYJSON_OBJECT_CLOSED, lyjson_ctx_status(jsonctx)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_OBJECT_CLOSED, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LYJSON_OBJECT_CLOSED, lyjson_ctx_status(jsonctx)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx)); lyjson_ctx_free(jsonctx); - /* new line is allowed only as escaped character in JSON */ + /* unquoted string */ str = "{ unquoted : \"data\"}"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_EVALID, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - CHECK_LOG_CTX("Invalid character sequence \"unquoted : \"data\"}\", expected a JSON object's member.", "Line number 1."); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx)); + + assert_int_equal(LY_EVALID, lyjson_ctx_next(jsonctx, NULL)); + CHECK_LOG_CTX("Invalid character sequence \"unquoted : \"data\"}\", expected a JSON object name.", "Line number 1."); + lyjson_ctx_free(jsonctx); ly_in_free(in, 0); } @@ -711,67 +678,79 @@ test_array(void **state) /* empty */ str = " [ ] "; assert_int_equal(LY_SUCCESS, ly_in_new_memory(str, &in)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_ARRAY_EMPTY, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_ARRAY, lyjson_ctx_status(jsonctx)); + + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); + assert_int_equal(LYJSON_ARRAY_CLOSED, lyjson_ctx_status(jsonctx)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx)); lyjson_ctx_free(jsonctx); /* simple value */ str = "[ null]"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_ARRAY, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_ARRAY, lyjson_ctx_status(jsonctx)); assert_null(jsonctx->value); assert_int_equal(0, jsonctx->value_len); assert_int_equal(0, jsonctx->dynamic); - assert_string_equal("null]", jsonctx->in->current); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_NULL, lyjson_ctx_status(jsonctx, 0)); - assert_string_equal("]", jsonctx->in->current); + assert_int_equal(LYJSON_NULL, lyjson_ctx_status(jsonctx)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_ARRAY_CLOSED, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LYJSON_ARRAY_CLOSED, lyjson_ctx_status(jsonctx)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx)); lyjson_ctx_free(jsonctx); /* two values */ str = "[{\"a\":null},\"x\"]"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); - assert_int_equal(LYJSON_ARRAY, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); + assert_int_equal(LYJSON_ARRAY, lyjson_ctx_status(jsonctx)); assert_null(jsonctx->value); assert_int_equal(0, jsonctx->value_len); assert_int_equal(0, jsonctx->dynamic); - assert_string_equal("{\"a\":null},\"x\"]", jsonctx->in->current); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LYJSON_OBJECT, lyjson_ctx_status(jsonctx)); + + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); + assert_int_equal(LYJSON_OBJECT_NAME, lyjson_ctx_status(jsonctx)); assert_string_equal("a\":null},\"x\"]", jsonctx->value); assert_int_equal(1, jsonctx->value_len); assert_int_equal(0, jsonctx->dynamic); - assert_string_equal("null},\"x\"]", jsonctx->in->current); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_NULL, lyjson_ctx_status(jsonctx, 0)); - assert_string_equal("},\"x\"]", jsonctx->in->current); + assert_int_equal(LYJSON_NULL, lyjson_ctx_status(jsonctx)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_OBJECT_CLOSED, lyjson_ctx_status(jsonctx, 0)); - assert_string_equal(",\"x\"]", jsonctx->in->current); + assert_int_equal(LYJSON_OBJECT_CLOSED, lyjson_ctx_status(jsonctx)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_STRING, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LYJSON_ARRAY_NEXT, lyjson_ctx_status(jsonctx)); + + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); + assert_int_equal(LYJSON_STRING, lyjson_ctx_status(jsonctx)); assert_string_equal("x\"]", jsonctx->value); assert_int_equal(1, jsonctx->value_len); assert_int_equal(0, jsonctx->dynamic); - assert_string_equal("]", jsonctx->in->current); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_ARRAY_CLOSED, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LYJSON_ARRAY_CLOSED, lyjson_ctx_status(jsonctx)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_next(jsonctx, NULL)); - assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx, 0)); + assert_int_equal(LYJSON_END, lyjson_ctx_status(jsonctx)); lyjson_ctx_free(jsonctx); /* new line is allowed only as escaped character in JSON */ str = "[ , null]"; assert_non_null(ly_in_memory(in, str)); - assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, 0, &jsonctx)); + assert_int_equal(LY_SUCCESS, lyjson_ctx_new(UTEST_LYCTX, in, &jsonctx)); assert_int_equal(LY_EVALID, lyjson_ctx_next(jsonctx, NULL)); CHECK_LOG_CTX("Invalid character sequence \", null]\", expected a JSON value.", "Line number 1."); lyjson_ctx_free(jsonctx); diff --git a/tests/utests/basic/test_plugins.c b/tests/utests/basic/test_plugins.c index 7f3fe40..de13c0b 100644 --- a/tests/utests/basic/test_plugins.c +++ b/tests/utests/basic/test_plugins.c @@ -1,4 +1,4 @@ -/* +/** * @file test_plugins.c * @author: Radek Krejci <rkrejci@cesnet.cz> * @brief unit tests for functions from set.c diff --git a/tests/utests/basic/test_set.c b/tests/utests/basic/test_set.c index af39afa..9485927 100644 --- a/tests/utests/basic/test_set.c +++ b/tests/utests/basic/test_set.c @@ -1,4 +1,4 @@ -/* +/** * @file test_set.c * @author: Radek Krejci <rkrejci@cesnet.cz> * @brief unit tests for functions from set.c diff --git a/tests/utests/basic/test_xml.c b/tests/utests/basic/test_xml.c index 668de4b..071846a 100644 --- a/tests/utests/basic/test_xml.c +++ b/tests/utests/basic/test_xml.c @@ -1,4 +1,4 @@ -/* +/** * @file test_xml.c * @author: Radek Krejci <rkrejci@cesnet.cz> * @brief unit tests for functions from xml.c diff --git a/tests/utests/basic/test_xpath.c b/tests/utests/basic/test_xpath.c index b388dc3..754abf2 100644 --- a/tests/utests/basic/test_xpath.c +++ b/tests/utests/basic/test_xpath.c @@ -88,6 +88,19 @@ const char *schema_a = " type string;\n" " }\n" " }\n" + "\n" + " rpc r {\n" + " input {\n" + " leaf l {\n" + " type string;\n" + " }\n" + " }\n" + " output {\n" + " leaf l {\n" + " type string;\n" + " }\n" + " }\n" + " }\n" "}"; static int @@ -254,9 +267,11 @@ test_invalid(void **state) assert_int_equal(LY_EVALID, lyd_find_xpath(tree, "/a:foo2[.=]", &set)); assert_null(set); + CHECK_LOG_CTX("Unexpected XPath token \"]\" (\"]\").", NULL); assert_int_equal(LY_EVALID, lyd_find_xpath(tree, "/a:", &set)); assert_null(set); + CHECK_LOG_CTX("Invalid character 'a'[2] of expression '/a:'.", NULL); lyd_free_all(tree); } @@ -381,6 +396,31 @@ test_hash(void **state) } static void +test_rpc(void **state) +{ + const char *data = + "<r xmlns=\"urn:tests:a\">\n" + " <l>val</l>\n" + "</r>"; + struct ly_in *in; + struct lyd_node *tree; + struct ly_set *set; + + assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in)); + assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_XML, LYD_TYPE_REPLY_YANG, &tree, NULL)); + ly_in_free(in, 0); + assert_non_null(tree); + + /* name collision input/output, hashes are not used */ + assert_int_equal(LY_SUCCESS, lyd_find_xpath(tree, "/a:r/l", &set)); + assert_int_equal(1, set->count); + + ly_set_free(set, NULL); + + lyd_free_all(tree); +} + +static void test_toplevel(void **state) { const char *schema_b = @@ -479,7 +519,7 @@ test_atomize(void **state) /* some random paths just making sure the API function works */ assert_int_equal(LY_SUCCESS, lys_find_xpath_atoms(UTEST_LYCTX, NULL, "/a:*", 0, &set)); - assert_int_equal(6, set->count); + assert_int_equal(7, set->count); ly_set_free(set, NULL); /* all nodes from all modules (including internal, which can change easily, so check just the test modules) */ @@ -496,7 +536,7 @@ test_atomize(void **state) ly_set_free(set, NULL); assert_int_equal(LY_SUCCESS, lys_find_xpath_atoms(UTEST_LYCTX, NULL, "/*", 0, &set)); - assert_int_equal(13, set->count); + assert_int_equal(14, set->count); ly_set_free(set, NULL); /* @@ -530,7 +570,7 @@ test_atomize(void **state) /* descendant-or-self */ assert_int_equal(LY_SUCCESS, lys_find_xpath_atoms(UTEST_LYCTX, NULL, "/a:*/descendant-or-self::c", 0, &set)); - assert_int_equal(7, set->count); + assert_int_equal(8, set->count); ly_set_free(set, NULL); /* following */ @@ -545,11 +585,11 @@ test_atomize(void **state) /* parent */ assert_int_equal(LY_SUCCESS, lys_find_xpath_atoms(UTEST_LYCTX, NULL, "/child::a:*/c/parent::l1", 0, &set)); - assert_int_equal(7, set->count); + assert_int_equal(8, set->count); ly_set_free(set, NULL); assert_int_equal(LY_SUCCESS, lys_find_xpath_atoms(UTEST_LYCTX, NULL, "/child::a:c//..", 0, &set)); - assert_int_equal(8, set->count); + assert_int_equal(11, set->count); ly_set_free(set, NULL); /* preceding */ @@ -898,6 +938,7 @@ test_variables(void **state) LOCAL_SETUP(data, tree); assert_int_equal(LY_SUCCESS, lyxp_vars_set(&vars, "var1", "\"mstr\"")); assert_int_equal(LY_ENOTFOUND, lyd_find_xpath2(tree, "/foo[text() = $var55]", vars, &set)); + CHECK_LOG_CTX("Variable \"var55\" not defined.", NULL); LOCAL_TEARDOWN(set, tree, vars); /* Syntax error in value. */ @@ -906,6 +947,7 @@ test_variables(void **state) LOCAL_SETUP(data, tree); assert_int_equal(LY_SUCCESS, lyxp_vars_set(&vars, "var", "\"")); assert_int_equal(LY_EVALID, lyd_find_xpath2(tree, "/foo[$var]", vars, &set)); + CHECK_LOG_CTX("Unterminated string delimited with \" (\").", "Data location \"/a:foo\"."); LOCAL_TEARDOWN(set, tree, vars); /* Prefix is not supported. */ @@ -914,7 +956,7 @@ test_variables(void **state) LOCAL_SETUP(data, tree); assert_int_equal(LY_SUCCESS, lyxp_vars_set(&vars, "var", "\"")); assert_int_equal(LY_EVALID, lyd_find_xpath2(tree, "/foo[$pref:var]", vars, &set)); - assert_string_equal("Variable with prefix is not supported.", _UC->err_msg); + CHECK_LOG_CTX("Variable with prefix is not supported.", NULL); LOCAL_TEARDOWN(set, tree, vars); #undef LOCAL_SETUP @@ -1050,6 +1092,154 @@ test_axes(void **state) lyd_free_all(tree); } +static void +test_trim(void **state) +{ + const char *data; + char *str1; + struct lyd_node *tree; + + data = + "<l1 xmlns=\"urn:tests:a\">" + " <a>a1</a>" + " <b>b1</b>" + " <c>c1</c>" + "</l1>" + "<l1 xmlns=\"urn:tests:a\">" + " <a>a2</a>" + " <b>b2</b>" + "</l1>" + "<l1 xmlns=\"urn:tests:a\">" + " <a>a3</a>" + " <b>b3</b>" + "</l1>" + "<l1 xmlns=\"urn:tests:a\">" + " <a>a4</a>" + " <b>b4</b>" + " <c>c4</c>" + "</l1>" + "<l1 xmlns=\"urn:tests:a\">" + " <a>a5</a>" + " <b>b5</b>" + " <c>c5</c>" + "</l1>" + "<foo2 xmlns=\"urn:tests:a\">50</foo2>" + "<c xmlns=\"urn:tests:a\">" + " <x>key2</x>" + " <ll>" + " <a>key1</a>" + " <ll>" + " <a>key11</a>" + " <b>val11</b>" + " </ll>" + " <ll>" + " <a>key12</a>" + " <b>val12</b>" + " </ll>" + " <ll>" + " <a>key13</a>" + " <b>val13</b>" + " </ll>" + " </ll>" + " <ll>" + " <a>key2</a>" + " <ll>" + " <a>key21</a>" + " <b>val21</b>" + " </ll>" + " <ll>" + " <a>key22</a>" + " <b>val22</b>" + " </ll>" + " </ll>" + " <ll>" + " <a>key3</a>" + " <ll>" + " <a>key31</a>" + " <b>val31</b>" + " </ll>" + " <ll>" + " <a>key32</a>" + " <b>val32</b>" + " </ll>" + " </ll>" + "</c>"; + + /* trim #1 */ + assert_int_equal(LY_SUCCESS, lyd_parse_data_mem(UTEST_LYCTX, data, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, &tree)); + assert_non_null(tree); + + assert_int_equal(LY_SUCCESS, lyd_trim_xpath(&tree, "/a:c/ll/ll[a='key11']", NULL)); + lyd_print_mem(&str1, tree, LYD_XML, LYD_PRINT_WITHSIBLINGS); + assert_string_equal(str1, + "<c xmlns=\"urn:tests:a\">\n" + " <ll>\n" + " <a>key1</a>\n" + " <ll>\n" + " <a>key11</a>\n" + " <b>val11</b>\n" + " </ll>\n" + " </ll>\n" + "</c>\n"); + + free(str1); + lyd_free_all(tree); + + /* trim #2 */ + assert_int_equal(LY_SUCCESS, lyd_parse_data_mem(UTEST_LYCTX, data, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, &tree)); + assert_non_null(tree); + + assert_int_equal(LY_SUCCESS, lyd_trim_xpath(&tree, "/a:c/ll/ll[contains(.,'2')]", NULL)); + lyd_print_mem(&str1, tree, LYD_XML, LYD_PRINT_WITHSIBLINGS); + assert_string_equal(str1, + "<c xmlns=\"urn:tests:a\">\n" + " <ll>\n" + " <a>key1</a>\n" + " <ll>\n" + " <a>key12</a>\n" + " <b>val12</b>\n" + " </ll>\n" + " </ll>\n" + " <ll>\n" + " <a>key2</a>\n" + " <ll>\n" + " <a>key21</a>\n" + " <b>val21</b>\n" + " </ll>\n" + " <ll>\n" + " <a>key22</a>\n" + " <b>val22</b>\n" + " </ll>\n" + " </ll>\n" + " <ll>\n" + " <a>key3</a>\n" + " <ll>\n" + " <a>key32</a>\n" + " <b>val32</b>\n" + " </ll>\n" + " </ll>\n" + "</c>\n"); + + free(str1); + lyd_free_all(tree); + + /* trim #3 */ + assert_int_equal(LY_SUCCESS, lyd_parse_data_mem(UTEST_LYCTX, data, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, &tree)); + assert_non_null(tree); + + assert_int_equal(LY_SUCCESS, lyd_trim_xpath(&tree, "/l1[4]//.", NULL)); + lyd_print_mem(&str1, tree, LYD_XML, LYD_PRINT_WITHSIBLINGS); + assert_string_equal(str1, + "<l1 xmlns=\"urn:tests:a\">\n" + " <a>a4</a>\n" + " <b>b4</b>\n" + " <c>c4</c>\n" + "</l1>\n"); + + free(str1); + lyd_free_all(tree); +} + int main(void) { @@ -1058,6 +1248,7 @@ main(void) UTEST(test_union, setup), UTEST(test_invalid, setup), UTEST(test_hash, setup), + UTEST(test_rpc, setup), UTEST(test_toplevel, setup), UTEST(test_atomize, setup), UTEST(test_canonize, setup), @@ -1065,6 +1256,7 @@ main(void) UTEST(test_augment, setup), UTEST(test_variables, setup), UTEST(test_axes, setup), + UTEST(test_trim, setup), }; return cmocka_run_group_tests(tests, NULL, NULL); diff --git a/tests/utests/data/test_diff.c b/tests/utests/data/test_diff.c index 1b7592a..4400b5d 100644 --- a/tests/utests/data/test_diff.c +++ b/tests/utests/data/test_diff.c @@ -4,7 +4,7 @@ * @author Michal Vasko <mvasko@cesnet.cz> * @brief tests for lyd_diff() * - * Copyright (c) 2020 CESNET, z.s.p.o. + * Copyright (c) 2020 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -17,15 +17,15 @@ #include "libyang.h" -#define CHECK_PARSE_LYD(INPUT, MODEL) \ - CHECK_PARSE_LYD_PARAM(INPUT, LYD_XML, LYD_PARSE_ONLY, 0, LY_SUCCESS, MODEL) +#define CHECK_PARSE_LYD(INPUT, OUTPUT) \ + CHECK_PARSE_LYD_PARAM(INPUT, LYD_XML, LYD_PARSE_ONLY, 0, LY_SUCCESS, OUTPUT) -#define CHECK_LYD_STRING(IN_MODEL, TEXT) \ - CHECK_LYD_STRING_PARAM(IN_MODEL, TEXT, LYD_XML, LYD_PRINT_WITHSIBLINGS) +#define CHECK_LYD_STRING(INPUT, TEXT) \ + CHECK_LYD_STRING_PARAM(INPUT, TEXT, LYD_XML, LYD_PRINT_WITHSIBLINGS) -#define CHECK_PARSE_LYD_DIFF(INPUT_1, INPUT_2, OUT_MODEL) \ - assert_int_equal(LY_SUCCESS, lyd_diff_siblings(INPUT_1, INPUT_2, 0, &OUT_MODEL));\ - assert_non_null(OUT_MODEL) +#define CHECK_PARSE_LYD_DIFF(INPUT_1, INPUT_2, OUT_DIFF) \ + assert_int_equal(LY_SUCCESS, lyd_diff_siblings(INPUT_1, INPUT_2, 0, &OUT_DIFF));\ + assert_non_null(OUT_DIFF) #define TEST_DIFF_3(XML1, XML2, XML3, DIFF1, DIFF2, MERGE) \ { \ @@ -117,6 +117,12 @@ const char *schema1 = " leaf l2 {\n" " type int32;\n" " }\n" + "" + " container cont {\n" + " leaf l3 {\n" + " type string;\n" + " }\n" + " }\n" " }\n" "" " leaf-list dllist {\n" @@ -312,6 +318,8 @@ test_invalid(void **state) struct lyd_node *diff = NULL; assert_int_equal(lyd_diff_siblings(model_1, lyd_child(model_1), 0, &diff), LY_EINVAL); + CHECK_LOG_CTX("Invalid arguments - cannot create diff for unrelated data (lyd_diff()).", NULL); + assert_int_equal(lyd_diff_siblings(NULL, NULL, 0, NULL), LY_EINVAL); lyd_free_all(model_1); @@ -637,6 +645,126 @@ test_list(void **state) } static void +test_nested_list(void **state) +{ + struct lyd_node *data1, *data2, *diff; + const char *xml1, *xml2; + + (void) state; + + xml1 = + "<df xmlns=\"urn:libyang:tests:defaults\">" + " <list>" + " <name>n1</name>" + " <value>25</value>" + " <list2>" + " <name2>n22</name2>" + " <value2>26</value2>" + " </list2>" + " </list>" + " <list>" + " <name>n2</name>" + " <value>25</value>" + " <list2>" + " <name2>n22</name2>" + " <value2>26</value2>" + " </list2>" + " </list>" + " <list>" + " <name>n3</name>" + " <value>25</value>" + " <list2>" + " <name2>n22</name2>" + " <value2>26</value2>" + " </list2>" + " </list>" + " <list>" + " <name>n4</name>" + " <value>25</value>" + " <list2>" + " <name2>n22</name2>" + " <value2>26</value2>" + " </list2>" + " </list>" + " <list>" + " <name>n0</name>" + " <value>26</value>" + " <list2>" + " <name2>n22</name2>" + " <value2>26</value2>" + " </list2>" + " <list2>" + " <name2>n23</name2>" + " <value2>26</value2>" + " </list2>" + " </list>" + "</df>"; + xml2 = + "<df xmlns=\"urn:libyang:tests:defaults\">" + " <list>" + " <name>n0</name>" + " <value>30</value>" + " <list2>" + " <name2>n23</name2>" + " <value2>26</value2>" + " </list2>" + " </list>" + "</df>"; + + CHECK_PARSE_LYD(xml1, data1); + CHECK_PARSE_LYD(xml2, data2); + CHECK_PARSE_LYD_DIFF(data1, data2, diff); + + CHECK_LYD_STRING(diff, + "<df xmlns=\"urn:libyang:tests:defaults\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:operation=\"none\">\n" + " <list yang:operation=\"delete\">\n" + " <name>n1</name>\n" + " <value>25</value>\n" + " <list2>\n" + " <name2>n22</name2>\n" + " <value2>26</value2>\n" + " </list2>\n" + " </list>\n" + " <list yang:operation=\"delete\">\n" + " <name>n2</name>\n" + " <value>25</value>\n" + " <list2>\n" + " <name2>n22</name2>\n" + " <value2>26</value2>\n" + " </list2>\n" + " </list>\n" + " <list yang:operation=\"delete\">\n" + " <name>n3</name>\n" + " <value>25</value>\n" + " <list2>\n" + " <name2>n22</name2>\n" + " <value2>26</value2>\n" + " </list2>\n" + " </list>\n" + " <list yang:operation=\"delete\">\n" + " <name>n4</name>\n" + " <value>25</value>\n" + " <list2>\n" + " <name2>n22</name2>\n" + " <value2>26</value2>\n" + " </list2>\n" + " </list>\n" + " <list yang:operation=\"none\">\n" + " <name>n0</name>\n" + " <value yang:operation=\"replace\" yang:orig-default=\"false\" yang:orig-value=\"26\">30</value>\n" + " <list2 yang:operation=\"delete\">\n" + " <name2>n22</name2>\n" + " <value2>26</value2>\n" + " </list2>\n" + " </list>\n" + "</df>\n"); + + lyd_free_all(data1); + lyd_free_all(data2); + lyd_free_all(diff); +} + +static void test_userord_llist(void **state) { (void) state; @@ -940,6 +1068,118 @@ test_userord_list2(void **state) } static void +test_userord_list3(void **state) +{ + (void) state; + const char *xml1 = + "<df xmlns=\"urn:libyang:tests:defaults\">\n" + " <ul>\n" + " <l1>a</l1>\n" + " <l2>1</l2>\n" + " </ul>\n" + " <ul>\n" + " <l1>b</l1>\n" + " <l2>2</l2>\n" + " </ul>\n" + " <ul>\n" + " <l1>c</l1>\n" + " <cont>\n" + " <l3>val1</l3>\n" + " </cont>\n" + " </ul>\n" + " <ul>\n" + " <l1>d</l1>\n" + " <l2>4</l2>\n" + " </ul>\n" + "</df>\n"; + const char *xml2 = + "<df xmlns=\"urn:libyang:tests:defaults\">\n" + " <ul>\n" + " <l1>c</l1>\n" + " <l2>3</l2>\n" + " <cont>\n" + " <l3>val2</l3>\n" + " </cont>\n" + " </ul>\n" + " <ul>\n" + " <l1>a</l1>\n" + " <l2>1</l2>\n" + " </ul>\n" + " <ul>\n" + " <l1>d</l1>\n" + " <l2>44</l2>\n" + " </ul>\n" + " <ul>\n" + " <l1>b</l1>\n" + " <l2>2</l2>\n" + " </ul>\n" + "</df>\n"; + const char *xml3 = + "<df xmlns=\"urn:libyang:tests:defaults\">\n" + " <ul>\n" + " <l1>a</l1>\n" + " </ul>\n" + " <ul>\n" + " <l1>c</l1>\n" + " <l2>3</l2>\n" + " <cont>\n" + " <l3>val2</l3>\n" + " </cont>\n" + " </ul>\n" + " <ul>\n" + " <l1>d</l1>\n" + " <l2>44</l2>\n" + " </ul>\n" + " <ul>\n" + " <l1>b</l1>\n" + " <l2>2</l2>\n" + " </ul>\n" + "</df>\n"; + + const char *out_diff_1 = + "<df xmlns=\"urn:libyang:tests:defaults\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:operation=\"none\">\n" + " <ul yang:operation=\"replace\" yang:key=\"\" yang:orig-key=\"[l1='b']\">\n" + " <l1>c</l1>\n" + " <l2 yang:operation=\"create\">3</l2>\n" + " <cont yang:operation=\"none\">\n" + " <l3 yang:operation=\"replace\" yang:orig-default=\"false\" yang:orig-value=\"val1\">val2</l3>\n" + " </cont>\n" + " </ul>\n" + " <ul yang:operation=\"replace\" yang:key=\"[l1='a']\" yang:orig-key=\"[l1='b']\">\n" + " <l1>d</l1>\n" + " <l2 yang:operation=\"replace\" yang:orig-default=\"false\" yang:orig-value=\"4\">44</l2>\n" + " </ul>\n" + "</df>\n"; + const char *out_diff_2 = + "<df xmlns=\"urn:libyang:tests:defaults\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:operation=\"none\">\n" + " <ul yang:operation=\"replace\" yang:key=\"\" yang:orig-key=\"[l1='c']\">\n" + " <l1>a</l1>\n" + " <l2 yang:operation=\"delete\">1</l2>\n" + " </ul>\n" + "</df>\n"; + const char *out_merge = + "<df xmlns=\"urn:libyang:tests:defaults\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:operation=\"none\">\n" + " <ul yang:operation=\"replace\" yang:key=\"\" yang:orig-key=\"[l1='b']\">\n" + " <l1>c</l1>\n" + " <l2 yang:operation=\"create\">3</l2>\n" + " <cont yang:operation=\"none\">\n" + " <l3 yang:operation=\"replace\" yang:orig-default=\"false\" yang:orig-value=\"val1\">val2</l3>\n" + " </cont>\n" + " </ul>\n" + " <ul yang:operation=\"replace\" yang:key=\"[l1='a']\" yang:orig-key=\"[l1='b']\">\n" + " <l1>d</l1>\n" + " <l2 yang:operation=\"replace\" yang:orig-default=\"false\" yang:orig-value=\"4\">44</l2>\n" + " </ul>\n" + " <ul yang:key=\"\" yang:orig-key=\"[l1='c']\" yang:operation=\"replace\">\n" + " <l1>a</l1>\n" + " <l2 yang:operation=\"delete\">1</l2>\n" + " </ul>\n" + "</df>\n"; + + TEST_DIFF_3(xml1, xml2, xml3, out_diff_1, out_diff_2, out_merge); +} + +static void test_keyless_list(void **state) { (void) state; @@ -1207,11 +1447,13 @@ main(void) UTEST(test_delete_merge, setup), UTEST(test_leaf, setup), UTEST(test_list, setup), + UTEST(test_nested_list, setup), UTEST(test_userord_llist, setup), UTEST(test_userord_llist2, setup), UTEST(test_userord_mix, setup), UTEST(test_userord_list, setup), UTEST(test_userord_list2, setup), + UTEST(test_userord_list3, setup), UTEST(test_keyless_list, setup), UTEST(test_state_llist, setup), UTEST(test_wd, setup), diff --git a/tests/utests/data/test_new.c b/tests/utests/data/test_new.c index 7642960..5cee903 100644 --- a/tests/utests/data/test_new.c +++ b/tests/utests/data/test_new.c @@ -55,6 +55,7 @@ const char *schema_a = "module a {\n" " anydata any {\n" " config false;\n" " }\n" + " anyxml anyx;\n" " leaf-list ll2 {\n" " config false;\n" " type string;\n" @@ -128,6 +129,16 @@ test_top_level(void **state) assert_int_equal(lyd_new_list2(NULL, mod, "l1", "[a= 'a']\n[b =\t'b']", 0, &node), LY_SUCCESS); lyd_free_tree(node); + const char *key_vals[] = {"a", "b"}; + + assert_int_equal(lyd_new_list3(NULL, mod, "l1", key_vals, NULL, 0, &node), LY_SUCCESS); + lyd_free_tree(node); + + uint32_t val_lens[] = {1, 1}; + + assert_int_equal(lyd_new_list3_bin(NULL, mod, "l1", (const void **)key_vals, val_lens, 0, &node), LY_SUCCESS); + lyd_free_tree(node); + /* leaf */ assert_int_equal(lyd_new_term(NULL, mod, "foo", "[a='a'][b='b'][c='c']", 0, &node), LY_EVALID); CHECK_LOG_CTX("Invalid type uint16 value \"[a='a'][b='b'][c='c']\".", "Schema location \"/a:foo\"."); @@ -290,6 +301,7 @@ test_path(void **state) ret = lyd_new_path2(root, NULL, "/a:c2/l3[1]", NULL, 0, 0, 0, NULL, &node); assert_int_equal(ret, LY_EEXIST); + CHECK_LOG_CTX("Path \"/a:c2/l3[1]\" already exists.", "Data location \"/a:c2/l3[1]\"."); ret = lyd_new_path2(root, NULL, "/a:c2/l3[2]/x", "val2", 0, 0, 0, NULL, &node); assert_int_equal(ret, LY_SUCCESS); @@ -348,6 +360,7 @@ test_path(void **state) ret = lyd_new_path2(root, NULL, "/a:ll2[1]", "", 0, 0, 0, NULL, &node); assert_int_equal(ret, LY_EEXIST); + CHECK_LOG_CTX("Path \"/a:ll2[1]\" already exists.", "Data location \"/a:ll2[1]\"."); ret = lyd_new_path2(root, NULL, "/a:ll2[2]", "val2", 0, 0, 0, NULL, &node); assert_int_equal(ret, LY_SUCCESS); @@ -362,6 +375,7 @@ test_path(void **state) ret = lyd_new_path2(root, NULL, "/a:ll2[3][.='val3']", NULL, 0, 0, 0, NULL, &node); assert_int_equal(ret, LY_EVALID); + CHECK_LOG_CTX("Unparsed characters \"[.='val3']\" left at the end of path.", NULL); lyd_print_mem(&str, root, LYD_XML, LYD_PRINT_WITHSIBLINGS); assert_string_equal(str, @@ -391,6 +405,55 @@ test_path(void **state) "}\n"); free(str); lyd_free_siblings(root); + + /* anyxml */ + ret = lyd_new_path2(NULL, UTEST_LYCTX, "/a:anyx", "<a/><b/><c/>", 0, LYD_ANYDATA_XML, 0, &root, NULL); + assert_int_equal(ret, LY_SUCCESS); + assert_non_null(root); + + lyd_print_mem(&str, root, LYD_XML, LYD_PRINT_WITHSIBLINGS); + assert_string_equal(str, + "<anyx xmlns=\"urn:tests:a\">\n" + " <a/>\n" + " <b/>\n" + " <c/>\n" + "</anyx>\n"); + free(str); + lyd_print_mem(&str, root, LYD_JSON, LYD_PRINT_WITHSIBLINGS); + assert_string_equal(str, + "{\n" + " \"a:anyx\": {\n" + " \"a\": [null],\n" + " \"b\": [null],\n" + " \"c\": [null]\n" + " }\n" + "}\n"); + free(str); + lyd_free_siblings(root); + + ret = lyd_new_path2(NULL, UTEST_LYCTX, "/a:anyx", "{\"a\":[null],\"b\":[null],\"c\":[null]}", 0, LYD_ANYDATA_JSON, 0, &root, NULL); + assert_int_equal(ret, LY_SUCCESS); + assert_non_null(root); + + lyd_print_mem(&str, root, LYD_XML, LYD_PRINT_WITHSIBLINGS); + assert_string_equal(str, + "<anyx xmlns=\"urn:tests:a\">\n" + " <a/>\n" + " <b/>\n" + " <c/>\n" + "</anyx>\n"); + free(str); + lyd_print_mem(&str, root, LYD_JSON, LYD_PRINT_WITHSIBLINGS); + assert_string_equal(str, + "{\n" + " \"a:anyx\": {\n" + " \"a\": [null],\n" + " \"b\": [null],\n" + " \"c\": [null]\n" + " }\n" + "}\n"); + free(str); + lyd_free_siblings(root); } static void diff --git a/tests/utests/data/test_parser_json.c b/tests/utests/data/test_parser_json.c index d341e31..8feed9c 100644 --- a/tests/utests/data/test_parser_json.c +++ b/tests/utests/data/test_parser_json.c @@ -1,9 +1,10 @@ -/* +/** * @file test_parser_json.c - * @author: Radek Krejci <rkrejci@cesnet.cz> - * @brief unit tests for functions from parser_xml.c + * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Michal Vasko <mvasko@cesnet.cz> + * @brief unit tests for JSON parser * - * Copyright (c) 2019 CESNET, z.s.p.o. + * Copyright (c) 2019 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -44,7 +45,10 @@ setup(void **state) "leaf-list ll1 { type uint8; }" "leaf foo2 { type string; default \"default-val\"; }" "leaf foo3 { type uint32; }" - "notification n2;}"; + "leaf foo4 { type uint64; }" + "rpc r1 {input {leaf l1 {type string;} leaf l2 {type string;}}}" + "notification n2;" + "}"; UTEST_SETUP; @@ -160,6 +164,7 @@ test_leaf(void **state) /* reverse solidus in JSON object member name */ data = "{\"@a:foo\":{\"a:hi\\nt\":1},\"a:foo\":\"xxx\"}"; assert_int_equal(LY_EINVAL, lyd_parse_data_mem(UTEST_LYCTX, data, LYD_JSON, 0, LYD_VALIDATE_PRESENT, &tree)); + CHECK_LOG_CTX("Annotation definition for attribute \"a:hi\nt\" not found.", "Path \"/@a:foo/@a:hi\nt\", line number 1."); } static void @@ -427,12 +432,10 @@ test_list(void **state) assert_non_null(leaf = (struct lyd_node_term *)leaf->next); CHECK_LYSC_NODE(leaf->schema, NULL, 0, LYS_CONFIG_W | LYS_STATUS_CURR, 1, "d", 1, LYS_LEAF, 1, 0, NULL, 0); CHECK_LOG_CTX(NULL, NULL); - CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, "{\"a:l1\":[{\"a\":\"a\",\"b\":\"b\",\"c\":1,\"d\":\"d\"}]}"); lyd_free_all(tree); - /* */ CHECK_PARSE_LYD("{\"a:l1\":[{\"c\":1,\"b\":\"b\",\"a\":\"a\"}]}", LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, tree); CHECK_LYSC_NODE(tree->schema, NULL, 0, LYS_CONFIG_W | LYS_STATUS_CURR | LYS_ORDBY_SYSTEM, 1, "l1", 1, LYS_LIST, 0, 0, NULL, 0); @@ -447,11 +450,16 @@ test_list(void **state) CHECK_LYSC_NODE(leaf->schema, NULL, 0, LYS_CONFIG_W | LYS_STATUS_CURR | LYS_KEY, 1, "c", 1, LYS_LEAF, 1, 0, NULL, 0); CHECK_LOG_CTX(NULL, NULL); - CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, "{\"a:l1\":[{\"a\":\"a\",\"b\":\"b\",\"c\":1}]}"); lyd_free_all(tree); + /* skip unknown nested nodes */ + data = "{\"a:l1\":[{\"a\":\"val_a\",\"b\":\"val_b\",\"c\":3,\"counters\":{\"count1\":\"c1\",\"count2\":\"c2\"}}]}"; + CHECK_PARSE_LYD(data, LYD_PARSE_ONLY, 0, tree); + CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, "{\"a:l1\":[{\"a\":\"val_a\",\"b\":\"val_b\",\"c\":3}]}"); + lyd_free_all(tree); + data = "{\"a:cp\":{\"@\":{\"a:hint\":1}}}"; CHECK_PARSE_LYD(data, 0, LYD_VALIDATE_PRESENT, tree); assert_non_null(tree); @@ -460,7 +468,6 @@ test_list(void **state) 1, LYS_CONTAINER, 0, 0, NULL, 0); CHECK_LYD_META(tree->meta, 1, "hint", 0, 1, INT8, "1", 1); assert_null(tree->meta->next); - CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, data); lyd_free_all(tree); } @@ -516,6 +523,25 @@ test_opaq(void **state) CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, data); lyd_free_all(tree); + /* special chars */ + data = "{\"a:foo3\":\"ab\\\"\\\\\\r\\t\"}"; + CHECK_PARSE_LYD(data, LYD_PARSE_OPAQ | LYD_PARSE_ONLY, 0, tree); + CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, data); + lyd_free_all(tree); + + /* wrong encoding */ + data = "{\"a:foo3\":\"25\"}"; + CHECK_PARSE_LYD(data, LYD_PARSE_OPAQ | LYD_PARSE_ONLY, 0, tree); + CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)tree, 0, 0, LY_VALUE_JSON, "foo3", 0, 0, NULL, 0, "25"); + CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, data); + lyd_free_all(tree); + + data = "{\"a:foo4\":25}"; + CHECK_PARSE_LYD(data, LYD_PARSE_OPAQ | LYD_PARSE_ONLY, 0, tree); + CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)tree, 0, 0, LY_VALUE_JSON, "foo4", 0, 0, NULL, 0, "25"); + CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, data); + lyd_free_all(tree); + /* missing key, no flags */ data = "{\"a:l1\":[{\"a\":\"val_a\",\"b\":\"val_b\",\"d\":\"val_d\"}]}"; PARSER_CHECK_ERROR(data, 0, LYD_VALIDATE_PRESENT, tree, LY_EVALID, @@ -555,11 +581,12 @@ test_opaq(void **state) /* invalid metadata */ data = "{\"@a:foo\":\"str\",\"@a:foo3\":1,\"a:foo3\":2}"; PARSER_CHECK_ERROR(data, 0, LYD_VALIDATE_PRESENT, tree, LY_EVALID, - "Unknown module of node \"@a:foo\".", "Data location \"/@a:foo\"."); + "Unknown module of node \"@a:foo\".", "Path \"/\"."); + CHECK_LOG_CTX("Missing JSON data instance to be coupled with @a:foo metadata.", "Data location \"/@a:foo\", line number 1."); /* empty name */ PARSER_CHECK_ERROR("{\"@a:foo\":{\"\":0}}", 0, LYD_VALIDATE_PRESENT, tree, LY_EVALID, - "A JSON object member name cannot be a zero-length string.", "Line number 1."); + "JSON object member name cannot be a zero-length string.", "Data location \"/@a:foo\", line number 1."); /* opaque data tree format print */ data = @@ -626,6 +653,7 @@ static void test_rpc(void **state) { const char *data; + char *str; struct ly_in *in; struct lyd_node *tree, *op; const struct lyd_node *node; @@ -675,8 +703,16 @@ test_rpc(void **state) CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, data); lyd_free_all(tree); - /* wrong namespace, element name, whatever... */ - /* TODO */ + /* append to parent */ + assert_int_equal(LY_SUCCESS, lyd_new_path(NULL, UTEST_LYCTX, "/a:r1", NULL, 0, &op)); + assert_int_equal(LY_SUCCESS, ly_in_new_memory("{\"l1\": \"some str\", \"l2\": \"some other str\"}", &in)); + assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, op, in, LYD_JSON, LYD_TYPE_RPC_YANG, &tree, NULL)); + ly_in_free(in, 0); + + assert_int_equal(LY_SUCCESS, lyd_print_mem(&str, op, LYD_JSON, LYD_PRINT_WITHSIBLINGS | LYD_PRINT_SHRINK)); + lyd_free_tree(op); + assert_string_equal(str, "{\"a:r1\":{\"l1\":\"some str\",\"l2\":\"some other str\"}}"); + free(str); } static void @@ -772,6 +808,112 @@ test_reply(void **state) /* TODO */ } +static void +test_restconf_rpc(void **state) +{ + const char *data; + struct ly_in *in; + struct lyd_node *tree, *envp; + + assert_non_null((ly_ctx_load_module(UTEST_LYCTX, "ietf-netconf-nmda", "2019-01-07", NULL))); + + assert_int_equal(LY_SUCCESS, lyd_new_path(NULL, UTEST_LYCTX, "/ietf-netconf-nmda:edit-data", NULL, 0, &tree)); + + data = "{\"ietf-netconf-nmda:input\":{" + "\"datastore\":\"ietf-datastores:running\"," + "\"config\":{\"a:cp\":{\"z\":[null],\"@z\":{\"ietf-netconf:operation\":\"replace\"}}," + "\"a:l1\":[{\"@\":{\"ietf-netconf:operation\":\"replace\"},\"a\":\"val_a\",\"b\":\"val_b\",\"c\":\"val_c\"}]}" + "}}"; + assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in)); + assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, tree, in, LYD_JSON, LYD_TYPE_RPC_RESTCONF, &envp, NULL)); + ly_in_free(in, 0); + + /* the same just connected to the edit-data RPC */ + data = "{\"ietf-netconf-nmda:edit-data\":{" + "\"datastore\":\"ietf-datastores:running\"," + "\"config\":{\"a:cp\":{\"z\":[null],\"@z\":{\"ietf-netconf:operation\":\"replace\"}}," + "\"a:l1\":[{\"@\":{\"ietf-netconf:operation\":\"replace\"},\"a\":\"val_a\",\"b\":\"val_b\",\"c\":\"val_c\"}]}" + "}}"; + CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, data); + lyd_free_all(tree); + lyd_free_all(envp); +} + +static void +test_restconf_notification(void **state) +{ + const char *data; + struct ly_in *in; + struct lyd_node *tree, *ntf; + + data = "{\"ietf-restconf:notification\":{\"eventTime\":\"2013-12-21T00:01:00Z\",\"a:c\":{\"n1\":{\"nl\":\"value\"}}}}"; + assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in)); + assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_JSON, LYD_TYPE_NOTIF_RESTCONF, &tree, &ntf)); + ly_in_free(in, 0); + + /* envelopes separately */ + data = "{\"ietf-restconf:notification\":{\"eventTime\":\"2013-12-21T00:01:00Z\"}}"; + CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, data); + + /* notification with the parent node */ + data = "{\"a:c\":{\"n1\":{\"nl\":\"value\"}}}"; + CHECK_LYD_STRING(lyd_parent(ntf), LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, data); + + lyd_free_all(tree); + lyd_free_all(ntf); + + /* wrong order */ + data = "{\"ietf-restconf:notification\":{\"a:n2\":{},\"eventTime\":\"2013-12-21T00:01:00Z\"}}"; + assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in)); + assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_JSON, LYD_TYPE_NOTIF_RESTCONF, &tree, &ntf)); + ly_in_free(in, 0); + + lyd_free_all(tree); + lyd_free_all(ntf); + + /* unknown notification */ + data = "{\"ietf-restconf:notification\":{\"eventTime\":\"2013-12-21T00:01:00Z\",\"invalid:n2\":{}}}"; + assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in)); + assert_int_equal(LY_EVALID, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_JSON, LYD_TYPE_NOTIF_RESTCONF, &tree, &ntf)); + UTEST_LOG_CTX_CLEAN; + ly_in_free(in, 0); + lyd_free_all(tree); +} + +static void +test_restconf_reply(void **state) +{ + const char *data; + struct ly_in *in; + struct lyd_node *tree, *envp; + + assert_int_equal(LY_SUCCESS, lyd_new_path(NULL, UTEST_LYCTX, "/a:c/act", NULL, 0, &tree)); + + data = "{\"a:output\":{\"al\":25}}"; + assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in)); + assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, lyd_child(tree), in, LYD_JSON, LYD_TYPE_REPLY_RESTCONF, &envp, NULL)); + ly_in_free(in, 0); + + /* connected to the RPC with the parent */ + data = "{\"a:c\":{\"act\":{\"al\":25}}}"; + CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, data); + lyd_free_all(tree); + lyd_free_all(envp); +} + +static void +test_metadata(void **state) +{ + const char *data; + struct lyd_node *tree; + + /* invalid metadata value */ + data = "{\"a:c\":{\"x\":\"xval\",\"@x\":{\"a:hint\":\"value\"}}}"; + assert_int_equal(LY_EVALID, lyd_parse_data_mem(_UC->ctx, data, LYD_JSON, 0, LYD_VALIDATE_PRESENT, &tree)); + assert_null(tree); + CHECK_LOG_CTX("Invalid non-number-encoded int8 value \"value\".", "Path \"/a:c/x/@a:hint\", line number 1."); +} + int main(void) { @@ -787,6 +929,10 @@ main(void) UTEST(test_action, setup), UTEST(test_notification, setup), UTEST(test_reply, setup), + UTEST(test_restconf_rpc, setup), + UTEST(test_restconf_notification, setup), + UTEST(test_restconf_reply, setup), + UTEST(test_metadata, setup), }; return cmocka_run_group_tests(tests, NULL, NULL); diff --git a/tests/utests/data/test_parser_xml.c b/tests/utests/data/test_parser_xml.c index 7defd9c..4f33f00 100644 --- a/tests/utests/data/test_parser_xml.c +++ b/tests/utests/data/test_parser_xml.c @@ -31,6 +31,7 @@ setup(void **state) " namespace urn:tests:a;\n" " prefix a;\n" " yang-version 1.1;\n" + " import ietf-yang-metadata {prefix md;}" " list l1 { key \"a b c\"; leaf a {type string;} leaf b {type string;} leaf c {type int16;}" " leaf d {type string;}" " container cont {leaf e {type boolean;}}" @@ -42,9 +43,12 @@ setup(void **state) " notification n1 { leaf nl {type string;}}}\n" " container cp {presence \"container switch\"; leaf y {type string;} leaf z {type int8;}}\n" " anydata any {config false;}\n" + " anyxml anyx;\n" " leaf foo2 { type string; default \"default-val\"; }\n" " leaf foo3 { type uint32; }\n" - " notification n2;}"; + " notification n2;" + " md:annotation attr {type enumeration {enum val;}}" + "}"; UTEST_SETUP; @@ -122,6 +126,7 @@ static void test_anydata(void **state) { const char *data; + char *str; struct lyd_node *tree; data = "<any xmlns=\"urn:tests:a\">\n" @@ -145,6 +150,49 @@ test_anydata(void **state) "</any>\n"; CHECK_LYD_STRING(tree, LYD_PRINT_WITHSIBLINGS, data_expected); + + assert_int_equal(LY_SUCCESS, lyd_any_value_str(tree, &str)); + lyd_free_all(tree); + + assert_int_equal(LY_SUCCESS, lyd_new_path2(NULL, UTEST_LYCTX, "/a:any", str, strlen(str), LYD_ANYDATA_XML, 0, &tree, NULL)); + free(str); + CHECK_LYD_STRING(tree, LYD_PRINT_WITHSIBLINGS, data_expected); + lyd_free_all(tree); +} + +static void +test_anyxml(void **state) +{ + const char *data; + char *str; + struct lyd_node *tree; + + data = "<anyx xmlns=\"urn:tests:a\">\n" + " <element1>\n" + " <element2 x:attr2=\"test\" xmlns:x=\"urn:x\">data</element2>\n" + " </element1>\n" + " <element1a/>\n" + "</anyx>\n"; + CHECK_PARSE_LYD(data, 0, LYD_VALIDATE_PRESENT, tree); + assert_non_null(tree); + tree = tree->next; + + const char *data_expected = + "<anyx xmlns=\"urn:tests:a\">\n" + " <element1>\n" + " <element2 xmlns:x=\"urn:x\" x:attr2=\"test\">data</element2>\n" + " </element1>\n" + " <element1a/>\n" + "</anyx>\n"; + + CHECK_LYD_STRING(tree, LYD_PRINT_WITHSIBLINGS, data_expected); + + assert_int_equal(LY_SUCCESS, lyd_any_value_str(tree, &str)); + lyd_free_all(tree); + + assert_int_equal(LY_SUCCESS, lyd_new_path2(NULL, UTEST_LYCTX, "/a:anyx", str, strlen(str), LYD_ANYDATA_XML, 0, &tree, NULL)); + free(str); + CHECK_LYD_STRING(tree, LYD_PRINT_WITHSIBLINGS, data_expected); lyd_free_all(tree); } @@ -170,17 +218,21 @@ test_list(void **state) /* missing keys */ PARSER_CHECK_ERROR("<l1 xmlns=\"urn:tests:a\"><c>1</c><b>b</b></l1>", 0, LYD_VALIDATE_PRESENT, tree, LY_EVALID, "List instance is missing its key \"a\".", "Data location \"/a:l1[b='b'][c='1']\", line number 1."); + CHECK_LOG_CTX("Invalid position of the key \"b\" in a list.", NULL); PARSER_CHECK_ERROR("<l1 xmlns=\"urn:tests:a\"><a>a</a></l1>", 0, LYD_VALIDATE_PRESENT, tree, LY_EVALID, "List instance is missing its key \"b\".", "Data location \"/a:l1[a='a']\", line number 1."); PARSER_CHECK_ERROR("<l1 xmlns=\"urn:tests:a\"><b>b</b><a>a</a></l1>", 0, LYD_VALIDATE_PRESENT, tree, LY_EVALID, "List instance is missing its key \"c\".", "Data location \"/a:l1[a='a'][b='b']\", line number 1."); + CHECK_LOG_CTX("Invalid position of the key \"a\" in a list.", NULL); /* key duplicate */ PARSER_CHECK_ERROR("<l1 xmlns=\"urn:tests:a\"><c>1</c><b>b</b><a>a</a><c>1</c></l1>", 0, LYD_VALIDATE_PRESENT, tree, LY_EVALID, "Duplicate instance of \"c\".", "Data location \"/a:l1[a='a'][b='b'][c='1'][c='1']/c\", line number 1."); + CHECK_LOG_CTX("Invalid position of the key \"a\" in a list.", NULL); + CHECK_LOG_CTX("Invalid position of the key \"b\" in a list.", NULL); /* keys order */ CHECK_PARSE_LYD("<l1 xmlns=\"urn:tests:a\"><d>d</d><a>a</a><c>1</c><b>b</b></l1>", 0, LYD_VALIDATE_PRESENT, tree); @@ -209,6 +261,7 @@ test_list(void **state) assert_non_null(leaf = (struct lyd_node_term *)leaf->next); CHECK_LYSC_NODE(leaf->schema, NULL, 0, LYS_CONFIG_W | LYS_STATUS_CURR | LYS_KEY, 1, "c", 1, LYS_LEAF, 1, 0, NULL, 0); CHECK_LOG_CTX("Invalid position of the key \"a\" in a list.", NULL); + CHECK_LOG_CTX("Invalid position of the key \"b\" in a list.", NULL); lyd_free_all(tree); PARSER_CHECK_ERROR(data, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, tree, LY_EVALID, @@ -250,14 +303,14 @@ test_opaq(void **state) /* opaq flag */ CHECK_PARSE_LYD(data, LYD_PARSE_OPAQ | LYD_PARSE_ONLY, 0, tree); - CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)tree, 0, 0, LY_VALUE_XML, "foo3", 0, 0, NULL, 0, ""); + CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)tree, 0, 0, LY_VALUE_XML, "foo3", 0, 0, NULL, 1, ""); CHECK_LYD_STRING(tree, LYD_PRINT_WITHSIBLINGS, "<foo3 xmlns=\"urn:tests:a\"/>\n"); lyd_free_all(tree); /* list, opaq flag */ data = "<l1 xmlns=\"urn:tests:a\"/>"; CHECK_PARSE_LYD(data, LYD_PARSE_OPAQ | LYD_PARSE_ONLY, 0, tree); - CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)tree, 0, 0, LY_VALUE_XML, "l1", 0, 0, NULL, 0, ""); + CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)tree, 0, 0, LY_VALUE_XML, "l1", 0, 0, NULL, 1, ""); CHECK_LYD_STRING(tree, LYD_PRINT_WITHSIBLINGS, "<l1 xmlns=\"urn:tests:a\"/>\n"); lyd_free_all(tree); @@ -273,7 +326,7 @@ test_opaq(void **state) /* opaq flag */ CHECK_PARSE_LYD(data, LYD_PARSE_OPAQ | LYD_PARSE_ONLY, 0, tree); - CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)tree, 0, 0x1, LY_VALUE_XML, "l1", 0, 0, NULL, 0, ""); + CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)tree, 0, 0x1, LY_VALUE_XML, "l1", 0, 0, NULL, 1, ""); CHECK_LYD_STRING(tree, LYD_PRINT_WITHSIBLINGS, data); lyd_free_all(tree); @@ -289,7 +342,7 @@ test_opaq(void **state) /* opaq flag */ CHECK_PARSE_LYD(data, LYD_PARSE_OPAQ | LYD_PARSE_ONLY, 0, tree); - CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)tree, 0, 0x1, LY_VALUE_XML, "l1", 0, 0, NULL, 0, ""); + CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)tree, 0, 0x1, LY_VALUE_XML, "l1", 0, 0, NULL, 1, ""); CHECK_LYD_STRING(tree, LYD_PRINT_WITHSIBLINGS, data); lyd_free_all(tree); @@ -300,7 +353,7 @@ test_opaq(void **state) " <c xmld:id=\"D\">1</c>\n" "</a>\n", LYD_XML, LYD_PARSE_OPAQ, LYD_VALIDATE_PRESENT, &tree)); - CHECK_LOG_CTX("Unknown XML prefix \"xmld\".", "Line number 3."); + CHECK_LOG_CTX("Unknown XML prefix \"xmld\".", "Data location \"/a\", line number 3."); } static void @@ -358,10 +411,10 @@ test_rpc(void **state) node = lyd_child(node); /* z has no value */ - CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)node, 0x1, 0, LY_VALUE_XML, "z", 0, 0, NULL, 0, ""); + CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)node, 0x1, 0, LY_VALUE_XML, "z", 0, 0, NULL, 1, ""); node = node->parent->next; /* l1 key c has invalid value so it is at the end */ - CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)node, 0x1, 0x1, LY_VALUE_XML, "l1", 0, 0, NULL, 0, ""); + CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)node, 0x1, 0x1, LY_VALUE_XML, "l1", 0, 0, NULL, 1, ""); CHECK_LYD_STRING(tree, LYD_PRINT_WITHSIBLINGS, "<edit-config xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n" @@ -554,10 +607,10 @@ test_netconf_rpc(void **state) node = lyd_child(node); /* z has no value */ - CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)node, 0x1, 0, LY_VALUE_XML, "z", 0, 0, NULL, 0, ""); + CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)node, 0x1, 0, LY_VALUE_XML, "z", 0, 0, NULL, 1, ""); node = node->parent->next; /* l1 key c has invalid value so it is at the end */ - CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)node, 0x1, 0x1, LY_VALUE_XML, "l1", 0, 0, NULL, 0, ""); + CHECK_LYD_NODE_OPAQ((struct lyd_node_opaq *)node, 0x1, 0x1, LY_VALUE_XML, "l1", 0, 0, NULL, 1, ""); CHECK_LYD_STRING(tree, LYD_PRINT_WITHSIBLINGS, "<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" message-id=\"25\"/>\n"); @@ -581,8 +634,33 @@ test_netconf_rpc(void **state) lyd_free_all(tree); lyd_free_all(op); - /* wrong namespace, element name, whatever... */ - /* TODO */ + /* invalid anyxml nested metadata value */ + data = "<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" message-id=\"1\" pid=\"4114692032\">\n" + " <copy-config>\n" + " <target>\n" + " <running/>\n" + " </target>\n" + " <source>\n" + " <config>\n" + " <l1 xmlns=\"urn:tests:a\" xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n" + " <a>val_a</a>\n" + " <b>val_b</b>\n" + " <c>5</c>\n" + " <cont nc:operation=\"merge\">\n" + " <e nc:operation=\"merge2\">false</e>\n" + " </cont>\n" + " </l1>\n" + " </config>\n" + " </source>\n" + " </copy-config>\n" + "</rpc>\n"; + assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in)); + assert_int_equal(LY_EVALID, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_XML, LYD_TYPE_RPC_NETCONF, &tree, &op)); + ly_in_free(in, 0); + CHECK_LOG_CTX("Invalid enumeration value \"merge2\".", + "Path \"/ietf-netconf:copy-config/source/config/a:l1[a='val_a'][b='val_b'][c='5']/cont/e/@ietf-netconf:operation\", line number 13."); + lyd_free_all(tree); + assert_null(op); } static void @@ -678,6 +756,22 @@ test_netconf_reply_or_notification(void **state) lyd_free_all(tree); lyd_free_all(op2); + /* notification with a different order */ + data = "<notification xmlns=\"urn:ietf:params:xml:ns:netconf:notification:1.0\">\n" + "<c xmlns=\"urn:tests:a\">\n" + " <n1>\n" + " <nl>value</nl>\n" + " </n1>\n" + "</c>\n" + "<eventTime>2010-12-06T08:00:01Z</eventTime>\n" + "</notification>\n"; + assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in)); + assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, NULL, in, LYD_XML, LYD_TYPE_NOTIF_NETCONF, &tree, &op2)); + ly_in_free(in, 0); + + lyd_free_all(tree); + lyd_free_all(op2); + /* parse a data reply */ data = "<rpc-reply message-id=\"55\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n" " <al xmlns=\"urn:tests:a\">25</al>\n" @@ -739,6 +833,64 @@ test_netconf_reply_or_notification(void **state) } static void +test_restconf_rpc(void **state) +{ + const char *data; + struct ly_in *in; + struct lyd_node *tree, *envp; + + assert_non_null((ly_ctx_load_module(UTEST_LYCTX, "ietf-netconf-nmda", "2019-01-07", NULL))); + + assert_int_equal(LY_SUCCESS, lyd_new_path(NULL, UTEST_LYCTX, "/ietf-netconf-nmda:edit-data", NULL, 0, &tree)); + + data = "<input xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-nmda\">" + "<datastore xmlns:ds=\"urn:ietf:params:xml:ns:yang:ietf-datastores\">ds:running</datastore>" + "<config>" + "<cp xmlns=\"urn:tests:a\"><z xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\" nc:operation=\"replace\"/></cp>" + "<l1 xmlns=\"urn:tests:a\" xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\" nc:operation=\"replace\">" + "<a>val_a</a><b>val_b</b><c>val_c</c>" + "</l1>" + "</config></input>"; + assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in)); + assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, tree, in, LYD_XML, LYD_TYPE_RPC_RESTCONF, &envp, NULL)); + ly_in_free(in, 0); + + /* the same just connected to the edit-data RPC */ + data = "<edit-data xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-nmda\">" + "<datastore xmlns:ds=\"urn:ietf:params:xml:ns:yang:ietf-datastores\">ds:running</datastore>" + "<config>" + "<cp xmlns=\"urn:tests:a\"><z xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\" nc:operation=\"replace\"/></cp>" + "<l1 xmlns=\"urn:tests:a\" xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\" nc:operation=\"replace\">" + "<a>val_a</a><b>val_b</b><c>val_c</c>" + "</l1>" + "</config></edit-data>"; + CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, data); + lyd_free_all(tree); + lyd_free_all(envp); +} + +static void +test_restconf_reply(void **state) +{ + const char *data; + struct ly_in *in; + struct lyd_node *tree, *envp; + + assert_int_equal(LY_SUCCESS, lyd_new_path(NULL, UTEST_LYCTX, "/a:c/act", NULL, 0, &tree)); + + data = "<output xmlns=\"urn:tests:a\"><al>25</al></output>"; + assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in)); + assert_int_equal(LY_SUCCESS, lyd_parse_op(UTEST_LYCTX, lyd_child(tree), in, LYD_XML, LYD_TYPE_REPLY_RESTCONF, &envp, NULL)); + ly_in_free(in, 0); + + /* connected to the RPC with the parent */ + data = "<c xmlns=\"urn:tests:a\"><act><al>25</al></act></c>"; + CHECK_LYD_STRING(tree, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS, data); + lyd_free_all(tree); + lyd_free_all(envp); +} + +static void test_filter_attributes(void **state) { const char *data; @@ -812,12 +964,58 @@ test_data_skip(void **state) lyd_free_all(tree); } +static void +test_metadata(void **state) +{ + const char *data; + struct lyd_node *tree; + + /* invalid metadata value */ + data = "<c xmlns=\"urn:tests:a\" xmlns:a=\"urn:tests:a\"><x a:attr=\"value\">xval</x></c>"; + assert_int_equal(LY_EVALID, lyd_parse_data_mem(_UC->ctx, data, LYD_XML, 0, LYD_VALIDATE_PRESENT, &tree)); + assert_null(tree); + CHECK_LOG_CTX("Invalid enumeration value \"value\".", "Path \"/a:c/x/@a:attr\", line number 1."); +} + +static void +test_subtree(void **state) +{ + const char *data; + struct ly_in *in; + struct lyd_node *tree; + + /* prepare data with the parent */ + data = "<l1 xmlns=\"urn:tests:a\">\n" + " <a>val_a</a>\n" + " <b>val_b</b>\n" + " <c>1</c>\n" + "</l1>\n"; + assert_int_equal(LY_SUCCESS, lyd_parse_data_mem(UTEST_LYCTX, data, LYD_XML, 0, LYD_VALIDATE_PRESENT, &tree)); + + /* parse a subtree of it */ + data = "<cont xmlns=\"urn:tests:a\">\n" + " <e>true</e>\n" + "</cont>\n"; + assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in)); + assert_int_equal(LY_SUCCESS, lyd_parse_data(UTEST_LYCTX, tree, in, LYD_XML, 0, LYD_VALIDATE_PRESENT, NULL)); + ly_in_free(in, 0); + + /* parse another container, fails */ + assert_int_equal(LY_SUCCESS, ly_in_new_memory(data, &in)); + assert_int_equal(LY_EVALID, lyd_parse_data(UTEST_LYCTX, tree, in, LYD_XML, 0, LYD_VALIDATE_PRESENT, NULL)); + ly_in_free(in, 0); + CHECK_LOG_CTX("Duplicate instance of \"cont\".", "Data location \"/a:l1[a='val_a'][b='val_b'][c='1']/cont\"."); + + lyd_free_all(tree); +} + int main(void) { const struct CMUnitTest tests[] = { UTEST(test_leaf, setup), UTEST(test_anydata, setup), + UTEST(test_anyxml, setup), UTEST(test_list, setup), UTEST(test_container, setup), UTEST(test_opaq, setup), @@ -828,8 +1026,12 @@ main(void) UTEST(test_netconf_rpc, setup), UTEST(test_netconf_action, setup), UTEST(test_netconf_reply_or_notification, setup), + UTEST(test_restconf_rpc, setup), + UTEST(test_restconf_reply, setup), UTEST(test_filter_attributes, setup), UTEST(test_data_skip, setup), + UTEST(test_metadata, setup), + UTEST(test_subtree, setup), }; return cmocka_run_group_tests(tests, NULL, NULL); diff --git a/tests/utests/data/test_printer_xml.c b/tests/utests/data/test_printer_xml.c index d533c41..6213a37 100644 --- a/tests/utests/data/test_printer_xml.c +++ b/tests/utests/data/test_printer_xml.c @@ -145,7 +145,8 @@ test_anydata(void **state) " <cont>\n" " <elem1 xmlns=\"urn:tests:defs\">\n" " <elem2 xmlns=\"urn:tests:types\" xmlns:defs=\"urn:tests:defs\" xmlns:defaults=\"urn:defaults\" " - "defs:attr1=\"defaults:val\" attr2=\"/defaults:node/defs:node2\"/>\n" + "defs:attr1=\"defaults:val\" attr2=\"/defaults:node/defs:node2\">\n" + " </elem2>\n" " </elem1>\n" " </cont>\n" "</any>\n"; diff --git a/tests/utests/data/test_tree_data.c b/tests/utests/data/test_tree_data.c index 27dba42..494fdf3 100644 --- a/tests/utests/data/test_tree_data.c +++ b/tests/utests/data/test_tree_data.c @@ -1,9 +1,9 @@ /** * @file test_tree_data.c - * @author: Radek Krejci <rkrejci@cesnet.cz> - * @brief unit tests for functions from tress_data.c + * @author Radek Krejci <rkrejci@cesnet.cz> + * @brief unit tests for functions from tree_data.c * - * Copyright (c) 2018-2019 CESNET, z.s.p.o. + * Copyright (c) 2018-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -100,7 +100,7 @@ test_compare(void **state) data2 = "<l2 xmlns=\"urn:tests:a\"><c><x>b</x></c></l2>"; CHECK_PARSE_LYD(data1, 0, LYD_VALIDATE_PRESENT, tree1); CHECK_PARSE_LYD(data2, 0, LYD_VALIDATE_PRESENT, tree2); - assert_int_equal(LY_ENOT, lyd_compare_single(tree1->next, tree2->next, 0)); + assert_int_equal(LY_ENOT, lyd_compare_single(tree1->next, tree2->next, LYD_COMPARE_FULL_RECURSION)); assert_int_equal(LY_SUCCESS, lyd_compare_single(tree1->next->next, tree2->next, 0)); lyd_free_all(tree1); lyd_free_all(tree2); @@ -145,6 +145,14 @@ test_compare(void **state) assert_int_equal(LY_SUCCESS, lyd_compare_single(tree1, tree2, 0)); lyd_free_all(tree1); lyd_free_all(tree2); + + data1 = "<c xmlns=\"urn:tests:a\"><x>c</x><x>a</x><x>b</x></c>"; + data2 = "<c xmlns=\"urn:tests:a\"><x>a</x><x>b</x><x>c</x></c>"; + CHECK_PARSE_LYD(data1, 0, LYD_VALIDATE_PRESENT, tree1); + CHECK_PARSE_LYD(data2, 0, LYD_VALIDATE_PRESENT, tree2); + assert_int_equal(LY_SUCCESS, lyd_compare_single(tree1, tree2, LYD_COMPARE_FULL_RECURSION)); + lyd_free_all(tree1); + lyd_free_all(tree2); } static void @@ -196,7 +204,7 @@ test_compare_diff_ctx(void **state) data2 = "<l2 xmlns=\"urn:tests:b\"><c><x>b</x></c></l2>"; CHECK_PARSE_LYD_PARAM_CTX(UTEST_LYCTX, data1, 0, tree1); CHECK_PARSE_LYD_PARAM_CTX(ctx2, data2, 0, tree2); - assert_int_equal(LY_ENOT, lyd_compare_single(tree1, tree2, 0)); + assert_int_equal(LY_SUCCESS, lyd_compare_single(tree1, tree2, 0)); lyd_free_all(tree1); lyd_free_all(tree2); @@ -210,7 +218,7 @@ test_compare_diff_ctx(void **state) data2 = "<l2 xmlns=\"urn:tests:b\"><c><x>b</x></c></l2>"; CHECK_PARSE_LYD_PARAM_CTX(UTEST_LYCTX, data1, 0, tree1); CHECK_PARSE_LYD_PARAM_CTX(ctx2, data2, 0, tree2); - assert_int_equal(LY_ENOT, lyd_compare_single(tree1, tree2, 0)); + assert_int_equal(LY_SUCCESS, lyd_compare_single(tree1, tree2, 0)); lyd_free_all(tree1); lyd_free_all(tree2); @@ -329,8 +337,7 @@ test_dup(void **state) data = "<l2 xmlns=\"urn:tests:a\"><c><x>b</x></c></l2>"; CHECK_PARSE_LYD(data, 0, LYD_VALIDATE_PRESENT, tree1); - assert_int_equal(LY_SUCCESS, lyd_dup_single(((struct lyd_node_inner *)((struct lyd_node_inner *)tree1->next)->child)->child, NULL, - LYD_DUP_WITH_PARENTS, &tree2)); + assert_int_equal(LY_SUCCESS, lyd_dup_single(lyd_child(lyd_child(tree1->next)), NULL, LYD_DUP_WITH_PARENTS, &tree2)); int unsigned flag = LYS_CONFIG_R | LYS_SET_ENUM; CHECK_LYSC_NODE(tree2->schema, NULL, 0, flag, 1, "x", 1, LYS_LEAF, 1, 0, NULL, 0); @@ -352,8 +359,8 @@ test_dup(void **state) data = "<l2 xmlns=\"urn:tests:a\"><c><x>b</x></c></l2>"; CHECK_PARSE_LYD(data, 0, LYD_VALIDATE_PRESENT, tree1); assert_int_equal(LY_SUCCESS, lyd_dup_single(tree1->next, NULL, 0, &tree2)); - assert_int_equal(LY_SUCCESS, lyd_dup_single(((struct lyd_node_inner *)((struct lyd_node_inner *)tree1->next)->child)->child, - (struct lyd_node_inner *)tree2, LYD_DUP_WITH_PARENTS, NULL)); + assert_int_equal(LY_SUCCESS, lyd_dup_single(lyd_child(lyd_child(tree1->next)), (struct lyd_node_inner *)tree2, + LYD_DUP_WITH_PARENTS, NULL)); assert_int_equal(LY_SUCCESS, lyd_compare_single(tree1->next, tree2, LYD_COMPARE_FULL_RECURSION)); lyd_free_all(tree1); lyd_free_all(tree2); @@ -363,6 +370,8 @@ test_dup(void **state) CHECK_PARSE_LYD(data, 0, LYD_VALIDATE_PRESENT, tree1); assert_int_equal(LY_EINVAL, lyd_dup_single(((struct lyd_node_inner *)tree1)->child->prev, (struct lyd_node_inner *)tree1->next, LYD_DUP_WITH_PARENTS, NULL)); + CHECK_LOG_CTX("None of the duplicated node \"c\" schema parents match the provided parent \"c\".", + NULL); lyd_free_all(tree1); } @@ -488,6 +497,11 @@ test_find_path(void **state) assert_int_equal(LY_SUCCESS, lyd_find_path(root, "/c:cont/nexthop[gateway='10.0.0.1']", 0, NULL)); assert_int_equal(LY_SUCCESS, lyd_find_path(root, "/c:cont/nexthop[gateway='2100::1']", 0, NULL)); assert_int_equal(LY_SUCCESS, lyd_find_path(root, "/c:cont/pref[.='fc00::/64']", 0, NULL)); + + assert_int_equal(LY_EVALID, lyd_find_path(root, "/cont", 0, NULL)); + CHECK_LOG_CTX("Prefix missing for \"cont\" in path.", "Schema location \"/c:cont\"."); + assert_int_equal(LY_SUCCESS, lyd_find_path(root, "nexthop[gateway='2100::1']", 0, NULL)); + lyd_free_all(root); } @@ -525,6 +539,7 @@ test_data_hash(void **state) /* The run must not crash due to the assert that checks the hash. */ CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT, LY_EVALID, tree); + CHECK_LOG_CTX("Duplicate instance of \"ll\".", "Data location \"/test-data-hash:c/ll[.='']\", line number 1."); lyd_free_all(tree); } diff --git a/tests/utests/data/test_validation.c b/tests/utests/data/test_validation.c index 415e16a..d0dcae5 100644 --- a/tests/utests/data/test_validation.c +++ b/tests/utests/data/test_validation.c @@ -1154,6 +1154,92 @@ test_must(void **state) CHECK_LOG_CTX_APPTAG("l leaf is not left", "Data location \"/i:cont/l3\".", "not-left"); } +static void +test_multi_error(void **state) +{ + struct lyd_node *tree; + const char *schema = + "module ii {\n" + " namespace urn:tests:ii;\n" + " prefix ii;\n" + " yang-version 1.1;\n" + "\n" + " container cont {\n" + " leaf l {\n" + " type string;\n" + " }\n" + " leaf l2 {\n" + " must \"../l = 'right'\";\n" + " type string;\n" + " }\n" + " leaf l3 {\n" + " must \"../l = 'left'\" {\n" + " error-app-tag \"not-left\";\n" + " error-message \"l leaf is not left\";\n" + " }\n" + " type string;\n" + " }\n" + " leaf-list ll {\n" + " type uint32;\n" + " min-elements 2;\n" + " }\n" + " }\n" + "}"; + const char *data; + + UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, NULL); + + /* xml */ + data = + "<cont xmlns=\"urn:tests:ii\">\n" + " <l>wrong</l>\n" + " <l>wrong2</l>\n" + " <l2>val</l2>\n" + " <l3>val</l3>\n" + " <ll>ahoy</ll>\n" + "</cont>\n"; + CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT | LYD_VALIDATE_MULTI_ERROR, LY_EVALID, tree); + CHECK_LOG_CTX_APPTAG("Too few \"ll\" instances.", "Schema location \"/ii:cont/ll\".", "too-few-elements"); + CHECK_LOG_CTX_APPTAG("l leaf is not left", "Data location \"/ii:cont/l3\".", "not-left"); + CHECK_LOG_CTX_APPTAG("Must condition \"../l = 'right'\" not satisfied.", "Data location \"/ii:cont/l2\".", "must-violation"); + CHECK_LOG_CTX_APPTAG("Invalid type uint32 value \"ahoy\".", "Data location \"/ii:cont/ll\", line number 6.", NULL); + + /* json */ + data = "{\n" + " \"ii:cont\": {\n" + " \"l\": \"wrong\",\n" + " \"l\": \"wrong2\",\n" + " \"l2\": \"val\",\n" + " \"l3\": \"val\",\n" + " \"ll\": [\"ahoy\"]\n" + " }\n" + "}\n"; + CHECK_PARSE_LYD_PARAM(data, LYD_JSON, 0, LYD_VALIDATE_PRESENT | LYD_VALIDATE_MULTI_ERROR, LY_EVALID, tree); + CHECK_LOG_CTX_APPTAG("Too few \"ll\" instances.", "Schema location \"/ii:cont/ll\".", "too-few-elements"); + CHECK_LOG_CTX_APPTAG("l leaf is not left", "Data location \"/ii:cont/l3\".", "not-left"); + CHECK_LOG_CTX_APPTAG("Must condition \"../l = 'right'\" not satisfied.", "Data location \"/ii:cont/l2\".", "must-violation"); + CHECK_LOG_CTX_APPTAG("Invalid non-number-encoded uint32 value \"ahoy\".", "Data location \"/ii:cont/ll\", line number 7.", NULL); + + /* validation */ + data = "{\n" + " \"ii:cont\": {\n" + " \"l\": \"wrong\",\n" + " \"l\": \"wrong2\",\n" + " \"l2\": \"val\",\n" + " \"l3\": \"val\",\n" + " \"ll\": [25]\n" + " }\n" + "}\n"; + CHECK_PARSE_LYD_PARAM(data, LYD_JSON, LYD_PARSE_ONLY, 0, LY_SUCCESS, tree); + assert_int_equal(LY_EVALID, lyd_validate_all(&tree, NULL, LYD_VALIDATE_PRESENT | LYD_VALIDATE_MULTI_ERROR, NULL)); + lyd_free_tree(tree); + CHECK_LOG_CTX_APPTAG("Too few \"ll\" instances.", "Schema location \"/ii:cont/ll\".", "too-few-elements"); + CHECK_LOG_CTX_APPTAG("l leaf is not left", "Data location \"/ii:cont/l3\".", "not-left"); + CHECK_LOG_CTX_APPTAG("Must condition \"../l = 'right'\" not satisfied.", "Data location \"/ii:cont/l2\".", "must-violation"); + CHECK_LOG_CTX_APPTAG("Duplicate instance of \"l\".", "Data location \"/ii:cont/l\".", NULL); + CHECK_LOG_CTX_APPTAG("Duplicate instance of \"l\".", "Data location \"/ii:cont/l\".", NULL); +} + const char *schema_j = "module j {\n" " namespace urn:tests:j;\n" @@ -1212,6 +1298,7 @@ test_action(void **state) struct lyd_node *tree, *op_tree; UTEST_ADD_MODULE(schema_j, LYS_IN_YANG, feats_j, NULL); + UTEST_LOG_CTX_CLEAN; assert_int_equal(LY_SUCCESS, ly_in_new_memory( "<cont xmlns=\"urn:tests:j\">\n" @@ -1304,6 +1391,7 @@ test_reply(void **state) struct lyd_node *tree, *op_tree; UTEST_ADD_MODULE(schema_j, LYS_IN_YANG, feats_j, NULL); + UTEST_LOG_CTX_CLEAN; assert_int_equal(LY_SUCCESS, ly_in_new_memory( "<cont xmlns=\"urn:tests:j\">\n" @@ -1422,7 +1510,7 @@ test_case(void **state) " }\n" "}\n", LYD_JSON, 0, LYD_VALIDATE_PRESENT, LY_EVALID, tree); CHECK_LOG_CTX("Data for both cases \"v0\" and \"v2\" exist.", - "Data location \"/k:ch\", line number 5."); + "Data location \"/k:ch\", line number 6."); CHECK_PARSE_LYD_PARAM( "{\n" @@ -1432,7 +1520,7 @@ test_case(void **state) " }\n" "}\n", LYD_JSON, 0, LYD_VALIDATE_PRESENT, LY_EVALID, tree); CHECK_LOG_CTX("Data for both cases \"v0\" and \"v2\" exist.", - "Data location \"/k:ch\", line number 5."); + "Data location \"/k:ch\", line number 6."); } int @@ -1450,6 +1538,7 @@ main(void) UTEST(test_defaults), UTEST(test_state), UTEST(test_must), + UTEST(test_multi_error), UTEST(test_action), UTEST(test_rpc), UTEST(test_reply), diff --git a/tests/utests/extensions/test_metadata.c b/tests/utests/extensions/test_metadata.c index 39d29be..f1edd29 100644 --- a/tests/utests/extensions/test_metadata.c +++ b/tests/utests/extensions/test_metadata.c @@ -53,7 +53,7 @@ test_yang(void **state) "md:annotation aa;}"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL)); CHECK_LOG_CTX("Ext plugin \"ly2 metadata v1\": Missing mandatory keyword \"type\" as a child of \"md:annotation aa\".", - "/aa:{extension='md:annotation'}/aa"); + "Path \"/aa:{extension='md:annotation'}/aa\"."); /* not allowed substatement */ data = "module aa {yang-version 1.1; namespace urn:tests:extensions:metadata:aa; prefix aa;" @@ -61,7 +61,7 @@ test_yang(void **state) "md:annotation aa {default x;}}"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL)); CHECK_LOG_CTX("Invalid keyword \"default\" as a child of \"md:annotation aa\" extension instance.", - "/aa:{extension='md:annotation'}/aa"); + "Path \"/aa:{extension='md:annotation'}/aa\"."); /* invalid cardinality of units substatement */ data = "module aa {yang-version 1.1; namespace urn:tests:extensions:metadata:aa; prefix aa;" @@ -69,7 +69,7 @@ test_yang(void **state) "md:annotation aa {type string; units x; units y;}}"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL)); CHECK_LOG_CTX("Duplicate keyword \"units\".", - "/aa:{extension='md:annotation'}/aa"); + "Path \"/aa:{extension='md:annotation'}/aa\"."); /* invalid cardinality of status substatement */ data = "module aa {yang-version 1.1; namespace urn:tests:extensions:metadata:aa; prefix aa;" @@ -77,7 +77,7 @@ test_yang(void **state) "md:annotation aa {type string; status current; status obsolete;}}"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL)); CHECK_LOG_CTX("Duplicate keyword \"status\".", - "/aa:{extension='md:annotation'}/aa"); + "Path \"/aa:{extension='md:annotation'}/aa\"."); /* invalid cardinality of status substatement */ data = "module aa {yang-version 1.1; namespace urn:tests:extensions:metadata:aa; prefix aa;" @@ -85,7 +85,7 @@ test_yang(void **state) "md:annotation aa {type string; type uint8;}}"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL)); CHECK_LOG_CTX("Duplicate keyword \"type\".", - "/aa:{extension='md:annotation'}/aa"); + "Path \"/aa:{extension='md:annotation'}/aa\"."); /* duplication of the same annotation */ data = "module aa {yang-version 1.1; namespace urn:tests:extensions:metadata:aa; prefix aa;" @@ -93,7 +93,7 @@ test_yang(void **state) "md:annotation aa {type string;} md:annotation aa {type uint8;}}"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL)); CHECK_LOG_CTX("Ext plugin \"ly2 metadata v1\": Extension md:annotation is instantiated multiple times.", - "/aa:{extension='md:annotation'}/aa"); + "Path \"/aa:{extension='md:annotation'}/aa\"."); } static void @@ -134,7 +134,7 @@ test_yin(void **state) "</module>"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YIN, NULL)); CHECK_LOG_CTX("Ext plugin \"ly2 metadata v1\": Missing mandatory keyword \"type\" as a child of \"md:annotation aa\".", - "/aa:{extension='md:annotation'}/aa"); + "Path \"/aa:{extension='md:annotation'}/aa\"."); /* not allowed substatement */ data = "<module xmlns=\"urn:ietf:params:xml:ns:yang:yin:1\" xmlns:md=\"urn:ietf:params:xml:ns:yang:ietf-yang-metadata\" name=\"aa\">\n" @@ -145,7 +145,7 @@ test_yin(void **state) "</md:annotation></module>"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YIN, NULL)); CHECK_LOG_CTX("Invalid keyword \"default\" as a child of \"md:annotation aa\" extension instance.", - "/aa:{extension='md:annotation'}/aa"); + "Path \"/aa:{extension='md:annotation'}/aa\"."); /* invalid cardinality of units substatement */ data = "<module xmlns=\"urn:ietf:params:xml:ns:yang:yin:1\" xmlns:md=\"urn:ietf:params:xml:ns:yang:ietf-yang-metadata\" name=\"aa\">\n" @@ -158,7 +158,7 @@ test_yin(void **state) "</md:annotation></module>"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YIN, NULL)); CHECK_LOG_CTX("Duplicate keyword \"units\".", - "/aa:{extension='md:annotation'}/aa"); + "Path \"/aa:{extension='md:annotation'}/aa\"."); /* invalid cardinality of status substatement */ data = "<module xmlns=\"urn:ietf:params:xml:ns:yang:yin:1\" xmlns:md=\"urn:ietf:params:xml:ns:yang:ietf-yang-metadata\" name=\"aa\">\n" @@ -171,7 +171,7 @@ test_yin(void **state) "</md:annotation></module>"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YIN, NULL)); CHECK_LOG_CTX("Duplicate keyword \"status\".", - "/aa:{extension='md:annotation'}/aa"); + "Path \"/aa:{extension='md:annotation'}/aa\"."); /* invalid cardinality of status substatement */ data = "<module xmlns=\"urn:ietf:params:xml:ns:yang:yin:1\" xmlns:md=\"urn:ietf:params:xml:ns:yang:ietf-yang-metadata\" name=\"aa\">\n" @@ -183,7 +183,7 @@ test_yin(void **state) "</md:annotation></module>"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YIN, NULL)); CHECK_LOG_CTX("Duplicate keyword \"type\".", - "/aa:{extension='md:annotation'}/aa"); + "Path \"/aa:{extension='md:annotation'}/aa\"."); /* duplication of the same annotation */ data = "<module xmlns=\"urn:ietf:params:xml:ns:yang:yin:1\" xmlns:md=\"urn:ietf:params:xml:ns:yang:ietf-yang-metadata\" name=\"aa\">\n" @@ -196,7 +196,7 @@ test_yin(void **state) "</md:annotation></module>"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YIN, NULL)); CHECK_LOG_CTX("Ext plugin \"ly2 metadata v1\": Extension md:annotation is instantiated multiple times.", - "/aa:{extension='md:annotation'}/aa"); + "Path \"/aa:{extension='md:annotation'}/aa\"."); } int diff --git a/tests/utests/extensions/test_nacm.c b/tests/utests/extensions/test_nacm.c index 1c999fb..5f7028e 100644 --- a/tests/utests/extensions/test_nacm.c +++ b/tests/utests/extensions/test_nacm.c @@ -58,7 +58,7 @@ test_deny_all(void **state) assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL)); CHECK_LOG_CTX("Ext plugin \"ly2 NACM v1\": " "Extension nacm:default-deny-all is allowed only in a data nodes, but it is placed in \"module\" statement.", - "/b:{extension='nacm:default-deny-all'}"); + "Path \"/b:{extension='nacm:default-deny-all'}\"."); /* invalid */ data = "module aa {yang-version 1.1; namespace urn:tests:extensions:nacm:aa; prefix en;" @@ -67,7 +67,7 @@ test_deny_all(void **state) assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL)); CHECK_LOG_CTX("Ext plugin \"ly2 NACM v1\": " "Extension nacm:default-deny-write is mixed with nacm:default-deny-all.", - "/aa:l/{extension='nacm:default-deny-all'}"); + "Path \"/aa:l/{extension='nacm:default-deny-all'}\"."); } static void @@ -100,7 +100,7 @@ test_deny_write(void **state) assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL)); CHECK_LOG_CTX("Ext plugin \"ly2 NACM v1\": " "Extension nacm:default-deny-write is not allowed in notification statement.", - "/b:notif/{extension='nacm:default-deny-write'}"); + "Path \"/b:notif/{extension='nacm:default-deny-write'}\"."); /* invalid */ data = "module aa {yang-version 1.1; namespace urn:tests:extensions:nacm:aa; prefix en;" @@ -109,7 +109,7 @@ test_deny_write(void **state) assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL)); CHECK_LOG_CTX("Ext plugin \"ly2 NACM v1\": " "Extension nacm:default-deny-write is instantiated multiple times.", - "/aa:l/{extension='nacm:default-deny-write'}"); + "Path \"/aa:l/{extension='nacm:default-deny-write'}\"."); } int diff --git a/tests/utests/extensions/test_schema_mount.c b/tests/utests/extensions/test_schema_mount.c index be879ec..17a4c94 100644 --- a/tests/utests/extensions/test_schema_mount.c +++ b/tests/utests/extensions/test_schema_mount.c @@ -75,7 +75,7 @@ test_schema(void **state) assert_int_equal(LY_EINVAL, lys_parse_mem(UTEST_LYCTX, schema, LYS_IN_YANG, NULL)); CHECK_LOG_CTX("Ext plugin \"ly2 schema mount v1\": " "Extension \"yangmnt:mount-point\" instance not allowed in YANG version 1 module.", - "/sm:root/{extension='yangmnt:mount-point'}/root"); + "Path \"/sm:root/{extension='yangmnt:mount-point'}/root\"."); schema = "module sm {\n" @@ -92,7 +92,7 @@ test_schema(void **state) assert_int_equal(LY_EINVAL, lys_parse_mem(UTEST_LYCTX, schema, LYS_IN_YANG, NULL)); CHECK_LOG_CTX("Ext plugin \"ly2 schema mount v1\": " "Extension \"yangmnt:mount-point\" instance allowed only in container or list statement.", - "/sm:{extension='yangmnt:mount-point'}/root"); + "Path \"/sm:{extension='yangmnt:mount-point'}/root\"."); schema = "module sm {\n" @@ -114,7 +114,7 @@ test_schema(void **state) assert_int_equal(LY_EINVAL, lys_parse_mem(UTEST_LYCTX, schema, LYS_IN_YANG, NULL)); CHECK_LOG_CTX("Ext plugin \"ly2 schema mount v1\": " "Extension \"yangmnt:mount-point\" instance allowed only in container or list statement.", - "/sm:root/l/{extension='yangmnt:mount-point'}/root"); + "Path \"/sm:root/l/{extension='yangmnt:mount-point'}/root\"."); schema = "module sm {\n" @@ -138,7 +138,7 @@ test_schema(void **state) assert_int_equal(LY_EINVAL, lys_parse_mem(UTEST_LYCTX, schema, LYS_IN_YANG, NULL)); CHECK_LOG_CTX("Ext plugin \"ly2 schema mount v1\": " "Multiple extension \"yangmnt:mount-point\" instances.", - "/sm:l/{extension='yangmnt:mount-point'}/root"); + "Path \"/sm:l/{extension='yangmnt:mount-point'}/root\"."); /* valid */ schema = @@ -410,25 +410,25 @@ test_parse_invalid(void **state) CHECK_PARSE_LYD_PARAM(xml, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, LY_EVALID, data); CHECK_LOG_CTX("Ext plugin \"ly2 schema mount v1\": " "Mandatory node \"type\" instance does not exist.", - "Schema location \"/ietf-interfaces:interfaces/interface/type\"."); + "Data location \"/ietf-interfaces:interfaces/interface[name='bu']\"."); CHECK_PARSE_LYD_PARAM(json, LYD_JSON, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, LY_EVALID, data); CHECK_LOG_CTX("Ext plugin \"ly2 schema mount v1\": " "Mandatory node \"type\" instance does not exist.", - "Schema location \"/ietf-interfaces:interfaces/interface/type\"."); + "Data location \"/ietf-interfaces:interfaces/interface[name='bu']\"."); /* same validation fail in separate validation */ CHECK_PARSE_LYD_PARAM(xml, LYD_XML, LYD_PARSE_STRICT | LYD_PARSE_ONLY, 0, LY_SUCCESS, data); assert_int_equal(LY_EVALID, lyd_validate_all(&data, NULL, LYD_VALIDATE_PRESENT, NULL)); CHECK_LOG_CTX("Ext plugin \"ly2 schema mount v1\": " "Mandatory node \"type\" instance does not exist.", - "Schema location \"/ietf-interfaces:interfaces/interface/type\"."); + "Data location \"/ietf-interfaces:interfaces/interface[name='bu']\"."); lyd_free_siblings(data); CHECK_PARSE_LYD_PARAM(json, LYD_JSON, LYD_PARSE_STRICT | LYD_PARSE_ONLY, 0, LY_SUCCESS, data); assert_int_equal(LY_EVALID, lyd_validate_all(&data, NULL, LYD_VALIDATE_PRESENT, NULL)); CHECK_LOG_CTX("Ext plugin \"ly2 schema mount v1\": " "Mandatory node \"type\" instance does not exist.", - "Schema location \"/ietf-interfaces:interfaces/interface/type\"."); + "Data location \"/ietf-interfaces:interfaces/interface[name='bu']\"."); lyd_free_siblings(data); /* success */ @@ -878,7 +878,7 @@ test_parse_shared(void **state) CHECK_PARSE_LYD_PARAM(xml, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, LY_EVALID, data); CHECK_LOG_CTX("Ext plugin \"ly2 schema mount v1\": " "Shared-schema yang-library content-id \"2\" differs from \"1\" used previously.", - "/ietf-yang-library:yang-library/content-id"); + "Path \"/ietf-yang-library:yang-library/content-id\"."); /* data for 2 mount points */ ly_ctx_set_ext_data_clb(UTEST_LYCTX, test_ext_data_clb, @@ -1549,6 +1549,108 @@ test_new(void **state) lyd_free_siblings(data); } +static void +test_lys_getnext(void **state) +{ + const struct lysc_node *parent, *node; + struct ly_ctx *sm_ctx; + + ly_ctx_set_ext_data_clb(UTEST_LYCTX, test_ext_data_clb, + "<yang-library xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\" " + " xmlns:ds=\"urn:ietf:params:xml:ns:yang:ietf-datastores\">" + " <module-set>" + " <name>test-set</name>" + " <module>" + " <name>ietf-datastores</name>" + " <revision>2018-02-14</revision>" + " <namespace>urn:ietf:params:xml:ns:yang:ietf-datastores</namespace>" + " </module>" + " <module>" + " <name>ietf-yang-library</name>" + " <revision>2019-01-04</revision>" + " <namespace>urn:ietf:params:xml:ns:yang:ietf-yang-library</namespace>" + " </module>" + " <module>" + " <name>ietf-yang-schema-mount</name>" + " <revision>2019-01-14</revision>" + " <namespace>urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount</namespace>" + " </module>" + " <module>" + " <name>ietf-interfaces</name>" + " <revision>2014-05-08</revision>" + " <namespace>urn:ietf:params:xml:ns:yang:ietf-interfaces</namespace>" + " </module>" + " <module>" + " <name>iana-if-type</name>" + " <revision>2014-05-08</revision>" + " <namespace>urn:ietf:params:xml:ns:yang:iana-if-type</namespace>" + " </module>" + " <module>" + " <name>ietf-ip</name>" + " <revision>2014-06-16</revision>" + " <namespace>urn:ietf:params:xml:ns:yang:ietf-ip</namespace>" + " </module>" + " <import-only-module>" + " <name>ietf-yang-types</name>" + " <revision>2013-07-15</revision>" + " <namespace>urn:ietf:params:xml:ns:yang:ietf-yang-types</namespace>" + " </import-only-module>" + " </module-set>" + " <schema>" + " <name>test-schema</name>" + " <module-set>test-set</module-set>" + " </schema>" + " <datastore>" + " <name>ds:running</name>" + " <schema>test-schema</schema>" + " </datastore>" + " <datastore>" + " <name>ds:operational</name>" + " <schema>test-schema</schema>" + " </datastore>" + " <content-id>1</content-id>" + "</yang-library>" + "<modules-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\">" + " <module-set-id>1</module-set-id>" + "</modules-state>" + "<schema-mounts xmlns=\"urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount\">" + " <mount-point>" + " <module>sm</module>" + " <label>root</label>" + " <shared-schema/>" + " </mount-point>" + "</schema-mounts>"); + + parent = lys_find_path(UTEST_LYCTX, NULL, "/sm:root", 0); + assert_non_null(parent); + + node = lys_getnext(NULL, parent, NULL, LYS_GETNEXT_WITHSCHEMAMOUNT); + assert_non_null(node); + assert_string_equal(node->name, "schema-mounts"); + sm_ctx = node->module->ctx; + + node = lys_getnext(node, parent, NULL, LYS_GETNEXT_WITHSCHEMAMOUNT); + assert_non_null(node); + assert_string_equal(node->name, "yang-library"); + + node = lys_getnext(node, parent, NULL, LYS_GETNEXT_WITHSCHEMAMOUNT); + assert_non_null(node); + assert_string_equal(node->name, "modules-state"); + + node = lys_getnext(node, parent, NULL, LYS_GETNEXT_WITHSCHEMAMOUNT); + assert_non_null(node); + assert_string_equal(node->name, "interfaces"); + + node = lys_getnext(node, parent, NULL, LYS_GETNEXT_WITHSCHEMAMOUNT); + assert_non_null(node); + assert_string_equal(node->name, "interfaces-state"); + + node = lys_getnext(node, parent, NULL, LYS_GETNEXT_WITHSCHEMAMOUNT); + assert_null(node); + + ly_ctx_destroy(sm_ctx); +} + int main(void) { @@ -1560,6 +1662,7 @@ main(void) UTEST(test_parse_shared_parent_ref, setup), UTEST(test_parse_config, setup), UTEST(test_new, setup), + UTEST(test_lys_getnext, setup), }; return cmocka_run_group_tests(tests, NULL, NULL); diff --git a/tests/utests/extensions/test_structure.c b/tests/utests/extensions/test_structure.c index 23af450..9bad7a7 100644 --- a/tests/utests/extensions/test_structure.c +++ b/tests/utests/extensions/test_structure.c @@ -148,7 +148,7 @@ test_schema_invalid(void **state) "sx:structure struct {import yang;}}"; UTEST_INVALID_MODULE(data, LYS_IN_YANG, NULL, LY_EVALID); CHECK_LOG_CTX("Invalid keyword \"import\" as a child of \"sx:structure struct\" extension instance.", - "/a:{extension='sx:structure'}/struct"); + "Path \"/a:{extension='sx:structure'}/struct\"."); data = "module a {yang-version 1.1; namespace urn:tests:extensions:structure:a; prefix self;" "import ietf-yang-structure-ext {prefix sx;}" @@ -156,14 +156,14 @@ test_schema_invalid(void **state) UTEST_INVALID_MODULE(data, LYS_IN_YANG, NULL, LY_EVALID); CHECK_LOG_CTX("Ext plugin \"ly2 structure v1\": " "Extension sx:structure must not be used as a non top-level statement in \"container\" statement.", - "/a:b/{extension='sx:structure'}/struct"); + "Path \"/a:b/{extension='sx:structure'}/struct\"."); data = "module a {yang-version 1.1; namespace urn:tests:extensions:structure:a; prefix self;" "import ietf-yang-structure-ext {prefix sx;}" "sx:structure { container x { leaf x {type string;}}}}"; UTEST_INVALID_MODULE(data, LYS_IN_YANG, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Extension instance \"sx:structure\" missing argument element \"name\".", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Extension instance \"sx:structure\" missing argument element \"name\".", NULL); data = "module a {yang-version 1.1; namespace urn:tests:extensions:structure:a; prefix self;" "import ietf-yang-structure-ext {prefix sx;}" @@ -171,7 +171,7 @@ test_schema_invalid(void **state) "sx:structure struct { container y { leaf y {type string;}}}}"; UTEST_INVALID_MODULE(data, LYS_IN_YANG, NULL, LY_EVALID); CHECK_LOG_CTX("Ext plugin \"ly2 structure v1\": Extension sx:structure is instantiated multiple times.", - "/a:{extension='sx:structure'}/struct"); + "Path \"/a:{extension='sx:structure'}/struct\"."); data = "module a {yang-version 1.1; namespace urn:tests:extensions:structure:a; prefix self;" "import ietf-yang-structure-ext {prefix sx;}" @@ -179,7 +179,7 @@ test_schema_invalid(void **state) "choice struct { container y { leaf y {type string;}}}}"; UTEST_INVALID_MODULE(data, LYS_IN_YANG, NULL, LY_EVALID); CHECK_LOG_CTX("Ext plugin \"ly2 structure v1\": Extension sx:structure collides with a choice with the same identifier.", - "/a:{extension='sx:structure'}/struct"); + "Path \"/a:{extension='sx:structure'}/struct\"."); /* augment-structure */ data = "module a {yang-version 1.1; namespace urn:tests:extensions:structure:a; prefix a;" @@ -199,7 +199,7 @@ test_schema_invalid(void **state) "}}"; UTEST_INVALID_MODULE(data, LYS_IN_YANG, NULL, LY_ENOTFOUND); CHECK_LOG_CTX("Augment extension target node \"/a:n1\" from module \"b\" was not found.", - "/b:{extension='sx:augment-structure'}/{augment='/a:n1'}"); + "Path \"/b:{extension='sx:augment-structure'}/{augment='/a:n1'}\"."); } static void diff --git a/tests/utests/extensions/test_yangdata.c b/tests/utests/extensions/test_yangdata.c index 8c0176f..57caaf2 100644 --- a/tests/utests/extensions/test_yangdata.c +++ b/tests/utests/extensions/test_yangdata.c @@ -119,7 +119,7 @@ test_schema(void **state) assert_null(mod->compiled->exts); CHECK_LOG_CTX("Ext plugin \"ly2 yang-data v1\": " "Extension rc:yang-data is ignored since it appears as a non top-level statement in \"container\" statement.", - "/b:b/{extension='rc:yang-data'}/template"); + "Path \"/b:b/{extension='rc:yang-data'}/template\"."); assert_int_equal(LY_SUCCESS, lys_print_mem(&printed, mod, LYS_OUT_YANG_COMPILED, 0)); assert_string_equal(printed, info); free(printed); @@ -168,7 +168,7 @@ test_schema_invalid(void **state) assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL)); CHECK_LOG_CTX("Invalid keyword \"leaf\" as a child of \"rc:yang-data template\" extension instance.", - "/a:{extension='rc:yang-data'}/template"); + "Path \"/a:{extension='rc:yang-data'}/template\"."); data = "module a {yang-version 1.1; namespace urn:tests:extensions:yangdata:a; prefix self;" "import ietf-restconf {revision-date 2017-01-26; prefix rc;}" @@ -177,7 +177,7 @@ test_schema_invalid(void **state) CHECK_LOG_CTX("Ext plugin \"ly2 yang-data v1\": " "Extension rc:yang-data is instantiated with leaf top level data node (inside a choice), " "but only a single container data node is allowed.", - "/a:{extension='rc:yang-data'}/template"); + "Path \"/a:{extension='rc:yang-data'}/template\"."); data = "module a {yang-version 1.1; namespace urn:tests:extensions:yangdata:a; prefix self;" "import ietf-restconf {revision-date 2017-01-26; prefix rc;}" @@ -186,7 +186,7 @@ test_schema_invalid(void **state) CHECK_LOG_CTX("Ext plugin \"ly2 yang-data v1\": " "Extension rc:yang-data is instantiated with multiple top level data nodes (inside a single choice's case), " "but only a single container data node is allowed.", - "/a:{extension='rc:yang-data'}/template"); + "Path \"/a:{extension='rc:yang-data'}/template\"."); data = "module a {yang-version 1.1; namespace urn:tests:extensions:yangdata:a; prefix self;" "import ietf-restconf {revision-date 2017-01-26; prefix rc;}" @@ -195,7 +195,7 @@ test_schema_invalid(void **state) CHECK_LOG_CTX("Ext plugin \"ly2 yang-data v1\": " "Extension rc:yang-data is instantiated with multiple top level data nodes, " "but only a single container data node is allowed.", - "/a:{extension='rc:yang-data'}/template"); + "Path \"/a:{extension='rc:yang-data'}/template\"."); data = "module a {yang-version 1.1; namespace urn:tests:extensions:yangdata:a; prefix self;" "import ietf-restconf {revision-date 2017-01-26; prefix rc;}" @@ -204,14 +204,14 @@ test_schema_invalid(void **state) CHECK_LOG_CTX("Ext plugin \"ly2 yang-data v1\": " "Extension rc:yang-data is instantiated without any top level data node, " "but exactly one container data node is expected.", - "/a:{extension='rc:yang-data'}/template"); + "Path \"/a:{extension='rc:yang-data'}/template\"."); data = "module a {yang-version 1.1; namespace urn:tests:extensions:yangdata:a; prefix self;" "import ietf-restconf {revision-date 2017-01-26; prefix rc;}" "rc:yang-data { container x { leaf x {type string;}}}}"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Extension instance \"rc:yang-data\" missing argument element \"name\".", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Extension instance \"rc:yang-data\" missing argument element \"name\".", NULL); data = "module a {yang-version 1.1; namespace urn:tests:extensions:yangdata:a; prefix self;" "import ietf-restconf {revision-date 2017-01-26; prefix rc;}" @@ -220,7 +220,7 @@ test_schema_invalid(void **state) assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, data, LYS_IN_YANG, NULL)); CHECK_LOG_CTX("Ext plugin \"ly2 yang-data v1\": " "Extension rc:yang-data is instantiated multiple times.", - "/a:{extension='rc:yang-data'}/template"); + "Path \"/a:{extension='rc:yang-data'}/template\"."); data = "module a {yang-version 1.1; namespace urn:tests:extensions:yangdata:a; prefix self;" "import ietf-restconf {revision-date 2017-01-26; prefix rc;}" @@ -230,7 +230,7 @@ test_schema_invalid(void **state) CHECK_LOG_CTX("Ext plugin \"ly2 yang-data v1\": " "Extension rc:yang-data is instantiated with leaf-list top level data node, " "but only a single container data node is allowed.", - "/a:{extension='rc:yang-data'}/template"); + "Path \"/a:{extension='rc:yang-data'}/template\"."); } static void diff --git a/tests/utests/node/list.c b/tests/utests/node/list.c index 8b14ece..987b416 100644 --- a/tests/utests/node/list.c +++ b/tests/utests/node/list.c @@ -169,8 +169,8 @@ test_schema_yang(void **state) "leaf group{type string;}" "}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_0\" failed.", NULL, - "Invalid value \"-1\" of \"max-elements\".", "Line number 5."); + CHECK_LOG_CTX("Parsing module \"TERR_0\" failed.", NULL); + CHECK_LOG_CTX("Invalid value \"-1\" of \"max-elements\".", "Line number 5."); schema = MODULE_CREATE_YANG("TERR_0", "list user {" "key uid;" @@ -181,8 +181,8 @@ test_schema_yang(void **state) "leaf group{type string;}" "}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_0\" failed.", NULL, - "Value \"4294967298\" is out of \"max-elements\" bounds.", "Line number 5."); + CHECK_LOG_CTX("Parsing module \"TERR_0\" failed.", NULL); + CHECK_LOG_CTX("Value \"4294967298\" is out of \"max-elements\" bounds.", "Line number 5."); schema = MODULE_CREATE_YANG("TERR_0", "list user {" "key uid;" @@ -193,7 +193,7 @@ test_schema_yang(void **state) "leaf group{type string;}" "}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); - CHECK_LOG_CTX("List min-elements 20 is bigger than max-elements 10.", "/TERR_0:user"); + CHECK_LOG_CTX("List min-elements 20 is bigger than max-elements 10.", "Path \"/TERR_0:user\"."); schema = MODULE_CREATE_YANG("TERR_0", "list user {" "key uid;" @@ -204,8 +204,8 @@ test_schema_yang(void **state) "leaf group{type string;}" "}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_0\" failed.", NULL, - "Invalid value \"-1\" of \"min-elements\".", "Line number 5."); + CHECK_LOG_CTX("Parsing module \"TERR_0\" failed.", NULL); + CHECK_LOG_CTX("Invalid value \"-1\" of \"min-elements\".", "Line number 5."); schema = MODULE_CREATE_YANG("TERR_0", "list user {" "key uid;" @@ -215,8 +215,8 @@ test_schema_yang(void **state) "leaf group{type string;}" "}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_0\" failed.", NULL, - "Duplicate keyword \"key\".", "Line number 5."); + CHECK_LOG_CTX("Parsing module \"TERR_0\" failed.", NULL); + CHECK_LOG_CTX("Duplicate keyword \"key\".", "Line number 5."); schema = MODULE_CREATE_YANG("T6", "list user {" "config false;" @@ -282,8 +282,8 @@ test_schema_yang(void **state) "leaf group{type string;}" "}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERROR0\" failed.", NULL, - "Invalid value \"systeme\" of \"ordered-by\".", "Line number 5."); + CHECK_LOG_CTX("Parsing module \"TERROR0\" failed.", NULL); + CHECK_LOG_CTX("Invalid value \"systeme\" of \"ordered-by\".", "Line number 5."); schema = MODULE_CREATE_YANG("TERROR0", "list \"\" {" "key uid;" @@ -294,8 +294,8 @@ test_schema_yang(void **state) "leaf group{type string;}" "}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERROR0\" failed.", NULL, - "Statement argument is required.", "Line number 5."); + CHECK_LOG_CTX("Parsing module \"TERROR0\" failed.", NULL); + CHECK_LOG_CTX("Statement argument is required.", "Line number 5."); schema = MODULE_CREATE_YANG("T9", "list user {" "key uid;" @@ -389,7 +389,7 @@ test_schema_yin(void **state) " <leaf name=\"group\"><type name=\"string\"/></leaf>" "</list>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); - CHECK_LOG_CTX("The list's key \"u<id\" not found.", "/T00:user"); + CHECK_LOG_CTX("The list's key \"u<id\" not found.", "Path \"/T00:user\"."); schema = MODULE_CREATE_YIN("T1", "<list name=\"user\"> " " <key value=\"uid\"/>" @@ -498,8 +498,8 @@ test_schema_yin(void **state) " <leaf name=\"uid\"> <type name=\"int32\"> </leaf>" "</list>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_0\" failed.", NULL, - "Invalid value \"-1\" of \"value\" attribute in \"max-elements\" element.", "Line number 8."); + CHECK_LOG_CTX("Parsing module \"TERR_0\" failed.", NULL); + CHECK_LOG_CTX("Invalid value \"-1\" of \"value\" attribute in \"max-elements\" element.", "Line number 8."); schema = MODULE_CREATE_YIN("TERR_0", "<list name=\"user\">" @@ -511,8 +511,8 @@ test_schema_yin(void **state) " <leaf name=\"group\"> <type name=\"string\"/> </leaf>" "</list>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_0\" failed.", NULL, - "Value \"4294967298\" of \"value\" attribute in \"max-elements\" element is out of bounds.", "Line number 8."); + CHECK_LOG_CTX("Parsing module \"TERR_0\" failed.", NULL); + CHECK_LOG_CTX("Value \"4294967298\" of \"value\" attribute in \"max-elements\" element is out of bounds.", "Line number 8."); schema = MODULE_CREATE_YIN("TERR_0", "<list name=\"user\">" @@ -524,8 +524,9 @@ test_schema_yin(void **state) " <leaf name=\"group\"> <type name=\"string\"/> </leaf>" "</list>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_0\" failed.", NULL, - "Invalid combination of min-elements and max-elements: min value 20 is bigger than the max value 10.", "Line number 8."); + CHECK_LOG_CTX("Parsing module \"TERR_0\" failed.", NULL); + CHECK_LOG_CTX("Invalid combination of min-elements and max-elements: min value 20 is bigger than the max value 10.", + "Line number 8."); schema = MODULE_CREATE_YIN("TERR_0", "<list name=\"user\">" @@ -537,8 +538,8 @@ test_schema_yin(void **state) " <leaf name=\"group\"> <type name=\"string\"/> </leaf>" "</list>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_0\" failed.", NULL, - "Value \"-1\" of \"value\" attribute in \"min-elements\" element is out of bounds.", "Line number 8."); + CHECK_LOG_CTX("Parsing module \"TERR_0\" failed.", NULL); + CHECK_LOG_CTX("Value \"-1\" of \"value\" attribute in \"min-elements\" element is out of bounds.", "Line number 8."); schema = MODULE_CREATE_YIN("TERR_0", "<list name=\"user\">" @@ -549,8 +550,8 @@ test_schema_yin(void **state) " <leaf name=\"group\"> <type name=\"string\"/> </leaf>" "</list>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_0\" failed.", NULL, - "Redefinition of \"key\" sub-element in \"list\" element.", "Line number 8."); + CHECK_LOG_CTX("Parsing module \"TERR_0\" failed.", NULL); + CHECK_LOG_CTX("Redefinition of \"key\" sub-element in \"list\" element.", "Line number 8."); schema = MODULE_CREATE_YIN("T6", "<list name=\"user\">" @@ -620,8 +621,8 @@ test_schema_yin(void **state) " <leaf name=\"group\"><type name=\"string\"/> </leaf>" "</list>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERROR0\" failed.", NULL, - "Invalid value \"systeme\" of \"value\" attribute in \"ordered-by\" element. Valid values are \"system\" and \"user\".", + CHECK_LOG_CTX("Parsing module \"TERROR0\" failed.", NULL); + CHECK_LOG_CTX("Invalid value \"systeme\" of \"value\" attribute in \"ordered-by\" element. Valid values are \"system\" and \"user\".", "Line number 8."); schema = MODULE_CREATE_YIN("T_DEFS1", diff --git a/tests/utests/schema/test_printer_tree.c b/tests/utests/schema/test_printer_tree.c index c076ece..40fb15f 100644 --- a/tests/utests/schema/test_printer_tree.c +++ b/tests/utests/schema/test_printer_tree.c @@ -1574,6 +1574,7 @@ print_compiled_node(void **state) " yang-version 1.1;\n" " namespace \"x:y\";\n" " prefix x;\n" + "\n" " container g {\n" " leaf a {\n" " type string;\n" @@ -1586,6 +1587,12 @@ print_compiled_node(void **state) " leaf c {\n" " type string;\n" " }\n" + " list l {\n" + " key \"ip\";\n" + " leaf ip {\n" + " type string;\n" + " }\n" + " }\n" " }\n" " }\n" "}\n"; @@ -1610,13 +1617,31 @@ print_compiled_node(void **state) ly_out_reset(UTEST_OUT); + /* pyang -f tree --tree-path /g/h/l */ + expect = + "module: a26\n" + " +--rw g\n" + " +--rw h\n" + " +--rw l* [ip]\n" + " +--rw ip string\n"; + + node = lys_find_path(UTEST_LYCTX, NULL, "/a26:g/h/l", 0); + CHECK_POINTER(node, 1); + assert_int_equal(LY_SUCCESS, lys_print_node(UTEST_OUT, node, LYS_OUT_TREE, 72, 0)); + assert_int_equal(strlen(expect), ly_out_printed(UTEST_OUT)); + assert_string_equal(printed, expect); + + ly_out_reset(UTEST_OUT); + /* pyang -f tree --tree-path /g/h */ expect = "module: a26\n" " +--rw g\n" " +--rw h\n" " +--rw b string\n" - " +--rw c? string\n"; + " +--rw c? string\n" + " +--rw l* [ip]\n" + " +--rw ip string\n"; node = lys_find_path(UTEST_LYCTX, NULL, "/a26:g/h", 0); CHECK_POINTER(node, 1); @@ -1643,6 +1668,59 @@ print_compiled_node(void **state) TEST_LOCAL_TEARDOWN; } +static void +print_compiled_node_augment(void **state) +{ + TEST_LOCAL_SETUP; + const struct lysc_node *node; + + orig = + "module b26xx {\n" + " yang-version 1.1;\n" + " namespace \"xx:y\";\n" + " prefix xx;\n" + " container c;\n" + "}\n"; + + UTEST_ADD_MODULE(orig, LYS_IN_YANG, NULL, &mod); + + /* module with import statement */ + orig = + "module b26 {\n" + " yang-version 1.1;\n" + " namespace \"x:y\";\n" + " prefix x;\n" + "\n" + " import b26xx {\n" + " prefix xx;\n" + " }\n" + "\n" + " augment \"/xx:c\" {\n" + " container e;\n" + " }\n" + "}\n"; + + UTEST_ADD_MODULE(orig, LYS_IN_YANG, NULL, &mod); + + /* pyang -f tree --tree-path /c/e ... but prefixes modified */ + expect = + "module: b26\n" + " +--rw xx:c\n" + " +--rw e\n"; + + /* using lysc tree */ + ly_ctx_set_options(UTEST_LYCTX, LY_CTX_SET_PRIV_PARSED); + node = lys_find_path(UTEST_LYCTX, NULL, "/b26xx:c/b26:e", 0); + CHECK_POINTER(node, 1); + assert_int_equal(LY_SUCCESS, lys_print_node(UTEST_OUT, node, LYS_OUT_TREE, 72, 0)); + assert_int_equal(strlen(expect), ly_out_printed(UTEST_OUT)); + assert_string_equal(printed, expect); + ly_out_reset(UTEST_OUT); + ly_ctx_unset_options(UTEST_LYCTX, LY_CTX_SET_PRIV_PARSED); + + TEST_LOCAL_TEARDOWN; +} + static LY_ERR local_imp_clb(const char *UNUSED(mod_name), const char *UNUSED(mod_rev), const char *UNUSED(submod_name), const char *UNUSED(sub_rev), void *user_data, LYS_INFORMAT *format, @@ -2365,6 +2443,43 @@ structure(void **state) TEST_LOCAL_TEARDOWN; } +static void +annotation(void **state) +{ + TEST_LOCAL_SETUP; + + orig = + "module ann {\n" + " yang-version 1.1;\n" + " namespace \"urn:example:ann\";\n" + " prefix an;\n" + "\n" + " import ietf-yang-metadata {\n" + " prefix md;\n" + " }\n" + "\n" + " leaf lf1 {\n" + " type string;\n" + " }\n" + " md:annotation avalue {\n" + " type string;\n" + " }\n" + "}\n"; + + expect = + "module: ann\n" + " +--rw lf1? string\n"; + + /* annotation is ignored without error message */ + UTEST_ADD_MODULE(orig, LYS_IN_YANG, NULL, &mod); + TEST_LOCAL_PRINT(mod, 72); + assert_int_equal(strlen(expect), ly_out_printed(UTEST_OUT)); + assert_string_equal(printed, expect); + ly_out_reset(UTEST_OUT); + + TEST_LOCAL_TEARDOWN; +} + int main(void) { @@ -2395,10 +2510,12 @@ main(void) UTEST(transition_between_rpc_and_notif), UTEST(local_augment), UTEST(print_compiled_node), + UTEST(print_compiled_node_augment), UTEST(print_parsed_submodule), UTEST(yang_data), UTEST(mount_point), UTEST(structure), + UTEST(annotation), }; return cmocka_run_group_tests(tests, NULL, NULL); diff --git a/tests/utests/schema/test_schema.c b/tests/utests/schema/test_schema.c index 175b569..17c4e4f 100644 --- a/tests/utests/schema/test_schema.c +++ b/tests/utests/schema/test_schema.c @@ -91,7 +91,8 @@ test_imp_clb(const char *UNUSED(mod_name), const char *UNUSED(mod_rev), const ch const char *test_str__; \ TEST_SCHEMA_STR(RFC7950, YIN, MOD_NAME, CONTENT, test_str__) \ assert_int_not_equal(lys_parse_mem(UTEST_LYCTX, test_str__, YIN ? LYS_IN_YIN : LYS_IN_YANG, NULL), LY_SUCCESS); \ - CHECK_LOG_CTX("Parsing module \""MOD_NAME"\" failed.", NULL, ERRMSG, ERRPATH); \ + CHECK_LOG_CTX("Parsing module \""MOD_NAME"\" failed.", NULL); \ + CHECK_LOG_CTX(ERRMSG, ERRPATH); \ } #define TEST_STMT_DUP(RFC7950, YIN, STMT, MEMBER, VALUE1, VALUE2, LINE) \ @@ -283,12 +284,12 @@ test_revisions(void **state) strcpy(rev->date, "2018-12-31"); assert_int_equal(2, LY_ARRAY_COUNT(revs)); - assert_string_equal("2018-01-01", &revs[0]); - assert_string_equal("2018-12-31", &revs[1]); + assert_string_equal("2018-01-01", revs[0].date); + assert_string_equal("2018-12-31", revs[1].date); /* the order should be fixed, so the newest revision will be the first in the array */ lysp_sort_revisions(revs); - assert_string_equal("2018-12-31", &revs[0]); - assert_string_equal("2018-01-01", &revs[1]); + assert_string_equal("2018-12-31", revs[0].date); + assert_string_equal("2018-01-01", revs[1].date); LY_ARRAY_FREE(revs); } @@ -306,80 +307,80 @@ test_collision_typedef(void **state) /* collision with a built-in type */ str = "module a {namespace urn:a; prefix a; typedef binary {type string;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"binary\" of typedef statement - name collision with a built-in type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"binary\" of typedef statement - name collision with a built-in type.", NULL); str = "module a {namespace urn:a; prefix a; typedef bits {type string;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"bits\" of typedef statement - name collision with a built-in type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"bits\" of typedef statement - name collision with a built-in type.", NULL); str = "module a {namespace urn:a; prefix a; typedef boolean {type string;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"boolean\" of typedef statement - name collision with a built-in type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"boolean\" of typedef statement - name collision with a built-in type.", NULL); str = "module a {namespace urn:a; prefix a; typedef decimal64 {type string;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"decimal64\" of typedef statement - name collision with a built-in type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"decimal64\" of typedef statement - name collision with a built-in type.", NULL); str = "module a {namespace urn:a; prefix a; typedef empty {type string;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"empty\" of typedef statement - name collision with a built-in type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"empty\" of typedef statement - name collision with a built-in type.", NULL); str = "module a {namespace urn:a; prefix a; typedef enumeration {type string;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"enumeration\" of typedef statement - name collision with a built-in type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"enumeration\" of typedef statement - name collision with a built-in type.", NULL); str = "module a {namespace urn:a; prefix a; typedef int8 {type string;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"int8\" of typedef statement - name collision with a built-in type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"int8\" of typedef statement - name collision with a built-in type.", NULL); str = "module a {namespace urn:a; prefix a; typedef int16 {type string;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"int16\" of typedef statement - name collision with a built-in type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"int16\" of typedef statement - name collision with a built-in type.", NULL); str = "module a {namespace urn:a; prefix a; typedef int32 {type string;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"int32\" of typedef statement - name collision with a built-in type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"int32\" of typedef statement - name collision with a built-in type.", NULL); str = "module a {namespace urn:a; prefix a; typedef int64 {type string;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"int64\" of typedef statement - name collision with a built-in type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"int64\" of typedef statement - name collision with a built-in type.", NULL); str = "module a {namespace urn:a; prefix a; typedef instance-identifier {type string;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"instance-identifier\" of typedef statement - name collision with a built-in type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"instance-identifier\" of typedef statement - name collision with a built-in type.", NULL); str = "module a {namespace urn:a; prefix a; typedef identityref {type string;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"identityref\" of typedef statement - name collision with a built-in type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"identityref\" of typedef statement - name collision with a built-in type.", NULL); str = "module a {namespace urn:a; prefix a; typedef leafref {type string;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"leafref\" of typedef statement - name collision with a built-in type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"leafref\" of typedef statement - name collision with a built-in type.", NULL); str = "module a {namespace urn:a; prefix a; typedef string {type int8;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"string\" of typedef statement - name collision with a built-in type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"string\" of typedef statement - name collision with a built-in type.", NULL); str = "module a {namespace urn:a; prefix a; typedef union {type string;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"union\" of typedef statement - name collision with a built-in type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"union\" of typedef statement - name collision with a built-in type.", NULL); str = "module a {namespace urn:a; prefix a; typedef uint8 {type string;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"uint8\" of typedef statement - name collision with a built-in type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"uint8\" of typedef statement - name collision with a built-in type.", NULL); str = "module a {namespace urn:a; prefix a; typedef uint16 {type string;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"uint16\" of typedef statement - name collision with a built-in type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"uint16\" of typedef statement - name collision with a built-in type.", NULL); str = "module a {namespace urn:a; prefix a; typedef uint32 {type string;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"uint32\" of typedef statement - name collision with a built-in type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"uint32\" of typedef statement - name collision with a built-in type.", NULL); str = "module a {namespace urn:a; prefix a; typedef uint64 {type string;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"uint64\" of typedef statement - name collision with a built-in type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"uint64\" of typedef statement - name collision with a built-in type.", NULL); str = "module mytypes {namespace urn:types; prefix t; typedef binary_ {type string;} typedef bits_ {type string;} typedef boolean_ {type string;} " "typedef decimal64_ {type string;} typedef empty_ {type string;} typedef enumeration_ {type string;} typedef int8_ {type string;} typedef int16_ {type string;}" @@ -391,34 +392,34 @@ test_collision_typedef(void **state) /* collision in node's scope */ str = "module a {namespace urn:a; prefix a; container c {typedef y {type int8;} typedef y {type string;}}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"y\" of typedef statement - name collision with sibling type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"y\" of typedef statement - name collision with sibling type.", NULL); /* collision with parent node */ str = "module a {namespace urn:a; prefix a; container c {container d {typedef y {type int8;}} typedef y {type string;}}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"y\" of typedef statement - name collision with another scoped type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"y\" of typedef statement - name collision with another scoped type.", NULL); /* collision with module's top-level */ str = "module a {namespace urn:a; prefix a; typedef x {type string;} container c {typedef x {type int8;}}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"x\" of typedef statement - scoped type collide with a top-level type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"x\" of typedef statement - scoped type collide with a top-level type.", NULL); /* collision of submodule's node with module's top-level */ ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, "submodule b {belongs-to a {prefix a;} container c {typedef x {type string;}}}"); str = "module a {namespace urn:a; prefix a; include b; typedef x {type int8;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"x\" of typedef statement - scoped type collide with a top-level type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"x\" of typedef statement - scoped type collide with a top-level type.", NULL); /* collision of module's node with submodule's top-level */ ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, "submodule b {belongs-to a {prefix a;} typedef x {type int8;}}"); str = "module a {namespace urn:a; prefix a; include b; container c {typedef x {type string;}}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"x\" of typedef statement - scoped type collide with a top-level type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"x\" of typedef statement - scoped type collide with a top-level type.", NULL); /* collision of submodule's node with another submodule's top-level */ str = "module a {yang-version 1.1; namespace urn:a; prefix a; include asub; include bsub;}"; @@ -426,29 +427,29 @@ test_collision_typedef(void **state) list[1].data = "submodule bsub {belongs-to a {prefix a;} container c {typedef g {type int;}}}"; ly_ctx_set_module_imp_clb(UTEST_LYCTX, module_clb, list); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"g\" of typedef statement - scoped type collide with a top-level type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"g\" of typedef statement - scoped type collide with a top-level type.", NULL); /* collision of module's top-levels */ str = "module a {namespace urn:a; prefix a; typedef test {type string;} typedef test {type int8;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"test\" of typedef statement - name collision with another top-level type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"test\" of typedef statement - name collision with another top-level type.", NULL); /* collision of submodule's top-levels */ submod = "submodule asub {belongs-to a {prefix a;} typedef g {type int;} typedef g {type int;}}"; str = "module a {yang-version 1.1; namespace urn:a; prefix a; include asub;}"; ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, submod); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"g\" of typedef statement - name collision with another top-level type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"g\" of typedef statement - name collision with another top-level type.", NULL); /* collision of module's top-level with submodule's top-levels */ ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, "submodule b {belongs-to a {prefix a;} typedef x {type string;}}"); str = "module a {namespace urn:a; prefix a; include b; typedef x {type int8;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"x\" of typedef statement - name collision with another top-level type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"x\" of typedef statement - name collision with another top-level type.", NULL); /* collision of submodule's top-level with another submodule's top-levels */ str = "module a {yang-version 1.1; namespace urn:a; prefix a; include asub; include bsub;}"; @@ -456,15 +457,14 @@ test_collision_typedef(void **state) list[1].data = "submodule bsub {belongs-to a {prefix a;} typedef g {type int;}}"; ly_ctx_set_module_imp_clb(UTEST_LYCTX, module_clb, list); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"g\" of typedef statement - name collision with another top-level type.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"g\" of typedef statement - name collision with another top-level type.", NULL); /* error in type-stmt */ str = "module a {namespace urn:a; prefix a; container c {typedef x {type t{}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Unexpected end-of-input.", "Line number 1."); - UTEST_LOG_CLEAN; + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Unexpected end-of-input.", "Line number 1."); /* no collision if the same names are in different scope */ str = "module a {yang-version 1.1; namespace urn:a; prefix a;" @@ -485,34 +485,34 @@ test_collision_grouping(void **state) /* collision in node's scope */ str = "module a {namespace urn:a; prefix a; container c {grouping y; grouping y;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"y\" of grouping statement - name collision with sibling grouping.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"y\" of grouping statement - name collision with sibling grouping.", NULL); /* collision with parent node */ str = "module a {namespace urn:a; prefix a; container c {container d {grouping y;} grouping y;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"y\" of grouping statement - name collision with another scoped grouping.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"y\" of grouping statement - name collision with another scoped grouping.", NULL); /* collision with module's top-level */ str = "module a {namespace urn:a; prefix a; grouping x; container c {grouping x;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"x\" of grouping statement - scoped grouping collide with a top-level grouping.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"x\" of grouping statement - scoped grouping collide with a top-level grouping.", NULL); /* collision of submodule's node with module's top-level */ ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, "submodule b {belongs-to a {prefix a;} container c {grouping x;}}"); str = "module a {namespace urn:a; prefix a; include b; grouping x;}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"x\" of grouping statement - scoped grouping collide with a top-level grouping.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"x\" of grouping statement - scoped grouping collide with a top-level grouping.", NULL); /* collision of module's node with submodule's top-level */ ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, "submodule b {belongs-to a {prefix a;} grouping x;}"); str = "module a {namespace urn:a; prefix a; include b; container c {grouping x;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"x\" of grouping statement - scoped grouping collide with a top-level grouping.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"x\" of grouping statement - scoped grouping collide with a top-level grouping.", NULL); /* collision of submodule's node with another submodule's top-level */ str = "module a {yang-version 1.1; namespace urn:a; prefix a; include asub; include bsub;}"; @@ -520,29 +520,29 @@ test_collision_grouping(void **state) list[1].data = "submodule bsub {belongs-to a {prefix a;} container c {grouping g;}}"; ly_ctx_set_module_imp_clb(UTEST_LYCTX, module_clb, list); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"g\" of grouping statement - scoped grouping collide with a top-level grouping.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"g\" of grouping statement - scoped grouping collide with a top-level grouping.", NULL); /* collision of module's top-levels */ str = "module a {namespace urn:a; prefix a; grouping test; grouping test;}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"test\" of grouping statement - name collision with another top-level grouping.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"test\" of grouping statement - name collision with another top-level grouping.", NULL); /* collision of submodule's top-levels */ submod = "submodule asub {belongs-to a {prefix a;} grouping g; grouping g;}"; str = "module a {yang-version 1.1; namespace urn:a; prefix a; include asub;}"; ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, submod); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"g\" of grouping statement - name collision with another top-level grouping.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"g\" of grouping statement - name collision with another top-level grouping.", NULL); /* collision of module's top-level with submodule's top-levels */ ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, "submodule b {belongs-to a {prefix a;} grouping x;}"); str = "module a {namespace urn:a; prefix a; include b; grouping x;}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"x\" of grouping statement - name collision with another top-level grouping.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"x\" of grouping statement - name collision with another top-level grouping.", NULL); /* collision of submodule's top-level with another submodule's top-levels */ str = "module a {yang-version 1.1; namespace urn:a; prefix a; include asub; include bsub;}"; @@ -550,25 +550,27 @@ test_collision_grouping(void **state) list[1].data = "submodule bsub {belongs-to a {prefix a;} grouping g;}"; ly_ctx_set_module_imp_clb(UTEST_LYCTX, module_clb, list); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"g\" of grouping statement - name collision with another top-level grouping.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"g\" of grouping statement - name collision with another top-level grouping.", NULL); /* collision in nested groupings, top-level */ str = "module a {namespace urn:a; prefix a; grouping g {grouping g;}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"g\" of grouping statement - scoped grouping collide with a top-level grouping.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"g\" of grouping statement - scoped grouping collide with a top-level grouping.", NULL); /* collision in nested groupings, in node */ str = "module a {namespace urn:a; prefix a; container c {grouping g {grouping g;}}}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"g\" of grouping statement - name collision with another scoped grouping.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"g\" of grouping statement - name collision with another scoped grouping.", NULL); /* no collision if the same names are in different scope */ str = "module a {yang-version 1.1; namespace urn:a; prefix a;" "container c {grouping g;} container d {grouping g;}}"; assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL)); + CHECK_LOG_CTX("Locally scoped grouping \"g\" not used.", NULL); + CHECK_LOG_CTX("Locally scoped grouping \"g\" not used.", NULL); } static void @@ -584,24 +586,24 @@ test_collision_identity(void **state) /* collision of module's top-levels */ str = "module a {yang-version 1.1; namespace urn:a; prefix a; identity g; identity g;}"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"g\" of identity statement - name collision with another top-level identity.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"g\" of identity statement - name collision with another top-level identity.", NULL); /* collision of submodule's top-levels */ submod = "submodule asub {belongs-to a {prefix a;} identity g; identity g;}"; str = "module a {yang-version 1.1; namespace urn:a; prefix a; include asub;}"; ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, submod); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"g\" of identity statement - name collision with another top-level identity.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"g\" of identity statement - name collision with another top-level identity.", NULL); /* collision of module's top-level with submodule's top-levels */ submod = "submodule asub {belongs-to a {prefix a;} identity g;}"; str = "module a {yang-version 1.1; namespace urn:a; prefix a; include asub; identity g;}"; ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, submod); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"g\" of identity statement - name collision with another top-level identity.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"g\" of identity statement - name collision with another top-level identity.", NULL); /* collision of submodule's top-level with another submodule's top-levels */ str = "module a {yang-version 1.1; namespace urn:a; prefix a; include asub; include bsub;}"; @@ -609,8 +611,8 @@ test_collision_identity(void **state) list[1].data = "submodule bsub {belongs-to a {prefix a;} identity g;}"; ly_ctx_set_module_imp_clb(UTEST_LYCTX, module_clb, list); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"g\" of identity statement - name collision with another top-level identity.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"g\" of identity statement - name collision with another top-level identity.", NULL); } static void @@ -626,24 +628,24 @@ test_collision_feature(void **state) /* collision of module's top-levels */ str = "module a {yang-version 1.1; namespace urn:a; prefix a; feature g; feature g;}"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"g\" of feature statement - name collision with another top-level feature.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"g\" of feature statement - name collision with another top-level feature.", NULL); /* collision of submodule's top-levels */ submod = "submodule asub {belongs-to a {prefix a;} feature g; feature g;}"; str = "module a {yang-version 1.1; namespace urn:a; prefix a; include asub;}"; ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, submod); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"g\" of feature statement - name collision with another top-level feature.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"g\" of feature statement - name collision with another top-level feature.", NULL); /* collision of module's top-level with submodule's top-levels */ submod = "submodule asub {belongs-to a {prefix a;} feature g;}"; str = "module a {yang-version 1.1; namespace urn:a; prefix a; include asub; feature g;}"; ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, submod); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"g\" of feature statement - name collision with another top-level feature.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"g\" of feature statement - name collision with another top-level feature.", NULL); /* collision of submodule's top-level with another submodule's top-levels */ str = "module a {yang-version 1.1; namespace urn:a; prefix a; include asub; include bsub;}"; @@ -651,8 +653,8 @@ test_collision_feature(void **state) list[1].data = "submodule bsub {belongs-to a {prefix a;} feature g;}"; ly_ctx_set_module_imp_clb(UTEST_LYCTX, module_clb, list); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL, - "Duplicate identifier \"g\" of feature statement - name collision with another top-level feature.", NULL); + CHECK_LOG_CTX("Parsing module \"a\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"g\" of feature statement - name collision with another top-level feature.", NULL); } static void @@ -1046,15 +1048,15 @@ test_includes(void **state) ly_ctx_set_module_imp_clb(UTEST_LYCTX, module_clb, list); mod = ly_ctx_load_module(UTEST_LYCTX, "main_b", NULL, NULL); assert_null(mod); - CHECK_LOG_CTX("Loading \"main_b\" module failed.", NULL, - "Data model \"main_b\" not found in local searchdirs.", NULL, - "Parsing module \"main_b\" failed.", NULL, - "Including \"sub_b_one\" submodule into \"main_b\" failed.", NULL, - "Data model \"sub_b_one\" not found in local searchdirs.", NULL, - "Parsing submodule \"sub_b_one\" failed.", NULL, - "YANG 1.1 requires all submodules to be included from main module. But submodule \"sub_b_one\" includes " - "submodule \"sub_b_two\" which is not included by main module \"main_b\".", NULL, - "YANG version 1.1 expects all includes in main module, includes in submodules (sub_b_one) are not necessary.", NULL); + CHECK_LOG_CTX("Loading \"main_b\" module failed.", NULL); + CHECK_LOG_CTX("Data model \"main_b\" not found in local searchdirs.", NULL); + CHECK_LOG_CTX("Parsing module \"main_b\" failed.", NULL); + CHECK_LOG_CTX("Including \"sub_b_one\" submodule into \"main_b\" failed.", NULL); + CHECK_LOG_CTX("Data model \"sub_b_one\" not found in local searchdirs.", NULL); + CHECK_LOG_CTX("Parsing submodule \"sub_b_one\" failed.", NULL); + CHECK_LOG_CTX("YANG 1.1 requires all submodules to be included from main module. But submodule \"sub_b_one\" includes " + "submodule \"sub_b_two\" which is not included by main module \"main_b\".", NULL); + CHECK_LOG_CTX("YANG version 1.1 expects all includes in main module, includes in submodules (sub_b_one) are not necessary.", NULL); } { @@ -1073,6 +1075,7 @@ test_includes(void **state) assert_false(mod->parsed->includes[1].injected); /* result is ok, but log includes the warning */ CHECK_LOG_CTX("YANG version 1.1 expects all includes in main module, includes in submodules (sub_c_two) are not necessary.", NULL); + CHECK_LOG_CTX("YANG version 1.1 expects all includes in main module, includes in submodules (sub_c_one) are not necessary.", NULL); } } @@ -1083,7 +1086,8 @@ test_key_order(void **state) const struct lysc_node *node; struct module_clb_list list1[] = { - {"a", "module a {" + { + "a", "module a {" "yang-version 1.1;" "namespace urn:test:a;" "prefix a;" @@ -1092,7 +1096,8 @@ test_key_order(void **state) " leaf k2 {type string;}" " leaf k1 {type string;}" "}" - "}"}, + "}" + }, {NULL, NULL} }; @@ -1106,7 +1111,8 @@ test_key_order(void **state) assert_string_equal("k2", node->name); struct module_clb_list list2[] = { - {"b", "module b {" + { + "b", "module b {" "yang-version 1.1;" "namespace urn:test:b;" "prefix b;" @@ -1121,7 +1127,8 @@ test_key_order(void **state) " leaf k1 {type string;}" " leaf k3 {type string;}" "}" - "}"}, + "}" + }, {NULL, NULL} }; @@ -1156,7 +1163,7 @@ test_disabled_enum(void **state) "}}" "}"; assert_int_equal(lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Enumeration type of node \"l\" without any (or all disabled) valid values.", "Schema location \"/a:l\"."); + CHECK_LOG_CTX("Node \"l\" without any (or all disabled) valid values.", "Schema location \"/a:l\"."); /* disabled default value */ str = "module a {" @@ -1263,10 +1270,10 @@ test_identity(void **state) assert_ptr_equal(mod->identities[1].derived[0], &mod->identities[0]); ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, "submodule inv_sub {belongs-to inv {prefix inv;} identity i1;}"); - TEST_SCHEMA_ERR(0, 0, "inv", "identity i1 {base i2;}", "Unable to find base (i2) of identity \"i1\".", "/inv:{identity='i1'}"); - TEST_SCHEMA_ERR(0, 0, "inv", "identity i1 {base i1;}", "Identity \"i1\" is derived from itself.", "/inv:{identity='i1'}"); + TEST_SCHEMA_ERR(0, 0, "inv", "identity i1 {base i2;}", "Unable to find base (i2) of identity \"i1\".", "Path \"/inv:{identity='i1'}\"."); + TEST_SCHEMA_ERR(0, 0, "inv", "identity i1 {base i1;}", "Identity \"i1\" is derived from itself.", "Path \"/inv:{identity='i1'}\"."); TEST_SCHEMA_ERR(0, 0, "inv", "identity i1 {base i2;}identity i2 {base i3;}identity i3 {base i1;}", - "Identity \"i1\" is indirectly derived from itself.", "/inv:{identity='i3'}"); + "Identity \"i1\" is indirectly derived from itself.", "Path \"/inv:{identity='i3'}\"."); /* base in non-implemented module */ ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, @@ -1606,8 +1613,8 @@ test_extension_argument_element(void **state) /* invalid */ mod_test_yang = "module x { namespace \"urn:x\"; prefix x; import a { prefix a; } a:e; }"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, mod_test_yang, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Parsing module \"x\" failed.", NULL, - "Extension instance \"a:e\" missing argument element \"name\".", NULL); + CHECK_LOG_CTX("Parsing module \"x\" failed.", NULL); + CHECK_LOG_CTX("Extension instance \"a:e\" missing argument element \"name\".", NULL); mod_test_yin = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" "<module name=\"x\"\n" @@ -1622,8 +1629,8 @@ test_extension_argument_element(void **state) " <a:e/>\n" "</module>\n"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, mod_test_yin, LYS_IN_YIN, NULL)); - CHECK_LOG_CTX("Parsing module \"x\" failed.", NULL, - "Extension instance \"a:e\" missing argument element \"name\".", NULL); + CHECK_LOG_CTX("Parsing module \"x\" failed.", NULL); + CHECK_LOG_CTX("Extension instance \"a:e\" missing argument element \"name\".", NULL); mod_test_yin = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" "<module name=\"x\"\n" @@ -1638,8 +1645,8 @@ test_extension_argument_element(void **state) " <a:e name=\"xxx\"/>\n" "</module>\n"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, mod_test_yin, LYS_IN_YIN, NULL)); - CHECK_LOG_CTX("Parsing module \"x\" failed.", NULL, - "Extension instance \"a:e\" missing argument element \"name\".", NULL); + CHECK_LOG_CTX("Parsing module \"x\" failed.", NULL); + CHECK_LOG_CTX("Extension instance \"a:e\" missing argument element \"name\".", NULL); mod_test_yin = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" "<module name=\"x\"\n" @@ -1656,8 +1663,8 @@ test_extension_argument_element(void **state) " </a:e>\n" "</module>\n"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, mod_test_yin, LYS_IN_YIN, NULL)); - CHECK_LOG_CTX("Parsing module \"x\" failed.", NULL, - "Extension instance \"a:e\" element and its argument element \"name\" are expected in the same namespace, but they differ.", + CHECK_LOG_CTX("Parsing module \"x\" failed.", NULL); + CHECK_LOG_CTX("Extension instance \"a:e\" element and its argument element \"name\" are expected in the same namespace, but they differ.", NULL); mod_test_yin = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" @@ -1675,8 +1682,8 @@ test_extension_argument_element(void **state) " </a:e>\n" "</module>\n"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, mod_test_yin, LYS_IN_YIN, NULL)); - CHECK_LOG_CTX("Parsing module \"x\" failed.", NULL, - "Extension instance \"a:e\" expects argument element \"name\" as its first XML child, but \"value\" element found.", + CHECK_LOG_CTX("Parsing module \"x\" failed.", NULL); + CHECK_LOG_CTX("Extension instance \"a:e\" expects argument element \"name\" as its first XML child, but \"value\" element found.", NULL); } diff --git a/tests/utests/schema/test_tree_schema_compile.c b/tests/utests/schema/test_tree_schema_compile.c index d6f0538..85da486 100644 --- a/tests/utests/schema/test_tree_schema_compile.c +++ b/tests/utests/schema/test_tree_schema_compile.c @@ -1,9 +1,10 @@ -/* +/** * @file test_tree_schema_compile.c - * @author: Radek Krejci <rkrejci@cesnet.cz> + * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Michal Vasko <mvasko@cesnet.cz> * @brief unit tests for functions from parser_yang.c * - * Copyright (c) 2018 CESNET, z.s.p.o. + * Copyright (c) 2018 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -82,6 +83,7 @@ test_module(void **state) ly_in_free(in, 0); assert_int_equal(0, mod->implemented); assert_int_equal(LY_EINVAL, lys_set_implemented(mod, feats)); + CHECK_LOG_CTX("Feature \"invalid\" not found in module \"test\".", NULL); assert_int_equal(LY_SUCCESS, lys_set_implemented(mod, NULL)); assert_non_null(mod->compiled); assert_string_equal("test", mod->name); @@ -112,7 +114,7 @@ test_module(void **state) assert_int_equal(LY_SUCCESS, ly_in_new_memory(str, &in)); assert_int_equal(LY_EEXIST, lys_parse(UTEST_LYCTX, in, LYS_IN_YANG, NULL, &mod)); ly_in_free(in, 0); - CHECK_LOG_CTX("Duplicate identifier \"a\" of data definition/RPC/action/notification statement.", "/aa:a"); + CHECK_LOG_CTX("Duplicate identifier \"a\" of data definition/RPC/action/notification statement.", "Path \"/aa:a\"."); } static void @@ -127,7 +129,7 @@ test_name_collisions(void **state) " leaf c {type empty;}" "}"; assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, yang_data, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Duplicate identifier \"c\" of data definition/RPC/action/notification statement.", "/a:c"); + CHECK_LOG_CTX("Duplicate identifier \"c\" of data definition/RPC/action/notification statement.", "Path \"/a:c\"."); UTEST_LOG_CLEAN; yang_data = "module a {namespace urn:a;prefix a;" @@ -136,7 +138,7 @@ test_name_collisions(void **state) " notification c;" "}"; assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, yang_data, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Duplicate identifier \"c\" of data definition/RPC/action/notification statement.", "/a:c"); + CHECK_LOG_CTX("Duplicate identifier \"c\" of data definition/RPC/action/notification statement.", "Path \"/a:c\"."); UTEST_LOG_CLEAN; yang_data = "module a {namespace urn:a;prefix a;" @@ -145,7 +147,7 @@ test_name_collisions(void **state) " rpc c;" "}"; assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, yang_data, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Duplicate identifier \"c\" of data definition/RPC/action/notification statement.", "/a:c"); + CHECK_LOG_CTX("Duplicate identifier \"c\" of data definition/RPC/action/notification statement.", "Path \"/a:c\"."); UTEST_LOG_CLEAN; yang_data = "module a {namespace urn:a;prefix a;" @@ -159,7 +161,7 @@ test_name_collisions(void **state) " }" "}"; assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, yang_data, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Duplicate identifier \"c\" of data definition/RPC/action/notification statement.", "/a:ch/c/c"); + CHECK_LOG_CTX("Duplicate identifier \"c\" of data definition/RPC/action/notification statement.", "Path \"/a:ch/c/c\"."); UTEST_LOG_CLEAN; /* nested */ @@ -168,7 +170,7 @@ test_name_collisions(void **state) "container a;" "}}}"; assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, yang_data, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Duplicate identifier \"a\" of data definition/RPC/action/notification statement.", "/a:c/l/a"); + CHECK_LOG_CTX("Duplicate identifier \"a\" of data definition/RPC/action/notification statement.", "Path \"/a:c/l/a\"."); UTEST_LOG_CLEAN; yang_data = "module a {yang-version 1.1;namespace urn:a;prefix a;container c { list l {key \"k\"; leaf k {type string;}" @@ -176,7 +178,7 @@ test_name_collisions(void **state) "notification a;" "}}}"; assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, yang_data, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Duplicate identifier \"a\" of data definition/RPC/action/notification statement.", "/a:c/l/a"); + CHECK_LOG_CTX("Duplicate identifier \"a\" of data definition/RPC/action/notification statement.", "Path \"/a:c/l/a\"."); UTEST_LOG_CLEAN; yang_data = "module a {yang-version 1.1;namespace urn:a;prefix a;container c { list l {key \"k\"; leaf k {type string;}" @@ -184,7 +186,7 @@ test_name_collisions(void **state) "action a;" "}}}"; assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, yang_data, LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Duplicate identifier \"a\" of data definition/RPC/action/notification statement.", "/a:c/l/a"); + CHECK_LOG_CTX("Duplicate identifier \"a\" of data definition/RPC/action/notification statement.", "Path \"/a:c/l/a\"."); UTEST_LOG_CLEAN; /* grouping */ @@ -250,15 +252,6 @@ test_node_leaflist(void **state) assert_non_null(((struct lysc_type_leafref *)type)->realtype); assert_int_equal(LY_TYPE_INT8, ((struct lysc_type_leafref *)type)->realtype->basetype); - /* now test for string type is in file ./tests/utests/types/string.c */ -#if 0 - assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, "module b {namespace urn:b;prefix b;leaf-list ll {type string;}}", LYS_IN_YANG, &mod)); - assert_non_null(mod->compiled); - assert_non_null((ll = (struct lysc_node_leaflist *)mod->compiled->data)); - assert_int_equal(0, ll->min); - assert_int_equal((uint32_t)-1, ll->max); -#endif - assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, "module c {yang-version 1.1;namespace urn:c;prefix c;typedef mytype {type int8;default 10;}" "leaf-list ll1 {type mytype;default 1; default 1; config false;}" "leaf-list ll2 {type mytype; ordered-by user;}}", LYS_IN_YANG, &mod)); @@ -304,7 +297,7 @@ test_node_leaflist(void **state) /* invalid */ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa;leaf-list ll {type empty;}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Leaf-list of type \"empty\" is allowed only in YANG 1.1 modules.", "/aa:ll"); + CHECK_LOG_CTX("Leaf-list of type \"empty\" is allowed only in YANG 1.1 modules.", "Path \"/aa:ll\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module bb {yang-version 1.1;namespace urn:bb;prefix bb;leaf-list ll {type empty; default x;}}", LYS_IN_YANG, NULL)); CHECK_LOG_CTX("Invalid default - value does not fit the type (Invalid empty value length 1.).", "Schema location \"/bb:ll\"."); @@ -317,12 +310,12 @@ test_node_leaflist(void **state) assert_int_equal(3, LY_ARRAY_COUNT(ll->dflts)); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module dd {yang-version 1.1;namespace urn:dd;prefix dd;" "leaf-list ll {type string; default one;default two;default one;}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Configuration leaf-list has multiple defaults of the same value \"one\".", "/dd:ll"); + CHECK_LOG_CTX("Configuration leaf-list has multiple defaults of the same value \"one\".", "Path \"/dd:ll\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ee {yang-version 1.1; namespace urn:ee;prefix ee;" "leaf ref {type instance-identifier {require-instance true;} default \"/ee:g\";}}", LYS_IN_YANG, NULL)); CHECK_LOG_CTX("Invalid default - value does not fit the type " - "(Invalid instance-identifier \"/ee:g\" value - semantic error.).", "Schema location \"/ee:ref\"."); + "(Invalid instance-identifier \"/ee:g\" value - semantic error: Not found node \"g\" in path.).", "Schema location \"/ee:ref\"."); } static void @@ -412,11 +405,11 @@ test_node_list(void **state) /* invalid */ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa;list l;}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Missing key in list representing configuration data.", "/aa:l"); + CHECK_LOG_CTX("Missing key in list representing configuration data.", "Path \"/aa:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module bb {yang-version 1.1; namespace urn:bb;prefix bb;" "list l {key x; leaf x {type string; when 1;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("List's key must not have any \"when\" statement.", "/bb:l/x"); + CHECK_LOG_CTX("List's key must not have any \"when\" statement.", "Path \"/bb:l/x\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module cc {yang-version 1.1;namespace urn:cc;prefix cc;feature f;" "list l {key x; leaf x {type string; if-feature f;}}}", LYS_IN_YANG, NULL)); @@ -424,43 +417,43 @@ test_node_list(void **state) assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module dd {namespace urn:dd;prefix dd;" "list l {key x; leaf x {type string; config false;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Key of a configuration list must not be a state leaf.", "/dd:l/x"); + CHECK_LOG_CTX("Key of a configuration list must not be a state leaf.", "Path \"/dd:l/x\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ee {namespace urn:ee;prefix ee;" "list l {config false;key x; leaf x {type string; config true;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Configuration node cannot be child of any state data node.", "/ee:l/x"); + CHECK_LOG_CTX("Configuration node cannot be child of any state data node.", "Path \"/ee:l/x\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ff {namespace urn:ff;prefix ff;" "list l {key x; leaf-list x {type string;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("The list's key \"x\" not found.", "/ff:l"); + CHECK_LOG_CTX("The list's key \"x\" not found.", "Path \"/ff:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module gg {namespace urn:gg;prefix gg;" "list l {key x; unique y;leaf x {type string;} leaf-list y {type string;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Unique's descendant-schema-nodeid \"y\" refers to leaf-list node instead of a leaf.", "/gg:l"); + CHECK_LOG_CTX("Unique's descendant-schema-nodeid \"y\" refers to leaf-list node instead of a leaf.", "Path \"/gg:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module hh {namespace urn:hh;prefix hh;" "list l {key x; unique \"x y\";leaf x {type string;} leaf y {config false; type string;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Unique statement \"x y\" refers to leaves with different config type.", "/hh:l"); + CHECK_LOG_CTX("Unique statement \"x y\" refers to leaves with different config type.", "Path \"/hh:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ii {namespace urn:ii;prefix ii;" "list l {key x; unique a:x;leaf x {type string;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid descendant-schema-nodeid value \"a:x\" - prefix \"a\" not defined in module \"ii\".", "/ii:l"); + CHECK_LOG_CTX("Invalid descendant-schema-nodeid value \"a:x\" - prefix \"a\" not defined in module \"ii\".", "Path \"/ii:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module jj {namespace urn:jj;prefix jj;" "list l {key x; unique c/x;leaf x {type string;}container c {leaf y {type string;}}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid descendant-schema-nodeid value \"c/x\" - target node not found.", "/jj:l"); + CHECK_LOG_CTX("Invalid descendant-schema-nodeid value \"c/x\" - target node not found.", "Path \"/jj:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module kk {namespace urn:kk;prefix kk;" "list l {key x; unique c^y;leaf x {type string;}container c {leaf y {type string;}}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid descendant-schema-nodeid value \"c^\" - missing \"/\" as node-identifier separator.", "/kk:l"); + CHECK_LOG_CTX("Invalid descendant-schema-nodeid value \"c^\" - missing \"/\" as node-identifier separator.", "Path \"/kk:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ll {namespace urn:ll;prefix ll;" "list l {key \"x y x\";leaf x {type string;}leaf y {type string;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Duplicated key identifier \"x\".", "/ll:l"); + CHECK_LOG_CTX("Duplicated key identifier \"x\".", "Path \"/ll:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module mm {namespace urn:mm;prefix mm;" "list l {key x;leaf x {type empty;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("List's key cannot be of \"empty\" type until it is in YANG 1.1 module.", "/mm:l/x"); + CHECK_LOG_CTX("List key of the \"empty\" type is allowed only in YANG 1.1 modules.", "Path \"/mm:l/x\"."); } static void @@ -499,26 +492,26 @@ test_node_choice(void **state) assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa;" "choice ch {case a {leaf x {type string;}}leaf x {type string;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Duplicate identifier \"x\" of data definition/RPC/action/notification statement.", "/aa:ch/x/x"); + CHECK_LOG_CTX("Duplicate identifier \"x\" of data definition/RPC/action/notification statement.", "Path \"/aa:ch/x/x\"."); assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, "module aa2 {namespace urn:aa2;prefix aa;" "choice ch {case a {leaf y {type string;}}case b {leaf y {type string;}}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Duplicate identifier \"y\" of data definition/RPC/action/notification statement.", "/aa2:ch/b/y"); + CHECK_LOG_CTX("Duplicate identifier \"y\" of data definition/RPC/action/notification statement.", "Path \"/aa2:ch/b/y\"."); assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, "module bb {namespace urn:bb;prefix bb;" "choice ch {case a {leaf x {type string;}}leaf a {type string;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Duplicate identifier \"a\" of case statement.", "/bb:ch/a"); + CHECK_LOG_CTX("Duplicate identifier \"a\" of case statement.", "Path \"/bb:ch/a\"."); assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, "module bb2 {namespace urn:bb2;prefix bb;" "choice ch {case b {leaf x {type string;}}case b {leaf y {type string;}}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Duplicate identifier \"b\" of case statement.", "/bb2:ch/b"); + CHECK_LOG_CTX("Duplicate identifier \"b\" of case statement.", "Path \"/bb2:ch/b\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ca {namespace urn:ca;prefix ca;" "choice ch {default c;case a {leaf x {type string;}}case b {leaf y {type string;}}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Default case \"c\" not found.", "/ca:ch"); + CHECK_LOG_CTX("Default case \"c\" not found.", "Path \"/ca:ch\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module cb {namespace urn:cb;prefix cb; import a {prefix a;}" "choice ch {default a:a;case a {leaf x {type string;}}case b {leaf y {type string;}}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Default case \"a:a\" not found.", "/cb:ch"); + CHECK_LOG_CTX("Default case \"a:a\" not found.", "Path \"/cb:ch\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module cc {namespace urn:cc;prefix cc;" "choice ch {default a;case a {leaf x {mandatory true;type string;}}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Mandatory node \"x\" under the default case \"a\".", "/cc:ch"); + CHECK_LOG_CTX("Mandatory node \"x\" under the default case \"a\".", "Path \"/cc:ch\"."); /* TODO check with mandatory nodes from augment placed into the case */ } @@ -544,8 +537,9 @@ test_node_anydata(void **state) /* invalid */ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa;anydata any;}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL, - "Invalid keyword \"anydata\" as a child of \"module\" - the statement is allowed only in YANG 1.1 modules.", "Line number 1."); + CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL); + CHECK_LOG_CTX("Invalid keyword \"anydata\" as a child of \"module\" - the statement is allowed only in YANG 1.1 modules.", + "Line number 1."); } static void @@ -579,31 +573,32 @@ test_action(void **state) /* invalid */ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa;container top {action x;}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL, - "Invalid keyword \"action\" as a child of \"container\" - the statement is allowed only in YANG 1.1 modules.", "Line number 1."); + CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL); + CHECK_LOG_CTX("Invalid keyword \"action\" as a child of \"container\" - the statement is allowed only in YANG 1.1 modules.", + "Line number 1."); assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, "module bb {namespace urn:bb;prefix bb;leaf x{type string;} rpc x;}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Duplicate identifier \"x\" of data definition/RPC/action/notification statement.", "/bb:x"); + CHECK_LOG_CTX("Duplicate identifier \"x\" of data definition/RPC/action/notification statement.", "Path \"/bb:x\"."); assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, "module cc {yang-version 1.1; namespace urn:cc;prefix cc;container c {leaf y {type string;} action y;}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Duplicate identifier \"y\" of data definition/RPC/action/notification statement.", "/cc:c/y"); + CHECK_LOG_CTX("Duplicate identifier \"y\" of data definition/RPC/action/notification statement.", "Path \"/cc:c/y\"."); assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, "module dd {yang-version 1.1; namespace urn:dd;prefix dd;container c {action z; action z;}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Duplicate identifier \"z\" of data definition/RPC/action/notification statement.", "/dd:c/z"); + CHECK_LOG_CTX("Duplicate identifier \"z\" of data definition/RPC/action/notification statement.", "Path \"/dd:c/z\"."); ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, "submodule eesub {belongs-to ee {prefix ee;} notification w;}"); assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, "module ee {yang-version 1.1; namespace urn:ee;prefix ee;include eesub; rpc w;}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Duplicate identifier \"w\" of data definition/RPC/action/notification statement.", "/ee:w"); + CHECK_LOG_CTX("Duplicate identifier \"w\" of data definition/RPC/action/notification statement.", "Path \"/ee:w\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ff {yang-version 1.1; namespace urn:ff;prefix ff; rpc test {input {container a {leaf b {type string;}}}}" "augment /test/input/a {action invalid {input {leaf x {type string;}}}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Action \"invalid\" is placed inside another RPC/action.", "/ff:{augment='/test/input/a'}/invalid"); + CHECK_LOG_CTX("Action \"invalid\" is placed inside another RPC/action.", "Path \"/ff:{augment='/test/input/a'}/invalid\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module gg {yang-version 1.1; namespace urn:gg;prefix gg; notification test {container a {leaf b {type string;}}}" "augment /test/a {action invalid {input {leaf x {type string;}}}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Action \"invalid\" is placed inside notification.", "/gg:{augment='/test/a'}/invalid"); + CHECK_LOG_CTX("Action \"invalid\" is placed inside notification.", "Path \"/gg:{augment='/test/a'}/invalid\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module hh {yang-version 1.1; namespace urn:hh;prefix hh; notification test {container a {uses grp;}}" "grouping grp {action invalid {input {leaf x {type string;}}}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Action \"invalid\" is placed inside notification.", "/hh:test/a/{uses='grp'}/invalid"); + CHECK_LOG_CTX("Action \"invalid\" is placed inside notification.", "Path \"/hh:test/a/{uses='grp'}/invalid\"."); } static void @@ -650,30 +645,31 @@ test_notification(void **state) /* invalid */ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa;container top {notification x;}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL, - "Invalid keyword \"notification\" as a child of \"container\" - the statement is allowed only in YANG 1.1 modules.", "Line number 1."); + CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL); + CHECK_LOG_CTX("Invalid keyword \"notification\" as a child of \"container\" - the statement is allowed only in YANG 1.1 modules.", + "Line number 1."); assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, "module bb {namespace urn:bb;prefix bb;leaf x{type string;} notification x;}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Duplicate identifier \"x\" of data definition/RPC/action/notification statement.", "/bb:x"); + CHECK_LOG_CTX("Duplicate identifier \"x\" of data definition/RPC/action/notification statement.", "Path \"/bb:x\"."); assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, "module cc {yang-version 1.1; namespace urn:cc;prefix cc;container c {leaf y {type string;} notification y;}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Duplicate identifier \"y\" of data definition/RPC/action/notification statement.", "/cc:c/y"); + CHECK_LOG_CTX("Duplicate identifier \"y\" of data definition/RPC/action/notification statement.", "Path \"/cc:c/y\"."); assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, "module dd {yang-version 1.1; namespace urn:dd;prefix dd;container c {notification z; notification z;}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Duplicate identifier \"z\" of data definition/RPC/action/notification statement.", "/dd:c/z"); + CHECK_LOG_CTX("Duplicate identifier \"z\" of data definition/RPC/action/notification statement.", "Path \"/dd:c/z\"."); ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, "submodule eesub {belongs-to ee {prefix ee;} rpc w;}"); assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, "module ee {yang-version 1.1; namespace urn:ee;prefix ee;include eesub; notification w;}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Duplicate identifier \"w\" of data definition/RPC/action/notification statement.", "/ee:w"); + CHECK_LOG_CTX("Duplicate identifier \"w\" of data definition/RPC/action/notification statement.", "Path \"/ee:w\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ff {yang-version 1.1; namespace urn:ff;prefix ff; rpc test {input {container a {leaf b {type string;}}}}" "augment /test/input/a {notification invalid {leaf x {type string;}}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Notification \"invalid\" is placed inside RPC/action.", "/ff:{augment='/test/input/a'}/invalid"); + CHECK_LOG_CTX("Notification \"invalid\" is placed inside RPC/action.", "Path \"/ff:{augment='/test/input/a'}/invalid\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module gg {yang-version 1.1; namespace urn:gg;prefix gg; notification test {container a {leaf b {type string;}}}" "augment /test/a {notification invalid {leaf x {type string;}}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Notification \"invalid\" is placed inside another notification.", "/gg:{augment='/test/a'}/invalid"); + CHECK_LOG_CTX("Notification \"invalid\" is placed inside another notification.", "Path \"/gg:{augment='/test/a'}/invalid\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module hh {yang-version 1.1; namespace urn:hh;prefix hh; rpc test {input {container a {uses grp;}}}" "grouping grp {notification invalid {leaf x {type string;}}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Notification \"invalid\" is placed inside RPC/action.", "/hh:test/input/a/{uses='grp'}/invalid"); + CHECK_LOG_CTX("Notification \"invalid\" is placed inside RPC/action.", "Path \"/hh:test/input/a/{uses='grp'}/invalid\"."); } /** @@ -686,21 +682,6 @@ test_type_range(void **state) struct lys_module *mod; struct lysc_type *type; -#if 0 - /*test about int8 should be in tests/utests/types/int8.c*/ - assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, "module a {namespace urn:a;prefix a;leaf l {type int8 {range min..10|max;}}}", LYS_IN_YANG, &mod)); - type = ((struct lysc_node_leaf *)mod->compiled->data)->type; - assert_non_null(type); - assert_int_equal(LY_TYPE_INT8, type->basetype); - assert_non_null(((struct lysc_type_num *)type)->range); - assert_non_null(((struct lysc_type_num *)type)->range->parts); - assert_int_equal(2, LY_ARRAY_COUNT(((struct lysc_type_num *)type)->range->parts)); - assert_int_equal(-128, ((struct lysc_type_num *)type)->range->parts[0].min_64); - assert_int_equal(10, ((struct lysc_type_num *)type)->range->parts[0].max_64); - assert_int_equal(127, ((struct lysc_type_num *)type)->range->parts[1].min_64); - assert_int_equal(127, ((struct lysc_type_num *)type)->range->parts[1].max_64); -#endif - assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, "module b {namespace urn:b;prefix b;leaf l {type int16 {range min..10|max;}}}", LYS_IN_YANG, &mod)); type = ((struct lysc_node_leaf *)mod->compiled->data)->type; assert_non_null(type); @@ -937,83 +918,56 @@ test_type_length(void **state) assert_int_equal(10, ((struct lysc_type_bin *)type)->length->parts[0].min_u64); assert_int_equal(100, ((struct lysc_type_bin *)type)->length->parts[0].max_u64); - /* new string is tested in file ./tests/utests/types/string.c */ -#if 0 - assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, "module l {namespace urn:l;prefix l;typedef mytype {type string {length 10..100;}}" - "typedef mytype2 {type mytype {pattern '[0-9]*';}} leaf l {type mytype2 {pattern '[0-4]*';}}}", LYS_IN_YANG, &mod)); - type = ((struct lysc_node_leaf *)mod->compiled->data)->type; - assert_non_null(type); - assert_int_equal(LY_TYPE_STRING, type->basetype); - assert_int_equal(1, type->refcount); - assert_non_null(((struct lysc_type_str *)type)->length); - assert_non_null(((struct lysc_type_str *)type)->length->parts); - assert_int_equal(1, LY_ARRAY_COUNT(((struct lysc_type_str *)type)->length->parts)); - assert_int_equal(10, ((struct lysc_type_str *)type)->length->parts[0].min_u64); - assert_int_equal(100, ((struct lysc_type_str *)type)->length->parts[0].max_u64); - - assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, "module m {namespace urn:m;prefix m;typedef mytype {type string {length 10;}}" - "leaf l {type mytype {length min..max;}}}", LYS_IN_YANG, &mod)); - type = ((struct lysc_node_leaf *)mod->compiled->data)->type; - assert_non_null(type); - assert_int_equal(LY_TYPE_STRING, type->basetype); - assert_int_equal(1, type->refcount); - assert_non_null(((struct lysc_type_str *)type)->length); - assert_non_null(((struct lysc_type_str *)type)->length->parts); - assert_int_equal(1, LY_ARRAY_COUNT(((struct lysc_type_str *)type)->length->parts)); - assert_int_equal(10, ((struct lysc_type_str *)type)->length->parts[0].min_u64); - assert_int_equal(10, ((struct lysc_type_str *)type)->length->parts[0].max_u64); -#endif - /* invalid values */ assert_int_equal(LY_EDENIED, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa;leaf l {type binary {length -10;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid length restriction - value \"-10\" does not fit the type limitations.", "/aa:l"); + CHECK_LOG_CTX("Invalid length restriction - value \"-10\" does not fit the type limitations.", "Path \"/aa:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module bb {namespace urn:bb;prefix bb;leaf l {type binary {length 18446744073709551616;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid length restriction - invalid value \"18446744073709551616\".", "/bb:l"); + CHECK_LOG_CTX("Invalid length restriction - invalid value \"18446744073709551616\".", "Path \"/bb:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module cc {namespace urn:cc;prefix cc;leaf l {type binary {length \"max .. 10\";}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid length restriction - unexpected data after max keyword (.. 10).", "/cc:l"); + CHECK_LOG_CTX("Invalid length restriction - unexpected data after max keyword (.. 10).", "Path \"/cc:l\"."); assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, "module dd {namespace urn:dd;prefix dd;leaf l {type binary {length 50..10;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid length restriction - values are not in ascending order (10).", "/dd:l"); + CHECK_LOG_CTX("Invalid length restriction - values are not in ascending order (10).", "Path \"/dd:l\"."); assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, "module ee {namespace urn:ee;prefix ee;leaf l {type binary {length \"50 | 10\";}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid length restriction - values are not in ascending order (10).", "/ee:l"); + CHECK_LOG_CTX("Invalid length restriction - values are not in ascending order (10).", "Path \"/ee:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ff {namespace urn:ff;prefix ff;leaf l {type binary {length \"x\";}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid length restriction - unexpected data (x).", "/ff:l"); + CHECK_LOG_CTX("Invalid length restriction - unexpected data (x).", "Path \"/ff:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module gg {namespace urn:gg;prefix gg;leaf l {type binary {length \"50 | min\";}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid length restriction - unexpected data before min keyword (50 | ).", "/gg:l"); + CHECK_LOG_CTX("Invalid length restriction - unexpected data before min keyword (50 | ).", "Path \"/gg:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module hh {namespace urn:hh;prefix hh;leaf l {type binary {length \"| 50\";}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid length restriction - unexpected beginning of the expression (| 50).", "/hh:l"); + CHECK_LOG_CTX("Invalid length restriction - unexpected beginning of the expression (| 50).", "Path \"/hh:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ii {namespace urn:ii;prefix ii;leaf l {type binary {length \"10 ..\";}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid length restriction - unexpected end of the expression after \"..\" (10 ..).", "/ii:l"); + CHECK_LOG_CTX("Invalid length restriction - unexpected end of the expression after \"..\" (10 ..).", "Path \"/ii:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module jj {namespace urn:jj;prefix jj;leaf l {type binary {length \".. 10\";}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid length restriction - unexpected \"..\" without a lower bound.", "/jj:l"); + CHECK_LOG_CTX("Invalid length restriction - unexpected \"..\" without a lower bound.", "Path \"/jj:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module kk {namespace urn:kk;prefix kk;leaf l {type binary {length \"10 |\";}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid length restriction - unexpected end of the expression (10 |).", "/kk:l"); + CHECK_LOG_CTX("Invalid length restriction - unexpected end of the expression (10 |).", "Path \"/kk:l\"."); assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, "module kl {namespace urn:kl;prefix kl;leaf l {type binary {length \"10..20 | 15..30\";}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid length restriction - values are not in ascending order (15).", "/kl:l"); + CHECK_LOG_CTX("Invalid length restriction - values are not in ascending order (15).", "Path \"/kl:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ll {namespace urn:ll;prefix ll;typedef mytype {type binary {length 10;}}" "leaf l {type mytype {length 11;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid length restriction - the derived restriction (11) is not equally or more limiting.", "/ll:l"); + CHECK_LOG_CTX("Invalid length restriction - the derived restriction (11) is not equally or more limiting.", "Path \"/ll:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module mm {namespace urn:mm;prefix mm;typedef mytype {type binary {length 10..100;}}" "leaf l {type mytype {length 1..11;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid length restriction - the derived restriction (1..11) is not equally or more limiting.", "/mm:l"); + CHECK_LOG_CTX("Invalid length restriction - the derived restriction (1..11) is not equally or more limiting.", "Path \"/mm:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module nn {namespace urn:nn;prefix nn;typedef mytype {type binary {length 10..100;}}" "leaf l {type mytype {length 20..110;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid length restriction - the derived restriction (20..110) is not equally or more limiting.", "/nn:l"); + CHECK_LOG_CTX("Invalid length restriction - the derived restriction (20..110) is not equally or more limiting.", "Path \"/nn:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module oo {namespace urn:oo;prefix oo;typedef mytype {type binary {length 10..100;}}" "leaf l {type mytype {length 20..30|110..120;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid length restriction - the derived restriction (20..30|110..120) is not equally or more limiting.", "/oo:l"); + CHECK_LOG_CTX("Invalid length restriction - the derived restriction (20..30|110..120) is not equally or more limiting.", "Path \"/oo:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module pp {namespace urn:pp;prefix pp;typedef mytype {type binary {length 10..11;}}" "leaf l {type mytype {length 15;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid length restriction - the derived restriction (15) is not equally or more limiting.", "/pp:l"); + CHECK_LOG_CTX("Invalid length restriction - the derived restriction (15) is not equally or more limiting.", "Path \"/pp:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module qq {namespace urn:qq;prefix qq;typedef mytype {type binary {length 10..20|30..40;}}" "leaf l {type mytype {length 15..35;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid length restriction - the derived restriction (15..35) is not equally or more limiting.", "/qq:l"); + CHECK_LOG_CTX("Invalid length restriction - the derived restriction (15..35) is not equally or more limiting.", "Path \"/qq:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module rr {namespace urn:rr;prefix rr;typedef mytype {type binary {length 10;}}" "leaf l {type mytype {length 10..35;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid length restriction - the derived restriction (10..35) is not equally or more limiting.", "/rr:l"); + CHECK_LOG_CTX("Invalid length restriction - the derived restriction (10..35) is not equally or more limiting.", "Path \"/rr:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ss {namespace urn:ss;prefix ss;leaf l {type binary {pattern '[0-9]*';}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid type restrictions for binary type.", "/ss:l"); + CHECK_LOG_CTX("Invalid type restrictions for binary type.", "Path \"/ss:l\"."); } static void @@ -1152,61 +1106,63 @@ test_type_enum(void **state) /* invalid cases */ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa; feature f; leaf l {type enumeration {" "enum one {if-feature f;}}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL, - "Invalid keyword \"if-feature\" as a child of \"enum\" - the statement is allowed only in YANG 1.1 modules.", "Line number 1."); + CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL); + CHECK_LOG_CTX("Invalid keyword \"if-feature\" as a child of \"enum\" - the statement is allowed only in YANG 1.1 modules.", + "Line number 1."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa; leaf l {type enumeration {" "enum one {value -2147483649;}}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL, - "Invalid value \"-2147483649\" of \"value\".", "Line number 1."); + CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL); + CHECK_LOG_CTX("Invalid value \"-2147483649\" of \"value\".", "Line number 1."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa; leaf l {type enumeration {" "enum one {value 2147483648;}}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL, - "Invalid value \"2147483648\" of \"value\".", "Line number 1."); + CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL); + CHECK_LOG_CTX("Invalid value \"2147483648\" of \"value\".", "Line number 1."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa; leaf l {type enumeration {" "enum one; enum one;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL, - "Duplicate identifier \"one\" of enum statement.", "Line number 1."); + CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"one\" of enum statement.", "Line number 1."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa; leaf l {type enumeration {" "enum '';}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL, - "Enum name must not be zero-length.", "Line number 1."); + CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL); + CHECK_LOG_CTX("Enum name must not be zero-length.", "Line number 1."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa; leaf l {type enumeration {" "enum ' x';}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL, - "Enum name must not have any leading or trailing whitespaces (\" x\").", "Line number 1."); + CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL); + CHECK_LOG_CTX("Enum name must not have any leading or trailing whitespaces (\" x\").", "Line number 1."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa; leaf l {type enumeration {" "enum 'x ';}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL, - "Enum name must not have any leading or trailing whitespaces (\"x \").", "Line number 1."); + CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL); + CHECK_LOG_CTX("Enum name must not have any leading or trailing whitespaces (\"x \").", "Line number 1."); assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa; leaf l {type enumeration {" "enum 'inva\nlid';}}}", LYS_IN_YANG, &mod)); CHECK_LOG_CTX("Control characters in enum name should be avoided (\"inva\nlid\", character number 5).", NULL); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module bb {namespace urn:bb;prefix bb; leaf l {type enumeration;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Missing enum substatement for enumeration type.", "/bb:l"); + CHECK_LOG_CTX("Missing enum substatement for enumeration type.", "Path \"/bb:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module cc {yang-version 1.1;namespace urn:cc;prefix cc;typedef mytype {type enumeration {enum one;}}" "leaf l {type mytype {enum two;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid enumeration - derived type adds new item \"two\".", "/cc:l"); + CHECK_LOG_CTX("Invalid enumeration - derived type adds new item \"two\".", "Path \"/cc:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module dd {yang-version 1.1;namespace urn:dd;prefix dd;typedef mytype {type enumeration {enum one;}}" "leaf l {type mytype {enum one {value 1;}}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid enumeration - value of the item \"one\" has changed from 0 to 1 in the derived type.", "/dd:l"); + CHECK_LOG_CTX("Invalid enumeration - value of the item \"one\" has changed from 0 to 1 in the derived type.", "Path \"/dd:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ee {namespace urn:ee;prefix ee;leaf l {type enumeration {enum x {value 2147483647;}enum y;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid enumeration - it is not possible to auto-assign enum value for \"y\" since the highest value is already 2147483647.", "/ee:l"); + CHECK_LOG_CTX("Invalid enumeration - it is not possible to auto-assign enum value for \"y\" since the highest value is already 2147483647.", + "Path \"/ee:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ff {namespace urn:ff;prefix ff;leaf l {type enumeration {enum x {value 1;}enum y {value 1;}}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid enumeration - value 1 collide in items \"y\" and \"x\".", "/ff:l"); + CHECK_LOG_CTX("Invalid enumeration - value 1 collide in items \"y\" and \"x\".", "Path \"/ff:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module gg {namespace urn:gg;prefix gg;typedef mytype {type enumeration;}" "leaf l {type mytype {enum one;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Missing enum substatement for enumeration type mytype.", "/gg:l"); + CHECK_LOG_CTX("Missing enum substatement for enumeration type mytype.", "Path \"/gg:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module hh {namespace urn:hh;prefix hh; typedef mytype {type enumeration {enum one;}}" "leaf l {type mytype {enum one;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Enumeration type can be subtyped only in YANG 1.1 modules.", "/hh:l"); + CHECK_LOG_CTX("Enumeration type can be subtyped only in YANG 1.1 modules.", "Path \"/hh:l\"."); } static void @@ -1256,43 +1212,43 @@ test_type_dec64(void **state) /* invalid cases */ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa; leaf l {type decimal64 {fraction-digits 0;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL, - "Invalid value \"0\" of \"fraction-digits\".", "Line number 1."); + CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL); + CHECK_LOG_CTX("Invalid value \"0\" of \"fraction-digits\".", "Line number 1."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa; leaf l {type decimal64 {fraction-digits -1;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL, - "Invalid value \"-1\" of \"fraction-digits\".", "Line number 1."); + CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL); + CHECK_LOG_CTX("Invalid value \"-1\" of \"fraction-digits\".", "Line number 1."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa; leaf l {type decimal64 {fraction-digits 19;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL, - "Value \"19\" is out of \"fraction-digits\" bounds.", "Line number 1."); + CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL); + CHECK_LOG_CTX("Value \"19\" is out of \"fraction-digits\" bounds.", "Line number 1."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa; leaf l {type decimal64;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Missing fraction-digits substatement for decimal64 type.", "/aa:l"); + CHECK_LOG_CTX("Missing fraction-digits substatement for decimal64 type.", "Path \"/aa:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ab {namespace urn:ab;prefix ab; typedef mytype {type decimal64;}leaf l {type mytype;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Missing fraction-digits substatement for decimal64 type mytype.", "/ab:l"); + CHECK_LOG_CTX("Missing fraction-digits substatement for decimal64 type mytype.", "Path \"/ab:l\"."); assert_int_equal(LY_EINVAL, lys_parse_mem(UTEST_LYCTX, "module bb {namespace urn:bb;prefix bb; leaf l {type decimal64 {fraction-digits 2;" "range '3.142';}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Range boundary \"3.142\" of decimal64 type exceeds defined number (2) of fraction digits.", "/bb:l"); + CHECK_LOG_CTX("Range boundary \"3.142\" of decimal64 type exceeds defined number (2) of fraction digits.", "Path \"/bb:l\"."); assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, "module cc {namespace urn:cc;prefix cc; leaf l {type decimal64 {fraction-digits 2;" "range '4 | 3.14';}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid range restriction - values are not in ascending order (3.14).", "/cc:l"); + CHECK_LOG_CTX("Invalid range restriction - values are not in ascending order (3.14).", "Path \"/cc:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module dd {namespace urn:dd;prefix dd; typedef mytype {type decimal64 {fraction-digits 2;}}" "leaf l {type mytype {fraction-digits 3;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid fraction-digits substatement for type not directly derived from decimal64 built-in type.", "/dd:l"); + CHECK_LOG_CTX("Invalid fraction-digits substatement for type not directly derived from decimal64 built-in type.", "Path \"/dd:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module de {namespace urn:de;prefix de; typedef mytype {type decimal64 {fraction-digits 2;}}" "typedef mytype2 {type mytype {fraction-digits 3;}}leaf l {type mytype2;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid fraction-digits substatement for type \"mytype2\" not directly derived from decimal64 built-in type.", "/de:l"); + CHECK_LOG_CTX("Invalid fraction-digits substatement for type \"mytype2\" not directly derived from decimal64 built-in type.", "Path \"/de:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ee {namespace urn:ee;prefix ee;typedef mytype {type decimal64 {" "fraction-digits 18;range '-10 .. 0';}}leaf l {type mytype;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid range restriction - invalid value \"-10000000000000000000\".", "/ee:l"); + CHECK_LOG_CTX("Invalid range restriction - invalid value \"-10000000000000000000\".", "Path \"/ee:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ee {namespace urn:ee;prefix ee;typedef mytype {type decimal64 {" "fraction-digits 18;range '0 .. 10';}}leaf l {type mytype;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid range restriction - invalid value \"10000000000000000000\".", "/ee:l"); + CHECK_LOG_CTX("Invalid range restriction - invalid value \"10000000000000000000\".", "Path \"/ee:l\"."); } static void @@ -1321,11 +1277,11 @@ test_type_instanceid(void **state) /* invalid cases */ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa; leaf l {type instance-identifier {require-instance yes;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL, - "Invalid value \"yes\" of \"require-instance\".", "Line number 1."); + CHECK_LOG_CTX("Parsing module \"aa\" failed.", NULL); + CHECK_LOG_CTX("Invalid value \"yes\" of \"require-instance\".", "Line number 1."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa; leaf l {type instance-identifier {fraction-digits 1;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid type restrictions for instance-identifier type.", "/aa:l"); + CHECK_LOG_CTX("Invalid type restrictions for instance-identifier type.", "Path \"/aa:l\"."); } static ly_bool @@ -1721,29 +1677,29 @@ test_type_identityref(void **state) /* invalid cases */ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa; leaf l {type identityref;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Missing base substatement for identityref type.", "/aa:l"); + CHECK_LOG_CTX("Missing base substatement for identityref type.", "Path \"/aa:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module bb {namespace urn:bb;prefix bb; typedef mytype {type identityref;}" "leaf l {type mytype;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Missing base substatement for identityref type mytype.", "/bb:l"); + CHECK_LOG_CTX("Missing base substatement for identityref type mytype.", "Path \"/bb:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module cc {namespace urn:cc;prefix cc; identity i; typedef mytype {type identityref {base i;}}" "leaf l {type mytype {base i;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid base substatement for the type not directly derived from identityref built-in type.", "/cc:l"); + CHECK_LOG_CTX("Invalid base substatement for the type not directly derived from identityref built-in type.", "Path \"/cc:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module dd {namespace urn:dd;prefix dd; identity i; typedef mytype {type identityref {base i;}}" "typedef mytype2 {type mytype {base i;}}leaf l {type mytype2;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid base substatement for the type \"mytype2\" not directly derived from identityref built-in type.", "/dd:l"); + CHECK_LOG_CTX("Invalid base substatement for the type \"mytype2\" not directly derived from identityref built-in type.", "Path \"/dd:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ee {namespace urn:ee;prefix ee; identity i; identity j;" "leaf l {type identityref {base i;base j;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Multiple bases in identityref type are allowed only in YANG 1.1 modules.", "/ee:l"); + CHECK_LOG_CTX("Multiple bases in identityref type are allowed only in YANG 1.1 modules.", "Path \"/ee:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ff {namespace urn:ff;prefix ff; identity i;leaf l {type identityref {base j;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Unable to find base (j) of identityref.", "/ff:l"); + CHECK_LOG_CTX("Unable to find base (j) of identityref.", "Path \"/ff:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module gg {namespace urn:gg;prefix gg;leaf l {type identityref {base x:j;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid prefix used for base (x:j) of identityref.", "/gg:l"); + CHECK_LOG_CTX("Invalid prefix used for base (x:j) of identityref.", "Path \"/gg:l\"."); } static void @@ -1759,18 +1715,27 @@ test_type_leafref(void **state) path = "invalid_path"; assert_int_equal(LY_EVALID, ly_path_parse(UTEST_LYCTX, NULL, path, strlen(path), 1, LY_PATH_BEGIN_EITHER, LY_PATH_PREFIX_OPTIONAL, LY_PATH_PRED_LEAFREF, &expr)); + CHECK_LOG_CTX("Unexpected XPath token \"NameTest\" (\"invalid_path\"), expected \"..\".", NULL); + path = ".."; assert_int_equal(LY_EVALID, ly_path_parse(UTEST_LYCTX, NULL, path, strlen(path), 1, LY_PATH_BEGIN_EITHER, LY_PATH_PREFIX_OPTIONAL, LY_PATH_PRED_LEAFREF, &expr)); + CHECK_LOG_CTX("Unexpected XPath expression end.", NULL); + path = "..["; assert_int_equal(LY_EVALID, ly_path_parse(UTEST_LYCTX, NULL, path, strlen(path), 1, LY_PATH_BEGIN_EITHER, LY_PATH_PREFIX_OPTIONAL, LY_PATH_PRED_LEAFREF, &expr)); + CHECK_LOG_CTX("Unexpected XPath token \"[\" (\"[\"), expected \"Operator(Path)\".", NULL); + path = "../"; assert_int_equal(LY_EVALID, ly_path_parse(UTEST_LYCTX, NULL, path, strlen(path), 1, LY_PATH_BEGIN_EITHER, LY_PATH_PREFIX_OPTIONAL, LY_PATH_PRED_LEAFREF, &expr)); + CHECK_LOG_CTX("Unexpected XPath expression end.", NULL); + path = "/"; assert_int_equal(LY_EVALID, ly_path_parse(UTEST_LYCTX, NULL, path, strlen(path), 1, LY_PATH_BEGIN_EITHER, LY_PATH_PREFIX_OPTIONAL, LY_PATH_PRED_LEAFREF, &expr)); + CHECK_LOG_CTX("Unexpected XPath expression end.", NULL); path = "../../pref:id/xxx[predicate]/invalid!!!"; assert_int_equal(LY_EVALID, ly_path_parse(UTEST_LYCTX, NULL, path, strlen(path), 1, LY_PATH_BEGIN_EITHER, @@ -1861,6 +1826,7 @@ test_type_leafref(void **state) "leaf target {if-feature 'f1'; type boolean;}}"; assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, str, LYS_IN_YANG, &mod)); CHECK_LOG_CTX("Target of leafref \"ref1\" cannot be referenced because it is disabled.", "Schema location \"/e:ref1\"."); + CHECK_LOG_CTX("Not found node \"target\" in path.", "Schema location \"/e:ref1\"."); str = "module en {yang-version 1.1;namespace urn:en;prefix en;feature f1;" "leaf ref1 {if-feature 'f1'; type leafref {path /target;}}" @@ -1881,6 +1847,7 @@ test_type_leafref(void **state) "leaf ref {must \"/cl:h > 0\"; type uint16;}}", LYS_IN_YANG, &mod)); ly_ctx_unset_options(UTEST_LYCTX, LY_CTX_REF_IMPLEMENTED); CHECK_LOG_CTX("Target of leafref \"g\" cannot be referenced because it is disabled.", "Schema location \"/cl:g\"."); + CHECK_LOG_CTX("Not found node \"f\" in path.", "Schema location \"/cl:g\"."); assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, "module f {namespace urn:f;prefix f;" "list interface{key name;leaf name{type string;}list address {key ip;leaf ip {type string;}}}" @@ -1956,6 +1923,13 @@ test_type_leafref(void **state) assert_int_equal(LY_TYPE_BOOL, ((struct lysc_node_leaf *)mod->compiled->data)->dflt->realtype->basetype); assert_int_equal(1, ((struct lysc_node_leaf *)mod->compiled->data)->dflt->boolean); + /* union reference */ + assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, "module m {namespace urn:m;prefix m;" + "typedef s-ref {type union {type leafref {path '/str';}}}" + "leaf str {type string {length \"1..16\" {error-message \"Custom message\";}}}" + "leaf ref1 {type s-ref;}" + "leaf ref2 {type s-ref;}}", LYS_IN_YANG, NULL)); + /* invalid paths */ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa;container a {leaf target2 {type uint8;}}" "leaf ref1 {type leafref {path ../a/invalid;}}}", LYS_IN_YANG, &mod)); @@ -1972,8 +1946,8 @@ test_type_leafref(void **state) CHECK_LOG_CTX("List predicate defined for container \"a\" in path.", "Schema location \"/dd:ref1\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ee {namespace urn:ee;prefix ee;\n container a {leaf target2 {type uint8;}}\n" "leaf ref1 {type leafref {path /a!invalid;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Parsing module \"ee\" failed.", NULL, - "Invalid character 0x21 ('!'), perhaps \"a\" is supposed to be a function call.", "Line number 3."); + CHECK_LOG_CTX("Parsing module \"ee\" failed.", NULL); + CHECK_LOG_CTX("Invalid character 0x21 ('!'), perhaps \"a\" is supposed to be a function call.", "Line number 3."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ff {namespace urn:ff;prefix ff;container a {leaf target2 {type uint8;}}" "leaf ref1 {type leafref {path /a;}}}", LYS_IN_YANG, &mod)); CHECK_LOG_CTX("Invalid leafref path \"/a\" - target node is container instead of leaf or leaf-list.", "Schema location \"/ff:ref1\"."); @@ -1983,36 +1957,36 @@ test_type_leafref(void **state) "Schema location \"/gg:ref1\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module hh {namespace urn:hh;prefix hh;" "leaf ref1 {type leafref;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Missing path substatement for leafref type.", "/hh:ref1"); + CHECK_LOG_CTX("Missing path substatement for leafref type.", "Path \"/hh:ref1\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ii {namespace urn:ii;prefix ii;typedef mytype {type leafref;}" "leaf ref1 {type mytype;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Missing path substatement for leafref type mytype.", "/ii:ref1"); + CHECK_LOG_CTX("Missing path substatement for leafref type mytype.", "Path \"/ii:ref1\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module kk {namespace urn:kk;prefix kk;" "leaf ref {type leafref {path /target;}}leaf target {type string;config false;}}", LYS_IN_YANG, &mod)); CHECK_LOG_CTX("Invalid leafref path \"/target\" - target is supposed to represent configuration data (as the leafref does), but it does not.", "Schema location \"/kk:ref\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ll {namespace urn:ll;prefix ll;" "leaf ref {type leafref {path /target; require-instance true;}}leaf target {type string;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Leafref type can be restricted by require-instance statement only in YANG 1.1 modules.", "/ll:ref"); + CHECK_LOG_CTX("Leafref type can be restricted by require-instance statement only in YANG 1.1 modules.", "Path \"/ll:ref\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module mm {namespace urn:mm;prefix mm;typedef mytype {type leafref {path /target;require-instance false;}}" "leaf ref {type mytype;}leaf target {type string;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Leafref type \"mytype\" can be restricted by require-instance statement only in YANG 1.1 modules.", "/mm:ref"); + CHECK_LOG_CTX("Leafref type \"mytype\" can be restricted by require-instance statement only in YANG 1.1 modules.", "Path \"/mm:ref\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module nn {namespace urn:nn;prefix nn;\n" "list interface{key name;leaf name{type string;}leaf ip {type string;}}\n" "leaf ifname{type leafref{ path \"../interface/name\";}}\n" "leaf address {type leafref{\n path \"/interface[name is current()/../ifname]/ip\";}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Parsing module \"nn\" failed.", NULL, - "Invalid character 0x69 ('i'), perhaps \"name\" is supposed to be a function call.", "Line number 5."); + CHECK_LOG_CTX("Parsing module \"nn\" failed.", NULL); + CHECK_LOG_CTX("Invalid character 0x69 ('i'), perhaps \"name\" is supposed to be a function call.", "Line number 5."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module oo {namespace urn:oo;prefix oo;\n" "list interface{key name;leaf name{type string;}leaf ip {type string;}}\n" "leaf ifname{type leafref{ path \"../interface/name\";}}\n" "leaf address {type leafref{\n path \"/interface[name=current()/../ifname/ip\";}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Parsing module \"oo\" failed.", NULL, - "Unexpected XPath expression end.", "Line number 5."); + CHECK_LOG_CTX("Parsing module \"oo\" failed.", NULL); + CHECK_LOG_CTX("Unexpected XPath expression end.", "Line number 5."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module pp {namespace urn:pp;prefix pp;" "list interface{key name;leaf name{type string;}leaf ip {type string;}}" @@ -2034,56 +2008,56 @@ test_type_leafref(void **state) "leaf ifname{type leafref{ path \"../interface/name\";}}leaf test{type string;}\n" "leaf address {type leafref{ path \"/interface[name=current() / .. / ifname][name=current()/../test]/ip\";}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Parsing module \"rr\" failed.", NULL, - "Duplicate predicate key \"name\" in path.", "Line number 4."); + CHECK_LOG_CTX("Parsing module \"rr\" failed.", NULL); + CHECK_LOG_CTX("Duplicate predicate key \"name\" in path.", "Line number 4."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ss {namespace urn:ss;prefix ss;\n" "list interface{key name;leaf name{type string;}leaf ip {type string;}}\n" "leaf ifname{type leafref{ path \"../interface/name\";}}leaf test{type string;}\n" "leaf address {type leafref{ path \"/interface[name = ../ifname]/ip\";}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Parsing module \"ss\" failed.", NULL, - "Unexpected XPath token \"..\" (\"../ifname]/ip\"), expected \"FunctionName\".", "Line number 4."); + CHECK_LOG_CTX("Parsing module \"ss\" failed.", NULL); + CHECK_LOG_CTX("Unexpected XPath token \"..\" (\"../ifname]/ip\"), expected \"FunctionName\".", "Line number 4."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module tt {namespace urn:tt;prefix tt;\n" "list interface{key name;leaf name{type string;}leaf ip {type string;}}\n" "leaf ifname{type leafref{ path \"../interface/name\";}}leaf test{type string;}\n" "leaf address {type leafref{ path \"/interface[name = current()../ifname]/ip\";}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Parsing module \"tt\" failed.", NULL, - "Unexpected XPath token \"..\" (\"../ifname]/ip\"), expected \"]\".", "Line number 4."); + CHECK_LOG_CTX("Parsing module \"tt\" failed.", NULL); + CHECK_LOG_CTX("Unexpected XPath token \"..\" (\"../ifname]/ip\"), expected \"Operator(Path)\".", "Line number 4."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module uu {namespace urn:uu;prefix uu;\n" "list interface{key name;leaf name{type string;}leaf ip {type string;}}\n" "leaf ifname{type leafref{ path \"../interface/name\";}}leaf test{type string;}\n" "leaf address {type leafref{ path \"/interface[name = current()/..ifname]/ip\";}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Parsing module \"uu\" failed.", NULL, - "Invalid character 'i'[31] of expression '/interface[name = current()/..ifname]/ip'.", "Line number 4."); + CHECK_LOG_CTX("Parsing module \"uu\" failed.", NULL); + CHECK_LOG_CTX("Invalid character 'i'[31] of expression '/interface[name = current()/..ifname]/ip'.", "Line number 4."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module vv {namespace urn:vv;prefix vv;\n" "list interface{key name;leaf name{type string;}leaf ip {type string;}}\n" "leaf ifname{type leafref{ path \"../interface/name\";}}leaf test{type string;}\n" "leaf address {type leafref{ path \"/interface[name = current()/ifname]/ip\";}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Parsing module \"vv\" failed.", NULL, - "Unexpected XPath token \"NameTest\" (\"ifname]/ip\"), expected \"..\".", "Line number 4."); + CHECK_LOG_CTX("Parsing module \"vv\" failed.", NULL); + CHECK_LOG_CTX("Unexpected XPath token \"NameTest\" (\"ifname]/ip\"), expected \"..\".", "Line number 4."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ww {namespace urn:ww;prefix ww;\n" "list interface{key name;leaf name{type string;}leaf ip {type string;}}\n" "leaf ifname{type leafref{ path \"../interface/name\";}}leaf test{type string;}\n" "leaf address {type leafref{ path \"/interface[name = current()/../]/ip\";}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Parsing module \"ww\" failed.", NULL, - "Unexpected XPath token \"]\" (\"]/ip\").", "Line number 4."); + CHECK_LOG_CTX("Parsing module \"ww\" failed.", NULL); + CHECK_LOG_CTX("Unexpected XPath token \"]\" (\"]/ip\"), expected \"NameTest\".", "Line number 4."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module xx {namespace urn:xx;prefix xx;\n" "list interface{key name;leaf name{type string;}leaf ip {type string;}}\n" "leaf ifname{type leafref{ path \"../interface/name\";}}leaf test{type string;}\n" "leaf address {type leafref{ path \"/interface[name = current()/../#node]/ip\";}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Parsing module \"xx\" failed.", NULL, - "Invalid character '#'[32] of expression '/interface[name = current()/../#node]/ip'.", "Line number 4."); + CHECK_LOG_CTX("Parsing module \"xx\" failed.", NULL); + CHECK_LOG_CTX("Invalid character '#'[32] of expression '/interface[name = current()/../#node]/ip'.", "Line number 4."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module yy {namespace urn:yy;prefix yy;\n" "list interface{key name;leaf name{type string;}leaf ip {type string;}}\n" @@ -2143,7 +2117,7 @@ test_type_empty(void **state) assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module bb {namespace urn:bb;prefix bb;typedef mytype {type empty; default x;}" "leaf l {type mytype;}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Invalid type \"mytype\" - \"empty\" type must not have a default value (x).", "/bb:l"); + CHECK_LOG_CTX("Invalid type \"mytype\" - \"empty\" type must not have a default value (x).", "Path \"/bb:l\"."); } static void @@ -2197,25 +2171,25 @@ test_type_union(void **state) /* invalid unions */ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa;typedef mytype {type union;}" "leaf l {type mytype;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Missing type substatement for union type mytype.", "/aa:l"); + CHECK_LOG_CTX("Missing type substatement for union type mytype.", "Path \"/aa:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module bb {namespace urn:bb;prefix bb;leaf l {type union;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Missing type substatement for union type.", "/bb:l"); + CHECK_LOG_CTX("Missing type substatement for union type.", "Path \"/bb:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module cc {namespace urn:cc;prefix cc;typedef mytype {type union{type int8; type string;}}" "leaf l {type mytype {type string;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid type substatement for the type not directly derived from union built-in type.", "/cc:l"); + CHECK_LOG_CTX("Invalid type substatement for the type not directly derived from union built-in type.", "Path \"/cc:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module dd {namespace urn:dd;prefix dd;typedef mytype {type union{type int8; type string;}}" "typedef mytype2 {type mytype {type string;}}leaf l {type mytype2;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid type substatement for the type \"mytype2\" not directly derived from union built-in type.", "/dd:l"); + CHECK_LOG_CTX("Invalid type substatement for the type \"mytype2\" not directly derived from union built-in type.", "Path \"/dd:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ee {namespace urn:ee;prefix ee;typedef mytype {type union{type mytype; type string;}}" "leaf l {type mytype;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid \"mytype\" type reference - circular chain of types detected.", "/ee:l"); + CHECK_LOG_CTX("Invalid \"mytype\" type reference - circular chain of types detected.", "Path \"/ee:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ef {namespace urn:ef;prefix ef;typedef mytype {type mytype2;}" "typedef mytype2 {type mytype;} leaf l {type mytype;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid \"mytype\" type reference - circular chain of types detected.", "/ef:l"); + CHECK_LOG_CTX("Invalid \"mytype\" type reference - circular chain of types detected.", "Path \"/ef:l\"."); } static void @@ -2304,20 +2278,173 @@ test_type_dflt(void **state) } static void +test_type_exts(void **state) +{ + const char *schema1, *schema2, *schema3, *schema4; + struct lys_module *mod; + const struct lysc_node *snode; + struct lysc_type *type; + struct lysc_type_union *type_u; + + schema1 = "module my-extensions {\n" + " namespace \"urn:my-extensions\";\n" + " prefix my-ext;\n" + "\n" + " extension shortdesc {\n" + " argument shortdsc;\n" + " }\n" + "}\n"; + schema2 = "module module-inet {\n" + " yang-version 1.1;\n" + " namespace \"urn:module-inet\";\n" + " prefix mod-inet;\n" + "\n" + " import ietf-inet-types {\n" + " prefix inet;\n" + " }\n" + "\n" + " import my-extensions {\n" + " prefix my-ext;\n" + " }\n" + "\n" + " typedef domain-name {\n" + " type inet:domain-name {\n" + " my-ext:shortdesc \"<host-name>\";\n" + " }\n" + " }\n" + "\n" + " typedef ipv4-address {\n" + " type inet:ipv4-address-no-zone {\n" + " my-ext:shortdesc \"<A.B.C.D>\";\n" + " }\n" + " }\n" + " typedef my-enum {\n" + " type enumeration {\n" + " enum one;\n" + " enum two;\n" + " enum three;\n" + " }\n" + " }\n" + "}\n"; + schema3 = "module module-a {\n" + " yang-version 1.1;\n" + " namespace \"urn:module-a\";\n" + " prefix mod-a;\n" + "\n" + " import module-inet {\n" + " prefix mod-inet;\n" + " }\n" + "\n" + " import my-extensions {\n" + " prefix my-ext;\n" + " }\n" + "\n" + " typedef server-address {\n" + " type union {\n" + " type mod-inet:ipv4-address {\n" + " my-ext:shortdesc \"<ipv4-address>\";\n" + " }\n" + " type mod-inet:domain-name {\n" + " my-ext:shortdesc \"<fqdn>\";\n" + " }\n" + " }\n" + " }\n" + "}\n"; + schema4 = "module main-module {\n" + " yang-version 1.1;\n" + " namespace \"urn:main-module\";\n" + " prefix main;\n" + "\n" + " import module-a {\n" + " prefix mod-a;\n" + " }\n" + "\n" + " import module-inet {\n" + " prefix mod-inet;\n" + " }\n" + "\n" + " import my-extensions {\n" + " prefix my-ext;\n" + " }\n" + "\n" + " container config {\n" + " leaf server {\n" + " type mod-a:server-address {\n" + " my-ext:shortdesc \"<server-address>\";\n" + " }\n" + " }\n" + "\n" + " leaf hostname {\n" + " type union {\n" + " type mod-inet:domain-name;\n" + " type string;\n" + " }\n" + " }\n" + " }\n" + "\n" + " leaf my-leaf {\n" + " type mod-inet:my-enum {\n" + " my-ext:shortdesc \"my enum\";\n" + " }\n" + " }\n" + "}\n"; + + assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, schema1, LYS_IN_YANG, NULL)); + assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, schema2, LYS_IN_YANG, NULL)); + assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, schema3, LYS_IN_YANG, NULL)); + assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, schema4, LYS_IN_YANG, &mod)); + + /* server */ + snode = lys_find_path(UTEST_LYCTX, NULL, "/main-module:config/server", 0); + assert_non_null(snode); + + type = ((struct lysc_node_leaf *)snode)->type; + assert_int_equal(LY_ARRAY_COUNT(type->exts), 1); + assert_string_equal(type->exts[0].argument, "<server-address>"); + type_u = (struct lysc_type_union *)type; + assert_int_equal(LY_ARRAY_COUNT(type_u->types), 2); + + type = type_u->types[0]; + assert_int_equal(LY_ARRAY_COUNT(type->exts), 2); + assert_string_equal(type->exts[0].argument, "<A.B.C.D>"); + assert_string_equal(type->exts[1].argument, "<ipv4-address>"); + + type = type_u->types[1]; + assert_int_equal(LY_ARRAY_COUNT(type->exts), 2); + assert_string_equal(type->exts[0].argument, "<host-name>"); + assert_string_equal(type->exts[1].argument, "<fqdn>"); + + /* hostname */ + snode = lys_find_path(UTEST_LYCTX, NULL, "/main-module:config/hostname", 0); + assert_non_null(snode); + type = ((struct lysc_node_leaf *)snode)->type; + assert_int_equal(LY_ARRAY_COUNT(type->exts), 0); + type_u = (struct lysc_type_union *)type; + assert_int_equal(LY_ARRAY_COUNT(type_u->types), 2); + + type = type_u->types[0]; + assert_int_equal(LY_ARRAY_COUNT(type->exts), 1); + assert_string_equal(type->exts[0].argument, "<host-name>"); + + type = type_u->types[1]; + assert_int_equal(LY_ARRAY_COUNT(type->exts), 0); +} + +static void test_status(void **state) { assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa;" "container c {status deprecated; leaf l {status current; type string;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Status \"current\" of \"l\" is in conflict with \"deprecated\" status of parent \"c\".", "/aa:c/l"); + CHECK_LOG_CTX("Status \"current\" of \"l\" is in conflict with \"deprecated\" status of parent \"c\".", "Path \"/aa:c/l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module bb {namespace urn:bb;prefix bb;" "container c {status obsolete; leaf l {status current; type string;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Status \"current\" of \"l\" is in conflict with \"obsolete\" status of parent \"c\".", "/bb:c/l"); + CHECK_LOG_CTX("Status \"current\" of \"l\" is in conflict with \"obsolete\" status of parent \"c\".", "Path \"/bb:c/l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module cc {namespace urn:cc;prefix cc;" "container c {status obsolete; leaf l {status deprecated; type string;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Status \"deprecated\" of \"l\" is in conflict with \"obsolete\" status of parent \"c\".", "/cc:c/l"); + CHECK_LOG_CTX("Status \"deprecated\" of \"l\" is in conflict with \"obsolete\" status of parent \"c\".", "Path \"/cc:c/l\"."); /* just a warning */ assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, "module cc {namespace urn:dd;prefix d;" @@ -2344,11 +2471,12 @@ test_grouping(void **state) /* invalid - error in a non-instantiated grouping */ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa;" "grouping grp {leaf x {type leafref;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Missing path substatement for leafref type.", "/aa:{grouping='grp'}/x"); - UTEST_LOG_CLEAN; + CHECK_LOG_CTX("Missing path substatement for leafref type.", "Path \"/aa:{grouping='grp'}/x\"."); + assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa;" "container a {grouping grp {leaf x {type leafref;}}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Missing path substatement for leafref type.", "/aa:a/{grouping='grp'}/x"); + CHECK_LOG_CTX("Missing path substatement for leafref type.", "Path \"/aa:a/{grouping='grp'}/x\"."); + CHECK_LOG_CTX("Locally scoped grouping \"grp\" not used.", NULL); /* config check */ ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, "module z1 {namespace urn:z1;prefix z1;" @@ -2519,52 +2647,53 @@ test_uses(void **state) /* invalid */ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa;uses missinggrp;}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Grouping \"missinggrp\" referenced by a uses statement not found.", "/aa:{uses='missinggrp'}"); + CHECK_LOG_CTX("Grouping \"missinggrp\" referenced by a uses statement not found.", "Path \"/aa:{uses='missinggrp'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module bb {namespace urn:bb;prefix bb;uses grp;" "grouping grp {leaf a{type string;}uses grp1;}" "grouping grp1 {leaf b {type string;}uses grp2;}" "grouping grp2 {leaf c {type string;}uses grp;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Grouping \"grp\" references itself through a uses statement.", "/bb:{uses='grp'}/{uses='grp1'}/{uses='grp2'}/{uses='grp'}"); + CHECK_LOG_CTX("Grouping \"grp\" references itself through a uses statement.", "Path \"/bb:{uses='grp'}/{uses='grp1'}/{uses='grp2'}/{uses='grp'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module cc {namespace urn:cc;prefix cc;uses a:missingprefix;}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid prefix used for grouping \"a:missingprefix\" reference.", "/cc:{uses='a:missingprefix'}"); + CHECK_LOG_CTX("Invalid prefix used for grouping \"a:missingprefix\" reference.", "Path \"/cc:{uses='a:missingprefix'}\"."); assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, "module dd {namespace urn:dd;prefix dd;grouping grp{leaf a{type string;}}" "leaf a {type string;}uses grp;}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Duplicate identifier \"a\" of data definition/RPC/action/notification statement.", "/dd:{uses='grp'}/dd:a"); + CHECK_LOG_CTX("Duplicate identifier \"a\" of data definition/RPC/action/notification statement.", "Path \"/dd:{uses='grp'}/dd:a\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ee {namespace urn:ee;prefix ee;grouping grp {leaf l {type string; status deprecated;}}" "uses grp {status obsolete;}}", LYS_IN_YANG, &mod)); CHECK_LOG_CTX("Inherited schema-only status \"obsolete\" is in conflict with \"deprecated\" status of \"l\".", - "/ee:{uses='grp'}/ee:l"); + "Path \"/ee:{uses='grp'}/ee:l\"."); assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, "module ff {namespace urn:ff;prefix ff;grouping grp {leaf l {type string;}}" "leaf l {type int8;}uses grp;}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Duplicate identifier \"l\" of data definition/RPC/action/notification statement.", "/ff:{uses='grp'}/ff:l"); + CHECK_LOG_CTX("Duplicate identifier \"l\" of data definition/RPC/action/notification statement.", "Path \"/ff:{uses='grp'}/ff:l\"."); assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, "module fg {namespace urn:fg;prefix fg;grouping grp {leaf m {type string;}}" "uses grp;leaf m {type int8;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Duplicate identifier \"m\" of data definition/RPC/action/notification statement.", "/fg:m"); + CHECK_LOG_CTX("Duplicate identifier \"m\" of data definition/RPC/action/notification statement.", "Path \"/fg:m\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module gg {namespace urn:gg;prefix gg; grouping grp {container g;}" "leaf g {type string;}" "container top {uses grp {augment /g {leaf x {type int8;}}}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid descendant-schema-nodeid value \"/g\" - name test expected instead of \"/\".", "/gg:top/{uses='grp'}/{augment='/g'}"); + CHECK_LOG_CTX("Invalid descendant-schema-nodeid value \"/g\" - name test expected instead of \"/\".", + "Path \"/gg:top/{uses='grp'}/{augment='/g'}\"."); assert_int_equal(LY_ENOTFOUND, lys_parse_mem(UTEST_LYCTX, "module hh {yang-version 1.1;namespace urn:hh;prefix hh;" "grouping grp {notification g { description \"super g\";}}" "container top {notification h; uses grp {refine h {description \"ultra h\";}}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Refine(s) target node \"h\" in grouping \"grp\" was not found.", "/hh:top/{uses='grp'}"); + CHECK_LOG_CTX("Refine(s) target node \"h\" in grouping \"grp\" was not found.", "Path \"/hh:top/{uses='grp'}\"."); assert_int_equal(LY_ENOTFOUND, lys_parse_mem(UTEST_LYCTX, "module ii {yang-version 1.1;namespace urn:ii;prefix ii;" "grouping grp {action g { description \"super g\";}}" "container top {action i; uses grp {refine i {description \"ultra i\";}}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Refine(s) target node \"i\" in grouping \"grp\" was not found.", "/ii:top/{uses='grp'}"); + CHECK_LOG_CTX("Refine(s) target node \"i\" in grouping \"grp\" was not found.", "Path \"/ii:top/{uses='grp'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module jj {yang-version 1.1;namespace urn:jj;prefix jj;" "grouping grp {leaf j { when \"1\"; type invalid;}}" "container top {uses grp;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Referenced type \"invalid\" not found.", "/jj:top/{uses='grp'}/j"); + CHECK_LOG_CTX("Referenced type \"invalid\" not found.", "Path \"/jj:top/{uses='grp'}/j\"."); } static void @@ -2666,65 +2795,71 @@ test_refine(void **state) /* invalid */ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa;import grp {prefix g;}" "uses g:grp {refine c {default hello;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid refine of container node - it is not possible to replace \"default\" property.", "/aa:{uses='g:grp'}/aa:c/{refine='c'}"); + CHECK_LOG_CTX("Invalid refine of container node - it is not possible to replace \"default\" property.", + "Path \"/aa:{uses='g:grp'}/aa:c/{refine='c'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module bb {namespace urn:bb;prefix bb;import grp {prefix g;}" "uses g:grp {refine c/l {default hello; default world;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid refine of leaf with too many (2) default properties.", "/bb:{uses='g:grp'}/bb:c/l/{refine='c/l'}"); + CHECK_LOG_CTX("Invalid refine of leaf with too many (2) default properties.", "Path \"/bb:{uses='g:grp'}/bb:c/l/{refine='c/l'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module cc {namespace urn:cc;prefix cc;import grp {prefix g;}" "uses g:grp {refine c/ll {default hello; default world;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid refine of default in leaf-list - the default statement is allowed only in YANG 1.1 modules.", "/cc:{uses='g:grp'}/cc:c/ll/{refine='c/ll'}"); + CHECK_LOG_CTX("Invalid refine of default in leaf-list - the default statement is allowed only in YANG 1.1 modules.", + "Path \"/cc:{uses='g:grp'}/cc:c/ll/{refine='c/ll'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module dd {namespace urn:dd;prefix dd;import grp {prefix g;}" "uses g:grp {refine c/ll {mandatory true;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid refine of leaf-list node - it is not possible to replace \"mandatory\" property.", "/dd:{uses='g:grp'}/dd:c/ll/{refine='c/ll'}"); + CHECK_LOG_CTX("Invalid refine of leaf-list node - it is not possible to replace \"mandatory\" property.", + "Path \"/dd:{uses='g:grp'}/dd:c/ll/{refine='c/ll'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ee {namespace urn:ee;prefix ee;import grp {prefix g;}" "uses g:grp {refine c/l {mandatory true;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "/ee:{uses='g:grp'}/ee:c/l", - "Invalid mandatory leaf with a default value.", "/ee:{uses='g:grp'}/ee:c/l"); + CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "Path \"/ee:{uses='g:grp'}/ee:c/l\"."); + CHECK_LOG_CTX("Invalid mandatory leaf with a default value.", "Path \"/ee:{uses='g:grp'}/ee:c/l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ef {namespace urn:ef;prefix ef;import grp {prefix g;}" "uses g:grp {refine c/ch {mandatory true;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "/ef:{uses='g:grp'}/ef:c/ch", - "Invalid mandatory choice with a default case.", "/ef:{uses='g:grp'}/ef:c/ch"); + CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "Path \"/ef:{uses='g:grp'}/ef:c/ch\"."); + CHECK_LOG_CTX("Invalid mandatory choice with a default case.", "Path \"/ef:{uses='g:grp'}/ef:c/ch\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ff {namespace urn:ff;prefix ff;import grp {prefix g;}" "uses g:grp {refine c/ch/ca/ca {mandatory true;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Mandatory node \"ca\" under the default case \"ca\".", "/ff:{uses='g:grp'}/ff:c/ch"); + CHECK_LOG_CTX("Mandatory node \"ca\" under the default case \"ca\".", "Path \"/ff:{uses='g:grp'}/ff:c/ch\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module gg {namespace urn:gg;prefix gg;import grp {prefix g;}" "uses g:grp {refine c/x {default hello;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "/gg:{uses='g:grp'}/gg:c/x", - "Invalid mandatory leaf with a default value.", "/gg:{uses='g:grp'}/gg:c/x"); + CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "Path \"/gg:{uses='g:grp'}/gg:c/x\"."); + CHECK_LOG_CTX("Invalid mandatory leaf with a default value.", "Path \"/gg:{uses='g:grp'}/gg:c/x\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module hh {namespace urn:hh;prefix hh;import grp {prefix g;}" "uses g:grp {refine c/c/l {config true;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "/hh:{uses='g:grp'}/hh:c/c/l", - "Configuration node cannot be child of any state data node.", "/hh:{uses='g:grp'}/hh:c/c/l"); + CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "Path \"/hh:{uses='g:grp'}/hh:c/c/l\"."); + CHECK_LOG_CTX("Configuration node cannot be child of any state data node.", "Path \"/hh:{uses='g:grp'}/hh:c/c/l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ii {namespace urn:ii;prefix ii;grouping grp {leaf l {type string; status deprecated;}}" "uses grp {status obsolete;}}", LYS_IN_YANG, &mod)); CHECK_LOG_CTX("Inherited schema-only status \"obsolete\" is in conflict with \"deprecated\" status of \"l\".", - "/ii:{uses='grp'}/ii:l"); + "Path \"/ii:{uses='grp'}/ii:l\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module jj {namespace urn:jj;prefix jj;import grp {prefix g;}" "uses g:grp {refine c/x {presence nonsence;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid refine of leaf node - it is not possible to replace \"presence\" property.", "/jj:{uses='g:grp'}/jj:c/x/{refine='c/x'}"); + CHECK_LOG_CTX("Invalid refine of leaf node - it is not possible to replace \"presence\" property.", + "Path \"/jj:{uses='g:grp'}/jj:c/x/{refine='c/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module kk {namespace urn:kk;prefix kk;import grp {prefix g;}" "uses g:grp {refine c/ch {must 1;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid refine of choice node - it is not possible to add \"must\" property.", "/kk:{uses='g:grp'}/kk:c/ch/{refine='c/ch'}"); + CHECK_LOG_CTX("Invalid refine of choice node - it is not possible to add \"must\" property.", + "Path \"/kk:{uses='g:grp'}/kk:c/ch/{refine='c/ch'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ll {namespace urn:ll;prefix ll;import grp {prefix g;}" "uses g:grp {refine c/x {min-elements 1;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid refine of leaf node - it is not possible to replace \"min-elements\" property.", "/ll:{uses='g:grp'}/ll:c/x/{refine='c/x'}"); + CHECK_LOG_CTX("Invalid refine of leaf node - it is not possible to replace \"min-elements\" property.", + "Path \"/ll:{uses='g:grp'}/ll:c/x/{refine='c/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module mm {namespace urn:mm;prefix mm;import grp {prefix g;}" "uses g:grp {refine c/ll {min-elements 1;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "/mm:{uses='g:grp'}/mm:c/ll", - "The default statement is present on leaf-list with a nonzero min-elements.", "/mm:{uses='g:grp'}/mm:c/ll"); + CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "Path \"/mm:{uses='g:grp'}/mm:c/ll\"."); + CHECK_LOG_CTX("The default statement is present on leaf-list with a nonzero min-elements.", "Path \"/mm:{uses='g:grp'}/mm:c/ll\"."); } static void @@ -2867,40 +3002,41 @@ test_augment(void **state) assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa; container c {leaf a {type string;}}" "augment /x/ {leaf a {type int8;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid absolute-schema-nodeid value \"/x/\" - unexpected end of expression.", "/aa:{augment='/x/'}"); + CHECK_LOG_CTX("Invalid absolute-schema-nodeid value \"/x/\" - unexpected end of expression.", "Path \"/aa:{augment='/x/'}\"."); assert_int_equal(LY_ENOTFOUND, lys_parse_mem(UTEST_LYCTX, "module aa {namespace urn:aa;prefix aa; container c {leaf a {type string;}}" "augment /x {leaf a {type int8;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Augment target node \"/x\" from module \"aa\" was not found.", "/aa:{augment='/x'}"); + CHECK_LOG_CTX("Augment target node \"/x\" from module \"aa\" was not found.", "Path \"/aa:{augment='/x'}\"."); assert_int_equal(LY_EEXIST, lys_parse_mem(UTEST_LYCTX, "module bb {namespace urn:bb;prefix bb; container c {leaf a {type string;}}" "augment /c {leaf a {type int8;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Duplicate identifier \"a\" of data definition/RPC/action/notification statement.", "/bb:{augment='/c'}/a"); + CHECK_LOG_CTX("Duplicate identifier \"a\" of data definition/RPC/action/notification statement.", "Path \"/bb:{augment='/c'}/a\"."); assert_int_equal(LY_ENOTFOUND, lys_parse_mem(UTEST_LYCTX, "module cc {namespace urn:cc;prefix cc; container c {leaf a {type string;}}" "augment /c/a {leaf a {type int8;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Augment target node \"/c/a\" from module \"cc\" was not found.", "/cc:{augment='/c/a'}"); + CHECK_LOG_CTX("Augment target node \"/c/a\" from module \"cc\" was not found.", "Path \"/cc:{augment='/c/a'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module dd {namespace urn:dd;prefix dd; container c {leaf a {type string;}}" "augment /c {case b {leaf d {type int8;}}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid augment of container node which is not allowed to contain case node \"b\".", "/dd:{augment='/c'}"); + CHECK_LOG_CTX("Invalid augment of container node which is not allowed to contain case node \"b\".", "Path \"/dd:{augment='/c'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ee {namespace urn:ee;prefix ee; import himp {prefix hi;}" "augment /hi:top {container c {leaf d {mandatory true; type int8;}}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid augment adding mandatory node \"c\" without making it conditional via when statement.", "/ee:{augment='/hi:top'}"); + CHECK_LOG_CTX("Invalid augment adding mandatory node \"c\" without making it conditional via when statement.", "Path \"/ee:{augment='/hi:top'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ff {namespace urn:ff;prefix ff; container top;" "augment ../top {leaf x {type int8;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid absolute-schema-nodeid value \"../top\" - \"/\" expected instead of \"..\".", "/ff:{augment='../top'}"); + CHECK_LOG_CTX("Invalid absolute-schema-nodeid value \"../top\" - \"/\" expected instead of \"..\".", "Path \"/ff:{augment='../top'}\"."); assert_int_equal(LY_ENOTFOUND, lys_parse_mem(UTEST_LYCTX, "module gg {namespace urn:gg;prefix gg; rpc func;" "augment /func {leaf x {type int8;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Augment target node \"/func\" from module \"gg\" was not found.", "/gg:{augment='/func'}"); + CHECK_LOG_CTX("Augment target node \"/func\" from module \"gg\" was not found.", "Path \"/gg:{augment='/func'}\"."); assert_int_equal(LY_ENOTFOUND, lys_parse_mem(UTEST_LYCTX, "module hh {namespace urn:hh;prefix hh;import himp {prefix hi;}" "augment /hi:func/input {leaf x {type string;}}" "augment /hi:func/output {leaf y {type string;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Augment target node \"/hi:func/input\" from module \"hh\" was not found.", "/hh:{augment='/hi:func/input'}"); + CHECK_LOG_CTX("Augment target node \"/hi:func/input\" from module \"hh\" was not found.", "Path \"/hh:{augment='/hi:func/input'}\"."); + CHECK_LOG_CTX("Augment target node \"/hi:func/output\" from module \"hh\" was not found.", "Path \"/hh:{augment='/hi:func/output'}\"."); } static void @@ -3104,18 +3240,18 @@ test_deviation(void **state) assert_true(node->flags & LYS_CONFIG_R); assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, "module l {namespace urn:l;prefix l; leaf a {config false; type string;}" - "container top {config false; leaf x {type string;}}" + "container top {leaf x {type string;}}" "deviation /a {deviate replace {config true;}}" - "deviation /top {deviate replace {config true;}}}", LYS_IN_YANG, &mod)); + "deviation /top {deviate replace {config false;}}}", LYS_IN_YANG, &mod)); assert_non_null(node = mod->compiled->data); assert_string_equal("a", node->name); assert_true(node->flags & LYS_CONFIG_W); assert_non_null(node = node->next); assert_string_equal("top", node->name); - assert_true(node->flags & LYS_CONFIG_W); + assert_true(node->flags & LYS_CONFIG_R); assert_non_null(node = lysc_node_child(node)); assert_string_equal("x", node->name); - assert_true(node->flags & LYS_CONFIG_W); + assert_true(node->flags & LYS_CONFIG_R); assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, "module m {namespace urn:m;prefix m;" "container a {leaf a {type string;}}" @@ -3356,194 +3492,199 @@ test_deviation(void **state) assert_int_equal(LY_ENOTFOUND, lys_parse_mem(UTEST_LYCTX, "module aa1 {namespace urn:aa1;prefix aa1;import a {prefix a;}" "deviation /a:top/a:z {deviate not-supported;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Deviation(s) target node \"/a:top/a:z\" from module \"aa1\" was not found.", "/a:{deviation='/a:top/a:z'}"); + CHECK_LOG_CTX("Deviation(s) target node \"/a:top/a:z\" from module \"aa1\" was not found.", "Path \"/a:{deviation='/a:top/a:z'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module aa2 {namespace urn:aa2;prefix aa2;import a {prefix a;}" "deviation /a:top/a:a {deviate not-supported;}" "deviation /a:top/a:a {deviate add {default error;}}}", LYS_IN_YANG, NULL)); - CHECK_LOG_CTX("Multiple deviations of \"/a:top/a:a\" with one of them being \"not-supported\".", "/aa2:{deviation='/a:top/a:a'}"); + CHECK_LOG_CTX("Multiple deviations of \"/a:top/a:a\" with one of them being \"not-supported\".", "Path \"/aa2:{deviation='/a:top/a:a'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module bb {namespace urn:bb;prefix bb;import a {prefix a;}" "deviation a:top/a:a {deviate not-supported;}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid absolute-schema-nodeid value \"a:top/a:a\" - \"/\" expected instead of \"a:top\".", "/bb:{deviation='a:top/a:a'}"); + CHECK_LOG_CTX("Invalid absolute-schema-nodeid value \"a:top/a:a\" - \"/\" expected instead of \"a:top\".", "Path \"/bb:{deviation='a:top/a:a'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module cc {namespace urn:cc;prefix cc; container c;" "deviation /c {deviate add {units meters;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation of container node - it is not possible to add \"units\" property.", "/cc:{deviation='/c'}"); + CHECK_LOG_CTX("Invalid deviation of container node - it is not possible to add \"units\" property.", "Path \"/cc:{deviation='/c'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module cd {namespace urn:cd;prefix cd; leaf c {type string; units centimeters;}" "deviation /c {deviate add {units meters;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation adding \"units\" property which already exists (with value \"centimeters\").", "/cd:{deviation='/c'}"); + CHECK_LOG_CTX("Invalid deviation adding \"units\" property which already exists (with value \"centimeters\").", "Path \"/cd:{deviation='/c'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module dd1 {namespace urn:dd1;prefix dd1; container c;" "deviation /c {deviate delete {units meters;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation of container node - it is not possible to delete \"units\" property.", "/dd1:{deviation='/c'}"); + CHECK_LOG_CTX("Invalid deviation of container node - it is not possible to delete \"units\" property.", "Path \"/dd1:{deviation='/c'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module dd2 {namespace urn:dd2;prefix dd2; leaf c {type string;}" "deviation /c {deviate delete {units meters;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation deleting \"units\" property \"meters\" which is not present.", "/dd2:{deviation='/c'}"); + CHECK_LOG_CTX("Invalid deviation deleting \"units\" property \"meters\" which is not present.", "Path \"/dd2:{deviation='/c'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module dd3 {namespace urn:dd3;prefix dd3; leaf c {type string; units centimeters;}" "deviation /c {deviate delete {units meters;}}}", LYS_IN_YANG, &mod)); CHECK_LOG_CTX("Invalid deviation deleting \"units\" property \"meters\" which does not match the target's property value \"centimeters\".", - "/dd3:{deviation='/c'}"); + "Path \"/dd3:{deviation='/c'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ee1 {namespace urn:ee1;prefix ee1; container c;" "deviation /c {deviate replace {units meters;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation of container node - it is not possible to replace \"units\" property.", "/ee1:{deviation='/c'}"); + CHECK_LOG_CTX("Invalid deviation of container node - it is not possible to replace \"units\" property.", "Path \"/ee1:{deviation='/c'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ee2 {namespace urn:ee2;prefix ee2; leaf c {type string;}" "deviation /c {deviate replace {units meters;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation replacing \"units\" property \"meters\" which is not present.", "/ee2:{deviation='/c'}"); + CHECK_LOG_CTX("Invalid deviation replacing \"units\" property \"meters\" which is not present.", "Path \"/ee2:{deviation='/c'}\"."); /* the default is already deleted in /e:a byt module f */ assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ff1 {namespace urn:ff1;prefix ff1; import e {prefix e;}" "deviation /e:a {deviate delete {default x:aa;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation deleting \"default\" property \"x:aa\" which is not present.", "/ff1:{deviation='/e:a'}"); + CHECK_LOG_CTX("Invalid deviation deleting \"default\" property \"x:aa\" which is not present.", "Path \"/ff1:{deviation='/e:a'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ff3 {namespace urn:ff3;prefix ff3; import e {prefix e;}" "deviation /e:b {deviate delete {default e:b;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation deleting \"default\" property \"e:b\" which does not match the target's property value \"x:ba\".", "/ff3:{deviation='/e:b'}"); + CHECK_LOG_CTX("Invalid deviation deleting \"default\" property \"e:b\" which does not match the target's property value \"x:ba\".", + "Path \"/ff3:{deviation='/e:b'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ff5 {namespace urn:ff5;prefix ff5; anyxml a;" "deviation /a {deviate delete {default x;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation of anyxml node - it is not possible to delete \"default\" property.", "/ff5:{deviation='/a'}"); + CHECK_LOG_CTX("Invalid deviation of anyxml node - it is not possible to delete \"default\" property.", "Path \"/ff5:{deviation='/a'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ff6 {namespace urn:ff6;prefix ff6; import e {prefix e;}" "deviation /e:c {deviate delete {default hi;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation deleting \"default\" property \"hi\" which does not match the target's property value \"hello\".", "/ff6:{deviation='/e:c'}"); + CHECK_LOG_CTX("Invalid deviation deleting \"default\" property \"hi\" which does not match the target's property value \"hello\".", + "Path \"/ff6:{deviation='/e:c'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ff7 {namespace urn:ff7;prefix ff7; import e {prefix e;}" "deviation /e:d {deviate delete {default hi;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation deleting \"default\" property \"hi\" which does not match any of the target's property values.", "/ff7:{deviation='/e:d'}"); + CHECK_LOG_CTX("Invalid deviation deleting \"default\" property \"hi\" which does not match any of the target's property values.", + "Path \"/ff7:{deviation='/e:d'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module gg1 {namespace urn:gg1;prefix gg1; import e {prefix e;}" "deviation /e:b {deviate add {default e:a;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation adding \"default\" property which already exists (with value \"x:ba\").", "/gg1:{deviation='/e:b'}"); + CHECK_LOG_CTX("Invalid deviation adding \"default\" property which already exists (with value \"x:ba\").", "Path \"/gg1:{deviation='/e:b'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module gg2 {namespace urn:gg2;prefix gg2; import e {prefix e;}" "deviation /e:a {deviate add {default x:a;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "/e:a", - "Default case prefix \"x\" not found in imports of \"gg2\".", "/e:a"); + CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "Path \"/e:a\"."); + CHECK_LOG_CTX("Default case prefix \"x\" not found in imports of \"gg2\".", "Path \"/e:a\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module gg3 {namespace urn:gg3;prefix gg3; import e {prefix e;}" "deviation /e:a {deviate add {default a;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "/e:a", - "Default case \"a\" not found.", "/e:a"); + CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "Path \"/e:a\"."); + CHECK_LOG_CTX("Default case \"a\" not found.", "Path \"/e:a\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module gg4 {namespace urn:gg4;prefix gg4; import e {prefix e;}" "deviation /e:c {deviate add {default hi;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation adding \"default\" property which already exists (with value \"hello\").", "/gg4:{deviation='/e:c'}"); + CHECK_LOG_CTX("Invalid deviation adding \"default\" property which already exists (with value \"hello\").", "Path \"/gg4:{deviation='/e:c'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module gg4 {namespace urn:gg4;prefix gg4; import e {prefix e;}" "deviation /e:a {deviate add {default e:ac;}}}", LYS_IN_YANG, &mod)); - /*CHECK_LOG_CTX("Invalid deviation adding \"default\" property \"e:ac\" of choice - mandatory node \"ac\" under the default case.", "/gg4:{deviation='/e:a'}");*/ - CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "/e:a"); + CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "Path \"/e:a\"."); + CHECK_LOG_CTX("Mandatory node \"ac\" under the default case \"e:ac\".", "Path \"/e:a\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module gg5 {namespace urn:gg5;prefix gg5; leaf x {type string; mandatory true;}" "deviation /x {deviate add {default error;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "/gg5:{deviation='/x'}", - "Invalid mandatory leaf with a default value.", "/gg5:{deviation='/x'}"); + CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "Path \"/gg5:{deviation='/x'}\"."); + CHECK_LOG_CTX("Invalid mandatory leaf with a default value.", "Path \"/gg5:{deviation='/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module hh1 {yang-version 1.1; namespace urn:hh1;prefix hh1; import e {prefix e;}" "deviation /e:d {deviate replace {default hi;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation of leaf-list node - it is not possible to replace \"default\" property.", "/hh1:{deviation='/e:d'}"); + CHECK_LOG_CTX("Invalid deviation of leaf-list node - it is not possible to replace \"default\" property.", + "Path \"/hh1:{deviation='/e:d'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ii1 {namespace urn:ii1;prefix ii1; import i {prefix i;}" "deviation /i:l1 {deviate delete {unique x;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation deleting \"unique\" property \"x\" which does not match any of the target's property values.", "/ii1:{deviation='/i:l1'}"); + CHECK_LOG_CTX("Invalid deviation deleting \"unique\" property \"x\" which does not match any of the target's property values.", + "Path \"/ii1:{deviation='/i:l1'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ii2 {namespace urn:ii2;prefix ii2; import i {prefix i;} leaf x { type string;}" "deviation /i:l2 {deviate delete {unique d;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation deleting \"unique\" property \"d\" which does not match any of the target's property values.", "/ii2:{deviation='/i:l2'}"); + CHECK_LOG_CTX("Invalid deviation deleting \"unique\" property \"d\" which does not match any of the target's property values.", + "Path \"/ii2:{deviation='/i:l2'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ii3 {namespace urn:ii3;prefix ii3; leaf x { type string;}" "deviation /x {deviate delete {unique d;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation of leaf node - it is not possible to delete \"unique\" property.", "/ii3:{deviation='/x'}"); + CHECK_LOG_CTX("Invalid deviation of leaf node - it is not possible to delete \"unique\" property.", "Path \"/ii3:{deviation='/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ii4 {namespace urn:ii4;prefix ii4; leaf x { type string;}" "deviation /x {deviate add {unique d;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation of leaf node - it is not possible to add \"unique\" property.", "/ii4:{deviation='/x'}"); + CHECK_LOG_CTX("Invalid deviation of leaf node - it is not possible to add \"unique\" property.", "Path \"/ii4:{deviation='/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module jj1 {namespace urn:jj1;prefix jj1; choice ch {case a {leaf a{type string;}}}" "deviation /ch/a {deviate add {config false;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation of case node - it is not possible to add \"config\" property.", "/jj1:{deviation='/ch/a'}"); + CHECK_LOG_CTX("Invalid deviation of case node - it is not possible to add \"config\" property.", "Path \"/jj1:{deviation='/ch/a'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module jj2 {namespace urn:jj2;prefix jj2; container top {config false; leaf x {type string;}}" "deviation /top/x {deviate add {config true;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "/jj2:{deviation='/top/x'}", - "Configuration node cannot be child of any state data node.", "/jj2:{deviation='/top/x'}"); - assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module jj3 {namespace urn:jj3;prefix jj3; container top {leaf x {type string;}}" - "deviation /top/x {deviate replace {config false;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation replacing \"config\" property \"config false\" which is not present.", "/jj3:{deviation='/top/x'}"); + CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "Path \"/jj2:{deviation='/top/x'}\"."); + CHECK_LOG_CTX("Configuration node cannot be child of any state data node.", "Path \"/jj2:{deviation='/top/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module jj4 {namespace urn:jj4;prefix jj4; choice ch {case a {leaf a{type string;}}}" "deviation /ch/a {deviate replace {config false;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation of case node - it is not possible to replace \"config\" property.", "/jj4:{deviation='/ch/a'}"); + CHECK_LOG_CTX("Invalid deviation of case node - it is not possible to replace \"config\" property.", "Path \"/jj4:{deviation='/ch/a'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module jj5 {namespace urn:jj5;prefix jj5; container top {leaf x {type string; config true;}}" "deviation /top {deviate add {config false;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "/jj5:top", - "Configuration node cannot be child of any state data node.", "/jj5:top/x"); + CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "Path \"/jj5:top\"."); + CHECK_LOG_CTX("Configuration node cannot be child of any state data node.", "Path \"/jj5:top/x\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module jj6 {namespace urn:jj6;prefix jj6; leaf x {config false; type string;}" "deviation /x {deviate add {config true;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation adding \"config\" property which already exists (with value \"config false\").", "/jj6:{deviation='/x'}"); + CHECK_LOG_CTX("Invalid deviation adding \"config\" property which already exists (with value \"config false\").", + "Path \"/jj6:{deviation='/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module kk1 {namespace urn:kk1;prefix kk1; container top {leaf a{type string;}}" "deviation /top {deviate add {mandatory true;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation of container node - it is not possible to add \"mandatory\" property.", "/kk1:{deviation='/top'}"); + CHECK_LOG_CTX("Invalid deviation of container node - it is not possible to add \"mandatory\" property.", "Path \"/kk1:{deviation='/top'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module kk2 {namespace urn:kk2;prefix kk2; container top {leaf a{type string;}}" "deviation /top {deviate replace {mandatory true;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation of container node - it is not possible to replace \"mandatory\" property.", "/kk2:{deviation='/top'}"); + CHECK_LOG_CTX("Invalid deviation of container node - it is not possible to replace \"mandatory\" property.", "Path \"/kk2:{deviation='/top'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module kk3 {namespace urn:kk3;prefix kk3; container top {leaf x {type string;}}" "deviation /top/x {deviate replace {mandatory true;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation replacing \"mandatory\" property \"mandatory true\" which is not present.", "/kk3:{deviation='/top/x'}"); + CHECK_LOG_CTX("Invalid deviation replacing \"mandatory\" property \"mandatory true\" which is not present.", "Path \"/kk3:{deviation='/top/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module kk4 {namespace urn:kk4;prefix kk4; leaf x {mandatory true; type string;}" "deviation /x {deviate add {mandatory false;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation adding \"mandatory\" property which already exists (with value \"mandatory true\").", "/kk4:{deviation='/x'}"); + CHECK_LOG_CTX("Invalid deviation adding \"mandatory\" property which already exists (with value \"mandatory true\").", + "Path \"/kk4:{deviation='/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ll1 {namespace urn:ll1;prefix ll1; leaf x {default test; type string;}" "deviation /x {deviate add {mandatory true;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "/ll1:{deviation='/x'}", - "Invalid mandatory leaf with a default value.", "/ll1:{deviation='/x'}"); + CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "Path \"/ll1:{deviation='/x'}\"."); + CHECK_LOG_CTX("Invalid mandatory leaf with a default value.", "Path \"/ll1:{deviation='/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ll2 {yang-version 1.1; namespace urn:ll2;prefix ll2; leaf-list x {default test; type string;}" "deviation /x {deviate add {min-elements 1;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "/ll2:{deviation='/x'}", - "The default statement is present on leaf-list with a nonzero min-elements.", "/ll2:{deviation='/x'}"); + CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "Path \"/ll2:{deviation='/x'}\"."); + CHECK_LOG_CTX("The default statement is present on leaf-list with a nonzero min-elements.", "Path \"/ll2:{deviation='/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module ll2 {namespace urn:ll2;prefix ll2; choice ch {default a; leaf a {type string;} leaf b {type string;}}" "deviation /ch {deviate add {mandatory true;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "/ll2:ch", - "Invalid mandatory choice with a default case.", "/ll2:ch"); + CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "Path \"/ll2:ch\"."); + CHECK_LOG_CTX("Invalid mandatory choice with a default case.", "Path \"/ll2:ch\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module mm1 {namespace urn:mm1;prefix mm1; leaf-list x {min-elements 10; type string;}" "deviation /x {deviate add {max-elements 5;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "/mm1:{deviation='/x'}", - "Leaf-list min-elements 10 is bigger than max-elements 5.", "/mm1:{deviation='/x'}"); + CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "Path \"/mm1:{deviation='/x'}\"."); + CHECK_LOG_CTX("Leaf-list min-elements 10 is bigger than max-elements 5.", "Path \"/mm1:{deviation='/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module mm2 {namespace urn:mm2;prefix mm2; leaf-list x {max-elements 10; type string;}" "deviation /x {deviate add {min-elements 20;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "/mm2:{deviation='/x'}", - "Leaf-list min-elements 20 is bigger than max-elements 10.", "/mm2:{deviation='/x'}"); + CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "Path \"/mm2:{deviation='/x'}\"."); + CHECK_LOG_CTX("Leaf-list min-elements 20 is bigger than max-elements 10.", "Path \"/mm2:{deviation='/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module mm3 {namespace urn:mm3;prefix mm3; list x {min-elements 5; max-elements 10; config false;}" "deviation /x {deviate replace {max-elements 1;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "/mm3:{deviation='/x'}", - "List min-elements 5 is bigger than max-elements 1.", "/mm3:{deviation='/x'}"); + CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "Path \"/mm3:{deviation='/x'}\"."); + CHECK_LOG_CTX("List min-elements 5 is bigger than max-elements 1.", "Path \"/mm3:{deviation='/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module mm4 {namespace urn:mm4;prefix mm4; list x {min-elements 5; max-elements 10; config false;}" "deviation /x {deviate replace {min-elements 20;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "/mm4:{deviation='/x'}", - "List min-elements 20 is bigger than max-elements 10.", "/mm4:{deviation='/x'}"); + CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "Path \"/mm4:{deviation='/x'}\"."); + CHECK_LOG_CTX("List min-elements 20 is bigger than max-elements 10.", "Path \"/mm4:{deviation='/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module mm5 {namespace urn:mm5;prefix mm5; leaf-list x {type string; min-elements 5;}" "deviation /x {deviate add {min-elements 1;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation adding \"min-elements\" property which already exists (with value \"5\").", "/mm5:{deviation='/x'}"); + CHECK_LOG_CTX("Invalid deviation adding \"min-elements\" property which already exists (with value \"5\").", "Path \"/mm5:{deviation='/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module mm6 {namespace urn:mm6;prefix mm6; list x {config false; min-elements 5;}" "deviation /x {deviate add {min-elements 1;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation adding \"min-elements\" property which already exists (with value \"5\").", "/mm6:{deviation='/x'}"); + CHECK_LOG_CTX("Invalid deviation adding \"min-elements\" property which already exists (with value \"5\").", "Path \"/mm6:{deviation='/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module mm7 {namespace urn:mm7;prefix mm7; leaf-list x {type string; max-elements 5;}" "deviation /x {deviate add {max-elements 1;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation adding \"max-elements\" property which already exists (with value \"5\").", "/mm7:{deviation='/x'}"); + CHECK_LOG_CTX("Invalid deviation adding \"max-elements\" property which already exists (with value \"5\").", "Path \"/mm7:{deviation='/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module mm8 {namespace urn:mm8;prefix mm8; list x {config false; max-elements 5;}" "deviation /x {deviate add {max-elements 1;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation adding \"max-elements\" property which already exists (with value \"5\").", "/mm8:{deviation='/x'}"); + CHECK_LOG_CTX("Invalid deviation adding \"max-elements\" property which already exists (with value \"5\").", "Path \"/mm8:{deviation='/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module mm9 {namespace urn:mm9;prefix mm9; leaf-list x {type string;}" "deviation /x {deviate replace {min-elements 1;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation replacing \"min-elements\" property which is not present.", "/mm9:{deviation='/x'}"); + CHECK_LOG_CTX("Invalid deviation replacing \"min-elements\" property which is not present.", "Path \"/mm9:{deviation='/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module mm10 {namespace urn:mm10;prefix mm10; list x {config false;}" "deviation /x {deviate replace {min-elements 1;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation replacing \"min-elements\" property which is not present.", "/mm10:{deviation='/x'}"); + CHECK_LOG_CTX("Invalid deviation replacing \"min-elements\" property which is not present.", "Path \"/mm10:{deviation='/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module mm11 {namespace urn:mm11;prefix mm11; leaf-list x {type string;}" "deviation /x {deviate replace {max-elements 1;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation replacing \"max-elements\" property which is not present.", "/mm11:{deviation='/x'}"); + CHECK_LOG_CTX("Invalid deviation replacing \"max-elements\" property which is not present.", "Path \"/mm11:{deviation='/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module mm12 {namespace urn:mm12;prefix mm12; list x {config false; }" "deviation /x {deviate replace {max-elements 1;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation replacing \"max-elements\" property which is not present.", "/mm12:{deviation='/x'}"); + CHECK_LOG_CTX("Invalid deviation replacing \"max-elements\" property which is not present.", "Path \"/mm12:{deviation='/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module nn1 {namespace urn:nn1;prefix nn1; anyxml x;" "deviation /x {deviate replace {type string;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Invalid deviation of anyxml node - it is not possible to replace \"type\" property.", "/nn1:{deviation='/x'}"); + CHECK_LOG_CTX("Invalid deviation of anyxml node - it is not possible to replace \"type\" property.", "Path \"/nn1:{deviation='/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module nn2 {namespace urn:nn2;prefix nn2; leaf-list x {type string;}" "deviation /x {deviate replace {type empty;}}}", LYS_IN_YANG, &mod)); - CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "/nn2:{deviation='/x'}", - "Leaf-list of type \"empty\" is allowed only in YANG 1.1 modules.", "/nn2:{deviation='/x'}"); + CHECK_LOG_CTX("Compilation of a deviated and/or refined node failed.", "Path \"/nn2:{deviation='/x'}\"."); + CHECK_LOG_CTX("Leaf-list of type \"empty\" is allowed only in YANG 1.1 modules.", "Path \"/nn2:{deviation='/x'}\"."); assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module oo1 {namespace urn:oo1;prefix oo1; leaf x {type uint16; default 300;}" "deviation /x {deviate replace {type uint8;}}}", LYS_IN_YANG, &mod)); @@ -3563,6 +3704,7 @@ test_deviation(void **state) assert_int_equal(LY_EVALID, lys_parse_mem(UTEST_LYCTX, "module pp1 {namespace urn:pp1;prefix pp1; import pp {prefix pp;}" "deviation /pp:c/pp:x {deviate not-supported;}}", LYS_IN_YANG, &mod)); CHECK_LOG_CTX("Target of leafref \"l\" cannot be referenced because it is disabled.", "Schema location \"/pp:l\"."); + CHECK_LOG_CTX("Not found node \"x\" in path.", "Schema location \"/pp:l\"."); } static void @@ -3755,6 +3897,55 @@ test_when(void **state) " }" "}", LYS_IN_YANG, NULL)); + + ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, + "module d1 {" + " namespace urn:d1;" + " prefix d1;" + " container ifm {" + " container interfaces {" + " list interface {" + " key \"name\";" + " leaf name {" + " type string;" + " }" + " container ethernet {" + " container main-interface {" + " container l2-attribute {" + " when \"not(/d1:ifm/d1:interfaces/d1:interface/d1:trunk/d1:members/d1:member[d1:name=current()/../../../d1:name])\";" + " presence \"\";" + " }" + " }" + " }" + " container trunk {" + " container members {" + " list member {" + " key \"name\";" + " leaf name {" + " type string;" + " }" + " }" + " }" + " }" + " }" + " }" + " }" + "}"); + assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, + "module d2 {" + " namespace \"urn:d2\";" + " prefix d2;" + " import d1 {" + " prefix d1;" + " }" + " augment \"/d1:ifm/d1:interfaces/d1:interface/d1:ethernet/d1:main-interface\" {" + " when \"not(d1:l2-attribute)\";" + " container extra-attribute {" + " presence \"\";" + " }" + " }" + "}", + LYS_IN_YANG, NULL)); } static void @@ -3796,6 +3987,68 @@ test_must(void **state) LYS_IN_YANG, NULL)); /* no warnings */ CHECK_LOG_CTX(NULL, NULL); + + /* must referencing disabled leafref in another module */ + ly_ctx_set_module_imp_clb(UTEST_LYCTX, test_imp_clb, + "module b-imp {" + " yang-version 1.1;" + " namespace \"urn:b-imp\";" + " prefix \"bi\";" + "" + " feature feat;" + "" + " grouping band-capabilities {" + " leaf band-number {" + " type uint16;" + " }" + "" + " container sub-band-info {" + " when \"../band-number = '46'\";" + " if-feature \"bi:feat\";" + " leaf number-of-laa-scarriers {" + " type uint8;" + " }" + " }" + " }" + "" + " container module-capability {" + " list band-capabilities {" + " key band-number;" + " config false;" + " uses band-capabilities;" + " }" + " container rw-sub-band-info {" + " if-feature \"bi:feat\";" + " leaf rw-number-of-laa-scarriers {" + " type leafref {" + " path \"/module-capability/band-capabilities/sub-band-info/number-of-laa-scarriers\";" + " require-instance false;" + " }" + " }" + " }" + " }" + "}"); + + ly_ctx_set_options(UTEST_LYCTX, LY_CTX_REF_IMPLEMENTED); + assert_int_equal(LY_SUCCESS, lys_parse_mem(UTEST_LYCTX, + "module b {" + " yang-version 1.1;" + " namespace \"urn:b\";" + " prefix \"b\";" + "" + " import b-imp {" + " prefix \"bi\";" + " }" + "" + " container laa-config {" + " must \"number-of-laa-scarriers <= /bi:module-capability/bi:rw-sub-band-info/bi:rw-number-of-laa-scarriers\";" + " }" + "}", + LYS_IN_YANG, NULL)); + ly_ctx_unset_options(UTEST_LYCTX, LY_CTX_REF_IMPLEMENTED); + + CHECK_LOG_CTX("Schema node \"number-of-laa-scarriers\" not found; in expr \"number-of-laa-scarriers\" " + "with context node \"/b:laa-config\".", NULL); } int @@ -3816,6 +4069,7 @@ main(void) UTEST(test_type_empty, setup), UTEST(test_type_union, setup), UTEST(test_type_dflt, setup), + UTEST(test_type_exts, setup), UTEST(test_status, setup), UTEST(test_node_container, setup), UTEST(test_node_leaflist, setup), diff --git a/tests/utests/schema/test_yang.c b/tests/utests/schema/test_yang.c index 78b1798..6d1c2ae 100644 --- a/tests/utests/schema/test_yang.c +++ b/tests/utests/schema/test_yang.c @@ -155,6 +155,7 @@ test_helpers(void **state) assert_int_equal(LY_EVALID, buf_store_char(YCTX, Y_IDENTIF_ARG, &p, &len, &buf, &size, 1, &prefix)); in.current = ":"; assert_int_equal(LY_EVALID, buf_store_char(YCTX, Y_IDENTIF_ARG, &p, &len, &buf, &size, 1, &prefix)); + UTEST_LOG_CTX_CLEAN; /* valid colon for prefixed identifiers */ len = size = 0; p = NULL; @@ -183,6 +184,7 @@ test_helpers(void **state) assert_int_equal(LY_SUCCESS, lysp_check_identifierchar((struct lysp_ctx *)YCTX, ':', 0, &prefix)); assert_int_equal(1, prefix); assert_int_equal(LY_EVALID, lysp_check_identifierchar((struct lysp_ctx *)YCTX, ':', 0, &prefix)); + CHECK_LOG_CTX("Invalid identifier first character ':' (0x003a).", "Line number 1."); assert_int_equal(1, prefix); assert_int_equal(LY_SUCCESS, lysp_check_identifierchar((struct lysp_ctx *)YCTX, 'b', 0, &prefix)); assert_int_equal(2, prefix); @@ -887,12 +889,18 @@ test_module(void **state) ly_ctx_set_module_imp_clb(PARSER_CUR_PMOD(YCTX)->mod->ctx, test_imp_clb, "module xxx { namespace urn:xxx; prefix x;}"); in.current = "module" SCHEMA_BEGINNING "include xxx;}"; assert_int_equal(lys_parse_mem(PARSER_CUR_PMOD(YCTX)->mod->ctx, in.current, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"name\" failed.", NULL, "Including \"xxx\" submodule into \"name\" failed.", NULL); + CHECK_LOG_CTX("Parsing module \"name\" failed.", NULL); + CHECK_LOG_CTX("Including \"xxx\" submodule into \"name\" failed.", NULL); + CHECK_LOG_CTX("Data model \"xxx\" not found in local searchdirs.", NULL); + CHECK_LOG_CTX("Parsing submodule failed.", NULL); + CHECK_LOG_CTX("Input data contains module in situation when a submodule is expected.", NULL); ly_ctx_set_module_imp_clb(PARSER_CUR_PMOD(YCTX)->mod->ctx, test_imp_clb, "submodule xxx {belongs-to wrong-name {prefix w;}}"); in.current = "module" SCHEMA_BEGINNING "include xxx;}"; assert_int_equal(lys_parse_mem(PARSER_CUR_PMOD(YCTX)->mod->ctx, in.current, LYS_IN_YANG, NULL), LY_EVALID); - CHECK_LOG_CTX("Parsing module \"name\" failed.", NULL, "Including \"xxx\" submodule into \"name\" failed.", NULL); + CHECK_LOG_CTX("Parsing module \"name\" failed.", NULL); + CHECK_LOG_CTX("Including \"xxx\" submodule into \"name\" failed.", NULL); + UTEST_LOG_CTX_CLEAN; ly_ctx_set_module_imp_clb(PARSER_CUR_PMOD(YCTX)->mod->ctx, test_imp_clb, "submodule xxx {belongs-to name {prefix x;}}"); TEST_GENERIC("include xxx;}", mod->includes, diff --git a/tests/utests/schema/test_yin.c b/tests/utests/schema/test_yin.c index 0ce3abc..a531b64 100644 --- a/tests/utests/schema/test_yin.c +++ b/tests/utests/schema/test_yin.c @@ -395,11 +395,13 @@ test_validate_value(void **state) YCTX->xmlctx->value = "pre:b"; YCTX->xmlctx->value_len = 5; assert_int_equal(yin_validate_value(YCTX, Y_IDENTIF_ARG), LY_EVALID); + CHECK_LOG_CTX("Invalid identifier character ':' (0x003a).", "Line number 1."); assert_int_equal(yin_validate_value(YCTX, Y_PREF_IDENTIF_ARG), LY_SUCCESS); YCTX->xmlctx->value = "pre:pre:b"; YCTX->xmlctx->value_len = 9; assert_int_equal(yin_validate_value(YCTX, Y_PREF_IDENTIF_ARG), LY_EVALID); + CHECK_LOG_CTX("Invalid identifier character ':' (0x003a).", "Line number 1."); } static void @@ -3097,7 +3099,7 @@ test_module_elem(void **state) assert_int_equal(yin_parse_mod(YCTX, lysp_mod), LY_SUCCESS); assert_string_equal(lysp_mod->mod->name, "mod"); - assert_string_equal(lysp_mod->revs, "2019-02-02"); + assert_string_equal(lysp_mod->revs[0].date, "2019-02-02"); assert_string_equal(lysp_mod->mod->ns, "ns"); assert_string_equal(lysp_mod->mod->prefix, "pref"); assert_null(lysp_mod->mod->filepath); @@ -3230,8 +3232,10 @@ test_submodule_elem(void **state) assert_int_equal(lyxml_ctx_new(UTEST_LYCTX, UTEST_IN, &YCTX->xmlctx), LY_SUCCESS); assert_int_equal(yin_parse_submod(YCTX, lysp_submod), LY_SUCCESS); + CHECK_LOG_CTX("YANG version 1.1 expects all includes in main module, includes in submodules (mod) are not necessary.", + NULL); assert_string_equal(lysp_submod->name, "mod"); - assert_string_equal(lysp_submod->revs, "2019-02-02"); + assert_string_equal(lysp_submod->revs[0].date, "2019-02-02"); assert_string_equal(lysp_submod->prefix, "pref"); assert_null(lysp_submod->filepath); assert_string_equal(lysp_submod->org, "org"); diff --git a/tests/utests/types/binary.c b/tests/utests/types/binary.c index 05b6b97..4f3ea66 100644 --- a/tests/utests/types/binary.c +++ b/tests/utests/types/binary.c @@ -52,14 +52,15 @@ test_plugin_store(void **state) struct lys_module *mod; struct lyd_value value = {0}; struct lyplg_type *type = lyplg_type_plugin_find("", NULL, ly_data_type2str[LY_TYPE_BINARY]); - struct lysc_type *lysc_type; + struct lysc_type *lysc_type, *lysc_type2; LY_ERR ly_ret; const char *schema; /* create schema. Prepare common used variables */ - schema = MODULE_CREATE_YANG("a", "leaf l {type binary;}"); + schema = MODULE_CREATE_YANG("a", "leaf l {type binary;} leaf k {type binary {length 4..8;}}"); UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, &mod); lysc_type = ((struct lysc_node_leaf *)mod->compiled->data)->type; + lysc_type2 = ((struct lysc_node_leaf *)mod->compiled->data->next)->type; /* check proper type */ assert_string_equal("libyang 2 - binary, version 1", type->id); @@ -176,6 +177,35 @@ test_plugin_store(void **state) assert_ptr_equal(value.realtype, lysc_type); type->free(UTEST_LYCTX, &value); + /* length check */ + val = "Zm91cg=="; + dec_val = "four"; + assert_int_equal(LY_SUCCESS, type->store(UTEST_LYCTX, lysc_type2, val, strlen(val), + 0, LY_VALUE_XML, NULL, LYD_VALHINT_STRING, NULL, &value, NULL, &err)); + CHECK_LYD_VALUE(value, BINARY, val, dec_val, strlen(dec_val)); + assert_ptr_equal(value.realtype, lysc_type2); + type->free(UTEST_LYCTX, &value); + + assert_int_equal(LY_SUCCESS, type->store(UTEST_LYCTX, lysc_type2, dec_val, strlen(dec_val), + 0, LY_VALUE_LYB, NULL, 0, NULL, &value, NULL, &err)); + CHECK_LYD_VALUE(value, BINARY, val, dec_val, strlen(dec_val)); + assert_ptr_equal(value.realtype, lysc_type2); + type->free(UTEST_LYCTX, &value); + + val = "ZWlnaHQwMTI="; + dec_val = "eight012"; + assert_int_equal(LY_SUCCESS, type->store(UTEST_LYCTX, lysc_type2, val, strlen(val), + 0, LY_VALUE_XML, NULL, LYD_VALHINT_STRING, NULL, &value, NULL, &err)); + CHECK_LYD_VALUE(value, BINARY, val, dec_val, strlen(dec_val)); + assert_ptr_equal(value.realtype, lysc_type2); + type->free(UTEST_LYCTX, &value); + + assert_int_equal(LY_SUCCESS, type->store(UTEST_LYCTX, lysc_type2, dec_val, strlen(dec_val), + 0, LY_VALUE_LYB, NULL, 0, NULL, &value, NULL, &err)); + CHECK_LYD_VALUE(value, BINARY, val, dec_val, strlen(dec_val)); + assert_ptr_equal(value.realtype, lysc_type2); + type->free(UTEST_LYCTX, &value); + /* * ERROR TESTS */ @@ -194,6 +224,14 @@ test_plugin_store(void **state) assert_int_equal(LY_EVALID, ly_ret); assert_string_equal(err->msg, "Base64 encoded value length must be divisible by 4."); ly_err_free(err); + + val = "MTIz"; + err = NULL; + ly_ret = type->store(UTEST_LYCTX, lysc_type2, val, strlen(val), + 0, LY_VALUE_XML, NULL, LYD_VALHINT_STRING, NULL, &value, NULL, &err); + assert_int_equal(LY_EVALID, ly_ret); + assert_string_equal(err->msg, "Unsatisfied length - string \"MTIz\" length is not allowed."); + ly_err_free(err); } static void diff --git a/tests/utests/types/bits.c b/tests/utests/types/bits.c index 3d42ebc..eb40965 100644 --- a/tests/utests/types/bits.c +++ b/tests/utests/types/bits.c @@ -179,7 +179,7 @@ test_schema_yang(void **state) "leaf port {type my_type {" " bit ten {position 11;} bit two;}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); - CHECK_LOG_CTX("Invalid bits - position of the item \"ten\" has changed from 10 to 11 in the derived type.", "/TERR_0:port"); + CHECK_LOG_CTX("Invalid bits - position of the item \"ten\" has changed from 10 to 11 in the derived type.", "Path \"/TERR_0:port\"."); /* add new bit */ schema = MODULE_CREATE_YANG("TERR_1", "typedef my_type{type bits {" @@ -187,15 +187,15 @@ test_schema_yang(void **state) "leaf port {type my_type {" " bit ten {position 10;} bit two; bit test;}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); - CHECK_LOG_CTX("Invalid bits - derived type adds new item \"test\".", "/TERR_1:port"); + CHECK_LOG_CTX("Invalid bits - derived type adds new item \"test\".", "Path \"/TERR_1:port\"."); /* different max value => autoadd index */ schema = MODULE_CREATE_YANG("TERR_2", "leaf port {type bits {" " bit first {position -1;} bit second;" "}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_2\" failed.", NULL, - "Invalid value \"-1\" of \"position\".", "Line number 5."); + CHECK_LOG_CTX("Parsing module \"TERR_2\" failed.", NULL); + CHECK_LOG_CTX("Invalid value \"-1\" of \"position\".", "Line number 5."); /* different max value => autoadd index */ schema = MODULE_CREATE_YANG("TERR_3", "leaf port {type bits {" @@ -203,48 +203,48 @@ test_schema_yang(void **state) "}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); CHECK_LOG_CTX("Invalid bits - it is not possible to auto-assign bit position for \"second\" since the highest value is already 4294967295.", - "/TERR_3:port"); + "Path \"/TERR_3:port\"."); schema = MODULE_CREATE_YANG("TERR_4", "leaf port {type bits {" " bit first {position 10;} bit \"\";" "}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_4\" failed.", NULL, - "Statement argument is required.", "Line number 5."); + CHECK_LOG_CTX("Parsing module \"TERR_4\" failed.", NULL); + CHECK_LOG_CTX("Statement argument is required.", "Line number 5."); /* wrong character */ schema = MODULE_CREATE_YANG("TERR_5", "leaf port {type bits {" " bit first {position 10;} bit abcd^;" "}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_5\" failed.", NULL, - "Invalid identifier character '^' (0x005e).", "Line number 5."); + CHECK_LOG_CTX("Parsing module \"TERR_5\" failed.", NULL); + CHECK_LOG_CTX("Invalid identifier character '^' (0x005e).", "Line number 5."); schema = MODULE_CREATE_YANG("TERR_6", "leaf port {type bits {" " bit hi; bit hi;" "}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_6\" failed.", NULL, - "Duplicate identifier \"hi\" of bit statement.", "Line number 5."); + CHECK_LOG_CTX("Parsing module \"TERR_6\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"hi\" of bit statement.", "Line number 5."); /* wrong character */ schema = MODULE_CREATE_YANG("TERR_7", "leaf port {type bits {" " bit first {position 10;} bit \"ab&cd\";" "}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_7\" failed.", NULL, - "Invalid identifier character '&' (0x0026).", "Line number 5."); + CHECK_LOG_CTX("Parsing module \"TERR_7\" failed.", NULL); + CHECK_LOG_CTX("Invalid identifier character '&' (0x0026).", "Line number 5."); schema = MODULE_CREATE_YANG("TERR_8", "leaf port {type bits {" " bit first {position 10;} bit \"4abcd\";" "}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_8\" failed.", NULL, - "Invalid identifier first character '4' (0x0034).", "Line number 5."); + CHECK_LOG_CTX("Parsing module \"TERR_8\" failed.", NULL); + CHECK_LOG_CTX("Invalid identifier first character '4' (0x0034).", "Line number 5."); schema = MODULE_CREATE_YANG("TERR_9", "leaf port {type bits;}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); - CHECK_LOG_CTX("Missing bit substatement for bits type.", "/TERR_9:port"); + CHECK_LOG_CTX("Missing bit substatement for bits type.", "Path \"/TERR_9:port\"."); /* new features of YANG 1.1 in YANG 1.0 */ schema = "module TERR_10 {" @@ -256,8 +256,8 @@ test_schema_yang(void **state) " }}" "}"; UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_10\" failed.", NULL, - "Invalid keyword \"if-feature\" as a child of \"bit\" - the statement is allowed only in YANG 1.1 modules.", + CHECK_LOG_CTX("Parsing module \"TERR_10\" failed.", NULL); + CHECK_LOG_CTX("Invalid keyword \"if-feature\" as a child of \"bit\" - the statement is allowed only in YANG 1.1 modules.", "Line number 1."); schema = "module TERR_11 {" @@ -267,7 +267,7 @@ test_schema_yang(void **state) " leaf l {type mytype {bit one;}}" "}"; UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); - CHECK_LOG_CTX("Bits type can be subtyped only in YANG 1.1 modules.", "/TERR_11:l"); + CHECK_LOG_CTX("Bits type can be subtyped only in YANG 1.1 modules.", "Path \"/TERR_11:l\"."); /* feature is not present */ schema = MODULE_CREATE_YANG("IF_0", "feature f;" @@ -415,7 +415,7 @@ test_schema_yin(void **state) " <bit name=\"ten\"> <position value=\"11\"/> </bit> <bit name=\"two\"/>" "</type></leaf>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); - CHECK_LOG_CTX("Invalid bits - position of the item \"ten\" has changed from 10 to 11 in the derived type.", "/TERR_0:port"); + CHECK_LOG_CTX("Invalid bits - position of the item \"ten\" has changed from 10 to 11 in the derived type.", "Path \"/TERR_0:port\"."); /* add new bit */ schema = MODULE_CREATE_YIN("TERR_1", @@ -431,7 +431,7 @@ test_schema_yin(void **state) " <bit name=\"test\"/>" "</type></leaf>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); - CHECK_LOG_CTX("Invalid bits - derived type adds new item \"test\".", "/TERR_1:port"); + CHECK_LOG_CTX("Invalid bits - derived type adds new item \"test\".", "Path \"/TERR_1:port\"."); /* different max value => autoadd index */ schema = MODULE_CREATE_YIN("TERR_2", @@ -440,8 +440,8 @@ test_schema_yin(void **state) " <bit name=\"second\">" "</type></leaf>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_2\" failed.", NULL, - "Invalid value \"-1\" of \"value\" attribute in \"position\" element.", "Line number 8."); + CHECK_LOG_CTX("Parsing module \"TERR_2\" failed.", NULL); + CHECK_LOG_CTX("Invalid value \"-1\" of \"value\" attribute in \"position\" element.", "Line number 8."); /* different max value => autoadd index */ schema = MODULE_CREATE_YIN("TERR_3", @@ -451,7 +451,7 @@ test_schema_yin(void **state) "</type></leaf>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); CHECK_LOG_CTX("Invalid bits - it is not possible to auto-assign bit position for \"second\" since the highest value is already 4294967295.", - "/TERR_3:port"); + "Path \"/TERR_3:port\"."); schema = MODULE_CREATE_YIN("TERR_4", "<leaf name=\"port\"> <type name=\"bits\">" @@ -459,9 +459,8 @@ test_schema_yin(void **state) " <bit name=\"second\"/>" "</type></leaf>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_4\" failed.", NULL, - "Invalid identifier first character ' ' (0x0020).", - "Line number 8."); + CHECK_LOG_CTX("Parsing module \"TERR_4\" failed.", NULL); + CHECK_LOG_CTX("Invalid identifier first character ' ' (0x0020).", "Line number 8."); schema = MODULE_CREATE_YIN("TERR_5", "<leaf name=\"port\"> <type name=\"bits\">" @@ -469,9 +468,8 @@ test_schema_yin(void **state) " <bit name=\"second\"/>" "</type></leaf>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_5\" failed.", NULL, - "Invalid identifier character ' ' (0x0020).", - "Line number 8."); + CHECK_LOG_CTX("Parsing module \"TERR_5\" failed.", NULL); + CHECK_LOG_CTX("Invalid identifier character ' ' (0x0020).", "Line number 8."); schema = MODULE_CREATE_YIN("TERR_6", "<leaf name=\"port\"> <type name=\"bits\">" @@ -479,9 +477,8 @@ test_schema_yin(void **state) " <bit name=\"hi\"/>" "</type></leaf>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_6\" failed.", NULL, - "Duplicate identifier \"hi\" of bit statement.", - "Line number 8."); + CHECK_LOG_CTX("Parsing module \"TERR_6\" failed.", NULL); + CHECK_LOG_CTX("Duplicate identifier \"hi\" of bit statement.", "Line number 8."); schema = MODULE_CREATE_YIN("TERR_7", "<leaf name=\"port\"> <type name=\"bits\">" @@ -489,9 +486,8 @@ test_schema_yin(void **state) " <bit name=\"second\"/>" "</type></leaf>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_7\" failed.", NULL, - "Invalid identifier first character '4' (0x0034).", - "Line number 8."); + CHECK_LOG_CTX("Parsing module \"TERR_7\" failed.", NULL); + CHECK_LOG_CTX("Invalid identifier first character '4' (0x0034).", "Line number 8."); /* TEST EMPTY NAME*/ schema = MODULE_CREATE_YIN("TERR_8", @@ -500,9 +496,8 @@ test_schema_yin(void **state) " <bit name=\"second\"/>" "</type></leaf>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); - CHECK_LOG_CTX("Parsing module \"TERR_8\" failed.", NULL, - "Empty identifier is not allowed.", - "Line number 8."); + CHECK_LOG_CTX("Parsing module \"TERR_8\" failed.", NULL); + CHECK_LOG_CTX("Empty identifier is not allowed.", "Line number 8."); } static void @@ -954,30 +949,6 @@ test_plugin_compare(void **state) assert_int_equal(LY_ENOT, type->compare(&diff_type_val, &(values[1]))); type->free(UTEST_LYCTX, &(diff_type_val)); - /* - * derivated type add some limitations - */ - diff_type_text = val_init[2]; - diff_type = ((struct lysc_node_leaf *)mod->compiled->data->next->next)->type; - ly_ret = diff_type->plugin->store(UTEST_LYCTX, diff_type, diff_type_text, strlen(diff_type_text), - 0, LY_VALUE_XML, NULL, LYD_VALHINT_STRING, NULL, &diff_type_val, NULL, &err); - assert_int_equal(LY_SUCCESS, ly_ret); - assert_int_equal(LY_ENOT, type->compare(&diff_type_val, &(values[2]))); - assert_int_equal(LY_ENOT, type->compare(&diff_type_val, &(values[1]))); - type->free(UTEST_LYCTX, &(diff_type_val)); - - /* - * different type (STRING) - */ - diff_type_text = val_init[2]; - diff_type = ((struct lysc_node_leaf *)mod->compiled->data->next->next->next)->type; - ly_ret = diff_type->plugin->store(UTEST_LYCTX, diff_type, diff_type_text, strlen(diff_type_text), - 0, LY_VALUE_XML, NULL, LYD_VALHINT_STRING, NULL, &diff_type_val, NULL, &err); - assert_int_equal(LY_SUCCESS, ly_ret); - assert_int_equal(LY_ENOT, type->compare(&diff_type_val, &(values[2]))); - assert_int_equal(LY_ENOT, type->compare(&diff_type_val, &(values[0]))); - type->free(UTEST_LYCTX, &(diff_type_val)); - /* delete values */ for (unsigned int it = 0; it < sizeof(val_init) / sizeof(val_init[0]); it++) { type->free(UTEST_LYCTX, &(values[it])); diff --git a/tests/utests/types/identityref.c b/tests/utests/types/identityref.c index cdfe057..107164d 100644 --- a/tests/utests/types/identityref.c +++ b/tests/utests/types/identityref.c @@ -27,15 +27,6 @@ NODES \ "}\n" -#define TEST_SUCCESS_XML(MOD_NAME, NAMESPACES, NODE_NAME, DATA, TYPE, ...) \ - { \ - struct lyd_node *tree; \ - const char *data = "<" NODE_NAME " xmlns=\"urn:tests:" MOD_NAME "\" " NAMESPACES ">" DATA "</" NODE_NAME ">"; \ - CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree); \ - CHECK_LYD_NODE_TERM((struct lyd_node_term *)tree, 0, 0, 0, 0, 1, TYPE, __VA_ARGS__); \ - lyd_free_all(tree); \ - } - #define TEST_ERROR_XML(MOD_NAME, NAMESPACES, NODE_NAME, DATA) \ {\ struct lyd_node *tree; \ @@ -64,21 +55,44 @@ static void test_data_xml(void **state) { const char *schema, *schema2; + struct lyd_node *tree; + const char *data; /* xml test */ - schema = MODULE_CREATE_YANG("ident-base", "identity ident-base;" - "identity ident-imp {base ident-base;}"); + schema = "module ident-base {" + " yang-version 1.1;" + " namespace \"urn:tests:ident-base\";" + " prefix ib;" + " identity ident-base;" + " identity ident-imp {base ident-base;}" + "}"; UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, NULL); - schema2 = MODULE_CREATE_YANG("defs", "import ident-base {prefix ib;}" - "identity ident1 {base ib:ident-base;}" - "leaf l1 {type identityref {base ib:ident-base;}}"); + schema2 = "module defs {" + " yang-version 1.1;" + " namespace \"urn:tests:defs\";" + " prefix d;" + " import ident-base {prefix ib;}" + " identity ident1 {base ib:ident-base;}" + " leaf l1 {type identityref {base ib:ident-base;}}" + "}"; UTEST_ADD_MODULE(schema2, LYS_IN_YANG, NULL, NULL); - TEST_SUCCESS_XML("defs", "", "l1", "ident1", IDENT, "defs:ident1", "ident1"); - - TEST_SUCCESS_XML("defs", "xmlns:i=\"urn:tests:ident-base\"", "l1", "i:ident-imp", IDENT, "ident-base:ident-imp", - "ident-imp"); + /* local ident, XML/JSON print */ + data = "<l1 xmlns=\"urn:tests:defs\">ident1</l1>"; + CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree); + CHECK_LYD_NODE_TERM((struct lyd_node_term *)tree, 0, 0, 0, 0, 1, IDENT, "defs:ident1", "ident1"); + CHECK_LYD_STRING_PARAM(tree, data, LYD_XML, LYD_PRINT_SHRINK); + CHECK_LYD_STRING_PARAM(tree, "{\"defs:l1\":\"ident1\"}", LYD_JSON, LYD_PRINT_SHRINK); + lyd_free_all(tree); + + /* foreign ident, XML/JSON print */ + data = "<l1 xmlns=\"urn:tests:defs\" xmlns:ib=\"urn:tests:ident-base\">ib:ident-imp</l1>"; + CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree); + CHECK_LYD_NODE_TERM((struct lyd_node_term *)tree, 0, 0, 0, 0, 1, IDENT, "ident-base:ident-imp", "ident-imp"); + CHECK_LYD_STRING_PARAM(tree, data, LYD_XML, LYD_PRINT_SHRINK); + CHECK_LYD_STRING_PARAM(tree, "{\"defs:l1\":\"ident-base:ident-imp\"}", LYD_JSON, LYD_PRINT_SHRINK); + lyd_free_all(tree); /* invalid value */ TEST_ERROR_XML("defs", "", "l1", "fast-ethernet"); diff --git a/tests/utests/types/instanceid.c b/tests/utests/types/instanceid.c index 06c8622..3126f61 100644 --- a/tests/utests/types/instanceid.c +++ b/tests/utests/types/instanceid.c @@ -79,12 +79,12 @@ static void test_data_xml(void **state) { const char *schema, *schema2; - const enum ly_path_pred_type val1[] = {LY_PATH_PREDTYPE_NONE, LY_PATH_PREDTYPE_NONE}; - const enum ly_path_pred_type val2[] = {LY_PATH_PREDTYPE_LIST, LY_PATH_PREDTYPE_NONE}; + const enum ly_path_pred_type val1[] = {0, 0}; + const enum ly_path_pred_type val2[] = {LY_PATH_PREDTYPE_LIST, 0}; const enum ly_path_pred_type val3[] = {LY_PATH_PREDTYPE_LEAFLIST}; - const enum ly_path_pred_type val4[] = {LY_PATH_PREDTYPE_LIST, LY_PATH_PREDTYPE_NONE}; - const enum ly_path_pred_type val5[] = {LY_PATH_PREDTYPE_LIST, LY_PATH_PREDTYPE_NONE}; - const enum ly_path_pred_type val6[] = {LY_PATH_PREDTYPE_LIST, LY_PATH_PREDTYPE_NONE}; + const enum ly_path_pred_type val4[] = {LY_PATH_PREDTYPE_LIST, 0}; + const enum ly_path_pred_type val5[] = {LY_PATH_PREDTYPE_LIST, 0}; + const enum ly_path_pred_type val6[] = {LY_PATH_PREDTYPE_LIST, 0}; /* xml test */ schema = MODULE_CREATE_YANG("mod", "container cont {leaf l2 {type empty;}}"); @@ -136,22 +136,23 @@ test_data_xml(void **state) TEST_ERROR_XML2("<list xmlns=\"urn:tests:defs\"><id>a</id></list>" "<list xmlns=\"urn:tests:defs\"><id>b</id><value>x</value></list>", "defs", "xmlns:xdf=\"urn:tests:defs\"", "xdf:l1", "/xdf:list[2]/xdf:value", LY_EVALID); - CHECK_LOG_CTX("Invalid instance-identifier \"/xdf:list[2]/xdf:value\" value - semantic error.", + CHECK_LOG_CTX("Invalid instance-identifier \"/xdf:list[2]/xdf:value\" value - semantic error: " + "Positional predicate defined for configuration list \"list\" in path.", "Schema location \"/defs:l1\", line number 1."); TEST_ERROR_XML2("", "defs", "xmlns:xdf=\"urn:tests:defs\"", "xdf:l1", "/t:cont/t:1l", LY_EVALID); - CHECK_LOG_CTX("Invalid instance-identifier \"/t:cont/t:1l\" value - syntax error.", + CHECK_LOG_CTX("Invalid instance-identifier \"/t:cont/t:1l\" value - syntax error: Invalid character 't'[9] of expression '/t:cont/t:1l'.", "Schema location \"/defs:l1\", line number 1."); TEST_ERROR_XML2("", "defs", "xmlns:xdf=\"urn:tests:defs\"", "xdf:l1", "/t:cont:t:1l", LY_EVALID); - CHECK_LOG_CTX("Invalid instance-identifier \"/t:cont:t:1l\" value - syntax error.", + CHECK_LOG_CTX("Invalid instance-identifier \"/t:cont:t:1l\" value - syntax error: Invalid character ':'[8] of expression '/t:cont:t:1l'.", "Schema location \"/defs:l1\", line number 1."); TEST_ERROR_XML2("", "defs", "xmlns:xdf=\"urn:tests:defs\"", "xdf:l1", "/xdf:cont/xdf:invalid/xdf:path", LY_EVALID); - CHECK_LOG_CTX("Invalid instance-identifier \"/xdf:cont/xdf:invalid/xdf:path\" value - semantic error.", + CHECK_LOG_CTX("Invalid instance-identifier \"/xdf:cont/xdf:invalid/xdf:path\" value - semantic error: Not found node \"invalid\" in path.", "Schema location \"/defs:l1\", line number 1."); /* non-existing instances, instance-identifier is here in JSON format because it is already in internal @@ -190,72 +191,72 @@ test_data_xml(void **state) /* more errors */ TEST_ERROR_XML2("<llist xmlns=\"urn:tests:defs\">x</llist>", "defs", "xmlns:t=\"urn:tests:defs\"", "t:l1", "/t:llist[1", LY_EVALID); - CHECK_LOG_CTX("Invalid instance-identifier \"/t:llist[1\" value - syntax error.", + CHECK_LOG_CTX("Invalid instance-identifier \"/t:llist[1\" value - syntax error: Unexpected XPath expression end.", "Schema location \"/defs:l1\", line number 1."); TEST_ERROR_XML2("<cont xmlns=\"urn:tests:mod\"/>", "defs", "xmlns:m=\"urn:tests:mod\"", "l1", "/m:cont[1]", LY_EVALID); - CHECK_LOG_CTX("Invalid instance-identifier \"/m:cont[1]\" value - semantic error.", + CHECK_LOG_CTX("Invalid instance-identifier \"/m:cont[1]\" value - semantic error: Positional predicate defined for container \"cont\" in path.", "Schema location \"/defs:l1\", line number 1."); TEST_ERROR_XML2("<cont xmlns=\"urn:tests:mod\"/>", "defs", "xmlns:m=\"urn:tests:mod\"", "l1", "[1]", LY_EVALID); - CHECK_LOG_CTX("Invalid instance-identifier \"[1]\" value - syntax error.", + CHECK_LOG_CTX("Invalid instance-identifier \"[1]\" value - syntax error: Unexpected XPath token \"[\" (\"[1]\"), expected \"Operator(Path)\".", "Schema location \"/defs:l1\", line number 1."); TEST_ERROR_XML2("<cont xmlns=\"urn:tests:mod\"><l2/></cont>", "defs", "xmlns:m=\"urn:tests:mod\"", "l1", "/m:cont/m:l2[l2='1']", LY_EVALID); - CHECK_LOG_CTX("Invalid instance-identifier \"/m:cont/m:l2[l2='1']\" value - syntax error.", + CHECK_LOG_CTX("Invalid instance-identifier \"/m:cont/m:l2[l2='1']\" value - syntax error: Prefix missing for \"l2\" in path.", "Schema location \"/defs:l1\", line number 1."); TEST_ERROR_XML2("<cont xmlns=\"urn:tests:mod\"><l2/></cont>", "defs", "xmlns:m=\"urn:tests:mod\"", "l1", "/m:cont/m:l2[m:l2='1']", LY_EVALID); - CHECK_LOG_CTX("Invalid instance-identifier \"/m:cont/m:l2[m:l2='1']\" value - semantic error.", + CHECK_LOG_CTX("Invalid instance-identifier \"/m:cont/m:l2[m:l2='1']\" value - semantic error: List predicate defined for leaf \"l2\" in path.", "Schema location \"/defs:l1\", line number 1."); TEST_ERROR_XML2("<llist xmlns=\"urn:tests:defs\">1</llist><llist xmlns=\"urn:tests:defs\">2</llist>", "defs", "xmlns:t=\"urn:tests:defs\"", "t:l1", "/t:llist[4]", LY_EVALID); - CHECK_LOG_CTX("Invalid instance-identifier \"/t:llist[4]\" value - semantic error.", + CHECK_LOG_CTX("Invalid instance-identifier \"/t:llist[4]\" value - semantic error: Positional predicate defined for configuration leaf-list \"llist\" in path.", "Schema location \"/defs:l1\", line number 1."); TEST_ERROR_XML2("", "defs", "xmlns:xdf=\"urn:tests:defs\"", "xdf:l2", "/t:llist[6]", LY_EVALID); - CHECK_LOG_CTX("Invalid instance-identifier \"/t:llist[6]\" value - semantic error.", + CHECK_LOG_CTX("Invalid instance-identifier \"/t:llist[6]\" value - semantic error: No module connected with the prefix \"t\" found (prefix format XML prefixes).", "Schema location \"/defs:l2\", line number 1."); TEST_ERROR_XML2("<list xmlns=\"urn:tests:defs\"><id>1</id><value>x</value></list>", "defs", "xmlns:xdf=\"urn:tests:defs\"", "xdf:l2", "/xdf:list[xdf:value='x']", LY_EVALID); - CHECK_LOG_CTX("Invalid instance-identifier \"/xdf:list[xdf:value='x']\" value - semantic error.", + CHECK_LOG_CTX("Invalid instance-identifier \"/xdf:list[xdf:value='x']\" value - semantic error: Key expected instead of leaf \"value\" in path.", "Schema location \"/defs:l2\", line number 1."); TEST_ERROR_XML2("", "defs", "xmlns:xdf=\"urn:tests:defs\"", "xdf:l2", "/xdf:list[.='x']", LY_EVALID); - CHECK_LOG_CTX("Invalid instance-identifier \"/xdf:list[.='x']\" value - semantic error.", + CHECK_LOG_CTX("Invalid instance-identifier \"/xdf:list[.='x']\" value - semantic error: Leaf-list predicate defined for list \"list\" in path.", "Schema location \"/defs:l2\", line number 1."); TEST_ERROR_XML2("<llist xmlns=\"urn:tests:defs\">1</llist>", "defs", "xmlns:t=\"urn:tests:defs\"", "t:l1", "/t:llist[.='x']", LY_EVALID); - CHECK_LOG_CTX("Invalid instance-identifier \"/t:llist[.='x']\" value - semantic error.", + CHECK_LOG_CTX("Invalid instance-identifier \"/t:llist[.='x']\" value - semantic error: Invalid type uint32 value \"x\".", "Schema location \"/defs:l1\", line number 1."); TEST_ERROR_XML2("", "defs", "xmlns:xdf=\"urn:tests:defs\"", "xdf:l2", "/t:llist[1][2]", LY_EVALID); - CHECK_LOG_CTX("Invalid instance-identifier \"/t:llist[1][2]\" value - syntax error.", + CHECK_LOG_CTX("Invalid instance-identifier \"/t:llist[1][2]\" value - syntax error: Unparsed characters \"[2]\" left at the end of path.", "Schema location \"/defs:l2\", line number 1."); TEST_ERROR_XML2("", "defs", "xmlns:xdf=\"urn:tests:defs\"", "xdf:l2", "/t:llist[.='a'][.='b']", LY_EVALID); - CHECK_LOG_CTX("Invalid instance-identifier \"/t:llist[.='a'][.='b']\" value - syntax error.", + CHECK_LOG_CTX("Invalid instance-identifier \"/t:llist[.='a'][.='b']\" value - syntax error: Unparsed characters \"[.='b']\" left at the end of path.", "Schema location \"/defs:l2\", line number 1."); TEST_ERROR_XML2("<list xmlns=\"urn:tests:defs\"><id>1</id><value>x</value></list>", "defs", "xmlns:xdf=\"urn:tests:defs\"", "xdf:l2", "/xdf:list[xdf:id='1'][xdf:id='2']/xdf:value", LY_EVALID); - CHECK_LOG_CTX("Invalid instance-identifier \"/xdf:list[xdf:id='1'][xdf:id='2']/xdf:value\" value - syntax error.", + CHECK_LOG_CTX("Invalid instance-identifier \"/xdf:list[xdf:id='1'][xdf:id='2']/xdf:value\" value - syntax error: Duplicate predicate key \"id\" in path.", "Schema location \"/defs:l2\", line number 1."); TEST_ERROR_XML2("", "defs", "xmlns:xdf=\"urn:tests:defs\"", "xdf:l2", "/xdf:list2[xdf:id='1']/xdf:value", LY_EVALID); - CHECK_LOG_CTX("Invalid instance-identifier \"/xdf:list2[xdf:id='1']/xdf:value\" value - semantic error.", + CHECK_LOG_CTX("Invalid instance-identifier \"/xdf:list2[xdf:id='1']/xdf:value\" value - semantic error: Predicate missing for a key of list \"list2\" in path.", "Schema location \"/defs:l2\", line number 1."); } diff --git a/tests/utests/types/int8.c b/tests/utests/types/int8.c index 7d0b9ad..198d1f7 100644 --- a/tests/utests/types/int8.c +++ b/tests/utests/types/int8.c @@ -240,22 +240,22 @@ test_schema_yang(void **state) /* TEST ERROR -60 .. 0 | 0 .. 127 */ schema = MODULE_CREATE_YANG("ERR0", "leaf port {type int8 {range \"-60 .. 0 | 0 .. 127\";}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EEXIST); - CHECK_LOG_CTX("Invalid range restriction - values are not in ascending order (0).", "/ERR0:port"); + CHECK_LOG_CTX("Invalid range restriction - values are not in ascending order (0).", "Path \"/ERR0:port\"."); /* TEST ERROR 0 .. 128 */ schema = MODULE_CREATE_YANG("ERR1", "leaf port {type int8 {range \"0 .. 128\";}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EDENIED); - CHECK_LOG_CTX("Invalid range restriction - value \"128\" does not fit the type limitations.", "/ERR1:port"); + CHECK_LOG_CTX("Invalid range restriction - value \"128\" does not fit the type limitations.", "Path \"/ERR1:port\"."); /* TEST ERROR -129 .. 126 */ schema = MODULE_CREATE_YANG("ERR2", "leaf port {type int8 {range \"-129 .. 0\";}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EDENIED); - CHECK_LOG_CTX("Invalid range restriction - value \"-129\" does not fit the type limitations.", "/ERR2:port"); + CHECK_LOG_CTX("Invalid range restriction - value \"-129\" does not fit the type limitations.", "Path \"/ERR2:port\"."); /* TEST ERROR 0 */ schema = MODULE_CREATE_YANG("ERR3", "leaf port {type int8 {range \"-129\";}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EDENIED); - CHECK_LOG_CTX("Invalid range restriction - value \"-129\" does not fit the type limitations.", "/ERR3:port"); + CHECK_LOG_CTX("Invalid range restriction - value \"-129\" does not fit the type limitations.", "Path \"/ERR3:port\"."); /* * TEST MODULE SUBTYPE @@ -374,7 +374,7 @@ test_schema_yang(void **state) "leaf my_leaf {type my_int_type {range \"min .. max\";}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); CHECK_LOG_CTX("Invalid range restriction - the derived restriction (min .. max) is not equally or more limiting.", - "/TS_ERR0:my_leaf"); + "Path \"/TS_ERR0:my_leaf\"."); /* TEST SUBTYPE ERROR -80 .. 80 */ schema = MODULE_CREATE_YANG("TS_ERR1", @@ -382,7 +382,7 @@ test_schema_yang(void **state) " leaf my_leaf {type my_int_type {range \"-80 .. 80\";}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); CHECK_LOG_CTX("Invalid range restriction - the derived restriction (-80 .. 80) is not equally or more limiting.", - "/TS_ERR1:my_leaf"); + "Path \"/TS_ERR1:my_leaf\"."); /* TEST SUBTYPE ERROR 0 .. max */ schema = MODULE_CREATE_YANG("TS_ERR2", @@ -390,7 +390,7 @@ test_schema_yang(void **state) "leaf my_leaf {type my_int_type {range \"0 .. max\";}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); CHECK_LOG_CTX("Invalid range restriction - the derived restriction (0 .. max) is not equally or more limiting.", - "/TS_ERR2:my_leaf"); + "Path \"/TS_ERR2:my_leaf\"."); /* TEST SUBTYPE ERROR -2 .. 2 */ schema = MODULE_CREATE_YANG("TS_ERR3", @@ -398,7 +398,7 @@ test_schema_yang(void **state) "leaf my_leaf {type my_int_type {range \"-2 .. 2\";}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); CHECK_LOG_CTX("Invalid range restriction - the derived restriction (-2 .. 2) is not equally or more limiting.", - "/TS_ERR3:my_leaf"); + "Path \"/TS_ERR3:my_leaf\"."); /* TEST SUBTYPE ERROR -2 .. 2 */ schema = MODULE_CREATE_YANG("TS_ERR4", @@ -406,7 +406,7 @@ test_schema_yang(void **state) "leaf my_leaf {type my_int_type {range \"-100 .. -90 | 100 .. 128\";}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EDENIED); CHECK_LOG_CTX("Invalid range restriction - value \"128\" does not fit the type limitations.", - "/TS_ERR4:my_leaf"); + "Path \"/TS_ERR4:my_leaf\"."); /* * TEST DEFAULT VALUE @@ -745,7 +745,7 @@ test_schema_yin(void **state) " <type name=\"int8\"> <range value = \"min .. 0 | 0 .. 12\"/> </type>" "</leaf>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EEXIST); - CHECK_LOG_CTX("Invalid range restriction - values are not in ascending order (0).", "/TE0:port"); + CHECK_LOG_CTX("Invalid range restriction - values are not in ascending order (0).", "Path \"/TE0:port\"."); /* TEST ERROR 0 .. 128 */ schema = MODULE_CREATE_YIN("TE1", @@ -753,7 +753,7 @@ test_schema_yin(void **state) " <type name=\"int8\"> <range value = \"0 .. 128\"/> </type>" "</leaf>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EDENIED); - CHECK_LOG_CTX("Invalid range restriction - value \"128\" does not fit the type limitations.", "/TE1:port"); + CHECK_LOG_CTX("Invalid range restriction - value \"128\" does not fit the type limitations.", "Path \"/TE1:port\"."); /* TEST ERROR -129 .. 126 */ schema = MODULE_CREATE_YIN("TE2", @@ -761,7 +761,7 @@ test_schema_yin(void **state) " <type name=\"int8\"> <range value =\"-129 .. 126\"/> </type>" "</leaf>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EDENIED); - CHECK_LOG_CTX("Invalid range restriction - value \"-129\" does not fit the type limitations.", "/TE2:port"); + CHECK_LOG_CTX("Invalid range restriction - value \"-129\" does not fit the type limitations.", "Path \"/TE2:port\"."); /* TEST YIN */ schema = MODULE_CREATE_YIN("TS0", @@ -817,7 +817,7 @@ test_schema_yin(void **state) "</leaf>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); CHECK_LOG_CTX("Invalid range restriction - the derived restriction (min .. max) is not equally or more limiting.", - "/TS_ERR1:port"); + "Path \"/TS_ERR1:port\"."); /* TEST ERROR */ schema = MODULE_CREATE_YIN("TS_ERR2", @@ -829,7 +829,7 @@ test_schema_yin(void **state) "</leaf>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); CHECK_LOG_CTX("Invalid range restriction - the derived restriction (5 .. 11) is not equally or more limiting.", - "/TS_ERR2:port"); + "Path \"/TS_ERR2:port\"."); /* TEST DEFAULT VALUE */ schema = MODULE_CREATE_YIN("DF0", @@ -1243,8 +1243,14 @@ test_data_json(void **state) TEST_SUCCESS_JSON("T0", "127", INT8, "127", 127); /* leading zeros */ TEST_ERROR_JSON("T0", "015"); + CHECK_LOG_CTX("Invalid character sequence \"15}\", expected a JSON object-end or next item.", + "Line number 1."); TEST_ERROR_JSON("T0", "-015"); + CHECK_LOG_CTX("Invalid character sequence \"15}\", expected a JSON object-end or next item.", + "Line number 1."); TEST_ERROR_JSON("defs", "+50"); + CHECK_LOG_CTX("Invalid character sequence \"+50}\", expected a JSON value.", + "Line number 1."); TEST_ERROR_JSON("T0", "-129"); CHECK_LOG_CTX("Value \"-129\" is out of type int8 min/max bounds.", "Schema location \"/T0:port\", line number 1."); @@ -1510,6 +1516,7 @@ test_plugin_store(void **state) ly_ret = type->store(UTEST_LYCTX, &lysc_type_test, val_text, strlen(val_text), 0, LY_VALUE_XML, NULL, LYD_VALHINT_HEXNUM, NULL, &value, NULL, &err); assert_int_equal(LY_EINT, ly_ret); + UTEST_LOG_CTX_CLEAN; /* * ERROR TESTS @@ -1541,6 +1548,8 @@ test_plugin_store(void **state) 0, LY_VALUE_XML, NULL, LYD_VALHINT_DECNUM, NULL, &value, NULL, &err); assert_int_equal(LY_EVALID, ly_ret); ly_err_free(err); + + UTEST_LOG_CTX_CLEAN; } static void diff --git a/tests/utests/types/leafref.c b/tests/utests/types/leafref.c index c8d0cb6..21e91cd 100644 --- a/tests/utests/types/leafref.c +++ b/tests/utests/types/leafref.c @@ -209,6 +209,70 @@ test_plugin_lyb(void **state) TEST_SUCCESS_LYB("lyb", "lst", "key_str", "lref", "key_str"); } +static void +test_data_xpath_json(void **state) +{ + const char *schema, *data; + struct lyd_node *tree; + + ly_ctx_set_options(UTEST_LYCTX, LY_CTX_LEAFREF_EXTENDED); + + /* json xpath test */ + schema = MODULE_CREATE_YANG("xp_test", + "list l1 {key t1;" + "leaf t1 {type uint8;}" + "list l2 {key t2;" + "leaf t2 {type uint8;}" + "leaf-list l3 {type uint8;}" + "}}" + "leaf r1 {type leafref {path \"../l1/t1\";}}" + "leaf r2 {type leafref {path \"deref(../r1)/../l2/t2\";}}" + "leaf r3 {type leafref {path \"deref(../r2)/../l3\";}}"); + + UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, NULL); + + data = "{" + " \"xp_test:l1\":[{\"t1\": 1,\"l2\":[{\"t2\": 2,\"l3\":[3]}]}]," + " \"xp_test:r1\": 1," + " \"xp_test:r2\": 2," + " \"xp_test:r3\": 3" + "}"; + CHECK_PARSE_LYD_PARAM(data, LYD_JSON, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree); + lyd_free_all(tree); +} + +static void +test_xpath_invalid_schema(void **state) +{ + const char *schema1, *schema2; + + ly_ctx_set_options(UTEST_LYCTX, LY_CTX_LEAFREF_EXTENDED); + schema1 = MODULE_CREATE_YANG("xp_test", + "list l1 {key t1;" + "leaf t1 {type uint8;}" + "list l2 {key t2;" + "leaf t2 {type uint8;}" + "leaf-list l3 {type uint8;}" + "}}" + "leaf r1 {type leafref {path \"deref(../l1)/../l2/t2\";}}"); + + UTEST_INVALID_MODULE(schema1, LYS_IN_YANG, NULL, LY_EVALID) + CHECK_LOG_CTX("The deref function target node \"l1\" is not leaf nor leaflist", "Schema location \"/xp_test:r1\"."); + + schema2 = MODULE_CREATE_YANG("xp_test", + "list l1 {key t1;" + "leaf t1 {type uint8;}" + "list l2 {key t2;" + "leaf t2 {type uint8;}" + "leaf-list l3 {type uint8;}" + "}}" + "leaf r1 {type uint8;}" + "leaf r2 {type leafref {path \"deref(../r1)/../l2/t2\";}}"); + + UTEST_INVALID_MODULE(schema2, LYS_IN_YANG, NULL, LY_EVALID) + CHECK_LOG_CTX("The deref function target node \"r1\" is not leafref", "Schema location \"/xp_test:r2\"."); +} + int main(void) { @@ -216,6 +280,8 @@ main(void) UTEST(test_data_xml), UTEST(test_data_json), UTEST(test_plugin_lyb), + UTEST(test_data_xpath_json), + UTEST(test_xpath_invalid_schema) }; return cmocka_run_group_tests(tests, NULL, NULL); diff --git a/tests/utests/types/string.c b/tests/utests/types/string.c index d232e9d..ce5ae8d 100644 --- a/tests/utests/types/string.c +++ b/tests/utests/types/string.c @@ -200,15 +200,15 @@ test_schema_yang(void **state) /* ERROR TESTS NEGATIVE VALUE */ schema = MODULE_CREATE_YANG("ERR0", "leaf port {type string {length \"-1 .. 20\";}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EDENIED); - CHECK_LOG_CTX("Invalid length restriction - value \"-1\" does not fit the type limitations.", "/ERR0:port"); + CHECK_LOG_CTX("Invalid length restriction - value \"-1\" does not fit the type limitations.", "Path \"/ERR0:port\"."); schema = MODULE_CREATE_YANG("ERR1", "leaf port {type string {length \"100 .. 18446744073709551616\";}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); - CHECK_LOG_CTX("Invalid length restriction - invalid value \"18446744073709551616\".", "/ERR1:port"); + CHECK_LOG_CTX("Invalid length restriction - invalid value \"18446744073709551616\".", "Path \"/ERR1:port\"."); schema = MODULE_CREATE_YANG("ERR2", "leaf port {type string {length \"10 .. 20 | 20 .. 30\";}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EEXIST); - CHECK_LOG_CTX("Invalid length restriction - values are not in ascending order (20).", "/ERR2:port"); + CHECK_LOG_CTX("Invalid length restriction - values are not in ascending order (20).", "Path \"/ERR2:port\"."); schema = MODULE_CREATE_YANG("ERR3", "typedef my_type {" @@ -216,7 +216,7 @@ test_schema_yang(void **state) "}" "leaf port {type my_type {length \"-1 .. 15\";}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EDENIED); - CHECK_LOG_CTX("Invalid length restriction - value \"-1\" does not fit the type limitations.", "/ERR3:port"); + CHECK_LOG_CTX("Invalid length restriction - value \"-1\" does not fit the type limitations.", "Path \"/ERR3:port\"."); /* * PATTERN @@ -286,7 +286,7 @@ test_schema_yang(void **state) "}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); CHECK_LOG_CTX("Regular expression \"[a-zA-Z_[a-zA-Z0-9\\-_.*\" is not valid (\"\": missing terminating ] for character class).", - "/TPATTERN_ERR_0:port"); + "Path \"/TPATTERN_ERR_0:port\"."); schema = MODULE_CREATE_YANG("TDEFAULT_0", "typedef my_type {" @@ -323,14 +323,14 @@ test_schema_yang(void **state) "}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); CHECK_LOG_CTX("Regular expression \"\\[a]b\" is not valid (\"]b\": character group doesn't begin with '[').", - "/TPATTERN_BC_ERR_1:port"); + "Path \"/TPATTERN_BC_ERR_1:port\"."); schema = MODULE_CREATE_YANG("TPATTERN_BC_ERR_2", "leaf port {type string {" "pattern \"\\\\[a]b\";" /* pattern "\\[a]b"; */ "}}"); UTEST_INVALID_MODULE(schema, LYS_IN_YANG, NULL, LY_EVALID); CHECK_LOG_CTX("Regular expression \"\\[a]b\" is not valid (\"]b\": character group doesn't begin with '[').", - "/TPATTERN_BC_ERR_2:port"); + "Path \"/TPATTERN_BC_ERR_2:port\"."); /* PATTERN AND LENGTH */ schema = MODULE_CREATE_YANG("TPL_0", @@ -467,26 +467,26 @@ test_schema_yin(void **state) schema = MODULE_CREATE_YIN("ERR0", "<leaf name=\"port\"> <type name=\"string\">" "<length value =\"-1 .. 20\"/> </type></leaf>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EDENIED); - CHECK_LOG_CTX("Invalid length restriction - value \"-1\" does not fit the type limitations.", "/ERR0:port"); + CHECK_LOG_CTX("Invalid length restriction - value \"-1\" does not fit the type limitations.", "Path \"/ERR0:port\"."); schema = MODULE_CREATE_YIN("ERR1", "<leaf name=\"port\"> <type name=\"string\">" "<length value=\"100 .. 18446744073709551616\"/>" "</type> </leaf>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); - CHECK_LOG_CTX("Invalid length restriction - invalid value \"18446744073709551616\".", "/ERR1:port"); + CHECK_LOG_CTX("Invalid length restriction - invalid value \"18446744073709551616\".", "Path \"/ERR1:port\"."); schema = MODULE_CREATE_YIN("ERR2", "<leaf name=\"port\">" "<type name=\"string\"> <length value=\"10 .. 20 | 20 .. 30\"/>" "</type> </leaf>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EEXIST); - CHECK_LOG_CTX("Invalid length restriction - values are not in ascending order (20).", "/ERR2:port"); + CHECK_LOG_CTX("Invalid length restriction - values are not in ascending order (20).", "Path \"/ERR2:port\"."); schema = MODULE_CREATE_YIN("ERR3", "<typedef name=\"my_type\"> <type name=\"string\"/> </typedef>" "<leaf name=\"port\"> <type name=\"my_type\"> <length value=\"-1 .. 15\"/>" "</type> </leaf>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EDENIED); - CHECK_LOG_CTX("Invalid length restriction - value \"-1\" does not fit the type limitations.", "/ERR3:port"); + CHECK_LOG_CTX("Invalid length restriction - value \"-1\" does not fit the type limitations.", "Path \"/ERR3:port\"."); /* * PATTERN @@ -557,7 +557,7 @@ test_schema_yin(void **state) "</type> </leaf>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); CHECK_LOG_CTX("Regular expression \"[a-zA-Z_][a-zA-Z0-9\\-_.*\" is not valid (\"\": missing terminating ] for character class).", - "/TPATTERN_ERR_0:port"); + "Path \"/TPATTERN_ERR_0:port\"."); /* * DEFAUT VALUE @@ -618,16 +618,8 @@ test_schema_yin(void **state) "</typedef>" "<leaf name=\"port\"> <type name=\"my_type\"/> </leaf>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); - - schema = MODULE_CREATE_YIN("TDEFAULT_2", - "<typedef name=\"my_type\">" - " <type name=\"string\">" - " <length value=\"2\"/>" - " </type>" - " <default value=\"a1i-j<\"/>" - "</typedef>" - "<leaf name=\"port\"> <type name=\"my_type\"/> </leaf>"); - UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); + CHECK_LOG_CTX("Invalid default - value does not fit the type (Unsatisfied length - string \"a1i-j<\" length is not allowed.).", + "Schema location \"/TDEFAULT_2:port\"."); schema = MODULE_CREATE_YIN("TDEFAULT_3", "<typedef name=\"my_type\">" @@ -636,6 +628,8 @@ test_schema_yin(void **state) "</type> </typedef>" "<leaf name=\"port\"><type name=\"my_type\"> <pattern value=\"bcd.*\"/> </type></leaf>"); UTEST_INVALID_MODULE(schema, LYS_IN_YIN, NULL, LY_EVALID); + CHECK_LOG_CTX("Invalid default - value does not fit the type (Unsatisfied pattern - \"a1i-j<\" does not conform to \"bcd.*\".).", + "Schema location \"/TDEFAULT_3:port\"."); } @@ -874,7 +868,7 @@ test_data_json(void **state) CHECK_LOG_CTX("Invalid character reference \"\\f\" (0x0000000c).", "Line number 1."); TEST_ERROR_JSON("T0", "\""); - CHECK_LOG_CTX("Unexpected character \"\"\" after JSON string.", "Line number 1."); + CHECK_LOG_CTX("Invalid character sequence \"\"}\", expected a JSON object-end or next item.", "Line number 1."); TEST_ERROR_JSON("T0", "aabb \\x"); CHECK_LOG_CTX("Invalid character escape sequence \\x.", "Line number 1."); @@ -1258,26 +1252,6 @@ test_plugin_compare(void **state) assert_int_equal(LY_ENOT, type->compare(&diff_type_val, &(values[1]))); type->free(UTEST_LYCTX, &(diff_type_val)); - /* original type */ - diff_type_text = "hi"; - diff_type = ((struct lysc_node_leaf *) mod->compiled->data->next->next)->type; - ly_ret = diff_type->plugin->store(UTEST_LYCTX, diff_type, diff_type_text, strlen(diff_type_text), - 0, LY_VALUE_XML, NULL, LYD_VALHINT_STRING, NULL, &diff_type_val, NULL, &err); - assert_int_equal(LY_SUCCESS, ly_ret); - assert_int_equal(LY_ENOT, type->compare(&diff_type_val, &(values[0]))); - assert_int_equal(LY_ENOT, type->compare(&diff_type_val, &(values[1]))); - type->free(UTEST_LYCTX, &(diff_type_val)); - - /* different type (UINT8) */ - diff_type_text = "20"; - diff_type = ((struct lysc_node_leaf *) mod->compiled->data->next->next->next)->type; - ly_ret = diff_type->plugin->store(UTEST_LYCTX, diff_type, diff_type_text, strlen(diff_type_text), - 0, LY_VALUE_XML, NULL, LYD_VALHINT_DECNUM, NULL, &diff_type_val, NULL, &err); - assert_int_equal(LY_SUCCESS, ly_ret); - assert_int_equal(LY_ENOT, type->compare(&diff_type_val, &(values[0]))); - assert_int_equal(LY_ENOT, type->compare(&diff_type_val, &(values[1]))); - type->free(UTEST_LYCTX, &(diff_type_val)); - /* delete values */ for (int unsigned it = 0; it < sizeof(val_init) / sizeof(val_init[0]); it++) { type->free(UTEST_LYCTX, &(values[it])); diff --git a/tests/utests/types/union.c b/tests/utests/types/union.c index 9a0705a..d23cbf1 100644 --- a/tests/utests/types/union.c +++ b/tests/utests/types/union.c @@ -104,11 +104,34 @@ test_data_xml(void **state) /* invalid value */ TEST_ERROR_XML2("", "defs", "", "un1", "123456789012345678901", LY_EVALID); - CHECK_LOG_CTX("Invalid union value \"123456789012345678901\" - no matching subtype found.", + CHECK_LOG_CTX("Invalid union value \"123456789012345678901\" - no matching subtype found:\n" + " libyang 2 - leafref, version 1: Invalid type int8 value \"123456789012345678901\".\n" + " libyang 2 - leafref, version 1: Invalid type int64 value \"123456789012345678901\".\n" + " libyang 2 - identityref, version 1: Invalid identityref \"123456789012345678901\" value - identity not found in module \"defs\".\n" + " libyang 2 - instance-identifier, version 1: Invalid instance-identifier \"123456789012345678901\" value - syntax error.\n" + " libyang 2 - string, version 1: Unsatisfied length - string \"123456789012345678901\" length is not allowed.\n", "Schema location \"/defs:un1\", line number 1."); } static void +test_data_json(void **state) +{ + const char *schema, *data; + struct lyd_node *tree; + + /* xml test */ + schema = MODULE_CREATE_YANG("defs", "leaf un21 {type union {type uint8; type string;}}" + "leaf un22 {type union {type uint16; type string;}}" + "leaf un2 {type union {type leafref {path /un21; require-instance false;} type leafref {path /un22; require-instance false;}}}"); + UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, NULL); + + data = "{\"defs:un2\":\"str\"}"; + CHECK_PARSE_LYD_PARAM(data, LYD_JSON, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree); + CHECK_LYD_STRING_PARAM(tree, data, LYD_JSON, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS); + lyd_free_all(tree); +} + +static void test_plugin_lyb(void **state) { const char *schema; @@ -124,12 +147,60 @@ test_plugin_lyb(void **state) TEST_SUCCESS_LYB("lyb", "un1", ""); } +static void +test_validation(void **state) +{ + const char *schema, *data; + struct lyd_node *tree; + char *out; + + schema = MODULE_CREATE_YANG("val", + "leaf l1 {\n" + " type union {\n" + " type uint32 {\n" + " range \"0..1048575\";\n" + " }\n" + " type enumeration {\n" + " enum auto;\n" + " }\n" + " }\n" + "}\n" + "leaf int8 {type int8 {range 10..20;}}\n" + "leaf l2 {\n" + " type union {\n" + " type leafref {path /int8; require-instance true;}\n" + " type string;\n" + " }\n" + "}\n"); + UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, NULL); + + /* parse from LYB */ + data = "<l1 xmlns=\"urn:tests:val\">auto</l1><int8 xmlns=\"urn:tests:val\">15</int8><l2 xmlns=\"urn:tests:val\">15</l2>"; + CHECK_PARSE_LYD_PARAM(data, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree); + assert_int_equal(LY_SUCCESS, lyd_print_mem(&out, tree, LYD_LYB, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS)); + lyd_free_all(tree); + CHECK_PARSE_LYD_PARAM(out, LYD_LYB, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree); + free(out); + + /* validate */ + assert_int_equal(LY_SUCCESS, lyd_validate_all(&tree, NULL, LYD_VALIDATE_PRESENT, NULL)); + + /* print and compare */ + assert_int_equal(LY_SUCCESS, lyd_print_mem(&out, tree, LYD_XML, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS)); + assert_string_equal(out, data); + + free(out); + lyd_free_all(tree); +} + int main(void) { const struct CMUnitTest tests[] = { UTEST(test_data_xml), + UTEST(test_data_json), UTEST(test_plugin_lyb), + UTEST(test_validation), }; return cmocka_run_group_tests(tests, NULL, NULL); diff --git a/tests/utests/types/yang_types.c b/tests/utests/types/yang_types.c index 6ce7671..0f78455 100644 --- a/tests/utests/types/yang_types.c +++ b/tests/utests/types/yang_types.c @@ -3,7 +3,7 @@ * @author Michal VaÅ¡ko <mvasko@cesnet.cz> * @brief test for ietf-yang-types values * - * Copyright (c) 2021 CESNET, z.s.p.o. + * Copyright (c) 2021 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -52,11 +52,11 @@ lyd_free_all(tree); \ } -#define TEST_ERROR_XML(MOD_NAME, NODE_NAME, DATA) \ +#define TEST_ERROR_XML(MOD_NAME, NODE_NAME, DATA, RET) \ {\ struct lyd_node *tree; \ const char *data = "<" NODE_NAME " xmlns=\"urn:tests:" MOD_NAME "\">" DATA "</" NODE_NAME ">"; \ - CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT, LY_EVALID, tree); \ + CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT, RET, tree); \ assert_null(tree); \ } @@ -84,14 +84,15 @@ test_data_xml(void **state) /* xml test */ schema = MODULE_CREATE_YANG("a", "leaf l {type yang:date-and-time;}" - "leaf l2 {type yang:xpath1.0;}"); + "leaf l21 {type yang:hex-string;}" + "leaf l22 {type yang:uuid;}" + "leaf l3 {type yang:xpath1.0;}"); UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, NULL); schema = MODULE_CREATE_YANG("b", ""); UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, NULL); /* date-and-time */ -#if defined (HAVE_TM_GMTOFF) && defined (HAVE_TIME_H_TIMEZONE) TEST_SUCCESS_XML("a", "l", "2005-05-25T23:15:15.88888Z", STRING, "2005-05-25T21:15:15.88888-02:00"); TEST_SUCCESS_XML("a", "l", "2005-05-31T23:15:15-08:59", STRING, "2005-06-01T06:14:15-02:00"); TEST_SUCCESS_XML("a", "l", "2005-05-31T23:15:15-23:00", STRING, "2005-06-01T20:15:15-02:00"); @@ -105,51 +106,44 @@ test_data_xml(void **state) /* fractional hours */ TEST_SUCCESS_XML("a", "l", "2005-05-25T23:15:15.88888+04:30", STRING, "2005-05-25T16:45:15.88888-02:00"); -#else - /* Tests run with a TZ offset of +02:00, but this platform cannot represent that in time_t, - * so libyang always returns unspecified TZ. */ - TEST_SUCCESS_XML("a", "l", "2005-05-25T23:15:15.88888Z", STRING, "2005-05-25T23:15:15.88888-00:00"); - TEST_SUCCESS_XML("a", "l", "2005-05-31T23:15:15-08:59", STRING, "2005-06-01T08:14:15-00:00"); - TEST_SUCCESS_XML("a", "l", "2005-05-31T23:15:15-23:00", STRING, "2005-06-01T22:15:15-00:00"); - - /* test 1 second before epoch (mktime returns -1, but it is a correct value), with and without DST */ - TEST_SUCCESS_XML("a", "l", "1970-01-01T00:59:59-02:00", STRING, "1970-01-01T02:59:59-00:00"); - TEST_SUCCESS_XML("a", "l", "1969-12-31T23:59:59-02:00", STRING, "1970-01-01T01:59:59-00:00"); - - /* canonize */ - TEST_SUCCESS_XML("a", "l", "2005-02-29T23:15:15-02:00", STRING, "2005-03-02T01:15:15-00:00"); - - /* fractional hours */ - TEST_SUCCESS_XML("a", "l", "2005-05-25T23:15:15.88888+04:30", STRING, "2005-05-25T18:45:15.88888-00:00"); -#endif /* unknown timezone -- timezone conversion MUST NOT happen */ TEST_SUCCESS_XML("a", "l", "2017-02-01T00:00:00-00:00", STRING, "2017-02-01T00:00:00-00:00"); TEST_SUCCESS_XML("a", "l", "2021-02-29T00:00:00-00:00", STRING, "2021-03-01T00:00:00-00:00"); - TEST_ERROR_XML("a", "l", "2005-05-31T23:15:15.-08:00"); + TEST_ERROR_XML("a", "l", "2005-05-31T23:15:15.-08:00", LY_EVALID); CHECK_LOG_CTX("Unsatisfied pattern - \"2005-05-31T23:15:15.-08:00\" does not conform to " "\"\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[\\+\\-]\\d{2}:\\d{2})\".", "Schema location \"/a:l\", line number 1."); + TEST_ERROR_XML("a", "l", "2023-16-15T20:13:01+01:00", LY_EINVAL); + CHECK_LOG_CTX("Invalid date-and-time month \"15\".", "Schema location \"/a:l\", line number 1."); + + TEST_ERROR_XML("a", "l", "2023-10-15T20:13:01+95:00", LY_EINVAL); + CHECK_LOG_CTX("Invalid date-and-time timezone hour \"95\".", "Schema location \"/a:l\", line number 1."); + + /* hex-string */ + TEST_SUCCESS_XML("a", "l21", "DB:BA:12:54:fa", STRING, "db:ba:12:54:fa"); + TEST_SUCCESS_XML("a", "l22", "f81D4fAE-7dec-11d0-A765-00a0c91E6BF6", STRING, "f81d4fae-7dec-11d0-a765-00a0c91e6bf6"); + /* xpath1.0 */ - TEST_SUCCESS_XML("a\" xmlns:aa=\"urn:tests:a", "l2", "/aa:l2[. = '4']", STRING, "/a:l2[.='4']"); + TEST_SUCCESS_XML("a\" xmlns:aa=\"urn:tests:a", "l3", "/aa:l3[. = '4']", STRING, "/a:l3[.='4']"); TEST_SUCCESS_XML("a\" xmlns:yl=\"urn:ietf:params:xml:ns:yang:ietf-yang-library\" " - "xmlns:ds=\"urn:ietf:params:xml:ns:yang:ietf-datastores", "l2", + "xmlns:ds=\"urn:ietf:params:xml:ns:yang:ietf-datastores", "l3", "/yl:yang-library/yl:datastore/yl:name = 'ds:running'", STRING, "/ietf-yang-library:yang-library/datastore/name='ietf-datastores:running'"); - TEST_SUCCESS_XML("a\" xmlns:a1=\"urn:tests:a\" xmlns:a2=\"urn:tests:a\" xmlns:bb=\"urn:tests:b", "l2", + TEST_SUCCESS_XML("a\" xmlns:a1=\"urn:tests:a\" xmlns:a2=\"urn:tests:a\" xmlns:bb=\"urn:tests:b", "l3", "/a1:node1/a2:node2[a1:node3/bb:node4]/bb:node5 | bb:node6 and (bb:node7)", STRING, "/a:node1/node2[node3/b:node4]/b:node5 | b:node6 and (b:node7)"); - TEST_SUCCESS_XML("a", "l2", "/l2[. = '4']", STRING, "/l2[.='4']"); - - TEST_ERROR_XML("a", "l2", "/a:l2[. = '4']"); - CHECK_LOG_CTX("Failed to resolve prefix \"a\".", "Schema location \"/a:l2\", line number 1."); - TEST_ERROR_XML("a\" xmlns:yl=\"urn:ietf:params:xml:ns:yang:ietf-yang-library", "l2", - "/yl:yang-library/yl:datastore/yl::name"); - CHECK_LOG_CTX("Storing value failed.", "Schema location \"/a:l2\", line number 1.", - "Invalid character 'y'[31] of expression '/yl:yang-library/yl:datastore/yl::name'.", - "Schema location \"/a:l2\", line number 1."); + TEST_SUCCESS_XML("a", "l3", "/l3[. = '4']", STRING, "/l3[.='4']"); + + TEST_ERROR_XML("a", "l3", "/a:l3[. = '4']", LY_EVALID); + CHECK_LOG_CTX("Failed to resolve prefix \"a\".", "Schema location \"/a:l3\", line number 1."); + TEST_ERROR_XML("a\" xmlns:yl=\"urn:ietf:params:xml:ns:yang:ietf-yang-library", "l3", + "/yl:yang-library/yl:datastore/yl::name", LY_EVALID); + CHECK_LOG_CTX("Storing value failed.", "Schema location \"/a:l3\", line number 1."); + CHECK_LOG_CTX("Invalid character 'y'[31] of expression '/yl:yang-library/yl:datastore/yl::name'.", + "Schema location \"/a:l3\", line number 1."); } static void @@ -191,6 +185,31 @@ test_print(void **state) } static void +test_duplicate(void **state) +{ + const char *schema = MODULE_CREATE_YANG("a", "leaf l1 {type yang:date-and-time;} leaf l2 {type yang:xpath1.0;}"); + const char *data, *expected; + struct lyd_node *tree, *dup; + + UTEST_ADD_MODULE(schema, LYS_IN_YANG, NULL, NULL); + + data = "<l1 xmlns=\"urn:tests:a\">2005-05-25T23:15:15.88888+04:30</l1>" + "<l2 xmlns=\"urn:tests:a\" xmlns:aa=\"urn:tests:a\">/aa:l2[. = '/aa:l2']</l2>"; + CHECK_PARSE_LYD_PARAM(data, LYD_XML, 0, LYD_VALIDATE_PRESENT, LY_SUCCESS, tree); + + /* duplicate */ + assert_int_equal(LY_SUCCESS, lyd_dup_siblings(tree, NULL, 0, &dup)); + + /* print */ + expected = "<l1 xmlns=\"urn:tests:a\">2005-05-25T16:45:15.88888-02:00</l1>" + "<l2 xmlns=\"urn:tests:a\" xmlns:pref=\"urn:tests:a\">/pref:l2[.='/pref:l2']</l2>"; + CHECK_LYD_STRING_PARAM(dup, expected, LYD_XML, LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS); + + lyd_free_siblings(tree); + lyd_free_siblings(dup); +} + +static void test_lyb(void **state) { const char *schema; @@ -216,6 +235,7 @@ main(void) const struct CMUnitTest tests[] = { UTEST(test_data_xml), UTEST(test_print), + UTEST(test_duplicate), UTEST(test_lyb), }; diff --git a/tests/utests/utests.h b/tests/utests/utests.h index 877d048..0e0649d 100644 --- a/tests/utests/utests.h +++ b/tests/utests/utests.h @@ -1010,7 +1010,9 @@ struct utest_context { LY_ARRAY_COUNT_TYPE arr_size = sizeof(VALUE) / sizeof(VALUE[0]); \ assert_int_equal(arr_size, LY_ARRAY_COUNT((NODE).target)); \ for (LY_ARRAY_COUNT_TYPE it = 0; it < arr_size; it++) { \ - assert_int_equal(VALUE[it], (NODE).target[it].pred_type); \ + if ((NODE).target[it].predicates) { \ + assert_int_equal(VALUE[it], (NODE).target[it].predicates[0].type); \ + } \ } \ } @@ -1224,97 +1226,32 @@ struct utest_context { _UC->in = NULL /** - * @brief Internal macro to compare error info record with the expected error message and path. - * If NULL is provided as MSG, no error info record (NULL) is expected. + * @brief Check expected last error message. * - * @param[in] ERR Error information record from libyang context. * @param[in] MSG Expected error message. - * @param[in] PATH Expected error path. - */ -#define _CHECK_LOG_CTX(ERR, MSG, PATH) \ - if (!MSG) { \ - assert_null(ERR); \ - } else { \ - assert_non_null(ERR); \ - CHECK_STRING((ERR)->msg, MSG); \ - CHECK_STRING((ERR)->path, PATH); \ - } - -/**` - * @brief Internal macro to check the last libyang's context error. - */ -#define _CHECK_LOG_CTX1(MSG, PATH) \ - _CHECK_LOG_CTX(ly_err_last(_UC->ctx), MSG, PATH) - -/** - * @brief Internal macro to check the last two libyang's context error. - */ -#define _CHECK_LOG_CTX2(MSG1, PATH1, MSG2, PATH2) \ - _CHECK_LOG_CTX(ly_err_last(_UC->ctx), MSG1, PATH1); \ - _CHECK_LOG_CTX(ly_err_last(_UC->ctx)->prev, MSG2, PATH2) - -/** - * @brief Internal macro to check the last three libyang's context error. - */ -#define _CHECK_LOG_CTX3(MSG1, PATH1, MSG2, PATH2, MSG3, PATH3) \ - _CHECK_LOG_CTX2(MSG1, PATH1, MSG2, PATH2); \ - _CHECK_LOG_CTX(ly_err_last(_UC->ctx)->prev->prev, MSG3, PATH3) - -/** - * @brief Internal macro to check the last three libyang's context error. - */ -#define _CHECK_LOG_CTX4(MSG1, PATH1, MSG2, PATH2, MSG3, PATH3, MSG4, PATH4) \ - _CHECK_LOG_CTX3(MSG1, PATH1, MSG2, PATH2, MSG3, PATH3); \ - _CHECK_LOG_CTX(ly_err_last(_UC->ctx)->prev->prev->prev, MSG4, PATH4) - -/** - * @brief Internal macro to check the last three libyang's context error. - */ -#define _CHECK_LOG_CTX5(MSG1, PATH1, MSG2, PATH2, MSG3, PATH3, MSG4, PATH4, MSG5, PATH5) \ - _CHECK_LOG_CTX4(MSG1, PATH1, MSG2, PATH2, MSG3, PATH3, MSG4, PATH4); \ - _CHECK_LOG_CTX(ly_err_last(_UC->ctx)->prev->prev->prev->prev, MSG5, PATH5) - -/** - * @brief Internal macro to check the last three libyang's context error. - */ -#define _CHECK_LOG_CTX6(MSG1, PATH1, MSG2, PATH2, MSG3, PATH3, MSG4, PATH4, MSG5, PATH5, MSG6, PATH6) \ - _CHECK_LOG_CTX5(MSG1, PATH1, MSG2, PATH2, MSG3, PATH3, MSG4, PATH4, MSG5, PATH5); \ - _CHECK_LOG_CTX(ly_err_last(_UC->ctx)->prev->prev->prev->prev->prev, MSG6, PATH6) - -/** - * @brief Internal macro to check the last three libyang's context error. - */ -#define _CHECK_LOG_CTX7(MSG1, PATH1, MSG2, PATH2, MSG3, PATH3, MSG4, PATH4, MSG5, PATH5, MSG6, PATH6, MSG7, PATH7) \ - _CHECK_LOG_CTX6(MSG1, PATH1, MSG2, PATH2, MSG3, PATH3, MSG4, PATH4, MSG5, PATH5, MSG6, PATH6); \ - _CHECK_LOG_CTX(ly_err_last(_UC->ctx)->prev->prev->prev->prev->prev->prev, MSG7, PATH7) - -/** - * @brief Internal macro to check the last three libyang's context error. */ -#define _CHECK_LOG_CTX8(MSG1, PATH1, MSG2, PATH2, MSG3, PATH3, MSG4, PATH4, MSG5, PATH5, MSG6, PATH6, MSG7, PATH7, MSG8, PATH8) \ - _CHECK_LOG_CTX7(MSG1, PATH1, MSG2, PATH2, MSG3, PATH3, MSG4, PATH4, MSG5, PATH5, MSG6, PATH6, MSG7, PATH7); \ - _CHECK_LOG_CTX(ly_err_last(_UC->ctx)->prev->prev->prev->prev->prev->prev->prev, MSG8, PATH8) +#define CHECK_LOG_LASTMSG(MSG) \ + CHECK_STRING(ly_last_errmsg(), MSG) /** - * @brief Internal helper macro to select _CHECK_LOG_CTX* macro according to the provided parameters. - */ -#define _GET_CHECK_LOG_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, NAME, ...) NAME - -/** - * @brief Check expected error(s) in libyang context. - * - * Macro has variadic parameters expected to be provided in pairs of error message and error path starting - * from the latest error. Current limit is checking at most 3 last errors. After checking, macro cleans up - * all the errors from the libyang context. + * @brief Check expected last error in libyang context, which is then cleared. Can be called repeatedly to check + * several errors. If NULL is provided as MSG, no error info record (NULL) is expected. * * @param[in] MSG Expected error message. * @param[in] PATH Expected error path. */ -#define CHECK_LOG_CTX(...) \ - _GET_CHECK_LOG_MACRO(__VA_ARGS__, _CHECK_LOG_CTX8, _INVAL, _CHECK_LOG_CTX7, _INVAL, \ - _CHECK_LOG_CTX6, _INVAL, _CHECK_LOG_CTX5, _INVAL, _CHECK_LOG_CTX4, _INVAL, \ - _CHECK_LOG_CTX3, _INVAL, _CHECK_LOG_CTX2, _INVAL, _CHECK_LOG_CTX1, DUMMY)(__VA_ARGS__); \ - ly_err_clean(_UC->ctx, NULL) +#define CHECK_LOG_CTX(MSG, PATH) \ + { \ + struct ly_err_item *_e = ly_err_last(_UC->ctx); \ + if (!MSG) { \ + assert_null(_e); \ + } else { \ + assert_non_null(_e); \ + CHECK_STRING(_e->msg, MSG); \ + CHECK_STRING(_e->path, PATH); \ + } \ + ly_err_clean(_UC->ctx, _e); \ + } /** * @brief Check expected error in libyang context including error-app-tag. @@ -1324,14 +1261,23 @@ struct utest_context { * @param[in] APPTAG Expected error-app-tag. */ #define CHECK_LOG_CTX_APPTAG(MSG, PATH, APPTAG) \ - if (!MSG) { \ - assert_null(ly_err_last(_UC->ctx)); \ - } else { \ - assert_non_null(ly_err_last(_UC->ctx)); \ - CHECK_STRING(ly_err_last(_UC->ctx)->msg, MSG); \ - CHECK_STRING(ly_err_last(_UC->ctx)->path, PATH); \ - CHECK_STRING(ly_err_last(_UC->ctx)->apptag, APPTAG); \ - } \ + { \ + struct ly_err_item *_e = ly_err_last(_UC->ctx); \ + if (!MSG) { \ + assert_null(_e); \ + } else { \ + assert_non_null(_e); \ + CHECK_STRING(_e->msg, MSG); \ + CHECK_STRING(_e->path, PATH); \ + CHECK_STRING(_e->apptag, APPTAG); \ + } \ + ly_err_clean(_UC->ctx, _e); \ + } + +/** + * @brief Clear all errors stored in the libyang context. + */ +#define UTEST_LOG_CTX_CLEAN \ ly_err_clean(_UC->ctx, NULL) /** @@ -1435,7 +1381,8 @@ utest_teardown(void **state) { *state = NULL; - /* libyang context */ + /* libyang context, no leftover messages */ + assert_null(ly_err_last(current_utest_context->ctx)); ly_ctx_destroy(current_utest_context->ctx); if (current_utest_context->orig_tz) { diff --git a/tests/yanglint/CMakeLists.txt b/tests/yanglint/CMakeLists.txt new file mode 100644 index 0000000..c1e081a --- /dev/null +++ b/tests/yanglint/CMakeLists.txt @@ -0,0 +1,36 @@ +if(WIN32) + set(YANGLINT_INTERACTIVE OFF) +else() + set(YANGLINT_INTERACTIVE ON) +endif() + +function(add_yanglint_test) + cmake_parse_arguments(ADDTEST "" "NAME;VIA;SCRIPT" "" ${ARGN}) + set(TEST_NAME yanglint_${ADDTEST_NAME}) + + if(${ADDTEST_VIA} STREQUAL "tclsh") + set(WRAPPER ${PATH_TCLSH}) + else() + message(FATAL_ERROR "build: unexpected wrapper '${ADDTEST_VIA}'") + endif() + + add_test(NAME ${TEST_NAME} COMMAND ${WRAPPER} ${CMAKE_CURRENT_SOURCE_DIR}/${ADDTEST_SCRIPT}) + set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "TESTS_DIR=${CMAKE_CURRENT_SOURCE_DIR}") + set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "YANG_MODULES_DIR=${CMAKE_CURRENT_SOURCE_DIR}/modules") + set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "YANGLINT=${PROJECT_BINARY_DIR}") +endfunction(add_yanglint_test) + +if(ENABLE_TESTS) + # tests of interactive mode using tclsh + find_program(PATH_TCLSH NAMES tclsh) + if(NOT PATH_TCLSH) + message(WARNING "'tclsh' not found! The yanglint(1) interactive tests will not be available.") + else() + if(YANGLINT_INTERACTIVE) + add_yanglint_test(NAME interactive VIA tclsh SCRIPT interactive/all.tcl) + add_yanglint_test(NAME non-interactive VIA tclsh SCRIPT non-interactive/all.tcl) + else() + add_yanglint_test(NAME non-interactive VIA tclsh SCRIPT non-interactive/all.tcl) + endif() + endif() +endif() diff --git a/tests/yanglint/README.md b/tests/yanglint/README.md new file mode 100644 index 0000000..6c51d89 --- /dev/null +++ b/tests/yanglint/README.md @@ -0,0 +1,107 @@ +# yanglint testing + +Testing yanglint is divided into two ways. +It is either tested in interactive mode using the tcl command 'expect' or non-interactively, classically from the command line. +For both modes, unit testing was used using the tcl package tcltest. + +## How to + +The sample commands in this chapter using `tclsh` are called in the `interactive` or `non-interactive` directories. + +### How to run all yanglint tests? + +In the build directory designated for cmake, enter: + +``` +ctest -R yanglint +``` + +### How to run all yanglint tests that are in interactive mode? + +In the interactive directory, run: + +``` +tclsh all.tcl +``` + +### How to run all yanglint tests that are in non-interactive mode? + +In the non-interactive directory, run: + +``` +tclsh all.tcl +``` + +### How to run all unit-tests from .test file? + +``` +tclsh clear.test +``` + +or alternatively: + +``` +tclsh all.tcl -file clear.test +``` + +### How to run one unit-test? + +``` +tclsh clear.test -match clear_ietf_yang_library +``` + +or alternatively: + +``` +tclsh all.tcl -file clear.test -match clear_ietf_yang_library +``` + +### How to run unit-tests for a certain yanglint command? + +Test names are assumed to consist of the command name: + +``` +tclsh all.tcl -match clear* +``` + +### How do I get more detailed information about 'expect' for a certain test? + +In the interactive directory, run: + +``` +tclsh clear.test -match clear_ietf_yang_library -load "exp_internal 1" +``` + +### How do I get more detailed dialog between 'expect' and yanglint for a certain test? + +In the interactive directory, run: + +``` +tclsh clear.test -match clear_ietf_yang_library -load "log_user 1" +``` + +### How do I suppress error message from tcltest? + +Probably only possible to do via `-verbose ""` + +### How can I also debug? + +You can write commands `interact` and `interpreter` from 'Expect' package into some test. +However, the most useful are the `exp_internal` and `log_user`, which can also be written directly into the test. +See also the rlwrap tool. +You can also use other debugging methods used in tcl programming. + +### Are the tests between interactive mode and non-interactive mode similar? + +Sort of... +- regex \n must be changed to \r\n in the tests for interactive yanglint + +### I would like to add a new "ly_" function. + +Add it to the ly.tcl file. +If you need to call other subfunctions in it, add them to namespace ly::private. + +### I would like to use function other than those prefixed with "ly_". + +Look in the common.tcl file in the "uti" namespace, +which contains general tcl functions that can be used in both interactive and non-interactive tests. diff --git a/tests/yanglint/common.tcl b/tests/yanglint/common.tcl new file mode 100644 index 0000000..d186282 --- /dev/null +++ b/tests/yanglint/common.tcl @@ -0,0 +1,114 @@ +# @brief Common functions and variables for yanglint-interactive and yanglint-non-interactive. +# +# The script requires variables: +# ::env(TESTS_DIR) - Main test directory. Must be set if the script is run via ctest. +# +# The script sets the variables: +# ::env(TESTS_DIR) - Main test directory. It is set by default if not defined. +# ::env(YANG_MODULES_DIR) - Directory of YANG modules. +# TUT_PATH - Assumed absolute path to the directory in which the TUT is located. +# TUT_NAME - TUT name (without path). +# ::tcltest::testConstraint ctest - A tcltest variable that is set to true if the script is run via ctest. Causes tests +# to be a skipped. + +package require tcltest +namespace import ::tcltest::test ::tcltest::cleanupTests + +# Set directory paths for testing yanglint. +if { ![info exists ::env(TESTS_DIR)] } { + # the script is not run via 'ctest' so paths must be set + set ::env(TESTS_DIR) "../" + set ::env(YANG_MODULES_DIR) "../modules" + set TUT_PATH "../../../build" + ::tcltest::testConstraint ctest false +} else { + # cmake (ctest) already sets ::env variables + set TUT_PATH $::env(YANGLINT) + ::tcltest::testConstraint ctest true +} + +set TUT_NAME "yanglint" + +# The script continues by defining functions specific to the yanglint tool. + +namespace eval uti { + namespace export * +} + +# Iterate through the items in the list 'lst' and return a new list where +# the items will have the form: <prefix><item><suffix>. +# Parameter 'index' determines at which index it will start wrapping. +# Parameter 'step' specifies how far the iterator must move to wrap the next item. +proc uti::wrap_list_items {lst {prefix ""} {suffix ""} {index 0} {step 1}} { + # counter to track when to insert wrapper + set cnt $step + set len [llength $lst] + + if {$index > 0} { + # copy list from interval <0;$index) + set ret [lrange $lst 0 [expr {$index - 1}]] + } else { + set ret {} + } + + for {set i $index} {$i < $len} {incr i} { + incr cnt + set item [lindex $lst $i] + if {$cnt >= $step} { + # insert wrapper for item + set cnt 0 + lappend ret [string cat $prefix $item $suffix] + } else { + # just copy item + lappend ret $item + } + } + + return $ret +} + +# Wrap list items with xml tags. +# The element format is: <tag>value</tag> +# Parameter 'values' is list of values. +# Parameter 'tag' is the name of the searched tag. +proc uti::wrap_to_xml {values tag {index 0} {step 1}} { + return [wrap_list_items $values "<$tag>" "</$tag>" $index $step] +} + +# Wrap list items with json attributes. +# The pair format is: "attribute": "value" +# Parameter 'values' is list of values. +# Parameter 'attribute' is the name of the searched attribute. +proc uti::wrap_to_json {values attribute {index 0} {step 1}} { + return [wrap_list_items $values "\"$attribute\": \"" "\"" $index $step] +} + +# Convert list to a regex (which is just a string) so that 'delim' is between items, +# 'begin' is at the beginning of the expression and 'end' is at the end. +proc uti::list_to_regex {lst {delim ".*"} {begin ".*"} {end ".*"}} { + return [string cat $begin [join $lst $delim] $end] +} + +# Merge two lists into one such that the nth items are merged into one separated by a delimiter. +# Returns a list that is the same length as 'lst1' and 'lst2' +proc uti::blend_lists {lst1 lst2 {delim ".*"}} { + return [lmap a $lst1 b $lst2 {string cat $a $delim $b}] +} + +# Create regex to find xml elements. +# The element format is: <tag>value</tag> +# Parameter 'values' is list of values. +# Parameter 'tag' is the name of the searched tag. +# The resulting expression looks like: ".*<tag>value1</tag>.*<tag>value2</tag>.*..." +proc uti::regex_xml_elements {values tag} { + return [list_to_regex [wrap_to_xml $values $tag]] +} + +# Create regex to find json pairs. +# The pair format is: "attribute": "value" +# Parameter 'values' is list of values. +# Parameter 'attribute' is the name of the searched attribute. +# The resulting expression looks like: ".*\"attribute\": \"value1\".*\"attribute\": \"value2\".*..." +proc uti::regex_json_pairs {values attribute} { + return [list_to_regex [wrap_to_json $values $attribute]] +} diff --git a/tests/yanglint/data/modaction.xml b/tests/yanglint/data/modaction.xml new file mode 100644 index 0000000..37faa2d --- /dev/null +++ b/tests/yanglint/data/modaction.xml @@ -0,0 +1,8 @@ +<con xmlns="urn:yanglint:modaction"> + <ls> + <lfkey>kv</lfkey> + <act> + <lfi>some_input</lfi> + </act> + </ls> +</con> diff --git a/tests/yanglint/data/modaction_ds.xml b/tests/yanglint/data/modaction_ds.xml new file mode 100644 index 0000000..a5a1727 --- /dev/null +++ b/tests/yanglint/data/modaction_ds.xml @@ -0,0 +1,5 @@ +<con xmlns="urn:yanglint:modaction"> + <ls> + <lfkey>kv</lfkey> + </ls> +</con> diff --git a/tests/yanglint/data/modaction_nc.xml b/tests/yanglint/data/modaction_nc.xml new file mode 100644 index 0000000..a74b6bf --- /dev/null +++ b/tests/yanglint/data/modaction_nc.xml @@ -0,0 +1,13 @@ +<rpc message-id="101" + xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> + <action xmlns="urn:ietf:params:xml:ns:yang:1"> + <con xmlns="urn:yanglint:modaction"> + <ls> + <lfkey>kv</lfkey> + <act> + <lfi>some_input</lfi> + </act> + </ls> + </con> + </action> +</rpc> diff --git a/tests/yanglint/data/modaction_reply.xml b/tests/yanglint/data/modaction_reply.xml new file mode 100644 index 0000000..7d6532d --- /dev/null +++ b/tests/yanglint/data/modaction_reply.xml @@ -0,0 +1,8 @@ +<con xmlns="urn:yanglint:modaction"> + <ls> + <lfkey>kv</lfkey> + <act> + <lfo>-56</lfo> + </act> + </ls> +</con> diff --git a/tests/yanglint/data/modaction_reply_nc.xml b/tests/yanglint/data/modaction_reply_nc.xml new file mode 100644 index 0000000..f7c3b8f --- /dev/null +++ b/tests/yanglint/data/modaction_reply_nc.xml @@ -0,0 +1,4 @@ +<rpc-reply message-id="101" + xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> + <lfo xmlns="urn:yanglint:modaction">-56</lfo> +</rpc-reply> diff --git a/tests/yanglint/data/modconfig.xml b/tests/yanglint/data/modconfig.xml new file mode 100644 index 0000000..f8a03a9 --- /dev/null +++ b/tests/yanglint/data/modconfig.xml @@ -0,0 +1,4 @@ +<mcc xmlns="urn:yanglint:modconfig"> + <lft>rw</lft> + <lff>ro</lff> +</mcc> diff --git a/tests/yanglint/data/modconfig2.xml b/tests/yanglint/data/modconfig2.xml new file mode 100644 index 0000000..c96e344 --- /dev/null +++ b/tests/yanglint/data/modconfig2.xml @@ -0,0 +1,3 @@ +<mcc xmlns="urn:yanglint:modconfig"> + <lft>rw</lft> +</mcc> diff --git a/tests/yanglint/data/modconfig_ctx.xml b/tests/yanglint/data/modconfig_ctx.xml new file mode 100644 index 0000000..124989c --- /dev/null +++ b/tests/yanglint/data/modconfig_ctx.xml @@ -0,0 +1,13 @@ +<yang-library xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library"> + <module-set> + <name>main-set</name> + <module> + <name>modconfig</name> + <namespace>urn:yanglint:modconfig</namespace> + </module> + </module-set> + <content-id>1</content-id> +</yang-library> +<modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library"> + <module-set-id>1</module-set-id> +</modules-state> diff --git a/tests/yanglint/data/moddatanodes.xml b/tests/yanglint/data/moddatanodes.xml new file mode 100644 index 0000000..8ae6e97 --- /dev/null +++ b/tests/yanglint/data/moddatanodes.xml @@ -0,0 +1,17 @@ +<dnc xmlns="urn:yanglint:moddatanodes"> + <lf>x</lf> + <lfl>1</lfl> + <lfl>2</lfl> + <con> + <lt> + <kalf>ka1</kalf> + <kblf>kb1</kblf> + <vlf>v1</vlf> + </lt> + <lt> + <kalf>ka2</kalf> + <kblf>kb2</kblf> + <vlf>v2</vlf> + </lt> + </con> +</dnc> diff --git a/tests/yanglint/data/moddefault.xml b/tests/yanglint/data/moddefault.xml new file mode 100644 index 0000000..00f3a9d --- /dev/null +++ b/tests/yanglint/data/moddefault.xml @@ -0,0 +1,4 @@ +<mdc xmlns="urn:yanglint:moddefault"> + <lf>0</lf> + <di>5</di> +</mdc> diff --git a/tests/yanglint/data/modimp_type_ctx.xml b/tests/yanglint/data/modimp_type_ctx.xml new file mode 100644 index 0000000..e6d158a --- /dev/null +++ b/tests/yanglint/data/modimp_type_ctx.xml @@ -0,0 +1,13 @@ +<yang-library xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library"> + <module-set> + <name>main-set</name> + <module> + <name>modimp-type</name> + <namespace>urn:yanglint:modimp-type</namespace> + </module> + </module-set> + <content-id>1</content-id> +</yang-library> +<modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library"> + <module-set-id>1</module-set-id> +</modules-state> diff --git a/tests/yanglint/data/modleaf.djson b/tests/yanglint/data/modleaf.djson new file mode 100644 index 0000000..25af218 --- /dev/null +++ b/tests/yanglint/data/modleaf.djson @@ -0,0 +1,3 @@ +{ + "modleaf:lfl": 7 +} diff --git a/tests/yanglint/data/modleaf.dxml b/tests/yanglint/data/modleaf.dxml new file mode 100644 index 0000000..408936a --- /dev/null +++ b/tests/yanglint/data/modleaf.dxml @@ -0,0 +1 @@ +<lfl xmlns="urn:yanglint:modleaf">7</lfl> diff --git a/tests/yanglint/data/modleaf.xml b/tests/yanglint/data/modleaf.xml new file mode 100644 index 0000000..408936a --- /dev/null +++ b/tests/yanglint/data/modleaf.xml @@ -0,0 +1 @@ +<lfl xmlns="urn:yanglint:modleaf">7</lfl> diff --git a/tests/yanglint/data/modleafref.xml b/tests/yanglint/data/modleafref.xml new file mode 100644 index 0000000..c9fb147 --- /dev/null +++ b/tests/yanglint/data/modleafref.xml @@ -0,0 +1,2 @@ +<lfl xmlns="urn:yanglint:modleaf">7</lfl> +<lfr xmlns="urn:yanglint:modleafref">7</lfr> diff --git a/tests/yanglint/data/modleafref2.xml b/tests/yanglint/data/modleafref2.xml new file mode 100644 index 0000000..3946daf --- /dev/null +++ b/tests/yanglint/data/modleafref2.xml @@ -0,0 +1,2 @@ +<lfl xmlns="urn:yanglint:modleaf">7</lfl> +<lfr xmlns="urn:yanglint:modleafref">10</lfr> diff --git a/tests/yanglint/data/modmandatory.xml b/tests/yanglint/data/modmandatory.xml new file mode 100644 index 0000000..108cb2a --- /dev/null +++ b/tests/yanglint/data/modmandatory.xml @@ -0,0 +1,3 @@ +<mmc xmlns="urn:yanglint:modmandatory"> + <lft>9</lft> +</mmc> diff --git a/tests/yanglint/data/modmandatory_invalid.xml b/tests/yanglint/data/modmandatory_invalid.xml new file mode 100644 index 0000000..de71895 --- /dev/null +++ b/tests/yanglint/data/modmandatory_invalid.xml @@ -0,0 +1,3 @@ +<mmc xmlns="urn:yanglint:modmandatory"> + <lff>9</lff> +</mmc> diff --git a/tests/yanglint/data/modmerge.xml b/tests/yanglint/data/modmerge.xml new file mode 100644 index 0000000..b52eff5 --- /dev/null +++ b/tests/yanglint/data/modmerge.xml @@ -0,0 +1,4 @@ +<mmc xmlns="urn:yanglint:modmerge"> + <en>one</en> + <lm>4</lm> +</mmc> diff --git a/tests/yanglint/data/modmerge2.xml b/tests/yanglint/data/modmerge2.xml new file mode 100644 index 0000000..e7f17c4 --- /dev/null +++ b/tests/yanglint/data/modmerge2.xml @@ -0,0 +1,3 @@ +<mmc xmlns="urn:yanglint:modmerge"> + <en>zero</en> +</mmc> diff --git a/tests/yanglint/data/modmerge3.xml b/tests/yanglint/data/modmerge3.xml new file mode 100644 index 0000000..6ef857e --- /dev/null +++ b/tests/yanglint/data/modmerge3.xml @@ -0,0 +1,3 @@ +<mmc xmlns="urn:yanglint:modmerge"> + <lf>str</lf> +</mmc> diff --git a/tests/yanglint/data/modnotif.xml b/tests/yanglint/data/modnotif.xml new file mode 100644 index 0000000..81cab21 --- /dev/null +++ b/tests/yanglint/data/modnotif.xml @@ -0,0 +1,5 @@ +<con xmlns="urn:yanglint:modnotif"> + <nfn> + <lf>nested</lf> + </nfn> +</con> diff --git a/tests/yanglint/data/modnotif2.xml b/tests/yanglint/data/modnotif2.xml new file mode 100644 index 0000000..fc75b57 --- /dev/null +++ b/tests/yanglint/data/modnotif2.xml @@ -0,0 +1,3 @@ +<nfg xmlns="urn:yanglint:modnotif"> + <lf>top</lf> +</nfg> diff --git a/tests/yanglint/data/modnotif2_nc.xml b/tests/yanglint/data/modnotif2_nc.xml new file mode 100644 index 0000000..c87cfa0 --- /dev/null +++ b/tests/yanglint/data/modnotif2_nc.xml @@ -0,0 +1,6 @@ +<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"> + <eventTime>2010-12-06T08:00:01Z</eventTime> + <nfg xmlns="urn:yanglint:modnotif"> + <lf>top</lf> + </nfg> +</notification> diff --git a/tests/yanglint/data/modnotif_ds.xml b/tests/yanglint/data/modnotif_ds.xml new file mode 100644 index 0000000..efd835b --- /dev/null +++ b/tests/yanglint/data/modnotif_ds.xml @@ -0,0 +1 @@ +<con xmlns="urn:yanglint:modnotif"></con> diff --git a/tests/yanglint/data/modnotif_nc.xml b/tests/yanglint/data/modnotif_nc.xml new file mode 100644 index 0000000..39a3440 --- /dev/null +++ b/tests/yanglint/data/modnotif_nc.xml @@ -0,0 +1,8 @@ +<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"> + <eventTime>2010-12-06T08:00:01Z</eventTime> + <con xmlns="urn:yanglint:modnotif"> + <nfn> + <lf>nested</lf> + </nfn> + </con> +</notification> diff --git a/tests/yanglint/data/modoper_leafref_action.xml b/tests/yanglint/data/modoper_leafref_action.xml new file mode 100644 index 0000000..7ccf29f --- /dev/null +++ b/tests/yanglint/data/modoper_leafref_action.xml @@ -0,0 +1,8 @@ +<cond xmlns="urn:yanglint:modoper-leafref"> + <list> + <klf>key_val</klf> + <act> + <lfi>rw</lfi> + </act> + </list> +</cond> diff --git a/tests/yanglint/data/modoper_leafref_action_reply.xml b/tests/yanglint/data/modoper_leafref_action_reply.xml new file mode 100644 index 0000000..39ec672 --- /dev/null +++ b/tests/yanglint/data/modoper_leafref_action_reply.xml @@ -0,0 +1,8 @@ +<cond xmlns="urn:yanglint:modoper-leafref"> + <list> + <klf>key_val</klf> + <act> + <lfo>rw</lfo> + </act> + </list> +</cond> diff --git a/tests/yanglint/data/modoper_leafref_ds.xml b/tests/yanglint/data/modoper_leafref_ds.xml new file mode 100644 index 0000000..f934b9b --- /dev/null +++ b/tests/yanglint/data/modoper_leafref_ds.xml @@ -0,0 +1,9 @@ +<mcc xmlns="urn:yanglint:modconfig"> + <lft>rw</lft> + <lff>ro</lff> +</mcc> +<cond xmlns="urn:yanglint:modoper-leafref"> + <list> + <klf>key_val</klf> + </list> +</cond> diff --git a/tests/yanglint/data/modoper_leafref_notif.xml b/tests/yanglint/data/modoper_leafref_notif.xml new file mode 100644 index 0000000..2c56b67 --- /dev/null +++ b/tests/yanglint/data/modoper_leafref_notif.xml @@ -0,0 +1,3 @@ +<notifg xmlns="urn:yanglint:modoper-leafref"> + <lfr>rw</lfr> +</notifg> diff --git a/tests/yanglint/data/modoper_leafref_notif2.xml b/tests/yanglint/data/modoper_leafref_notif2.xml new file mode 100644 index 0000000..466697c --- /dev/null +++ b/tests/yanglint/data/modoper_leafref_notif2.xml @@ -0,0 +1,8 @@ +<cond xmlns="urn:yanglint:modoper-leafref"> + <list> + <klf>key_val</klf> + <notif> + <lfn>rw</lfn> + </notif> + </list> +</cond> diff --git a/tests/yanglint/data/modoper_leafref_notif_err.xml b/tests/yanglint/data/modoper_leafref_notif_err.xml new file mode 100644 index 0000000..1622ded --- /dev/null +++ b/tests/yanglint/data/modoper_leafref_notif_err.xml @@ -0,0 +1,7 @@ +<mcc xmlns="urn:yanglint:modconfig"> + <lft>rw</lft> + <lff>ro</lff> +</mcc> +<notifg xmlns="urn:yanglint:modoper-leafref"> + <lf>rw</lf> +</notifg> diff --git a/tests/yanglint/data/modoper_leafref_rpc.xml b/tests/yanglint/data/modoper_leafref_rpc.xml new file mode 100644 index 0000000..b294544 --- /dev/null +++ b/tests/yanglint/data/modoper_leafref_rpc.xml @@ -0,0 +1,3 @@ +<rpcg xmlns="urn:yanglint:modoper-leafref"> + <lfi>rw</lfi> +</rpcg> diff --git a/tests/yanglint/data/modoper_leafref_rpc_reply.xml b/tests/yanglint/data/modoper_leafref_rpc_reply.xml new file mode 100644 index 0000000..e8f7af3 --- /dev/null +++ b/tests/yanglint/data/modoper_leafref_rpc_reply.xml @@ -0,0 +1,5 @@ +<rpcg xmlns="urn:yanglint:modoper-leafref"> + <cono> + <lfo>rw</lfo> + </cono> +</rpcg> diff --git a/tests/yanglint/data/modrpc.xml b/tests/yanglint/data/modrpc.xml new file mode 100644 index 0000000..a4f924d --- /dev/null +++ b/tests/yanglint/data/modrpc.xml @@ -0,0 +1,3 @@ +<rpc xmlns="urn:yanglint:modrpc"> + <lfi>some_input</lfi> +</rpc> diff --git a/tests/yanglint/data/modrpc_nc.xml b/tests/yanglint/data/modrpc_nc.xml new file mode 100644 index 0000000..78d3149 --- /dev/null +++ b/tests/yanglint/data/modrpc_nc.xml @@ -0,0 +1,6 @@ +<rpc message-id="101" + xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> + <rpc xmlns="urn:yanglint:modrpc"> + <lfi>some_input</lfi> + </rpc> +</rpc> diff --git a/tests/yanglint/data/modrpc_reply.xml b/tests/yanglint/data/modrpc_reply.xml new file mode 100644 index 0000000..632971c --- /dev/null +++ b/tests/yanglint/data/modrpc_reply.xml @@ -0,0 +1,5 @@ +<rpc xmlns="urn:yanglint:modrpc"> + <con> + <lfo>-56</lfo> + </con> +</rpc> diff --git a/tests/yanglint/data/modrpc_reply_nc.xml b/tests/yanglint/data/modrpc_reply_nc.xml new file mode 100644 index 0000000..da2a01c --- /dev/null +++ b/tests/yanglint/data/modrpc_reply_nc.xml @@ -0,0 +1,6 @@ +<rpc-reply message-id="101" + xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> + <con xmlns="urn:yanglint:modrpc"> + <lfo>-56</lfo> + </con> +</rpc-reply> diff --git a/tests/yanglint/data/modsm.xml b/tests/yanglint/data/modsm.xml new file mode 100644 index 0000000..bb0793c --- /dev/null +++ b/tests/yanglint/data/modsm.xml @@ -0,0 +1,3 @@ +<root xmlns="urn:yanglint:modsm"> + <lfl xmlns="urn:yanglint:modleaf">7</lfl> +</root> diff --git a/tests/yanglint/data/modsm2.xml b/tests/yanglint/data/modsm2.xml new file mode 100644 index 0000000..ff6f103 --- /dev/null +++ b/tests/yanglint/data/modsm2.xml @@ -0,0 +1,4 @@ +<root xmlns="urn:yanglint:modsm"> + <lfl xmlns="urn:yanglint:modleaf">7</lfl> + <alf xmlns="urn:yanglint:modsm-augment">str</alf> +</root> diff --git a/tests/yanglint/data/modsm_ctx_ext.xml b/tests/yanglint/data/modsm_ctx_ext.xml new file mode 100644 index 0000000..e80141a --- /dev/null +++ b/tests/yanglint/data/modsm_ctx_ext.xml @@ -0,0 +1,20 @@ +<yang-library xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library"> + <module-set> + <name>test-set</name> + <module> + <name>modleaf</name> + <namespace>urn:yanglint:modleaf</namespace> + </module> + </module-set> + <content-id>1</content-id> +</yang-library> +<modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library"> + <module-set-id>1</module-set-id> +</modules-state> +<schema-mounts xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount"> + <mount-point> + <module>modsm</module> + <label>root</label> + <inline></inline> + </mount-point> +</schema-mounts> diff --git a/tests/yanglint/data/modsm_ctx_main.xml b/tests/yanglint/data/modsm_ctx_main.xml new file mode 100644 index 0000000..5405d4d --- /dev/null +++ b/tests/yanglint/data/modsm_ctx_main.xml @@ -0,0 +1,17 @@ +<yang-library xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library"> + <module-set> + <name>main-set</name> + <module> + <name>modsm</name> + <namespace>urn:yanglint:modsm</namespace> + </module> + <module> + <name>modsm-augment</name> + <namespace>urn:yanglint:modsm-augment</namespace> + </module> + </module-set> + <content-id>1</content-id> +</yang-library> +<modules-state xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library"> + <module-set-id>1</module-set-id> +</modules-state> diff --git a/tests/yanglint/interactive/add.test b/tests/yanglint/interactive/add.test new file mode 100644 index 0000000..d1cacc1 --- /dev/null +++ b/tests/yanglint/interactive/add.test @@ -0,0 +1,59 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}] + +set mdir $::env(YANG_MODULES_DIR) + +test add_basic {} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "add $mdir/modleafref.yang" + ly_cmd "list" "I modleafref\r.*I modleaf" +}} + +test add_disable_searchdir_once {add --disable-searchdir} { +-setup $ly_setup -cleanup $ly_cleanup -constraints {!ctest} -body { + ly_cmd "add $mdir/modimp-cwd.yang" + ly_cmd "clear" + ly_cmd_err "add -D $mdir/modimp-cwd.yang" "not found in local searchdirs" +}} + +test add_disable_searchdir_twice {add -D -D} { +-setup $ly_setup -cleanup $ly_cleanup -constraints {!ctest} -body { + ly_cmd "add $mdir/ietf-ip.yang" + ly_cmd "clear" + ly_cmd_err "add -D -D $mdir/ietf-ip.yang" "Loading \"ietf-interfaces\" module failed." +}} + +test add_with_feature {Add module with feature} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "add --feature modfeature:ftr2 $mdir/modfeature.yang" + ly_cmd "feature -a" "modfeature:\r\n\tftr1 \\(off\\)\r\n\tftr2 \\(on\\)" +}} + +test add_make_implemented_once {add --make-implemented} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_ignore "add $mdir/modmust.yang" + ly_cmd "list" "I modmust\r.*i modleaf" + ly_cmd "clear" + ly_ignore "add -i $mdir/modmust.yang" + ly_cmd "list" "I modmust\r.*I modleaf" +}} + +test add_make_implemented_twice {add -i -i} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "add $mdir/modimp-type.yang" + ly_cmd "list" "I modimp-type\r.*i modtypedef" + ly_cmd "clear" + ly_cmd "add -i -i $mdir/modimp-type.yang" + ly_cmd "list" "I modimp-type\r.*I modtypedef" +}} + +test add_extended_leafref_enabled {Valid module with --extended-leafref option} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "add -X $mdir/modextleafref.yang" +}} + +test add_extended_leafref_disabled {Expected error if --extended-leafref is not set} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd_err "add $mdir/modextleafref.yang" "Unexpected XPath token \"FunctionName\"" +}} + +cleanupTests diff --git a/tests/yanglint/interactive/all.tcl b/tests/yanglint/interactive/all.tcl new file mode 100644 index 0000000..b22a5ab --- /dev/null +++ b/tests/yanglint/interactive/all.tcl @@ -0,0 +1,15 @@ +package require tcltest + +# Hook to determine if any of the tests failed. +# Sets a global variable exitCode to 1 if any test fails otherwise it is set to 0. +proc tcltest::cleanupTestsHook {} { + variable numTests + set ::exitCode [expr {$numTests(Failed) > 0}] +} + +if {[info exists ::env(TESTS_DIR)]} { + tcltest::configure -testdir "$env(TESTS_DIR)/interactive" +} + +tcltest::runAllTests +exit $exitCode diff --git a/tests/yanglint/interactive/clear.test b/tests/yanglint/interactive/clear.test new file mode 100644 index 0000000..cac0810 --- /dev/null +++ b/tests/yanglint/interactive/clear.test @@ -0,0 +1,53 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}] + +set mdir $::env(YANG_MODULES_DIR) +set ddir $::env(TESTS_DIR)/data + +test clear_searchpath {searchpath is also deleted} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "searchpath ./" + ly_cmd "clear" + ly_cmd "searchpath" "List of the searchpaths:" -ex +}} + +test clear_make_implemented_once {clear --make-implemented} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "clear -i" + ly_cmd "add $mdir/modmust.yang" + ly_cmd "list" "I modmust\r.*I modleaf" +}} + +test clear_make_implemented_twice {clear -i -i} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "clear -i -i" + ly_cmd "add $mdir/modmust.yang" + ly_cmd "list" "I modmust\r.*I modleaf" +}} + +test clear_ietf_yang_library {clear --yang-library} { +-setup $ly_setup -cleanup $ly_cleanup -body { + # add models + ly_cmd "clear -y" + ly_cmd "list" "I ietf-yang-library" +}} + +test clear_ylf_list {apply --yang-library-file and check result by --list} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "clear -Y $ddir/modimp_type_ctx.xml" + ly_cmd "list" "I modimp-type.*i modtypedef" +}} + +test clear_ylf_make_implemented {apply --yang-library-file and --make-implemented} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "clear -Y $ddir/modimp_type_ctx.xml -i -i" + ly_cmd "list" "I modimp-type.*I modtypedef" +}} + +test clear_ylf_augment_ctx {Setup context by yang-library-file and augment module} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "clear -Y $ddir/modconfig_ctx.xml" + ly_cmd "add $mdir/modconfig-augment.yang" + ly_cmd "print -f tree modconfig" "mca:alf" +}} + +cleanupTests diff --git a/tests/yanglint/interactive/completion.test b/tests/yanglint/interactive/completion.test new file mode 100644 index 0000000..86ded1f --- /dev/null +++ b/tests/yanglint/interactive/completion.test @@ -0,0 +1,69 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}] + +set mdir "$::env(YANG_MODULES_DIR)" + +variable ly_cleanup { + ly_ignore + ly_exit +} + +test completion_hints_ietf_ip {Completion and hints for ietf-ip.yang} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "add $mdir/ietf-ip.yang" + + # completion and hint + ly_completion "print -f info -P " "print -f info -P /ietf-" + + set hints {"/ietf-yang-schema-mount:schema-mounts" "/ietf-interfaces:interfaces" "/ietf-interfaces:interfaces-state"} + ly_hint "" "print -f info -P /ietf-" $hints + + # double completion + ly_completion "i" "print -f info -P /ietf-interfaces:interfaces" + ly_completion "/" "print -f info -P /ietf-interfaces:interfaces/interface" + + # a lot of hints + set hints {"/ietf-interfaces:interfaces/interface" + "/ietf-interfaces:interfaces/interface/name" "/ietf-interfaces:interfaces/interface/description" + "/ietf-interfaces:interfaces/interface/type" "/ietf-interfaces:interfaces/interface/enabled" + "/ietf-interfaces:interfaces/interface/link-up-down-trap-enable" + "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv6" + } + ly_hint "" "print -f info -P /ietf-interfaces:interfaces/interface" $hints + + # double tab + ly_completion "/i" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv" + ly_completion "4" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv4" + set hints { "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/enabled" + "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/forwarding" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/mtu" + "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/address" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/neighbor" + } + ly_hint "\t" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv" $hints + + # no more completion + ly_completion "/e" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv4/enabled " +}} + +# Note that somehow a command is automatically sent again (\t\t replaced by \r) after the hints. +# But that doesn't affect the test because the tests only focus on the word in the hint. + +test hint_data_file {Show file hints for command data} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_hint "data $mdir\t\t" "data $mdir" "modleaf.yang.*" +}} + +test hint_data_format {Show print hints for command data --format} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_hint "data -f \t\t" "data -f " "xml.*" +}} + +test hint_data_file_after_opt {Show file hints after option with argument} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_hint "data -f xml $mdir\t\t" "data -f xml $mdir" "modleaf.yang.*" +}} + +test hint_data_file_after_opt2 {Show file hints after option without argument} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_hint "data -m $mdir\t\t" "data -m $mdir" "modleaf.yang.*" +}} + +cleanupTests diff --git a/tests/yanglint/interactive/data_default.test b/tests/yanglint/interactive/data_default.test new file mode 100644 index 0000000..1953acc --- /dev/null +++ b/tests/yanglint/interactive/data_default.test @@ -0,0 +1,41 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}] + +set mods "ietf-netconf-with-defaults moddefault" +set data "$::env(TESTS_DIR)/data/moddefault.xml" + +test data_default_not_set {Print data without --default parameter} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load $mods" + ly_cmd "data -f xml $data" "</lf>.*</di>\r\n</mdc>" + ly_cmd "data -f json $data" "lf\".*di\"\[^\"]*" +}} + +test data_default_all {data --default all} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load $mods" + ly_cmd "data -d all -f xml $data" "</lf>.*</di>.*</ds>\r\n</mdc>" + ly_cmd "data -d all -f json $data" "lf\".*di\".*ds\"\[^\"]*" +}} + +test data_default_all_tagged {data --default all-tagged} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load $mods" + ly_cmd "data -d all-tagged -f xml $data" "</lf>.*<di.*default.*</di>.*<ds.*default.*</ds>\r\n</mdc>" + ly_cmd "data -d all-tagged -f json $data" "lf\".*di\".*ds\".*@ds\".*default\"\[^\"]*" +}} + +test data_default_trim {data --default trim} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load $mods" + ly_cmd "data -d trim -f xml $data" "</lf>\r\n</mdc>" + ly_cmd "data -d trim -f json $data" "lf\"\[^\"]*" +}} + +test data_default_implicit_tagged {data --default implicit-tagged} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load $mods" + ly_cmd "data -d implicit-tagged -f xml $data" "</lf>.*<di>5</di>.*<ds.*default.*</ds>\r\n</mdc>" + ly_cmd "data -d implicit-tagged -f json $data" "lf\".*di\"\[^@]*ds\".*default\"\[^\"]*" +}} + +cleanupTests diff --git a/tests/yanglint/interactive/data_format.test b/tests/yanglint/interactive/data_format.test new file mode 100644 index 0000000..dc4b7e0 --- /dev/null +++ b/tests/yanglint/interactive/data_format.test @@ -0,0 +1,23 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}] + +set ddir "$::env(TESTS_DIR)/data" + +test data_format_xml {Print data in xml format} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modleaf" + ly_cmd "data -f xml $ddir/modleaf.xml" "<lfl xmlns=\"urn:yanglint:modleaf\">7</lfl>" +}} + +test data_format_json {Print data in json format} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modleaf" + ly_cmd "data -f json $ddir/modleaf.xml" "{\r\n \"modleaf:lfl\": 7\r\n}" +}} + +test data_format_lyb_err {Print data in lyb format} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modleaf" + ly_cmd_err "data -f lyb $ddir/modleaf.xml" "The LYB format requires the -o" +}} + +cleanupTests diff --git a/tests/yanglint/interactive/data_in_format.test b/tests/yanglint/interactive/data_in_format.test new file mode 100644 index 0000000..cc5f37e --- /dev/null +++ b/tests/yanglint/interactive/data_in_format.test @@ -0,0 +1,21 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}] + +set ddir "$::env(TESTS_DIR)/data" + +test data_in_format_xml {--in-format xml} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modleaf" + ly_cmd "data -F xml $ddir/modleaf.dxml" + ly_cmd_err "data -F json $ddir/modleaf.dxml" "Failed to parse" + ly_cmd_err "data -F lyb $ddir/modleaf.dxml" "Failed to parse" +}} + +test data_in_format_json {--in-format json} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modleaf" + ly_cmd "data -F json $ddir/modleaf.djson" + ly_cmd_err "data -F xml $ddir/modleaf.djson" "Failed to parse" + ly_cmd_err "data -F lyb $ddir/modleaf.djson" "Failed to parse" +}} + +cleanupTests diff --git a/tests/yanglint/interactive/data_merge.test b/tests/yanglint/interactive/data_merge.test new file mode 100644 index 0000000..38754c7 --- /dev/null +++ b/tests/yanglint/interactive/data_merge.test @@ -0,0 +1,33 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}] + +set ddir "$::env(TESTS_DIR)/data" + +test data_merge_basic {Data is merged and the node is added} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modmerge" + ly_cmd "data -m -f xml $ddir/modmerge.xml $ddir/modmerge3.xml" "<en>.*<lm>.*<lf>" +}} + +test data_merge_validation_failed {Data is merged but validation failed.} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modmerge" + ly_cmd "data $ddir/modmerge.xml" + ly_cmd "data $ddir/modmerge2.xml" + ly_cmd "data -m $ddir/modmerge2.xml $ddir/modmerge.xml" + ly_cmd_err "data -m $ddir/modmerge.xml $ddir/modmerge2.xml" "Merged data are not valid" +}} + +test data_merge_dataconfig {The merge option has effect only for 'data' and 'config' TYPEs} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modrpc modnotif modconfig modleaf" + set wrn1 "option has effect only for" + ly_cmd_wrn "data -m -t rpc $ddir/modrpc.xml $ddir/modrpc.xml" $wrn1 + ly_cmd_wrn "data -m -t notif $ddir/modnotif2.xml $ddir/modnotif2.xml" $wrn1 + ly_cmd_wrn "data -m -t get $ddir/modleaf.xml $ddir/modconfig.xml" $wrn1 + ly_cmd_wrn "data -m -t getconfig $ddir/modleaf.xml $ddir/modconfig2.xml" $wrn1 + ly_cmd_wrn "data -m -t edit $ddir/modleaf.xml $ddir/modconfig2.xml" $wrn1 + ly_cmd "data -m -t config $ddir/modleaf.xml $ddir/modconfig2.xml" + ly_cmd "data -m -t data $ddir/modleaf.xml $ddir/modconfig.xml" +}} + +cleanupTests diff --git a/tests/yanglint/interactive/data_not_strict.test b/tests/yanglint/interactive/data_not_strict.test new file mode 100644 index 0000000..201a5a9 --- /dev/null +++ b/tests/yanglint/interactive/data_not_strict.test @@ -0,0 +1,25 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}] + +set ddir $::env(TESTS_DIR)/data + +test data_no_strict_basic {} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modleaf" + ly_cmd_err "data $ddir/modmandatory.xml" "No module with namespace \"urn:yanglint:modmandatory\" in the context." + ly_cmd "data -n $ddir/modmandatory.xml" +}} + +test data_no_strict_invalid_data {validation with --no-strict but data are invalid} { +-setup $ly_setup -cleanup $ly_cleanup -body { + set errmsg "Mandatory node \"lft\" instance does not exist." + ly_cmd "load modmandatory" + ly_cmd_err "data -n $ddir/modmandatory_invalid.xml" $errmsg +}} + +test data_no_strict_ignore_invalid_data {--no-strict ignore invalid data if no schema is provided} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modleaf" + ly_cmd "data -f xml -n $ddir/modmandatory_invalid.xml $ddir/modleaf.xml" "modleaf.*</lfl>$" +}} + +cleanupTests diff --git a/tests/yanglint/interactive/data_operational.test b/tests/yanglint/interactive/data_operational.test new file mode 100644 index 0000000..c0c7b1c --- /dev/null +++ b/tests/yanglint/interactive/data_operational.test @@ -0,0 +1,86 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}] + +set ddir "$::env(TESTS_DIR)/data" +set err1 "Operational datastore takes effect only with RPCs/Actions/Replies/Notification input data types" + +test data_operational_twice {it is not allowed to specify more than one --operational parameter} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modoper-leafref" + ly_cmd "data -t notif -O $ddir/modconfig.xml -O $ddir/modleaf.xml" "cannot be set multiple times" +}} + +test data_operational_no_type {--operational should be with parameter --type} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modoper-leafref" + ly_cmd_wrn "data -O $ddir/modconfig.xml $ddir/modoper_leafref_notif.xml" $err1 +}} + +test data_operational_missing {--operational is omitted and the datastore contents is in the data file} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modoper-leafref" + ly_cmd_err "data $ddir/modoper_leafref_notif_err.xml" "Failed to parse input data file" +}} + +test data_operational_wrong_type {data are not defined as an operation} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd_wrn "data -t data -O $ddir/modconfig.xml $ddir/modleaf.xml" $err1 +}} + +test data_operational_datastore_with_unknown_data {unknown data are ignored} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modrpc" + ly_cmd "data -t rpc -O $ddir/modmandatory_invalid.xml $ddir/modrpc.xml" +}} + +test data_operational_empty_datastore {datastore is considered empty because it contains unknown data} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modrpc modnotif" + ly_cmd "data -t rpc -O $ddir/modmandatory_invalid.xml $ddir/modrpc.xml" + set msg "parent \"/modnotif:con\" not found in the operational data" + ly_cmd_err "data -t notif -O $ddir/modmandatory_invalid.xml $ddir/modnotif.xml" $msg +}} + +test data_operational_notif_leafref {--operational data is referenced from notification-leafref} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modoper-leafref" + ly_cmd "data -t notif -O $ddir/modconfig.xml $ddir/modoper_leafref_notif.xml" +}} + +test data_operational_nested_notif_leafref {--operational data is referenced from nested-notification-leafref} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modoper-leafref" + ly_cmd "data -t notif -O $ddir/modoper_leafref_ds.xml $ddir/modoper_leafref_notif2.xml" +}} + +test data_operational_nested_notif_parent_missing {--operational data are invalid due to missing parent node} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modoper-leafref" + set msg "klf='key_val']\" not found in the operational data" + ly_cmd_err "data -t notif -O $ddir/modconfig.xml $ddir/modoper_leafref_notif2.xml" $msg +}} + +test data_operational_action_leafref {--operational data is referenced from action-leafref} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modoper-leafref" + ly_cmd "data -t rpc -O $ddir/modoper_leafref_ds.xml $ddir/modoper_leafref_action.xml" +}} + +test data_operational_action_reply_leafref {--operational data is referenced from action-leafref output} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modoper-leafref" + ly_cmd "data -t reply -O $ddir/modoper_leafref_ds.xml $ddir/modoper_leafref_action_reply.xml" +}} + +test data_operational_rpc_leafref {--operational data is referenced from rpc-leafref} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modoper-leafref" + ly_cmd "data -t rpc -O $ddir/modconfig.xml $ddir/modoper_leafref_rpc.xml" +}} + +test data_operational_rpc_reply_leafref {--operational data is referenced from rpc-leafref output} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modoper-leafref" + ly_cmd "data -t reply -O $ddir/modconfig.xml $ddir/modoper_leafref_rpc_reply.xml" +}} + +cleanupTests diff --git a/tests/yanglint/interactive/data_present.test b/tests/yanglint/interactive/data_present.test new file mode 100644 index 0000000..4bba596 --- /dev/null +++ b/tests/yanglint/interactive/data_present.test @@ -0,0 +1,25 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}] + +set ddir "$::env(TESTS_DIR)/data" + +test data_present_via_mandatory {validation of mandatory-stmt will pass only with the --present} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modleaf modmandatory" + ly_cmd_err "data $ddir/modleaf.xml" "Mandatory node \"lft\" instance does not exist." + ly_cmd "data -e $ddir/modleaf.xml" +}} + +test data_present_merge {validation with --present and --merge} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modleaf modmandatory moddefault" + ly_cmd_err "data -m $ddir/modleaf.xml $ddir/moddefault.xml" "Mandatory node \"lft\" instance does not exist." + ly_cmd "data -e -m $ddir/modleaf.xml $ddir/moddefault.xml" +}} + +test data_present_merge_invalid {using --present and --merge but data are invalid} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modleaf modmandatory" + ly_cmd_err "data -e -m $ddir/modleaf.xml $ddir/modmandatory_invalid.xml" "Mandatory node \"lft\" instance does not exist." +}} + +cleanupTests diff --git a/tests/yanglint/interactive/data_type.test b/tests/yanglint/interactive/data_type.test new file mode 100644 index 0000000..a442813 --- /dev/null +++ b/tests/yanglint/interactive/data_type.test @@ -0,0 +1,140 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}] + +set ddir "$::env(TESTS_DIR)/data" + +test data_type_data {data --type data} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modconfig" + ly_cmd "data -t data $ddir/modconfig.xml" +}} + +test data_type_config {data --type config} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modconfig" + ly_cmd_err "data -t config $ddir/modconfig.xml" "Unexpected data state node \"lff\"" + ly_cmd "data -t config $ddir/modconfig2.xml" +}} + +test data_type_get {data --type get} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modleafref" + ly_cmd_err "data -t data $ddir/modleafref2.xml" "Invalid leafref value" + ly_cmd "data -t get $ddir/modleafref2.xml" +}} + +test data_type_getconfig_no_state {No state node for data --type getconfig} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modconfig" + ly_cmd_err "data -t getconfig $ddir/modconfig.xml" "Unexpected data state node \"lff\"" + ly_cmd "data -t getconfig $ddir/modconfig2.xml" +}} + +test data_type_getconfig_parse_only {No validation performed for data --type getconfig} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modleafref" + ly_cmd_err "data -t data $ddir/modleafref2.xml" "Invalid leafref value" + ly_cmd "data -t getconfig $ddir/modleafref2.xml" +}} + +test data_type_edit_no_state {No state node for data --type edit} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modconfig" + ly_cmd_err "data -t edit $ddir/modconfig.xml" "Unexpected data state node \"lff\"" + ly_cmd "data -t edit $ddir/modconfig2.xml" +}} + +test data_type_edit_parse_only {No validation performed for data --type edit} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modleafref" + ly_cmd_err "data -t data $ddir/modleafref2.xml" "Invalid leafref value" + ly_cmd "data -t edit $ddir/modleafref2.xml" +}} + +test data_type_rpc {Validation of rpc-statement by data --type rpc} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modrpc modleaf" + ly_cmd_err "data -t rpc $ddir/modleaf.xml" "Missing the operation node." + ly_cmd "data -t rpc $ddir/modrpc.xml" +}} + +test data_type_rpc_nc {Validation of rpc-statement by data --type nc-rpc} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modrpc modleaf ietf-netconf" + ly_cmd_err "data -t nc-rpc $ddir/modleaf.xml" "Missing NETCONF <rpc> envelope" + ly_cmd "data -t nc-rpc $ddir/modrpc_nc.xml" +}} + +test data_type_rpc_reply {Validation of rpc-reply by data --type reply} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modrpc modleaf" + ly_cmd_err "data -t rpc $ddir/modleaf.xml" "Missing the operation node." + ly_cmd_wrn "data -t reply -R $ddir/modrpc.xml $ddir/modrpc_reply.xml" "needed only for NETCONF" + ly_cmd "data -t reply $ddir/modrpc_reply.xml" +}} + +test data_type_rpc_reply_nc {Validation of rpc-reply by data --type nc-reply} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modrpc modleaf" + ly_cmd_err "data -t nc-reply -R $ddir/modrpc_nc.xml $ddir/modleaf.xml" "Missing NETCONF <rpc-reply> envelope" + ly_cmd_err "data -t nc-reply $ddir/modrpc_reply_nc.xml" "Missing source RPC" + ly_cmd "data -t nc-reply -R $ddir/modrpc_nc.xml $ddir/modrpc_reply_nc.xml" +}} + +test data_type_rpc_action {Validation of action-statement by data --type rpc} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modaction modleaf" + ly_cmd_err "data -t rpc $ddir/modleaf.xml" "Missing the operation node." + ly_cmd "data -t rpc -O $ddir/modaction_ds.xml $ddir/modaction.xml" +}} + +test data_type_rpc_action_nc {Validation of action-statement by data --type nc-rpc} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modaction modleaf" + ly_cmd_err "data -t nc-rpc $ddir/modleaf.xml" "Missing NETCONF <rpc> envelope" + ly_cmd "data -t nc-rpc -O $ddir/modaction_ds.xml $ddir/modaction_nc.xml" +}} + +test data_type_rpc_action_reply {Validation of action-reply by data --type reply} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modaction modleaf" + ly_cmd_err "data -t rpc $ddir/modleaf.xml" "Missing the operation node." + ly_cmd "data -t reply -O $ddir/modaction_ds.xml $ddir/modaction_reply.xml" +}} + +test data_type_rpc_action_reply_nc {Validation of action-reply by data --type nc-reply} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modaction modleaf" + ly_cmd_err "data -t nc-reply -R $ddir/modaction_nc.xml $ddir/modleaf.xml" "Missing NETCONF <rpc-reply> envelope" + ly_cmd_err "data -t nc-reply $ddir/modaction_reply_nc.xml" "Missing source RPC" + ly_cmd_err "data -t nc-reply -R $ddir/modaction_nc.xml $ddir/modaction_reply_nc.xml" "operational parameter needed" + ly_cmd "data -t nc-reply -O $ddir/modaction_ds.xml -R $ddir/modaction_nc.xml $ddir/modaction_reply_nc.xml" +}} + +test data_type_notif {Validation of notification-statement by data --type notif} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modnotif modleaf" + ly_cmd_err "data -t notif $ddir/modleaf.xml" "Missing the operation node." + ly_cmd "data -t notif $ddir/modnotif2.xml" +}} + +test data_type_notif_nc {Validation of notification-statement by data --type nc-notif} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modnotif modleaf ietf-netconf" + ly_cmd_err "data -t nc-notif $ddir/modleaf.xml" "Missing NETCONF <notification> envelope" + ly_cmd "data -t nc-notif $ddir/modnotif2_nc.xml" +}} + +test data_type_notif_nested {Validation of nested-notification-statement by data --type notif} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modnotif modleaf" + ly_cmd "data -t notif -O $ddir/modnotif_ds.xml $ddir/modnotif.xml" +}} + +test data_type_notif_nested_nc {Validation of nested-notification-statement by data --type nc-notif} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modnotif modleaf ietf-netconf" + ly_cmd_err "data -t nc-notif $ddir/modleaf.xml" "Missing NETCONF <notification> envelope" + ly_cmd "data -t nc-notif -O $ddir/modnotif_ds.xml $ddir/modnotif_nc.xml" +}} + +cleanupTests diff --git a/tests/yanglint/interactive/data_xpath.test b/tests/yanglint/interactive/data_xpath.test new file mode 100644 index 0000000..398cb9f --- /dev/null +++ b/tests/yanglint/interactive/data_xpath.test @@ -0,0 +1,57 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}] + +set data "$::env(TESTS_DIR)/data/moddatanodes.xml" + +test data_xpath_empty {--xpath to missing node} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load moddatanodes" + ly_cmd "data -x /moddatanodes:dnc/mis $data" "Empty" +}} + +test data_xpath_leaf {--xpath to leaf node} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load moddatanodes" + ly_cmd "data -x /moddatanodes:dnc/lf $data" "leaf \"lf\" \\(value: \"x\"\\)" +}} + +test data_xpath_leaflist {--xpath to leaf-list node} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load moddatanodes" + set r1 "leaf-list \"lfl\" \\(value: \"1\"\\)" + set r2 "leaf-list \"lfl\" \\(value: \"2\"\\)" + ly_cmd "data -x /moddatanodes:dnc/lfl $data" "$r1\r\n $r2" +}} + +test data_xpath_list {--xpath to list} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load moddatanodes" + set r1 "list \"lt\" \\(\"kalf\": \"ka1\"; \"kblf\": \"kb1\";\\)" + set r2 "list \"lt\" \\(\"kalf\": \"ka2\"; \"kblf\": \"kb2\";\\)" + ly_cmd "data -x /moddatanodes:dnc/con/lt $data" "$r1\r\n $r2" +}} + +test data_xpath_container {--xpath to container} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load moddatanodes" + ly_cmd "data -x /moddatanodes:dnc/con $data" "container \"con\"" +}} + +test data_xpath_wrong_path {--xpath to a non-existent node} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load moddatanodes" + ly_cmd_err "data -x /moddatanodes:dnc/wrng $data" "xpath failed" +}} + +test data_xpath_err_format {--xpath cannot be combined with --format} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load moddatanodes" + ly_cmd_err "data -f xml -x /moddatanodes:dnc/lf $data" "option cannot be combined" +}} + +test data_xpath_err_default {--xpath cannot be combined with --default} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load moddatanodes ietf-netconf-with-defaults" + ly_cmd_err "data -d all -x /moddatanodes:dnc/lf $data" "option cannot be combined" +}} + +cleanupTests diff --git a/tests/yanglint/interactive/debug.test b/tests/yanglint/interactive/debug.test new file mode 100644 index 0000000..8a64c92 --- /dev/null +++ b/tests/yanglint/interactive/debug.test @@ -0,0 +1,33 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}] + +set mdir $::env(YANG_MODULES_DIR) + +test debug_dict {Check debug message DICT} { +-setup $ly_setup -cleanup $ly_cleanup -constraints {[yanglint_debug]} -body { + ly_cmd "verb debug" + ly_cmd "debug dict" + ly_cmd "load modleaf" "DICT" +}} + +test debug_xpath {Check debug message XPATH} { +-setup $ly_setup -cleanup $ly_cleanup -constraints {[yanglint_debug]} -body { + ly_cmd "verb debug" + ly_cmd "debug xpath" + ly_cmd "load modmust" "XPATH" +}} + +test debug_dep_sets {Check debug message DEPSETS} { +-setup $ly_setup -cleanup $ly_cleanup -constraints {[yanglint_debug]} -body { + ly_cmd "verb debug" + ly_cmd "debug dep-sets" + ly_cmd "load modleaf" "DEPSETS" +}} + +test debug_depsets_xpath {Check debug message DEPSETS and XPATH} { +-setup $ly_setup -cleanup $ly_cleanup -constraints {[yanglint_debug]} -body { + ly_cmd "verb debug" + ly_cmd "debug dep-sets xpath" + ly_cmd "load modmust" "DEPSETS.*XPATH" +}} + +cleanupTests diff --git a/tests/yanglint/interactive/extdata.test b/tests/yanglint/interactive/extdata.test new file mode 100644 index 0000000..e253d1a --- /dev/null +++ b/tests/yanglint/interactive/extdata.test @@ -0,0 +1,63 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}] + +set mdir "$::env(YANG_MODULES_DIR)" +set ddir "$::env(TESTS_DIR)/data" + +test extdata_set_clear {Set and clear extdata file} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "extdata" "No file set" + ly_cmd "extdata $ddir/modsm_ctx_ext.xml" + ly_cmd "extdata" "$ddir/modsm_ctx_ext.xml" + ly_cmd "extdata -c" + ly_cmd "extdata" "No file set" +}} + +test extdata_clear_cmd {Clear extdata file by 'clear' command} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "extdata $ddir/modsm_ctx_ext.xml" + ly_cmd "clear" + ly_cmd "extdata" "No file set" +}} + +test extdata_one_only {Only one file for extdata} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd_err "extdata $ddir/modsm_ctx_ext.xml $ddir/modsm_ctx_ext.xml" "Only one file must be entered" +}} + +test extdata_schema_mount_tree {Print tree output of a model with Schema Mount} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "clear -y" + ly_cmd "searchpath $mdir" + ly_cmd "load modsm" + ly_cmd "extdata $ddir/modsm_ctx_ext.xml" + ly_cmd "print -f tree modsm" "--mp root.*--rw lfl/" +}} + +test ext_data_schema_mount_tree_yanglibfile {Print tree output of a model with Schema Mount and --yang-library-file} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "clear -Y $ddir/modsm_ctx_main.xml" + ly_cmd "searchpath $mdir" + ly_cmd "load modsm" + ly_cmd "extdata $ddir/modsm_ctx_ext.xml" + ly_cmd "print -f tree modsm" "--mp root.*--rw lfl/.*--rw msa:alf?" +}} + +test ext_data_schema_mount_xml {Validating and printing mounted data} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "clear -y" + ly_cmd "searchpath $mdir" + ly_cmd "load modsm" + ly_cmd "extdata $ddir/modsm_ctx_ext.xml" + ly_cmd "data -f xml -t config $ddir/modsm.xml" "</lfl>" +}} + +test ext_data_schema_mount_xml_yanglibfile {Validating and printing mounted data with --yang-library-file} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "clear -Y $ddir/modsm_ctx_main.xml" + ly_cmd "searchpath $mdir" + ly_cmd "load modsm" + ly_cmd "extdata $ddir/modsm_ctx_ext.xml" + ly_cmd "data -f xml -t config $ddir/modsm2.xml" "</lfl>.*</alf>" +}} + +cleanupTests diff --git a/tests/yanglint/interactive/feature.test b/tests/yanglint/interactive/feature.test new file mode 100644 index 0000000..84bfa8e --- /dev/null +++ b/tests/yanglint/interactive/feature.test @@ -0,0 +1,37 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}] + +test feature_all_default {Default output of feature --all} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "feature -a" "yang:\r\n\t(none)\r\n\r\nietf-yang-schema-mount:\r\n\t(none)\r\n" -ex +}} + +test feature_all_add_module {Add module with only one feature and call feature --all} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load --feature modfeature:ftr1 modfeature" + ly_cmd "feature -a" "modfeature:\r\n\tftr1 \\(on\\)\r\n\tftr2 \\(off\\)" +}} + +test feature_all_on {Add module with all enabled features and call feature --all} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load --feature modfeature:* modfeature" + ly_cmd "feature -a" "modfeature:\r\n\tftr1 \\(on\\)\r\n\tftr2 \\(on\\)" +}} + +test feature_one_module {Show features for one module} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load ietf-ip" + ly_cmd "feature -f ietf-ip" " -F ietf-ip:ipv4-non-contiguous-netmasks,ipv6-privacy-autoconf" -ex +}} + +test feature_more_modules {Show a mix of modules with and without features} { +-setup $ly_setup -cleanup $ly_cleanup -body { + + set features " -F modfeature:ftr1,ftr2\ +-F modleaf:\ +-F ietf-ip:ipv4-non-contiguous-netmasks,ipv6-privacy-autoconf" + + ly_cmd "load ietf-ip modleaf modfeature" + ly_cmd "feature -f modfeature modleaf ietf-ip" $features -ex +}} + +cleanupTests diff --git a/tests/yanglint/interactive/list.test b/tests/yanglint/interactive/list.test new file mode 100644 index 0000000..ab59a32 --- /dev/null +++ b/tests/yanglint/interactive/list.test @@ -0,0 +1,34 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}] +namespace import uti::regex_xml_elements uti::regex_json_pairs + +set modules {ietf-yang-library ietf-inet-types} + +test list_basic {basic test} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "list" "ietf-yang-types" +}} + +test list_format_xml {list --format xml} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "clear -y" + ly_cmd "list -f xml" [regex_xml_elements $modules "name"] +}} + +test list_format_json {list --format json} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "clear -y" + ly_cmd "list -f json" [regex_json_pairs $modules "name"] +}} + +test list_ietf_yang_library {Error due to missing ietf-yang-library} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd_err "list -f xml" "Module \"ietf-yang-library\" is not implemented." +}} + +test list_bad_format {Error due to bad format} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "clear -y" + ly_cmd_err "list -f csv" "Unknown output format csv" +}} + +cleanupTests diff --git a/tests/yanglint/interactive/load.test b/tests/yanglint/interactive/load.test new file mode 100644 index 0000000..a95d044 --- /dev/null +++ b/tests/yanglint/interactive/load.test @@ -0,0 +1,45 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}] + +test load_basic {} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modleafref" + ly_cmd "list" "I modleafref\r.*I modleaf" +}} + +test load_with_feature {Load module with feature} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load --feature modfeature:ftr2 modfeature" + ly_cmd "feature -a" "modfeature:\r\n\tftr1 \\(off\\)\r\n\tftr2 \\(on\\)" +}} + +test load_make_implemented_once {load --make-implemented} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_ignore "load modmust" + ly_cmd "list" "I modmust\r.*i modleaf" + ly_cmd "clear" + ly_cmd "searchpath $::env(YANG_MODULES_DIR)" + ly_cmd "load -i modmust" + ly_cmd "list" "I modmust\r.*I modleaf" +}} + +test load_make_implemented_twice {load -i -i} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modimp-type" + ly_cmd "list" "I modimp-type\r.*i modtypedef" + ly_cmd "clear" + ly_cmd "searchpath $::env(YANG_MODULES_DIR)" + ly_cmd "load -i -i modimp-type" + ly_cmd "list" "I modimp-type\r.*I modtypedef" +}} + +test load_extended_leafref_enabled {Valid module with --extended-leafref option} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load -X modextleafref" +}} + +test load_extended_leafref_disabled {Expected error if --extended-leafref is not set} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd_err "load modextleafref" "Unexpected XPath token \"FunctionName\"" +}} + +cleanupTests diff --git a/tests/yanglint/interactive/ly.tcl b/tests/yanglint/interactive/ly.tcl new file mode 100644 index 0000000..4c56be4 --- /dev/null +++ b/tests/yanglint/interactive/ly.tcl @@ -0,0 +1,81 @@ +# @brief The main source of functions and variables for testing yanglint in the interactive mode. + +# For testing yanglint. +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/common.tcl" : "../common.tcl"}] +# For testing any interactive tool. +source "$::env(TESTS_DIR)/../tool_i.tcl" + +# The script continues by defining variables and functions specific to the interactive yanglint tool. + +# set the timeout to 5 seconds +set timeout 5 +# prompt of yanglint +set prompt "> " +# turn off dialog between expect and yanglint +log_user 0 +# setting some large terminal width +stty columns 720 + +# default setup for every unit test +variable ly_setup { + spawn $TUT + ly_skip_warnings + # Searchpath is set, so modules can be loaded via the 'load' command. + ly_cmd "searchpath $::env(YANG_MODULES_DIR)" +} + +# default cleanup for every unit test +variable ly_cleanup { + ly_exit +} + +# Skip no dir and/or no history warnings and prompt. +proc ly_skip_warnings {} { + global prompt + expect -re "(YANGLINT.*)*$prompt" {} +} + +# Send command 'cmd' to the process, expect error header and then check output string by 'pattern'. +# Parameter cmd is a string of arguments. +# Parameter pattern is a regex. It must not contain a prompt. +proc ly_cmd_err {cmd pattern} { + global prompt + + send -- "${cmd}\r" + expect -- "${cmd}\r\n" + + expect { + -re "YANGLINT\\\[E\\\]: .*${pattern}.*\r\n${prompt}$" {} + -re "libyang\\\[\[0-9]+\\\]: .*${pattern}.*\r\n${prompt}$" {} + -re "\r\n${prompt}$" { + error "unexpected output:\n$expect_out(buffer)" + } + } +} + +# Send command 'cmd' to the process, expect warning header and then check output string by 'pattern'. +# Parameter cmd is a string of arguments. +# Parameter pattern is a regex. It must not contain a prompt. +proc ly_cmd_wrn {cmd pattern} { + ly_cmd_header $cmd "YANGLINT\\\[W\\\]:" $pattern +} + +# Send 'exit' and wait for eof. +proc ly_exit {} { + send "exit\r" + expect eof +} + +# Check if yanglint is configured as DEBUG. +# Return 1 on success. +proc yanglint_debug {} { + global TUT + # Call non-interactive yanglint with --help. + set output [exec -- $TUT "-h"] + # Find option --debug. + if { [regexp -- "--debug=GROUPS" $output] } { + return 1 + } else { + return 0 + } +} diff --git a/tests/yanglint/interactive/modcwd.yang b/tests/yanglint/interactive/modcwd.yang new file mode 100644 index 0000000..db33e73 --- /dev/null +++ b/tests/yanglint/interactive/modcwd.yang @@ -0,0 +1,4 @@ +module modcwd { + namespace "urn:yanglint:modcwd"; + prefix mc; +} diff --git a/tests/yanglint/interactive/print.test b/tests/yanglint/interactive/print.test new file mode 100644 index 0000000..8b9d740 --- /dev/null +++ b/tests/yanglint/interactive/print.test @@ -0,0 +1,77 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}] + +set ipv6_path "/ietf-interfaces:interfaces/interface/ietf-ip:ipv6/address" + +test print_yang {} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modleaf" + ly_cmd "print -f yang modleaf" "leaf lfl" +}} + +test print_yang_submodule {Print submodule in yang format} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modinclude" + ly_cmd "print -f yang modsub" "submodule modsub" +}} + +test print_yin {} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modleaf" + ly_cmd "print -f yin modleaf" "<leaf name=\"lfl\">" +}} + +test print_yin_submodule {Print submodule in yin format} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modinclude" + ly_cmd "print -f yin modsub" "<submodule name=\"modsub\"" +}} + +test print_info {} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modleaf" + ly_cmd "print -f info modleaf" "status current" +}} + +test print_info_path {Print subtree in info format} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load ietf-ip" + ly_cmd "print -f info -P $ipv6_path" "^list address .* leaf prefix-length" +}} + +test print_info_path_single_node {Print node in info format} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load ietf-ip" + ly_cmd "print -f info -q -P $ipv6_path" "^list address .* IPv6 addresses on the interface.\";\r\n\}$" +}} + +test print_tree {} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modleaf" + ly_cmd "print -f tree modleaf" "\\+--rw lfl" +}} + +test print_tree_submodule {Print submodule in tree format} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load modinclude" + ly_cmd "print -f tree modsub" "submodule: modsub" +}} + +test print_tree_path {Print subtree in tree format} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load ietf-ip" + ly_cmd "print -f tree -P $ipv6_path" "\\+--rw address.*\\+--rw prefix-length" +}} + +test print_tree_path_single_node {Print node in tree format} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load ietf-ip" + ly_cmd "print -f tree -q -P $ipv6_path" "\\+--rw address\\* \\\[ip\\\]$" +}} + +test print_tree_path_single_node_line_length {Print node in the tree format and limit row size} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "load ietf-ip" + ly_cmd "print -f tree -L 20 -q -P $ipv6_path" "\\+--rw address\\*\r\n *\\\[ip\\\]$" +}} + +cleanupTests diff --git a/tests/yanglint/interactive/searchpath.test b/tests/yanglint/interactive/searchpath.test new file mode 100644 index 0000000..3bd6796 --- /dev/null +++ b/tests/yanglint/interactive/searchpath.test @@ -0,0 +1,24 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}] + +set mdir $::env(YANG_MODULES_DIR) + +variable ly_setup { + spawn $TUT + ly_skip_warnings +} + +test searchpath_basic {} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "searchpath $mdir" + ly_cmd "searchpath" "$mdir" + ly_cmd "load modleaf" +}} + +test searchpath_clear {searchpath --clear} { +-setup $ly_setup -cleanup $ly_cleanup -body { + ly_cmd "searchpath $mdir" + ly_cmd "searchpath --clear" + ly_cmd_err "load modleaf" "Data model \"modleaf\" not found in local searchdirs" +}} + +cleanupTests diff --git a/tests/yanglint/modules/ietf-interfaces.yang b/tests/yanglint/modules/ietf-interfaces.yang new file mode 100644 index 0000000..ad64425 --- /dev/null +++ b/tests/yanglint/modules/ietf-interfaces.yang @@ -0,0 +1,725 @@ +module ietf-interfaces { + + namespace "urn:ietf:params:xml:ns:yang:ietf-interfaces"; + prefix if; + + import ietf-yang-types { + prefix yang; + } + + organization + "IETF NETMOD (NETCONF Data Modeling Language) Working Group"; + + contact + "WG Web: <http://tools.ietf.org/wg/netmod/> + WG List: <mailto:netmod@ietf.org> + + WG Chair: Thomas Nadeau + <mailto:tnadeau@lucidvision.com> + + WG Chair: Juergen Schoenwaelder + <mailto:j.schoenwaelder@jacobs-university.de> + + Editor: Martin Bjorklund + <mailto:mbj@tail-f.com>"; + + description + "This module contains a collection of YANG definitions for + managing network interfaces. + + Copyright (c) 2014 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 7223; see + the RFC itself for full legal notices."; + + revision 2014-05-08 { + description + "Initial revision."; + reference + "RFC 7223: A YANG Data Model for Interface Management"; + } + + /* + * Typedefs + */ + + typedef interface-ref { + type leafref { + path "/if:interfaces/if:interface/if:name"; + } + description + "This type is used by data models that need to reference + configured interfaces."; + } + + typedef interface-state-ref { + type leafref { + path "/if:interfaces-state/if:interface/if:name"; + } + description + "This type is used by data models that need to reference + the operationally present interfaces."; + } + + /* + * Identities + */ + + identity interface-type { + description + "Base identity from which specific interface types are + derived."; + } + + /* + * Features + */ + + feature arbitrary-names { + description + "This feature indicates that the device allows user-controlled + interfaces to be named arbitrarily."; + } + feature pre-provisioning { + description + "This feature indicates that the device supports + pre-provisioning of interface configuration, i.e., it is + possible to configure an interface whose physical interface + hardware is not present on the device."; + } + + feature if-mib { + description + "This feature indicates that the device implements + the IF-MIB."; + reference + "RFC 2863: The Interfaces Group MIB"; + } + + /* + * Configuration data nodes + */ + + container interfaces { + description + "Interface configuration parameters."; + + list interface { + key "name"; + + description + "The list of configured interfaces on the device. + + The operational state of an interface is available in the + /interfaces-state/interface list. If the configuration of a + system-controlled interface cannot be used by the system + (e.g., the interface hardware present does not match the + interface type), then the configuration is not applied to + the system-controlled interface shown in the + /interfaces-state/interface list. If the configuration + of a user-controlled interface cannot be used by the system, + the configured interface is not instantiated in the + /interfaces-state/interface list."; + + leaf name { + type string; + description + "The name of the interface. + + A device MAY restrict the allowed values for this leaf, + possibly depending on the type of the interface. + For system-controlled interfaces, this leaf is the + device-specific name of the interface. The 'config false' + list /interfaces-state/interface contains the currently + existing interfaces on the device. + + If a client tries to create configuration for a + system-controlled interface that is not present in the + /interfaces-state/interface list, the server MAY reject + the request if the implementation does not support + pre-provisioning of interfaces or if the name refers to + an interface that can never exist in the system. A + NETCONF server MUST reply with an rpc-error with the + error-tag 'invalid-value' in this case. + + If the device supports pre-provisioning of interface + configuration, the 'pre-provisioning' feature is + advertised. + + If the device allows arbitrarily named user-controlled + interfaces, the 'arbitrary-names' feature is advertised. + + When a configured user-controlled interface is created by + the system, it is instantiated with the same name in the + /interface-state/interface list."; + } + + leaf description { + type string; + description + "A textual description of the interface. + + A server implementation MAY map this leaf to the ifAlias + MIB object. Such an implementation needs to use some + mechanism to handle the differences in size and characters + allowed between this leaf and ifAlias. The definition of + such a mechanism is outside the scope of this document. + + Since ifAlias is defined to be stored in non-volatile + storage, the MIB implementation MUST map ifAlias to the + value of 'description' in the persistently stored + datastore. + + Specifically, if the device supports ':startup', when + ifAlias is read the device MUST return the value of + 'description' in the 'startup' datastore, and when it is + written, it MUST be written to the 'running' and 'startup' + datastores. Note that it is up to the implementation to + + decide whether to modify this single leaf in 'startup' or + perform an implicit copy-config from 'running' to + 'startup'. + + If the device does not support ':startup', ifAlias MUST + be mapped to the 'description' leaf in the 'running' + datastore."; + reference + "RFC 2863: The Interfaces Group MIB - ifAlias"; + } + + leaf type { + type identityref { + base interface-type; + } + mandatory true; + description + "The type of the interface. + + When an interface entry is created, a server MAY + initialize the type leaf with a valid value, e.g., if it + is possible to derive the type from the name of the + interface. + + If a client tries to set the type of an interface to a + value that can never be used by the system, e.g., if the + type is not supported or if the type does not match the + name of the interface, the server MUST reject the request. + A NETCONF server MUST reply with an rpc-error with the + error-tag 'invalid-value' in this case."; + reference + "RFC 2863: The Interfaces Group MIB - ifType"; + } + + leaf enabled { + type boolean; + default "true"; + description + "This leaf contains the configured, desired state of the + interface. + + Systems that implement the IF-MIB use the value of this + leaf in the 'running' datastore to set + IF-MIB.ifAdminStatus to 'up' or 'down' after an ifEntry + has been initialized, as described in RFC 2863. + + + + Changes in this leaf in the 'running' datastore are + reflected in ifAdminStatus, but if ifAdminStatus is + changed over SNMP, this leaf is not affected."; + reference + "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; + } + + leaf link-up-down-trap-enable { + if-feature if-mib; + type enumeration { + enum enabled { + value 1; + } + enum disabled { + value 2; + } + } + description + "Controls whether linkUp/linkDown SNMP notifications + should be generated for this interface. + + If this node is not configured, the value 'enabled' is + operationally used by the server for interfaces that do + not operate on top of any other interface (i.e., there are + no 'lower-layer-if' entries), and 'disabled' otherwise."; + reference + "RFC 2863: The Interfaces Group MIB - + ifLinkUpDownTrapEnable"; + } + } + } + + /* + * Operational state data nodes + */ + + container interfaces-state { + config false; + description + "Data nodes for the operational state of interfaces."; + + list interface { + key "name"; + + + + + + description + "The list of interfaces on the device. + + System-controlled interfaces created by the system are + always present in this list, whether they are configured or + not."; + + leaf name { + type string; + description + "The name of the interface. + + A server implementation MAY map this leaf to the ifName + MIB object. Such an implementation needs to use some + mechanism to handle the differences in size and characters + allowed between this leaf and ifName. The definition of + such a mechanism is outside the scope of this document."; + reference + "RFC 2863: The Interfaces Group MIB - ifName"; + } + + leaf type { + type identityref { + base interface-type; + } + mandatory true; + description + "The type of the interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifType"; + } + + leaf admin-status { + if-feature if-mib; + type enumeration { + enum up { + value 1; + description + "Ready to pass packets."; + } + enum down { + value 2; + description + "Not ready to pass packets and not in some test mode."; + } + + + + enum testing { + value 3; + description + "In some test mode."; + } + } + mandatory true; + description + "The desired state of the interface. + + This leaf has the same read semantics as ifAdminStatus."; + reference + "RFC 2863: The Interfaces Group MIB - ifAdminStatus"; + } + + leaf oper-status { + type enumeration { + enum up { + value 1; + description + "Ready to pass packets."; + } + enum down { + value 2; + description + "The interface does not pass any packets."; + } + enum testing { + value 3; + description + "In some test mode. No operational packets can + be passed."; + } + enum unknown { + value 4; + description + "Status cannot be determined for some reason."; + } + enum dormant { + value 5; + description + "Waiting for some external event."; + } + enum not-present { + value 6; + description + "Some component (typically hardware) is missing."; + } + enum lower-layer-down { + value 7; + description + "Down due to state of lower-layer interface(s)."; + } + } + mandatory true; + description + "The current operational state of the interface. + + This leaf has the same semantics as ifOperStatus."; + reference + "RFC 2863: The Interfaces Group MIB - ifOperStatus"; + } + + leaf last-change { + type yang:date-and-time; + description + "The time the interface entered its current operational + state. If the current state was entered prior to the + last re-initialization of the local network management + subsystem, then this node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - ifLastChange"; + } + + leaf if-index { + if-feature if-mib; + type int32 { + range "1..2147483647"; + } + mandatory true; + description + "The ifIndex value for the ifEntry represented by this + interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifIndex"; + } + + leaf phys-address { + type yang:phys-address; + description + "The interface's address at its protocol sub-layer. For + example, for an 802.x interface, this object normally + contains a Media Access Control (MAC) address. The + interface's media-specific modules must define the bit + + + and byte ordering and the format of the value of this + object. For interfaces that do not have such an address + (e.g., a serial line), this node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - ifPhysAddress"; + } + + leaf-list higher-layer-if { + type interface-state-ref; + description + "A list of references to interfaces layered on top of this + interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifStackTable"; + } + + leaf-list lower-layer-if { + type interface-state-ref; + description + "A list of references to interfaces layered underneath this + interface."; + reference + "RFC 2863: The Interfaces Group MIB - ifStackTable"; + } + + leaf speed { + type yang:gauge64; + units "bits/second"; + description + "An estimate of the interface's current bandwidth in bits + per second. For interfaces that do not vary in + bandwidth or for those where no accurate estimation can + be made, this node should contain the nominal bandwidth. + For interfaces that have no concept of bandwidth, this + node is not present."; + reference + "RFC 2863: The Interfaces Group MIB - + ifSpeed, ifHighSpeed"; + } + + + + + + + + + + container statistics { + description + "A collection of interface-related statistics objects."; + + leaf discontinuity-time { + type yang:date-and-time; + mandatory true; + description + "The time on the most recent occasion at which any one or + more of this interface's counters suffered a + discontinuity. If no such discontinuities have occurred + since the last re-initialization of the local management + subsystem, then this node contains the time the local + management subsystem re-initialized itself."; + } + + leaf in-octets { + type yang:counter64; + description + "The total number of octets received on the interface, + including framing characters. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInOctets"; + } + + leaf in-unicast-pkts { + type yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were not addressed to a + multicast or broadcast address at this sub-layer. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCInUcastPkts"; + } + + + + + leaf in-broadcast-pkts { + type yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were addressed to a broadcast + address at this sub-layer. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCInBroadcastPkts"; + } + + leaf in-multicast-pkts { + type yang:counter64; + description + "The number of packets, delivered by this sub-layer to a + higher (sub-)layer, that were addressed to a multicast + address at this sub-layer. For a MAC-layer protocol, + this includes both Group and Functional addresses. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCInMulticastPkts"; + } + + leaf in-discards { + type yang:counter32; + description + "The number of inbound packets that were chosen to be + discarded even though no errors had been detected to + prevent their being deliverable to a higher-layer + protocol. One possible reason for discarding such a + packet could be to free up buffer space. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + + + reference + "RFC 2863: The Interfaces Group MIB - ifInDiscards"; + } + + leaf in-errors { + type yang:counter32; + description + "For packet-oriented interfaces, the number of inbound + packets that contained errors preventing them from being + deliverable to a higher-layer protocol. For character- + oriented or fixed-length interfaces, the number of + inbound transmission units that contained errors + preventing them from being deliverable to a higher-layer + protocol. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInErrors"; + } + + leaf in-unknown-protos { + type yang:counter32; + description + "For packet-oriented interfaces, the number of packets + received via the interface that were discarded because + of an unknown or unsupported protocol. For + character-oriented or fixed-length interfaces that + support protocol multiplexing, the number of + transmission units received via the interface that were + discarded because of an unknown or unsupported protocol. + For any interface that does not support protocol + multiplexing, this counter is not present. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifInUnknownProtos"; + } + + + + + + leaf out-octets { + type yang:counter64; + description + "The total number of octets transmitted out of the + interface, including framing characters. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutOctets"; + } + + leaf out-unicast-pkts { + type yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted, and that were not addressed + to a multicast or broadcast address at this sub-layer, + including those that were discarded or not sent. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifHCOutUcastPkts"; + } + + leaf out-broadcast-pkts { + type yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted, and that were addressed to a + broadcast address at this sub-layer, including those + that were discarded or not sent. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCOutBroadcastPkts"; + } + + + leaf out-multicast-pkts { + type yang:counter64; + description + "The total number of packets that higher-level protocols + requested be transmitted, and that were addressed to a + multicast address at this sub-layer, including those + that were discarded or not sent. For a MAC-layer + protocol, this includes both Group and Functional + addresses. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - + ifHCOutMulticastPkts"; + } + + leaf out-discards { + type yang:counter32; + description + "The number of outbound packets that were chosen to be + discarded even though no errors had been detected to + prevent their being transmitted. One possible reason + for discarding such a packet could be to free up buffer + space. + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifOutDiscards"; + } + + leaf out-errors { + type yang:counter32; + description + "For packet-oriented interfaces, the number of outbound + packets that could not be transmitted because of errors. + For character-oriented or fixed-length interfaces, the + number of outbound transmission units that could not be + transmitted because of errors. + + + + + Discontinuities in the value of this counter can occur + at re-initialization of the management system, and at + other times as indicated by the value of + 'discontinuity-time'."; + reference + "RFC 2863: The Interfaces Group MIB - ifOutErrors"; + } + } + } + } +} diff --git a/tests/yanglint/modules/ietf-ip.yang b/tests/yanglint/modules/ietf-ip.yang new file mode 100644 index 0000000..1499120 --- /dev/null +++ b/tests/yanglint/modules/ietf-ip.yang @@ -0,0 +1,758 @@ +module ietf-ip { + + namespace "urn:ietf:params:xml:ns:yang:ietf-ip"; + prefix ip; + + import ietf-interfaces { + prefix if; + } + import ietf-inet-types { + prefix inet; + } + import ietf-yang-types { + prefix yang; + } + + organization + "IETF NETMOD (NETCONF Data Modeling Language) Working Group"; + + contact + "WG Web: <http://tools.ietf.org/wg/netmod/> + WG List: <mailto:netmod@ietf.org> + + WG Chair: Thomas Nadeau + <mailto:tnadeau@lucidvision.com> + + WG Chair: Juergen Schoenwaelder + <mailto:j.schoenwaelder@jacobs-university.de> + + Editor: Martin Bjorklund + <mailto:mbj@tail-f.com>"; + + + + + + + + + + + description + "This module contains a collection of YANG definitions for + configuring IP implementations. + + Copyright (c) 2014 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 7277; see + the RFC itself for full legal notices."; + + revision 2014-06-16 { + description + "Initial revision."; + reference + "RFC 7277: A YANG Data Model for IP Management"; + } + + /* + + * Features + */ + + feature ipv4-non-contiguous-netmasks { + description + "Indicates support for configuring non-contiguous + subnet masks."; + } + + feature ipv6-privacy-autoconf { + description + "Indicates support for Privacy Extensions for Stateless Address + Autoconfiguration in IPv6."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6"; + } + + + + + + /* + * Typedefs + */ + + typedef ip-address-origin { + type enumeration { + enum other { + description + "None of the following."; + } + enum static { + description + "Indicates that the address has been statically + configured - for example, using NETCONF or a Command Line + Interface."; + } + enum dhcp { + description + "Indicates an address that has been assigned to this + system by a DHCP server."; + } + enum link-layer { + description + "Indicates an address created by IPv6 stateless + autoconfiguration that embeds a link-layer address in its + interface identifier."; + } + enum random { + description + "Indicates an address chosen by the system at + + random, e.g., an IPv4 address within 169.254/16, an + RFC 4941 temporary address, or an RFC 7217 semantically + opaque address."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6 + RFC 7217: A Method for Generating Semantically Opaque + Interface Identifiers with IPv6 Stateless + Address Autoconfiguration (SLAAC)"; + } + } + description + "The origin of an address."; + } + + + + typedef neighbor-origin { + type enumeration { + enum other { + description + "None of the following."; + } + enum static { + description + "Indicates that the mapping has been statically + configured - for example, using NETCONF or a Command Line + Interface."; + } + enum dynamic { + description + "Indicates that the mapping has been dynamically resolved + using, e.g., IPv4 ARP or the IPv6 Neighbor Discovery + protocol."; + } + } + description + "The origin of a neighbor entry."; + } + + /* + * Configuration data nodes + */ + + augment "/if:interfaces/if:interface" { + description + "Parameters for configuring IP on interfaces. + + If an interface is not capable of running IP, the server + must not allow the client to configure these parameters."; + + container ipv4 { + presence + "Enables IPv4 unless the 'enabled' leaf + (which defaults to 'true') is set to 'false'"; + description + "Parameters for the IPv4 address family."; + + + + + + + + + leaf enabled { + type boolean; + default true; + description + "Controls whether IPv4 is enabled or disabled on this + interface. When IPv4 is enabled, this interface is + connected to an IPv4 stack, and the interface can send + and receive IPv4 packets."; + } + leaf forwarding { + type boolean; + default false; + description + "Controls IPv4 packet forwarding of datagrams received by, + but not addressed to, this interface. IPv4 routers + forward datagrams. IPv4 hosts do not (except those + source-routed via the host)."; + } + leaf mtu { + type uint16 { + range "68..max"; + } + units octets; + description + "The size, in octets, of the largest IPv4 packet that the + interface will send and receive. + + The server may restrict the allowed values for this leaf, + depending on the interface's type. + + If this leaf is not configured, the operationally used MTU + depends on the interface's type."; + reference + "RFC 791: Internet Protocol"; + } + list address { + key "ip"; + description + "The list of configured IPv4 addresses on the interface."; + + leaf ip { + type inet:ipv4-address-no-zone; + description + "The IPv4 address on the interface."; + } + + + + choice subnet { + mandatory true; + description + "The subnet can be specified as a prefix-length, or, + if the server supports non-contiguous netmasks, as + a netmask."; + leaf prefix-length { + type uint8 { + range "0..32"; + } + description + "The length of the subnet prefix."; + } + leaf netmask { + if-feature ipv4-non-contiguous-netmasks; + type yang:dotted-quad; + description + "The subnet specified as a netmask."; + } + } + } + list neighbor { + key "ip"; + description + "A list of mappings from IPv4 addresses to + link-layer addresses. + + Entries in this list are used as static entries in the + ARP Cache."; + reference + "RFC 826: An Ethernet Address Resolution Protocol"; + + leaf ip { + type inet:ipv4-address-no-zone; + description + "The IPv4 address of the neighbor node."; + } + leaf link-layer-address { + type yang:phys-address; + mandatory true; + description + "The link-layer address of the neighbor node."; + } + } + + } + + + container ipv6 { + presence + "Enables IPv6 unless the 'enabled' leaf + (which defaults to 'true') is set to 'false'"; + description + "Parameters for the IPv6 address family."; + + leaf enabled { + type boolean; + default true; + description + "Controls whether IPv6 is enabled or disabled on this + interface. When IPv6 is enabled, this interface is + connected to an IPv6 stack, and the interface can send + and receive IPv6 packets."; + } + leaf forwarding { + type boolean; + default false; + description + "Controls IPv6 packet forwarding of datagrams received by, + but not addressed to, this interface. IPv6 routers + forward datagrams. IPv6 hosts do not (except those + source-routed via the host)."; + reference + "RFC 4861: Neighbor Discovery for IP version 6 (IPv6) + Section 6.2.1, IsRouter"; + } + leaf mtu { + type uint32 { + range "1280..max"; + } + units octets; + description + "The size, in octets, of the largest IPv6 packet that the + interface will send and receive. + + The server may restrict the allowed values for this leaf, + depending on the interface's type. + + If this leaf is not configured, the operationally used MTU + depends on the interface's type."; + reference + "RFC 2460: Internet Protocol, Version 6 (IPv6) Specification + Section 5"; + } + + + list address { + key "ip"; + description + "The list of configured IPv6 addresses on the interface."; + + leaf ip { + type inet:ipv6-address-no-zone; + description + "The IPv6 address on the interface."; + } + leaf prefix-length { + type uint8 { + range "0..128"; + } + mandatory true; + description + "The length of the subnet prefix."; + } + } + list neighbor { + key "ip"; + description + "A list of mappings from IPv6 addresses to + link-layer addresses. + + Entries in this list are used as static entries in the + Neighbor Cache."; + reference + "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)"; + + leaf ip { + type inet:ipv6-address-no-zone; + description + "The IPv6 address of the neighbor node."; + } + leaf link-layer-address { + type yang:phys-address; + mandatory true; + description + "The link-layer address of the neighbor node."; + } + } + + + + + + + leaf dup-addr-detect-transmits { + type uint32; + default 1; + description + "The number of consecutive Neighbor Solicitation messages + sent while performing Duplicate Address Detection on a + tentative address. A value of zero indicates that + Duplicate Address Detection is not performed on + tentative addresses. A value of one indicates a single + transmission with no follow-up retransmissions."; + reference + "RFC 4862: IPv6 Stateless Address Autoconfiguration"; + } + container autoconf { + description + "Parameters to control the autoconfiguration of IPv6 + addresses, as described in RFC 4862."; + reference + "RFC 4862: IPv6 Stateless Address Autoconfiguration"; + + leaf create-global-addresses { + type boolean; + default true; + description + "If enabled, the host creates global addresses as + described in RFC 4862."; + reference + "RFC 4862: IPv6 Stateless Address Autoconfiguration + Section 5.5"; + } + leaf create-temporary-addresses { + if-feature ipv6-privacy-autoconf; + type boolean; + default false; + description + "If enabled, the host creates temporary addresses as + described in RFC 4941."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6"; + } + + + + + + + + leaf temporary-valid-lifetime { + if-feature ipv6-privacy-autoconf; + type uint32; + units "seconds"; + default 604800; + description + "The time period during which the temporary address + is valid."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6 + - TEMP_VALID_LIFETIME"; + } + leaf temporary-preferred-lifetime { + if-feature ipv6-privacy-autoconf; + type uint32; + units "seconds"; + default 86400; + description + "The time period during which the temporary address is + preferred."; + reference + "RFC 4941: Privacy Extensions for Stateless Address + Autoconfiguration in IPv6 + - TEMP_PREFERRED_LIFETIME"; + } + } + } + } + + /* + * Operational state data nodes + */ + + augment "/if:interfaces-state/if:interface" { + description + "Data nodes for the operational state of IP on interfaces."; + + container ipv4 { + presence "Present if IPv4 is enabled on this interface"; + config false; + description + "Interface-specific parameters for the IPv4 address family."; + + + + + + leaf forwarding { + type boolean; + description + "Indicates whether IPv4 packet forwarding is enabled or + disabled on this interface."; + } + leaf mtu { + type uint16 { + range "68..max"; + } + units octets; + description + "The size, in octets, of the largest IPv4 packet that the + interface will send and receive."; + reference + "RFC 791: Internet Protocol"; + } + list address { + key "ip"; + description + "The list of IPv4 addresses on the interface."; + + leaf ip { + type inet:ipv4-address-no-zone; + description + "The IPv4 address on the interface."; + } + choice subnet { + description + "The subnet can be specified as a prefix-length, or, + if the server supports non-contiguous netmasks, as + a netmask."; + leaf prefix-length { + type uint8 { + range "0..32"; + } + description + "The length of the subnet prefix."; + } + leaf netmask { + if-feature ipv4-non-contiguous-netmasks; + type yang:dotted-quad; + description + "The subnet specified as a netmask."; + } + } + + + leaf origin { + type ip-address-origin; + description + "The origin of this address."; + } + } + list neighbor { + key "ip"; + description + "A list of mappings from IPv4 addresses to + link-layer addresses. + + This list represents the ARP Cache."; + reference + "RFC 826: An Ethernet Address Resolution Protocol"; + + leaf ip { + type inet:ipv4-address-no-zone; + description + "The IPv4 address of the neighbor node."; + } + leaf link-layer-address { + type yang:phys-address; + description + "The link-layer address of the neighbor node."; + } + leaf origin { + type neighbor-origin; + description + "The origin of this neighbor entry."; + } + } + + } + + container ipv6 { + presence "Present if IPv6 is enabled on this interface"; + config false; + description + "Parameters for the IPv6 address family."; + + + + + + + + + leaf forwarding { + type boolean; + default false; + description + "Indicates whether IPv6 packet forwarding is enabled or + disabled on this interface."; + reference + "RFC 4861: Neighbor Discovery for IP version 6 (IPv6) + Section 6.2.1, IsRouter"; + } + leaf mtu { + type uint32 { + range "1280..max"; + } + units octets; + description + "The size, in octets, of the largest IPv6 packet that the + interface will send and receive."; + reference + "RFC 2460: Internet Protocol, Version 6 (IPv6) Specification + Section 5"; + } + list address { + key "ip"; + description + "The list of IPv6 addresses on the interface."; + + leaf ip { + type inet:ipv6-address-no-zone; + description + "The IPv6 address on the interface."; + } + leaf prefix-length { + type uint8 { + range "0..128"; + } + mandatory true; + description + "The length of the subnet prefix."; + } + leaf origin { + type ip-address-origin; + description + "The origin of this address."; + } + + + + leaf status { + type enumeration { + enum preferred { + description + "This is a valid address that can appear as the + destination or source address of a packet."; + } + enum deprecated { + description + "This is a valid but deprecated address that should + no longer be used as a source address in new + communications, but packets addressed to such an + address are processed as expected."; + } + enum invalid { + description + "This isn't a valid address, and it shouldn't appear + as the destination or source address of a packet."; + } + enum inaccessible { + description + "The address is not accessible because the interface + to which this address is assigned is not + operational."; + } + enum unknown { + description + "The status cannot be determined for some reason."; + } + enum tentative { + description + "The uniqueness of the address on the link is being + verified. Addresses in this state should not be + used for general communication and should only be + used to determine the uniqueness of the address."; + } + enum duplicate { + description + "The address has been determined to be non-unique on + the link and so must not be used."; + } + + + + + + + + enum optimistic { + description + "The address is available for use, subject to + restrictions, while its uniqueness on a link is + being verified."; + } + } + description + "The status of an address. Most of the states correspond + to states from the IPv6 Stateless Address + Autoconfiguration protocol."; + reference + "RFC 4293: Management Information Base for the + Internet Protocol (IP) + - IpAddressStatusTC + RFC 4862: IPv6 Stateless Address Autoconfiguration"; + } + } + list neighbor { + key "ip"; + description + "A list of mappings from IPv6 addresses to + link-layer addresses. + + This list represents the Neighbor Cache."; + reference + "RFC 4861: Neighbor Discovery for IP version 6 (IPv6)"; + + leaf ip { + type inet:ipv6-address-no-zone; + description + "The IPv6 address of the neighbor node."; + } + leaf link-layer-address { + type yang:phys-address; + description + "The link-layer address of the neighbor node."; + } + leaf origin { + type neighbor-origin; + description + "The origin of this neighbor entry."; + } + leaf is-router { + type empty; + description + "Indicates that the neighbor node acts as a router."; + } + leaf state { + type enumeration { + enum incomplete { + description + "Address resolution is in progress, and the link-layer + address of the neighbor has not yet been + determined."; + } + enum reachable { + description + "Roughly speaking, the neighbor is known to have been + reachable recently (within tens of seconds ago)."; + } + enum stale { + description + "The neighbor is no longer known to be reachable, but + until traffic is sent to the neighbor no attempt + should be made to verify its reachability."; + } + enum delay { + description + "The neighbor is no longer known to be reachable, and + traffic has recently been sent to the neighbor. + Rather than probe the neighbor immediately, however, + delay sending probes for a short while in order to + give upper-layer protocols a chance to provide + reachability confirmation."; + } + enum probe { + description + "The neighbor is no longer known to be reachable, and + unicast Neighbor Solicitation probes are being sent + to verify reachability."; + } + } + description + "The Neighbor Unreachability Detection state of this + entry."; + reference + "RFC 4861: Neighbor Discovery for IP version 6 (IPv6) + Section 7.3.2"; + } + } + } + } +} diff --git a/tests/yanglint/modules/ietf-netconf-acm.yang b/tests/yanglint/modules/ietf-netconf-acm.yang new file mode 100644 index 0000000..d372fa0 --- /dev/null +++ b/tests/yanglint/modules/ietf-netconf-acm.yang @@ -0,0 +1,411 @@ +module ietf-netconf-acm { + namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-acm"; + prefix nacm; + + import ietf-yang-types { + prefix yang; + } + + organization + "IETF NETCONF (Network Configuration) Working Group"; + contact + "WG Web: <http://tools.ietf.org/wg/netconf/> + WG List: <mailto:netconf@ietf.org> + + WG Chair: Mehmet Ersue + <mailto:mehmet.ersue@nsn.com> + + WG Chair: Bert Wijnen + <mailto:bertietf@bwijnen.net> + + Editor: Andy Bierman + <mailto:andy@yumaworks.com> + + Editor: Martin Bjorklund + <mailto:mbj@tail-f.com>"; + description + "NETCONF Access Control Model. + + Copyright (c) 2012 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD + License set forth in Section 4.c of the IETF Trust's + Legal Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 6536; see + the RFC itself for full legal notices."; + + revision 2012-02-22 { + description + "Initial version"; + reference + "RFC 6536: Network Configuration Protocol (NETCONF) + Access Control Model"; + } + + extension default-deny-write { + description + "Used to indicate that the data model node + represents a sensitive security system parameter. + + If present, and the NACM module is enabled (i.e., + /nacm/enable-nacm object equals 'true'), the NETCONF server + will only allow the designated 'recovery session' to have + write access to the node. An explicit access control rule is + required for all other users. + + The 'default-deny-write' extension MAY appear within a data + definition statement. It is ignored otherwise."; + } + + extension default-deny-all { + description + "Used to indicate that the data model node + controls a very sensitive security system parameter. + + If present, and the NACM module is enabled (i.e., + /nacm/enable-nacm object equals 'true'), the NETCONF server + will only allow the designated 'recovery session' to have + read, write, or execute access to the node. An explicit + access control rule is required for all other users. + + The 'default-deny-all' extension MAY appear within a data + definition statement, 'rpc' statement, or 'notification' + statement. It is ignored otherwise."; + } + + typedef user-name-type { + type string { + length "1..max"; + } + description + "General Purpose Username string."; + } + + typedef matchall-string-type { + type string { + pattern "\\*"; + } + description + "The string containing a single asterisk '*' is used + to conceptually represent all possible values + for the particular leaf using this data type."; + } + + typedef access-operations-type { + type bits { + bit create { + description + "Any protocol operation that creates a + new data node."; + } + bit read { + description + "Any protocol operation or notification that + returns the value of a data node."; + } + bit update { + description + "Any protocol operation that alters an existing + data node."; + } + bit delete { + description + "Any protocol operation that removes a data node."; + } + bit exec { + description + "Execution access to the specified protocol operation."; + } + } + description + "NETCONF Access Operation."; + } + + typedef group-name-type { + type string { + length "1..max"; + pattern "[^\\*].*"; + } + description + "Name of administrative group to which + users can be assigned."; + } + + typedef action-type { + type enumeration { + enum "permit" { + description + "Requested action is permitted."; + } + enum "deny" { + description + "Requested action is denied."; + } + } + description + "Action taken by the server when a particular + rule matches."; + } + + typedef node-instance-identifier { + type yang:xpath1.0; + description + "Path expression used to represent a special + data node instance identifier string. + + A node-instance-identifier value is an + unrestricted YANG instance-identifier expression. + All the same rules as an instance-identifier apply + except predicates for keys are optional. If a key + predicate is missing, then the node-instance-identifier + represents all possible server instances for that key. + + This XPath expression is evaluated in the following context: + + o The set of namespace declarations are those in scope on + the leaf element where this type is used. + + o The set of variable bindings contains one variable, + 'USER', which contains the name of the user of the current + session. + + o The function library is the core function library, but + note that due to the syntax restrictions of an + instance-identifier, no functions are allowed. + + o The context node is the root node in the data tree."; + } + + container nacm { + nacm:default-deny-all; + description + "Parameters for NETCONF Access Control Model."; + leaf enable-nacm { + type boolean; + default "true"; + description + "Enables or disables all NETCONF access control + enforcement. If 'true', then enforcement + is enabled. If 'false', then enforcement + is disabled."; + } + leaf read-default { + type action-type; + default "permit"; + description + "Controls whether read access is granted if + no appropriate rule is found for a + particular read request."; + } + leaf write-default { + type action-type; + default "deny"; + description + "Controls whether create, update, or delete access + is granted if no appropriate rule is found for a + particular write request."; + } + leaf exec-default { + type action-type; + default "permit"; + description + "Controls whether exec access is granted if no appropriate + rule is found for a particular protocol operation request."; + } + leaf enable-external-groups { + type boolean; + default "true"; + description + "Controls whether the server uses the groups reported by the + NETCONF transport layer when it assigns the user to a set of + NACM groups. If this leaf has the value 'false', any group + names reported by the transport layer are ignored by the + server."; + } + leaf denied-operations { + type yang:zero-based-counter32; + config false; + mandatory true; + description + "Number of times since the server last restarted that a + protocol operation request was denied."; + } + leaf denied-data-writes { + type yang:zero-based-counter32; + config false; + mandatory true; + description + "Number of times since the server last restarted that a + protocol operation request to alter + a configuration datastore was denied."; + } + leaf denied-notifications { + type yang:zero-based-counter32; + config false; + mandatory true; + description + "Number of times since the server last restarted that + a notification was dropped for a subscription because + access to the event type was denied."; + } + container groups { + description + "NETCONF Access Control Groups."; + list group { + key "name"; + description + "One NACM Group Entry. This list will only contain + configured entries, not any entries learned from + any transport protocols."; + leaf name { + type group-name-type; + description + "Group name associated with this entry."; + } + leaf-list user-name { + type user-name-type; + description + "Each entry identifies the username of + a member of the group associated with + this entry."; + } + } + } + list rule-list { + key "name"; + ordered-by user; + description + "An ordered collection of access control rules."; + leaf name { + type string { + length "1..max"; + } + description + "Arbitrary name assigned to the rule-list."; + } + leaf-list group { + type union { + type matchall-string-type; + type group-name-type; + } + description + "List of administrative groups that will be + assigned the associated access rights + defined by the 'rule' list. + + The string '*' indicates that all groups apply to the + entry."; + } + list rule { + key "name"; + ordered-by user; + description + "One access control rule. + + Rules are processed in user-defined order until a match is + found. A rule matches if 'module-name', 'rule-type', and + 'access-operations' match the request. If a rule + matches, the 'action' leaf determines if access is granted + or not."; + leaf name { + type string { + length "1..max"; + } + description + "Arbitrary name assigned to the rule."; + } + leaf module-name { + type union { + type matchall-string-type; + type string; + } + default "*"; + description + "Name of the module associated with this rule. + + This leaf matches if it has the value '*' or if the + object being accessed is defined in the module with the + specified module name."; + } + choice rule-type { + description + "This choice matches if all leafs present in the rule + match the request. If no leafs are present, the + choice matches all requests."; + case protocol-operation { + leaf rpc-name { + type union { + type matchall-string-type; + type string; + } + description + "This leaf matches if it has the value '*' or if + its value equals the requested protocol operation + name."; + } + } + case notification { + leaf notification-name { + type union { + type matchall-string-type; + type string; + } + description + "This leaf matches if it has the value '*' or if its + value equals the requested notification name."; + } + } + case data-node { + leaf path { + type node-instance-identifier; + mandatory true; + description + "Data Node Instance Identifier associated with the + data node controlled by this rule. + + Configuration data or state data instance + identifiers start with a top-level data node. A + complete instance identifier is required for this + type of path value. + + The special value '/' refers to all possible + datastore contents."; + } + } + } + leaf access-operations { + type union { + type matchall-string-type; + type access-operations-type; + } + default "*"; + description + "Access operations associated with this rule. + + This leaf matches if it has the value '*' or if the + bit corresponding to the requested operation is set."; + } + leaf action { + type action-type; + mandatory true; + description + "The access control action associated with the + rule. If a rule is determined to match a + particular request, then this object is used + to determine whether to permit or deny the + request."; + } + leaf comment { + type string; + description + "A textual description of the access rule."; + } + } + } + } +} diff --git a/tests/yanglint/modules/ietf-netconf-with-defaults@2011-06-01.yang b/tests/yanglint/modules/ietf-netconf-with-defaults@2011-06-01.yang new file mode 100644 index 0000000..e19d2b3 --- /dev/null +++ b/tests/yanglint/modules/ietf-netconf-with-defaults@2011-06-01.yang @@ -0,0 +1,140 @@ +module ietf-netconf-with-defaults { + + namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults"; + + prefix ncwd; + + import ietf-netconf { prefix nc; } + + organization + "IETF NETCONF (Network Configuration Protocol) Working Group"; + + contact + "WG Web: <http://tools.ietf.org/wg/netconf/> + + WG List: <netconf@ietf.org> + + WG Chair: Bert Wijnen + <bertietf@bwijnen.net> + + WG Chair: Mehmet Ersue + <mehmet.ersue@nsn.com> + + Editor: Andy Bierman + <andy.bierman@brocade.com> + + Editor: Balazs Lengyel + <balazs.lengyel@ericsson.com>"; + + description + "This module defines an extension to the NETCONF protocol + that allows the NETCONF client to control how default + values are handled by the server in particular NETCONF + operations. + + Copyright (c) 2011 IETF Trust and the persons identified as + the document authors. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 6243; see + the RFC itself for full legal notices."; + + revision 2011-06-01 { + description + "Initial version."; + reference + "RFC 6243: With-defaults Capability for NETCONF"; + } + + typedef with-defaults-mode { + description + "Possible modes to report default data."; + reference + "RFC 6243; Section 3."; + type enumeration { + enum report-all { + description + "All default data is reported."; + reference + "RFC 6243; Section 3.1"; + } + enum report-all-tagged { + description + "All default data is reported. + Any nodes considered to be default data + will contain a 'default' XML attribute, + set to 'true' or '1'."; + reference + "RFC 6243; Section 3.4"; + } + enum trim { + description + "Values are not reported if they contain the default."; + reference + "RFC 6243; Section 3.2"; + } + enum explicit { + description + "Report values that contain the definition of + explicitly set data."; + reference + "RFC 6243; Section 3.3"; + } + } + } + + grouping with-defaults-parameters { + description + "Contains the <with-defaults> parameter for control + of defaults in NETCONF retrieval operations."; + + leaf with-defaults { + description + "The explicit defaults processing mode requested."; + reference + "RFC 6243; Section 4.5.1"; + + type with-defaults-mode; + } + } + + // extending the get-config operation + augment /nc:get-config/nc:input { + description + "Adds the <with-defaults> parameter to the + input of the NETCONF <get-config> operation."; + reference + "RFC 6243; Section 4.5.1"; + + uses with-defaults-parameters; + } + + // extending the get operation + augment /nc:get/nc:input { + description + "Adds the <with-defaults> parameter to + the input of the NETCONF <get> operation."; + reference + "RFC 6243; Section 4.5.1"; + + uses with-defaults-parameters; + } + + // extending the copy-config operation + augment /nc:copy-config/nc:input { + description + "Adds the <with-defaults> parameter to + the input of the NETCONF <copy-config> operation."; + reference + "RFC 6243; Section 4.5.1"; + + uses with-defaults-parameters; + } + +} diff --git a/tests/yanglint/modules/ietf-netconf@2011-06-01.yang b/tests/yanglint/modules/ietf-netconf@2011-06-01.yang new file mode 100644 index 0000000..3053db2 --- /dev/null +++ b/tests/yanglint/modules/ietf-netconf@2011-06-01.yang @@ -0,0 +1,934 @@ +module ietf-netconf { + + // the namespace for NETCONF XML definitions is unchanged + // from RFC 4741, which this document replaces + namespace "urn:ietf:params:xml:ns:netconf:base:1.0"; + + prefix nc; + + import ietf-inet-types { + prefix inet; + } + + import ietf-netconf-acm { prefix nacm; } + + organization + "IETF NETCONF (Network Configuration) Working Group"; + + contact + "WG Web: <http://tools.ietf.org/wg/netconf/> + WG List: <netconf@ietf.org> + + WG Chair: Bert Wijnen + <bertietf@bwijnen.net> + + WG Chair: Mehmet Ersue + <mehmet.ersue@nsn.com> + + Editor: Martin Bjorklund + <mbj@tail-f.com> + + Editor: Juergen Schoenwaelder + <j.schoenwaelder@jacobs-university.de> + + Editor: Andy Bierman + <andy.bierman@brocade.com>"; + description + "NETCONF Protocol Data Types and Protocol Operations. + + Copyright (c) 2011 IETF Trust and the persons identified as + the document authors. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 6241; see + the RFC itself for full legal notices."; + + revision 2011-06-01 { + description + "Initial revision; + 2013-09-29: Updated to include NACM attributes, + as specified in RFC 6536: sec 3.2.5 and 3.2.8"; + reference + "RFC 6241: Network Configuration Protocol"; + } + + extension get-filter-element-attributes { + description + "If this extension is present within an 'anyxml' + statement named 'filter', which must be conceptually + defined within the RPC input section for the <get> + and <get-config> protocol operations, then the + following unqualified XML attribute is supported + within the <filter> element, within a <get> or + <get-config> protocol operation: + + type : optional attribute with allowed + value strings 'subtree' and 'xpath'. + If missing, the default value is 'subtree'. + + If the 'xpath' feature is supported, then the + following unqualified XML attribute is + also supported: + + select: optional attribute containing a + string representing an XPath expression. + The 'type' attribute must be equal to 'xpath' + if this attribute is present."; + } + + // NETCONF capabilities defined as features + feature writable-running { + description + "NETCONF :writable-running capability; + If the server advertises the :writable-running + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.2"; + } + + feature candidate { + description + "NETCONF :candidate capability; + If the server advertises the :candidate + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.3"; + } + + feature confirmed-commit { + if-feature candidate; + description + "NETCONF :confirmed-commit:1.1 capability; + If the server advertises the :confirmed-commit:1.1 + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + + reference "RFC 6241, Section 8.4"; + } + + feature rollback-on-error { + description + "NETCONF :rollback-on-error capability; + If the server advertises the :rollback-on-error + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.5"; + } + + feature validate { + description + "NETCONF :validate:1.1 capability; + If the server advertises the :validate:1.1 + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.6"; + } + + feature startup { + description + "NETCONF :startup capability; + If the server advertises the :startup + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.7"; + } + + feature url { + description + "NETCONF :url capability; + If the server advertises the :url + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.8"; + } + + feature xpath { + description + "NETCONF :xpath capability; + If the server advertises the :xpath + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.9"; + } + + // NETCONF Simple Types + + typedef session-id-type { + type uint32 { + range "1..max"; + } + description + "NETCONF Session Id"; + } + + typedef session-id-or-zero-type { + type uint32; + description + "NETCONF Session Id or Zero to indicate none"; + } + typedef error-tag-type { + type enumeration { + enum in-use { + description + "The request requires a resource that + already is in use."; + } + enum invalid-value { + description + "The request specifies an unacceptable value for one + or more parameters."; + } + enum too-big { + description + "The request or response (that would be generated) is + too large for the implementation to handle."; + } + enum missing-attribute { + description + "An expected attribute is missing."; + } + enum bad-attribute { + description + "An attribute value is not correct; e.g., wrong type, + out of range, pattern mismatch."; + } + enum unknown-attribute { + description + "An unexpected attribute is present."; + } + enum missing-element { + description + "An expected element is missing."; + } + enum bad-element { + description + "An element value is not correct; e.g., wrong type, + out of range, pattern mismatch."; + } + enum unknown-element { + description + "An unexpected element is present."; + } + enum unknown-namespace { + description + "An unexpected namespace is present."; + } + enum access-denied { + description + "Access to the requested protocol operation or + data model is denied because authorization failed."; + } + enum lock-denied { + description + "Access to the requested lock is denied because the + lock is currently held by another entity."; + } + enum resource-denied { + description + "Request could not be completed because of + insufficient resources."; + } + enum rollback-failed { + description + "Request to roll back some configuration change (via + rollback-on-error or <discard-changes> operations) + was not completed for some reason."; + + } + enum data-exists { + description + "Request could not be completed because the relevant + data model content already exists. For example, + a 'create' operation was attempted on data that + already exists."; + } + enum data-missing { + description + "Request could not be completed because the relevant + data model content does not exist. For example, + a 'delete' operation was attempted on + data that does not exist."; + } + enum operation-not-supported { + description + "Request could not be completed because the requested + operation is not supported by this implementation."; + } + enum operation-failed { + description + "Request could not be completed because the requested + operation failed for some reason not covered by + any other error condition."; + } + enum partial-operation { + description + "This error-tag is obsolete, and SHOULD NOT be sent + by servers conforming to this document."; + } + enum malformed-message { + description + "A message could not be handled because it failed to + be parsed correctly. For example, the message is not + well-formed XML or it uses an invalid character set."; + } + } + description "NETCONF Error Tag"; + reference "RFC 6241, Appendix A"; + } + + typedef error-severity-type { + type enumeration { + enum error { + description "Error severity"; + } + enum warning { + description "Warning severity"; + } + } + description "NETCONF Error Severity"; + reference "RFC 6241, Section 4.3"; + } + + typedef edit-operation-type { + type enumeration { + enum merge { + description + "The configuration data identified by the + element containing this attribute is merged + with the configuration at the corresponding + level in the configuration datastore identified + by the target parameter."; + } + enum replace { + description + "The configuration data identified by the element + containing this attribute replaces any related + configuration in the configuration datastore + identified by the target parameter. If no such + configuration data exists in the configuration + datastore, it is created. Unlike a + <copy-config> operation, which replaces the + entire target configuration, only the configuration + actually present in the config parameter is affected."; + } + enum create { + description + "The configuration data identified by the element + containing this attribute is added to the + configuration if and only if the configuration + data does not already exist in the configuration + datastore. If the configuration data exists, an + <rpc-error> element is returned with an + <error-tag> value of 'data-exists'."; + } + enum delete { + description + "The configuration data identified by the element + containing this attribute is deleted from the + configuration if and only if the configuration + data currently exists in the configuration + datastore. If the configuration data does not + exist, an <rpc-error> element is returned with + an <error-tag> value of 'data-missing'."; + } + enum remove { + description + "The configuration data identified by the element + containing this attribute is deleted from the + configuration if the configuration + data currently exists in the configuration + datastore. If the configuration data does not + exist, the 'remove' operation is silently ignored + by the server."; + } + } + default "merge"; + description "NETCONF 'operation' attribute values"; + reference "RFC 6241, Section 7.2"; + } + + // NETCONF Standard Protocol Operations + + rpc get-config { + description + "Retrieve all or part of a specified configuration."; + + reference "RFC 6241, Section 7.1"; + + input { + container source { + description + "Particular configuration to retrieve."; + + choice config-source { + mandatory true; + description + "The configuration to retrieve."; + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config source."; + } + leaf running { + type empty; + description + "The running configuration is the config source."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config source. + This is optional-to-implement on the server because + not all servers will support filtering for this + datastore."; + } + } + } + + anyxml filter { + description + "Subtree or XPath filter to use."; + nc:get-filter-element-attributes; + } + } + + output { + anyxml data { + description + "Copy of the source datastore subset that matched + the filter criteria (if any). An empty data container + indicates that the request did not produce any results."; + } + } + } + + rpc edit-config { + description + "The <edit-config> operation loads all or part of a specified + configuration to the specified target configuration."; + + reference "RFC 6241, Section 7.2"; + + input { + container target { + description + "Particular configuration to edit."; + + choice config-target { + mandatory true; + description + "The configuration target."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config target."; + } + leaf running { + if-feature writable-running; + type empty; + description + "The running configuration is the config source."; + } + } + } + + leaf default-operation { + type enumeration { + enum merge { + description + "The default operation is merge."; + } + enum replace { + description + "The default operation is replace."; + } + enum none { + description + "There is no default operation."; + } + } + default "merge"; + description + "The default operation to use."; + } + + leaf test-option { + if-feature validate; + type enumeration { + enum test-then-set { + description + "The server will test and then set if no errors."; + } + enum set { + description + "The server will set without a test first."; + } + + enum test-only { + description + "The server will only test and not set, even + if there are no errors."; + } + } + default "test-then-set"; + description + "The test option to use."; + } + + leaf error-option { + type enumeration { + enum stop-on-error { + description + "The server will stop on errors."; + } + enum continue-on-error { + description + "The server may continue on errors."; + } + enum rollback-on-error { + description + "The server will roll back on errors. + This value can only be used if the 'rollback-on-error' + feature is supported."; + } + } + default "stop-on-error"; + description + "The error option to use."; + } + + choice edit-content { + mandatory true; + description + "The content for the edit operation."; + + anyxml config { + description + "Inline Config content."; + } + leaf url { + if-feature url; + type inet:uri; + description + "URL-based config content."; + } + } + } + } + + rpc copy-config { + description + "Create or replace an entire configuration datastore with the + contents of another complete configuration datastore."; + + reference "RFC 6241, Section 7.3"; + + input { + container target { + description + "Particular configuration to copy to."; + + choice config-target { + mandatory true; + description + "The configuration target of the copy operation."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config target."; + } + leaf running { + if-feature writable-running; + type empty; + description + "The running configuration is the config target. + This is optional-to-implement on the server."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config target."; + } + leaf url { + if-feature url; + type inet:uri; + description + "The URL-based configuration is the config target."; + } + } + } + + container source { + description + "Particular configuration to copy from."; + + choice config-source { + mandatory true; + description + "The configuration source for the copy operation."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config source."; + } + leaf running { + type empty; + description + "The running configuration is the config source."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config source."; + } + leaf url { + if-feature url; + type inet:uri; + description + "The URL-based configuration is the config source."; + } + anyxml config { + description + "Inline Config content: <config> element. Represents + an entire configuration datastore, not + a subset of the running datastore."; + } + } + } + } + } + + rpc delete-config { + nacm:default-deny-all; + description + "Delete a configuration datastore."; + + reference "RFC 6241, Section 7.4"; + + input { + container target { + description + "Particular configuration to delete."; + + choice config-target { + mandatory true; + description + "The configuration target to delete."; + + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config target."; + } + leaf url { + if-feature url; + type inet:uri; + description + "The URL-based configuration is the config target."; + } + } + } + } + } + + rpc lock { + description + "The lock operation allows the client to lock the configuration + system of a device."; + + reference "RFC 6241, Section 7.5"; + + input { + container target { + description + "Particular configuration to lock."; + + choice config-target { + mandatory true; + description + "The configuration target to lock."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config target."; + } + leaf running { + type empty; + description + "The running configuration is the config target."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config target."; + } + } + } + } + } + + rpc unlock { + description + "The unlock operation is used to release a configuration lock, + previously obtained with the 'lock' operation."; + + reference "RFC 6241, Section 7.6"; + + input { + container target { + description + "Particular configuration to unlock."; + + choice config-target { + mandatory true; + description + "The configuration target to unlock."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config target."; + } + leaf running { + type empty; + description + "The running configuration is the config target."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config target."; + } + } + } + } + } + + rpc get { + description + "Retrieve running configuration and device state information."; + + reference "RFC 6241, Section 7.7"; + + input { + anyxml filter { + description + "This parameter specifies the portion of the system + configuration and state data to retrieve."; + nc:get-filter-element-attributes; + } + } + + output { + anyxml data { + description + "Copy of the running datastore subset and/or state + data that matched the filter criteria (if any). + An empty data container indicates that the request did not + produce any results."; + } + } + } + + rpc close-session { + description + "Request graceful termination of a NETCONF session."; + + reference "RFC 6241, Section 7.8"; + } + + rpc kill-session { + nacm:default-deny-all; + description + "Force the termination of a NETCONF session."; + + reference "RFC 6241, Section 7.9"; + + input { + leaf session-id { + type session-id-type; + mandatory true; + description + "Particular session to kill."; + } + } + } + + rpc commit { + if-feature candidate; + + description + "Commit the candidate configuration as the device's new + current configuration."; + + reference "RFC 6241, Section 8.3.4.1"; + + input { + leaf confirmed { + if-feature confirmed-commit; + type empty; + description + "Requests a confirmed commit."; + reference "RFC 6241, Section 8.3.4.1"; + } + + leaf confirm-timeout { + if-feature confirmed-commit; + type uint32 { + range "1..max"; + } + units "seconds"; + default "600"; // 10 minutes + description + "The timeout interval for a confirmed commit."; + reference "RFC 6241, Section 8.3.4.1"; + } + + leaf persist { + if-feature confirmed-commit; + type string; + description + "This parameter is used to make a confirmed commit + persistent. A persistent confirmed commit is not aborted + if the NETCONF session terminates. The only way to abort + a persistent confirmed commit is to let the timer expire, + or to use the <cancel-commit> operation. + + The value of this parameter is a token that must be given + in the 'persist-id' parameter of <commit> or + <cancel-commit> operations in order to confirm or cancel + the persistent confirmed commit. + + The token should be a random string."; + reference "RFC 6241, Section 8.3.4.1"; + } + + leaf persist-id { + if-feature confirmed-commit; + type string; + description + "This parameter is given in order to commit a persistent + confirmed commit. The value must be equal to the value + given in the 'persist' parameter to the <commit> operation. + If it does not match, the operation fails with an + 'invalid-value' error."; + reference "RFC 6241, Section 8.3.4.1"; + } + + } + } + + rpc discard-changes { + if-feature candidate; + + description + "Revert the candidate configuration to the current + running configuration."; + reference "RFC 6241, Section 8.3.4.2"; + } + + rpc cancel-commit { + if-feature confirmed-commit; + description + "This operation is used to cancel an ongoing confirmed commit. + If the confirmed commit is persistent, the parameter + 'persist-id' must be given, and it must match the value of the + 'persist' parameter."; + reference "RFC 6241, Section 8.4.4.1"; + + input { + leaf persist-id { + type string; + description + "This parameter is given in order to cancel a persistent + confirmed commit. The value must be equal to the value + given in the 'persist' parameter to the <commit> operation. + If it does not match, the operation fails with an + 'invalid-value' error."; + } + } + } + + rpc validate { + if-feature validate; + + description + "Validates the contents of the specified configuration."; + + reference "RFC 6241, Section 8.6.4.1"; + + input { + container source { + description + "Particular configuration to validate."; + + choice config-source { + mandatory true; + description + "The configuration source to validate."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config source."; + } + leaf running { + type empty; + description + "The running configuration is the config source."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config source."; + } + leaf url { + if-feature url; + type inet:uri; + description + "The URL-based configuration is the config source."; + } + anyxml config { + description + "Inline Config content: <config> element. Represents + an entire configuration datastore, not + a subset of the running datastore."; + } + } + } + } + } + +} diff --git a/tests/yanglint/modules/modaction.yang b/tests/yanglint/modules/modaction.yang new file mode 100644 index 0000000..5a3f92f --- /dev/null +++ b/tests/yanglint/modules/modaction.yang @@ -0,0 +1,26 @@ +module modaction { + yang-version 1.1; + namespace "urn:yanglint:modaction"; + prefix ma; + + container con { + list ls { + key "lfkey"; + leaf lfkey { + type string; + } + action act { + input { + leaf lfi { + type string; + } + } + output { + leaf lfo { + type int16; + } + } + } + } + } +} diff --git a/tests/yanglint/modules/modconfig-augment.yang b/tests/yanglint/modules/modconfig-augment.yang new file mode 100644 index 0000000..d94b366 --- /dev/null +++ b/tests/yanglint/modules/modconfig-augment.yang @@ -0,0 +1,15 @@ +module modconfig-augment { + yang-version 1.1; + namespace "urn:yanglint:modconfig-augment"; + prefix "mca"; + + import modconfig { + prefix mc; + } + + augment "/mc:mcc" { + leaf alf { + type string; + } + } +} diff --git a/tests/yanglint/modules/modconfig.yang b/tests/yanglint/modules/modconfig.yang new file mode 100644 index 0000000..1d12ca6 --- /dev/null +++ b/tests/yanglint/modules/modconfig.yang @@ -0,0 +1,17 @@ +module modconfig { + namespace "urn:yanglint:modconfig"; + prefix mc; + + container mcc { + leaf lft { + type string; + config true; + mandatory true; + } + leaf lff { + type string; + config false; + mandatory true; + } + } +} diff --git a/tests/yanglint/modules/moddatanodes.yang b/tests/yanglint/modules/moddatanodes.yang new file mode 100644 index 0000000..ae4ab20 --- /dev/null +++ b/tests/yanglint/modules/moddatanodes.yang @@ -0,0 +1,31 @@ +module moddatanodes { + yang-version 1.1; + namespace "urn:yanglint:moddatanodes"; + prefix mdn; + + container dnc { + leaf lf { + type string; + } + leaf-list lfl { + type string; + } + leaf mis { + type string; + } + container con { + list lt { + key "kalf kblf"; + leaf kalf { + type string; + } + leaf kblf { + type string; + } + leaf vlf { + type string; + } + } + } + } +} diff --git a/tests/yanglint/modules/moddefault.yang b/tests/yanglint/modules/moddefault.yang new file mode 100644 index 0000000..26570c3 --- /dev/null +++ b/tests/yanglint/modules/moddefault.yang @@ -0,0 +1,19 @@ +module moddefault { + namespace "urn:yanglint:moddefault"; + prefix md; + + container mdc { + leaf lf { + type uint16; + } + leaf di { + type int16; + default "5"; + } + leaf ds { + type string; + default "str"; + } + } + +} diff --git a/tests/yanglint/modules/modextleafref.yang b/tests/yanglint/modules/modextleafref.yang new file mode 100644 index 0000000..d45ec71 --- /dev/null +++ b/tests/yanglint/modules/modextleafref.yang @@ -0,0 +1,24 @@ +module modextleafref { + namespace "urn:yanglint:modextleafref"; + prefix mel; + + list ls { + key k; + leaf k { + type string; + } + leaf lf { + type uint8; + } + } + leaf lfr { + type leafref { + path "../ls/k"; + } + } + leaf lfrderef { + type leafref { + path "deref(../lfr)/../lf"; + } + } +} diff --git a/tests/yanglint/modules/modfeature.yang b/tests/yanglint/modules/modfeature.yang new file mode 100644 index 0000000..f59d4c8 --- /dev/null +++ b/tests/yanglint/modules/modfeature.yang @@ -0,0 +1,7 @@ +module modfeature { + namespace "urn:yanglint:modfeature"; + prefix l; + + feature ftr1; + feature ftr2; +} diff --git a/tests/yanglint/modules/modimp-cwd.yang b/tests/yanglint/modules/modimp-cwd.yang new file mode 100644 index 0000000..3249462 --- /dev/null +++ b/tests/yanglint/modules/modimp-cwd.yang @@ -0,0 +1,8 @@ +module modimp-cwd { + namespace "urn:yanglint:modimp-cwd"; + prefix ic; + + import modcwd { + prefix mc; + } +} diff --git a/tests/yanglint/modules/modimp-path.yang b/tests/yanglint/modules/modimp-path.yang new file mode 100644 index 0000000..d9dbb9b --- /dev/null +++ b/tests/yanglint/modules/modimp-path.yang @@ -0,0 +1,8 @@ +module modimp-path { + namespace "urn:yanglint:modimp-path"; + prefix ip; + + import modpath { + prefix mp; + } +} diff --git a/tests/yanglint/modules/modimp-type.yang b/tests/yanglint/modules/modimp-type.yang new file mode 100644 index 0000000..ec21d31 --- /dev/null +++ b/tests/yanglint/modules/modimp-type.yang @@ -0,0 +1,12 @@ +module modimp-type { + namespace "urn:yanglint:modimp-type"; + prefix mit; + + import modtypedef { + prefix mtd; + } + + leaf lf { + type mtd:mui8; + } +} diff --git a/tests/yanglint/modules/modinclude.yang b/tests/yanglint/modules/modinclude.yang new file mode 100644 index 0000000..849d43f --- /dev/null +++ b/tests/yanglint/modules/modinclude.yang @@ -0,0 +1,9 @@ +module modinclude { + yang-version 1.1; + namespace "urn:yanglint:modinclude"; + prefix mi; + + include "modsub"; + + container mic; +} diff --git a/tests/yanglint/modules/modleaf.yang b/tests/yanglint/modules/modleaf.yang new file mode 100644 index 0000000..48ce786 --- /dev/null +++ b/tests/yanglint/modules/modleaf.yang @@ -0,0 +1,8 @@ +module modleaf { + namespace "urn:yanglint:modleaf"; + prefix l; + + leaf lfl { + type uint16; + } +} diff --git a/tests/yanglint/modules/modleafref.yang b/tests/yanglint/modules/modleafref.yang new file mode 100644 index 0000000..f86fb3f --- /dev/null +++ b/tests/yanglint/modules/modleafref.yang @@ -0,0 +1,14 @@ +module modleafref { + namespace "urn:yanglint:modleafref"; + prefix m; + + import modleaf { + prefix ml; + } + + leaf lfr { + type leafref { + path "/ml:lfl"; + } + } +} diff --git a/tests/yanglint/modules/modmandatory.yang b/tests/yanglint/modules/modmandatory.yang new file mode 100644 index 0000000..4d48540 --- /dev/null +++ b/tests/yanglint/modules/modmandatory.yang @@ -0,0 +1,14 @@ +module modmandatory { + namespace "urn:yanglint:modmandatory"; + prefix mm; + + container mmc { + leaf lft { + type int16; + mandatory true; + } + leaf lff { + type int16; + } + } +} diff --git a/tests/yanglint/modules/modmerge.yang b/tests/yanglint/modules/modmerge.yang new file mode 100644 index 0000000..60fd75c --- /dev/null +++ b/tests/yanglint/modules/modmerge.yang @@ -0,0 +1,21 @@ +module modmerge { + namespace "urn:yanglint:modmerge"; + prefix mm; + + container mmc { + leaf en { + type enumeration { + enum zero; + enum one; + } + } + leaf lm { + type int16; + must "../en != 'zero'"; + } + leaf lf { + type string; + } + } + +} diff --git a/tests/yanglint/modules/modmust.yang b/tests/yanglint/modules/modmust.yang new file mode 100644 index 0000000..99971bd --- /dev/null +++ b/tests/yanglint/modules/modmust.yang @@ -0,0 +1,13 @@ +module modmust { + namespace "urn:yanglint:modmust"; + prefix m; + + import modleaf { + prefix ml; + } + + leaf lfm { + type string; + must "/ml:lfl > 0"; + } +} diff --git a/tests/yanglint/modules/modnotif.yang b/tests/yanglint/modules/modnotif.yang new file mode 100644 index 0000000..a2155a0 --- /dev/null +++ b/tests/yanglint/modules/modnotif.yang @@ -0,0 +1,19 @@ +module modnotif { + yang-version 1.1; + namespace "urn:yanglint:modnotif"; + prefix mn; + + container con { + notification nfn { + leaf lf { + type string; + } + } + } + + notification nfg { + leaf lf { + type string; + } + } +} diff --git a/tests/yanglint/modules/modoper-leafref.yang b/tests/yanglint/modules/modoper-leafref.yang new file mode 100644 index 0000000..36a1124 --- /dev/null +++ b/tests/yanglint/modules/modoper-leafref.yang @@ -0,0 +1,68 @@ +module modoper-leafref { + yang-version 1.1; + namespace "urn:yanglint:modoper-leafref"; + prefix mol; + + import modconfig { + prefix mc; + } + + container cond { + list list { + key "klf"; + leaf klf { + type string; + } + action act { + input { + leaf lfi { + type leafref { + path "/mc:mcc/mc:lft"; + } + } + } + output { + leaf lfo { + type leafref { + path "/mc:mcc/mc:lft"; + } + } + } + } + notification notif { + leaf lfn { + type leafref { + path "/mc:mcc/mc:lft"; + } + } + } + } + } + + rpc rpcg { + input { + leaf lfi { + type leafref { + path "/mc:mcc/mc:lft"; + } + } + } + output { + container cono { + leaf lfo { + type leafref { + path "/mc:mcc/mc:lft"; + } + } + } + } + } + + notification notifg { + leaf lfr { + type leafref { + path "/mc:mcc/mc:lft"; + } + } + } +} diff --git a/tests/yanglint/modules/modpath.yang b/tests/yanglint/modules/modpath.yang new file mode 100644 index 0000000..da099a2 --- /dev/null +++ b/tests/yanglint/modules/modpath.yang @@ -0,0 +1,4 @@ +module modpath { + namespace "urn:yanglint:modpath"; + prefix mp; +} diff --git a/tests/yanglint/modules/modrpc.yang b/tests/yanglint/modules/modrpc.yang new file mode 100644 index 0000000..dc0cced --- /dev/null +++ b/tests/yanglint/modules/modrpc.yang @@ -0,0 +1,19 @@ +module modrpc { + namespace "urn:yanglint:modrpc"; + prefix mr; + + rpc rpc { + input { + leaf lfi { + type string; + } + } + output { + container con { + leaf lfo { + type int16; + } + } + } + } +} diff --git a/tests/yanglint/modules/modsm-augment.yang b/tests/yanglint/modules/modsm-augment.yang new file mode 100644 index 0000000..5d16fbd --- /dev/null +++ b/tests/yanglint/modules/modsm-augment.yang @@ -0,0 +1,15 @@ +module modsm-augment { + yang-version 1.1; + namespace "urn:yanglint:modsm-augment"; + prefix "msa"; + + import modsm { + prefix msm; + } + + augment "/msm:root" { + leaf alf { + type string; + } + } +} diff --git a/tests/yanglint/modules/modsm.yang b/tests/yanglint/modules/modsm.yang new file mode 100644 index 0000000..dfe8830 --- /dev/null +++ b/tests/yanglint/modules/modsm.yang @@ -0,0 +1,13 @@ +module modsm { + yang-version 1.1; + namespace "urn:yanglint:modsm"; + prefix "msm"; + + import ietf-yang-schema-mount { + prefix sm; + } + + container root { + sm:mount-point "root"; + } +} diff --git a/tests/yanglint/modules/modsub.yang b/tests/yanglint/modules/modsub.yang new file mode 100644 index 0000000..79d9286 --- /dev/null +++ b/tests/yanglint/modules/modsub.yang @@ -0,0 +1,8 @@ +submodule modsub { + yang-version 1.1; + belongs-to modinclude { + prefix mi; + } + + container msc; +} diff --git a/tests/yanglint/modules/modtypedef.yang b/tests/yanglint/modules/modtypedef.yang new file mode 100644 index 0000000..ea09c95 --- /dev/null +++ b/tests/yanglint/modules/modtypedef.yang @@ -0,0 +1,8 @@ +module modtypedef { + namespace "urn:yanglint:typedef"; + prefix mt; + + typedef mui8 { + type uint8; + } +} diff --git a/tests/yanglint/non-interactive/all.tcl b/tests/yanglint/non-interactive/all.tcl new file mode 100644 index 0000000..998c03a --- /dev/null +++ b/tests/yanglint/non-interactive/all.tcl @@ -0,0 +1,15 @@ +package require tcltest + +# Hook to determine if any of the tests failed. +# Sets a global variable exitCode to 1 if any test fails otherwise it is set to 0. +proc tcltest::cleanupTestsHook {} { + variable numTests + set ::exitCode [expr {$numTests(Failed) > 0}] +} + +if {[info exists ::env(TESTS_DIR)]} { + tcltest::configure -testdir "$env(TESTS_DIR)/non-interactive" +} + +tcltest::runAllTests +exit $exitCode diff --git a/tests/yanglint/non-interactive/data_default.test b/tests/yanglint/non-interactive/data_default.test new file mode 100644 index 0000000..be19d72 --- /dev/null +++ b/tests/yanglint/non-interactive/data_default.test @@ -0,0 +1,31 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}] + +set mods "$::env(YANG_MODULES_DIR)/ietf-netconf-with-defaults@2011-06-01.yang $::env(YANG_MODULES_DIR)/moddefault.yang" +set data "$::env(TESTS_DIR)/data/moddefault.xml" + +test data_default_not_set {Print data without --default parameter} { + ly_cmd "-f xml $mods $data" "</lf>.*</di>\n</mdc>" + ly_cmd "-f json $mods $data" "lf\".*di\"\[^\"]*" +} {} + +test data_default_all {data --default all} { + ly_cmd "-d all -f xml $mods $data" "</lf>.*</di>.*</ds>\n</mdc>" + ly_cmd "-d all -f json $mods $data" "lf\".*di\".*ds\"\[^\"]*" +} {} + +test data_default_all_tagged {data --default all-tagged} { + ly_cmd "-d all-tagged -f xml $mods $data" "</lf>.*<di.*default.*</di>.*<ds.*default.*</ds>\n</mdc>" + ly_cmd "-d all-tagged -f json $mods $data" "lf\".*di\".*ds\".*@ds\".*default\"\[^\"]*" +} {} + +test data_default_trim {data --default trim} { + ly_cmd "-d trim -f xml $mods $data" "</lf>\n</mdc>" + ly_cmd "-d trim -f json $mods $data" "lf\"\[^\"]*" +} {} + +test data_default_implicit_tagged {data --default implicit-tagged} { + ly_cmd "-d implicit-tagged -f xml $mods $data" "</lf>.*<di>5</di>.*<ds.*default.*</ds>\n</mdc>" + ly_cmd "-d implicit-tagged -f json $mods $data" "lf\".*di\"\[^@]*ds\".*default\"\[^\"]*" +} {} + +cleanupTests diff --git a/tests/yanglint/non-interactive/data_in_format.test b/tests/yanglint/non-interactive/data_in_format.test new file mode 100644 index 0000000..f1336dd --- /dev/null +++ b/tests/yanglint/non-interactive/data_in_format.test @@ -0,0 +1,18 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}] + +set mdir $::env(YANG_MODULES_DIR) +set ddir $::env(TESTS_DIR)/data + +test data_in_format_xml {--in-format xml} { + ly_cmd "-I xml $mdir/modleaf.yang $ddir/modleaf.dxml" + ly_cmd_err "-I json $mdir/modleaf.yang $ddir/modleaf.dxml" "Failed to parse" + ly_cmd_err "-I lyb $mdir/modleaf.yang $ddir/modleaf.dxml" "Failed to parse" +} {} + +test data_in_format_json {--in-format json} { + ly_cmd "-I json $mdir/modleaf.yang $ddir/modleaf.djson" + ly_cmd_err "-I xml $mdir/modleaf.yang $ddir/modleaf.djson" "Failed to parse" + ly_cmd_err "-I lyb $mdir/modleaf.yang $ddir/modleaf.djson" "Failed to parse" +} {} + +cleanupTests diff --git a/tests/yanglint/non-interactive/data_merge.test b/tests/yanglint/non-interactive/data_merge.test new file mode 100644 index 0000000..4ecfcee --- /dev/null +++ b/tests/yanglint/non-interactive/data_merge.test @@ -0,0 +1,28 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}] + +set mdir $::env(YANG_MODULES_DIR) +set ddir $::env(TESTS_DIR)/data + +test data_merge_basic {Data is merged and the node is added} { + ly_cmd "-m -f xml $mdir/modmerge.yang $ddir/modmerge.xml $ddir/modmerge3.xml" "<en>.*<lm>.*<lf>" +} {} + +test data_merge_validation_failed {Data is merged but validation failed.} { + ly_cmd "$mdir/modmerge.yang $ddir/modmerge.xml" + ly_cmd "$mdir/modmerge.yang $ddir/modmerge2.xml" + ly_cmd "-m $mdir/modmerge.yang $ddir/modmerge2.xml $ddir/modmerge.xml" + ly_cmd_err "-m $mdir/modmerge.yang $ddir/modmerge.xml $ddir/modmerge2.xml" "Merged data are not valid" +} {} + +test data_merge_dataconfig {The merge option has effect only for 'data' and 'config' TYPEs} { + set wrn1 "option has effect only for" + ly_cmd_wrn "-m -t rpc $mdir/modrpc.yang $ddir/modrpc.xml $ddir/modrpc.xml" $wrn1 + ly_cmd_wrn "-m -t notif $mdir/modnotif.yang $ddir/modnotif2.xml $ddir/modnotif2.xml" $wrn1 + ly_cmd_wrn "-m -t get $mdir/modconfig.yang $mdir/modleaf.yang $ddir/modleaf.xml $ddir/modconfig.xml" $wrn1 + ly_cmd_wrn "-m -t getconfig $mdir/modconfig.yang $mdir/modleaf.yang $ddir/modleaf.xml $ddir/modconfig2.xml" $wrn1 + ly_cmd_wrn "-m -t edit $mdir/modconfig.yang $mdir/modleaf.yang $ddir/modleaf.xml $ddir/modconfig2.xml" $wrn1 + ly_cmd "-m -t config $mdir/modconfig.yang $mdir/modleaf.yang $ddir/modleaf.xml $ddir/modconfig2.xml" + ly_cmd "-m -t data $mdir/modconfig.yang $mdir/modleaf.yang $ddir/modleaf.xml $ddir/modconfig.xml" +} {} + +cleanupTests diff --git a/tests/yanglint/non-interactive/data_not_strict.test b/tests/yanglint/non-interactive/data_not_strict.test new file mode 100644 index 0000000..b91eed8 --- /dev/null +++ b/tests/yanglint/non-interactive/data_not_strict.test @@ -0,0 +1,20 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}] + +set mdir $::env(YANG_MODULES_DIR) +set ddir $::env(TESTS_DIR)/data + +test data_no_strict_basic {} { + ly_cmd_err "$ddir/modmandatory.xml $mdir/modleaf.yang" "No module with namespace \"urn:yanglint:modmandatory\" in the context." + ly_cmd "-n $ddir/modmandatory.xml $mdir/modleaf.yang" +} {} + +test data_no_strict_invalid_data {validation with --no-strict but data are invalid} { + set errmsg "Mandatory node \"lft\" instance does not exist." + ly_cmd_err "-n $ddir/modmandatory_invalid.xml $mdir/modmandatory.yang" $errmsg +} {} + +test data_no_strict_ignore_invalid_data {--no-strict ignore invalid data if no schema is provided} { + ly_cmd "-f xml -n $ddir/modmandatory_invalid.xml $ddir/modleaf.xml $mdir/modleaf.yang" "modleaf.*</lfl>$" +} {} + +cleanupTests diff --git a/tests/yanglint/non-interactive/data_operational.test b/tests/yanglint/non-interactive/data_operational.test new file mode 100644 index 0000000..82e861e --- /dev/null +++ b/tests/yanglint/non-interactive/data_operational.test @@ -0,0 +1,62 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}] + +set mdir "$::env(YANG_MODULES_DIR)" +set ddir "$::env(TESTS_DIR)/data" +set err1 "Operational datastore takes effect only with RPCs/Actions/Replies/Notification input data types" + +test data_operational_twice {it is not allowed to specify more than one --operational parameter} { + ly_cmd_err "-t notif -O $ddir/modconfig.xml -O $ddir/modleaf.xml" "cannot be set multiple times" +} {} + +test data_operational_no_type {--operational should be with parameter --type} { + ly_cmd_err "-O $ddir/modconfig.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_notif.xml" $err1 +} {} + +test data_operational_missing {--operational is omitted and the datastore contents is in the data file} { + ly_cmd_err "$mdir/modoper-leafref.yang $ddir/modoper_leafref_notif_err.xml" "Failed to parse input data file" +} {} + +test data_operational_wrong_type {data are not defined as an operation} { + ly_cmd_wrn "-t data -O $ddir/modconfig.xml $mdir/modleaf.yang $ddir/modleaf.xml" $err1 +} {} + +test data_operational_datastore_with_unknown_data {unknown data are ignored} { + ly_cmd "-t rpc -O $ddir/modmandatory_invalid.xml $mdir/modrpc.yang $ddir/modrpc.xml" +} {} + +test data_operational_empty_datastore {datastore is considered empty because it contains unknown data} { + ly_cmd "-t rpc -O $ddir/modmandatory_invalid.xml $mdir/modrpc.yang $ddir/modrpc.xml" + set msg "parent \"/modnotif:con\" not found in the operational data" + ly_cmd_err "-t notif -O $ddir/modmandatory_invalid.xml $mdir/modnotif.yang $ddir/modnotif.xml" $msg +} {} + +test data_operational_notif_leafref {--operational data is referenced from notification-leafref} { + ly_cmd "-t notif -O $ddir/modconfig.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_notif.xml" +} {} + +test data_operational_nested_notif_leafref {--operational data is referenced from nested-notification-leafref} { + ly_cmd "-t notif -O $ddir/modoper_leafref_ds.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_notif2.xml" +} {} + +test data_operational_nested_notif_parent_missing {--operational data are invalid due to missing parent node} { + set msg "klf='key_val']\" not found in the operational data" + ly_cmd_err "-t notif -O $ddir/modconfig.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_notif2.xml" $msg +} {} + +test data_operational_action_leafref {--operational data is referenced from action-leafref} { + ly_cmd "-t rpc -O $ddir/modoper_leafref_ds.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_action.xml" +} {} + +test data_operational_action_reply_leafref {--operational data is referenced from action-leafref output} { + ly_cmd "-t reply -O $ddir/modoper_leafref_ds.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_action_reply.xml" +} {} + +test data_operational_rpc_leafref {--operational data is referenced from rpc-leafref} { + ly_cmd "-t rpc -O $ddir/modconfig.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_rpc.xml" +} {} + +test data_operational_rpc_reply_leafref {--operational data is referenced from rpc-leafref output} { + ly_cmd "-t reply -O $ddir/modconfig.xml $mdir/modoper-leafref.yang $ddir/modoper_leafref_rpc_reply.xml" +} {} + +cleanupTests diff --git a/tests/yanglint/non-interactive/data_present.test b/tests/yanglint/non-interactive/data_present.test new file mode 100644 index 0000000..81aac14 --- /dev/null +++ b/tests/yanglint/non-interactive/data_present.test @@ -0,0 +1,25 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}] + +set mdir $::env(YANG_MODULES_DIR) +set ddir $::env(TESTS_DIR)/data + +test data_present_via_mandatory {validation of mandatory-stmt will pass only with the --present} { + set mods "$mdir/modleaf.yang $mdir/modmandatory.yang" + ly_cmd_err "$ddir/modleaf.xml $mods" "Mandatory node \"lft\" instance does not exist." + ly_cmd "-e $ddir/modleaf.xml $mods" +} {} + +test data_present_merge {validation with --present and --merge} { + set mods "$mdir/modleaf.yang $mdir/modmandatory.yang $mdir/moddefault.yang" + set data "$ddir/modleaf.xml $ddir/moddefault.xml" + ly_cmd_err "-m $data $mods" "Mandatory node \"lft\" instance does not exist." + ly_cmd "-m -e $data $mods" +} {} + +test data_present_merge_invalid {using --present and --merge but data are invalid} { + set mods "$mdir/modleaf.yang $mdir/modmandatory.yang" + set data "$ddir/modleaf.xml $ddir/modmandatory_invalid.xml" + ly_cmd_err "-e -m $data $mods" "Mandatory node \"lft\" instance does not exist." +} {} + +cleanupTests diff --git a/tests/yanglint/non-interactive/data_type.test b/tests/yanglint/non-interactive/data_type.test new file mode 100644 index 0000000..e3691d7 --- /dev/null +++ b/tests/yanglint/non-interactive/data_type.test @@ -0,0 +1,107 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}] + +set mdir "$::env(YANG_MODULES_DIR)" +set ddir "$::env(TESTS_DIR)/data" +set modnc "$mdir/ietf-netconf@2011-06-01.yang" + +test data_type_data {data --type data} { + ly_cmd "-t data $mdir/modconfig.yang $ddir/modconfig.xml" +} {} + +test data_type_config {data --type config} { + ly_cmd_err "-t config $mdir/modconfig.yang $ddir/modconfig.xml" "Unexpected data state node \"lff\"" + ly_cmd "-t config $mdir/modconfig.yang $ddir/modconfig2.xml" +} {} + +test data_type_get {data --type get} { + ly_cmd_err "-t data $mdir/modleafref.yang $ddir/modleafref2.xml" "Invalid leafref value" + ly_cmd "-t get $mdir/modleafref.yang $ddir/modleafref2.xml" +} {} + +test data_type_getconfig_no_state {No state node for data --type getconfig} { + ly_cmd_err "-t getconfig $mdir/modconfig.yang $ddir/modconfig.xml" "Unexpected data state node \"lff\"" + ly_cmd "-t getconfig $mdir/modconfig.yang $ddir/modconfig2.xml" +} {} + +test data_type_getconfig_parse_only {No validation performed for data --type getconfig} { + ly_cmd_err "-t data $mdir/modleafref.yang $ddir/modleafref2.xml" "Invalid leafref value" + ly_cmd "-t getconfig $mdir/modleafref.yang $ddir/modleafref2.xml" +} {} + +test data_type_edit_no_state {No state node for data --type edit} { + ly_cmd_err "-t edit $mdir/modconfig.yang $ddir/modconfig.xml" "Unexpected data state node \"lff\"" + ly_cmd "-t edit $mdir/modconfig.yang $ddir/modconfig2.xml" +} {} + +test data_type_edit_parse_only {No validation performed for data --type edit} { + ly_cmd_err "-t data $mdir/modleafref.yang $ddir/modleafref2.xml" "Invalid leafref value" + ly_cmd "-t edit $mdir/modleafref.yang $ddir/modleafref2.xml" +} {} + +test data_type_rpc {Validation of rpc-statement by data --type rpc} { + ly_cmd_err "-t rpc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing the operation node." + ly_cmd "-t rpc $mdir/modrpc.yang $ddir/modrpc.xml" +} {} + +test data_type_rpc_nc {Validation of rpc-statement by data --type nc-rpc} { + ly_cmd_err "-t nc-rpc $modnc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing NETCONF <rpc> envelope" + ly_cmd "-t nc-rpc $modnc $mdir/modrpc.yang $ddir/modrpc_nc.xml" +} {} + +test data_type_rpc_reply {Validation of rpc-reply by data --type reply} { + ly_cmd_err "-t rpc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing the operation node." + ly_cmd_wrn "-t reply -R $ddir/modrpc.xml $mdir/modrpc.yang $ddir/modrpc_reply.xml" "needed only for NETCONF" + ly_cmd "-t reply $mdir/modrpc.yang $ddir/modrpc_reply.xml" +} {} + +test data_type_rpc_reply_nc {Validation of rpc-reply by data --type nc-reply} { + set err1 "Missing NETCONF <rpc-reply> envelope" + ly_cmd_err "-t nc-reply -R $ddir/modrpc_nc.xml $mdir/modrpc.yang $mdir/modleaf.yang $ddir/modleaf.xml" $err1 + ly_cmd_err "-t nc-reply $mdir/modrpc.yang $ddir/modrpc_reply_nc.xml" "Missing source RPC" + ly_cmd "-t nc-reply -R $ddir/modrpc_nc.xml $mdir/modrpc.yang $ddir/modrpc_reply_nc.xml" +} {} + +test data_type_rpc_action {Validation of action-statement by data --type rpc} { + ly_cmd_err "-t rpc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing the operation node." + ly_cmd "-t rpc -O $ddir/modaction_ds.xml $mdir/modaction.yang $ddir/modaction.xml" +} {} + +test data_type_rpc_action_nc {Validation of action-statement by data --type nc-rpc} { + ly_cmd_err "-t nc-rpc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing NETCONF <rpc> envelope" + ly_cmd "-t nc-rpc -O $ddir/modaction_ds.xml $mdir/modaction.yang $ddir/modaction_nc.xml" +} {} + +test data_type_rpc_action_reply {Validation of action-reply by data --type reply} { + ly_cmd_err "-t rpc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing the operation node." + ly_cmd "-t reply -O $ddir/modaction_ds.xml $mdir/modaction.yang $ddir/modaction_reply.xml" +} {} + +test data_type_rpc_action_reply_nc {Validation of action-reply by data --type nc-reply} { + set err1 "Missing NETCONF <rpc-reply> envelope" + set err2 "operational parameter needed" + ly_cmd_err "-t nc-reply -R $ddir/modaction_nc.xml $mdir/modaction.yang $mdir/modleaf.yang $ddir/modleaf.xml" $err1 + ly_cmd_err "-t nc-reply $mdir/modaction.yang $ddir/modaction_reply_nc.xml" "Missing source RPC" + ly_cmd_err "-t nc-reply -R $ddir/modaction_nc.xml $mdir/modaction.yang $ddir/modaction_reply_nc.xml" $err2 + ly_cmd "-t nc-reply -O $ddir/modaction_ds.xml -R $ddir/modaction_nc.xml $mdir/modaction.yang $ddir/modaction_reply_nc.xml" +} {} + +test data_type_notif {Validation of notification-statement by data --type notif} { + ly_cmd_err "-t notif $mdir/modleaf.yang $ddir/modleaf.xml" "Missing the operation node." + ly_cmd "-t notif $mdir/modnotif.yang $ddir/modnotif2.xml" +} {} + +test data_type_notif_nc {Validation of notification-statement by data --type nc-notif} { + ly_cmd_err "-t nc-notif $modnc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing NETCONF <notification> envelope" + ly_cmd "-t nc-notif $modnc $mdir/modnotif.yang $ddir/modnotif2_nc.xml" +} {} + +test data_type_notif_nested {Validation of nested-notification-statement by data --type notif} { + ly_cmd "-t notif -O $ddir/modnotif_ds.xml $mdir/modnotif.yang $ddir/modnotif.xml" +} {} + +test data_type_notif_nested_nc {Validation of nested-notification-statement by data --type nc-notif} { + ly_cmd_err "-t nc-notif $modnc $mdir/modleaf.yang $ddir/modleaf.xml" "Missing NETCONF <notification> envelope" + ly_cmd "-t nc-notif -O $ddir/modnotif_ds.xml $modnc $mdir/modnotif.yang $ddir/modnotif_nc.xml" +} {} + +cleanupTests diff --git a/tests/yanglint/non-interactive/data_xpath.test b/tests/yanglint/non-interactive/data_xpath.test new file mode 100644 index 0000000..1d96106 --- /dev/null +++ b/tests/yanglint/non-interactive/data_xpath.test @@ -0,0 +1,42 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}] + +set mod "$::env(YANG_MODULES_DIR)/moddatanodes.yang" +set data "$::env(TESTS_DIR)/data/moddatanodes.xml" + +test data_xpath_empty {--data-path to missing node} { + ly_cmd "-E /moddatanodes:dnc/mis $mod $data" "Empty" +} {} + +test data_xpath_leaf {--xpath to leaf node} { + ly_cmd "-E /moddatanodes:dnc/lf $mod $data" "leaf \"lf\" \\(value: \"x\"\\)" +} {} + +test data_xpath_leaflist {--xpath to leaf-list node} { + set r1 "leaf-list \"lfl\" \\(value: \"1\"\\)" + set r2 "leaf-list \"lfl\" \\(value: \"2\"\\)" + ly_cmd "-E /moddatanodes:dnc/lfl $mod $data" "$r1\n $r2" +} {} + +test data_xpath_list {--xpath to list} { + set r1 "list \"lt\" \\(\"kalf\": \"ka1\"; \"kblf\": \"kb1\";\\)" + set r2 "list \"lt\" \\(\"kalf\": \"ka2\"; \"kblf\": \"kb2\";\\)" + ly_cmd "-E /moddatanodes:dnc/con/lt $mod $data" "$r1\n $r2" +} {} + +test data_xpath_container {--xpath to container} { + ly_cmd "-E /moddatanodes:dnc/con $mod $data" "container \"con\"" +} {} + +test data_xpath_wrong_path {--xpath to a non-existent node} { + ly_cmd_err "-E /moddatanodes:dnc/wrng $mod $data" "xpath failed" +} {} + +test data_xpath_err_format {--xpath cannot be combined with --format} { + ly_cmd_err "-f xml -E /moddatanodes:dnc/lf $mod $data" "option cannot be combined" +} {} + +test data_xpath_err_default {--xpath cannot be combined with --default} { + ly_cmd_err "-d all -E /moddatanodes:dnc/lf $mod $data" "option cannot be combined" +} {} + +cleanupTests diff --git a/tests/yanglint/non-interactive/debug.test b/tests/yanglint/non-interactive/debug.test new file mode 100644 index 0000000..4543acb --- /dev/null +++ b/tests/yanglint/non-interactive/debug.test @@ -0,0 +1,25 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}] + +set mdir $::env(YANG_MODULES_DIR) + +test debug_dict {Check debug message DICT} { +-constraints {[ly_opt_exists "-G"]} -body { + ly_cmd_wrn "-V -V -G dict $mdir/modleaf.yang" "DICT" +}} + +test debug_xpath {Check debug message XPATH} { +-constraints {[ly_opt_exists "-G"]} -body { + ly_cmd_wrn "-V -V -G xpath $mdir/modmust.yang" "XPATH" +}} + +test debug_dep_sets {Check debug message DEPSETS} { +-constraints {[ly_opt_exists "-G"]} -body { + ly_cmd_wrn "-V -V -G dep-sets $mdir/modleaf.yang" "DEPSETS" +}} + +test debug_depsets_xpath {Check debug message DEPSETS and XPATH} { +-constraints {[ly_opt_exists "-G"]} -body { + ly_cmd_wrn "-V -V -G dep-sets,xpath $mdir/modmust.yang" "DEPSETS.*XPATH" +}} + +cleanupTests diff --git a/tests/yanglint/non-interactive/disabled_searchdir.test b/tests/yanglint/non-interactive/disabled_searchdir.test new file mode 100644 index 0000000..49fe13e --- /dev/null +++ b/tests/yanglint/non-interactive/disabled_searchdir.test @@ -0,0 +1,18 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}] + +set mdir $env(YANG_MODULES_DIR) + +# Test should be skipped if called by ctest. +test disable_searchdir_once {Unsuccessfully imports module due to disabled cwd searching} { +-constraints {!ctest} -body { + ly_cmd "$mdir/modimp-cwd.yang" + ly_cmd_err "-D $mdir/modimp-cwd.yang" "not found in local searchdirs" +}} + +test disable_searchdir_twice {Unsuccessfully imports module due to -D -D} { + ly_cmd "$mdir/ietf-ip.yang" + ly_cmd_err "-D -D $mdir/ietf-ip.yang" "Loading \"ietf-interfaces\" module failed." +} {} + +cleanupTests + diff --git a/tests/yanglint/non-interactive/ext_data.test b/tests/yanglint/non-interactive/ext_data.test new file mode 100644 index 0000000..d4e3c44 --- /dev/null +++ b/tests/yanglint/non-interactive/ext_data.test @@ -0,0 +1,29 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}] + +set mdir "$::env(YANG_MODULES_DIR)" +set ddir "$::env(TESTS_DIR)/data" + +test ext_data_schema_mount_tree {Print tree output of a model with Schema Mount} { + # mounting node lfl from modleaf.yang into modsm.yang + set out1 "--mp root.*--rw lfl/" + ly_cmd "-f tree -p $mdir -y -x $ddir/modsm_ctx_ext.xml $mdir/modsm.yang" $out1 +} {} + +test ext_data_schema_mount_tree_yanglibfile {Print tree output of a model with Schema Mount and --yang-library-file} { + # yang-library-file context contains an augment node 'alf' for modsm + set out1 "--mp root.*--rw lfl/.*--rw msa:alf?" + ly_cmd "-f tree -p $mdir -Y $ddir/modsm_ctx_main.xml -x $ddir/modsm_ctx_ext.xml $mdir/modsm.yang" $out1 +} {} + +test ext_data_schema_mount_xml {Validating and printing mounted data} { + ly_cmd "-f xml -t config -p $mdir -y -x $ddir/modsm_ctx_ext.xml $mdir/modsm.yang $ddir/modsm.xml" "</lfl>" +} {} + +test ext_data_schema_mount_xml_yanglibfile {Validating and printing mounted data with --yang-library-file} { + set yanglibfile "$ddir/modsm_ctx_main.xml" + set extdata "$ddir/modsm_ctx_ext.xml" + set out1 "</lfl>.*</alf>" + ly_cmd "-f xml -t config -p $mdir -Y $yanglibfile -x $extdata $mdir/modsm.yang $ddir/modsm2.xml" $out1 +} {} + +cleanupTests diff --git a/tests/yanglint/non-interactive/extended_leafref.test b/tests/yanglint/non-interactive/extended_leafref.test new file mode 100644 index 0000000..5e1a90e --- /dev/null +++ b/tests/yanglint/non-interactive/extended_leafref.test @@ -0,0 +1,13 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}] + +set mdir $::env(YANG_MODULES_DIR) + +test extended_leafref_enabled {Valid module with --extended-leafref option} { + ly_cmd "-X $mdir/modextleafref.yang" +} {} + +test extended_leafref_disabled {Expected error if --extended-leafref is not set} { + ly_cmd_err "$mdir/modextleafref.yang" "Unexpected XPath token \"FunctionName\"" +} {} + +cleanupTests diff --git a/tests/yanglint/non-interactive/format.test b/tests/yanglint/non-interactive/format.test new file mode 100644 index 0000000..8df5544 --- /dev/null +++ b/tests/yanglint/non-interactive/format.test @@ -0,0 +1,72 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}] + +set mdir $::env(YANG_MODULES_DIR) +set ddir $::env(TESTS_DIR)/data +set ipv6_path "/ietf-interfaces:interfaces/interface/ietf-ip:ipv6/address" + +test format_yang {} { + ly_cmd "-f yang $mdir/modleaf.yang" "leaf lfl" +} {} + +test format_yang_submodule {Print submodule in yang format} { + ly_cmd "-s modsub -f yang $mdir/modinclude.yang" "submodule modsub" +} {} + +test format_yin {} { + ly_cmd "-f yin $mdir/modleaf.yang" "<leaf name=\"lfl\">" +} {} + +test format_yin_submodule {Print submodule in yin format} { + ly_cmd "-s modsub -f yin $mdir/modinclude.yang" "<submodule name=\"modsub\"" +} {} + +test format_info {} { + ly_cmd "-f info $mdir/modleaf.yang" "status current" +} {} + +test format_tree {} { + ly_cmd "-f tree $mdir/modleaf.yang" "\\+--rw lfl" +} {} + +test format_data_xml {Print data in xml format} { + ly_cmd "-f xml $mdir/modleaf.yang $ddir/modleaf.xml" "<lfl xmlns=\"urn:yanglint:modleaf\">7</lfl>" +} {} + +test format_data_json {Print data in json format} { + ly_cmd "-f json $mdir/modleaf.yang $ddir/modleaf.xml" "{\n \"modleaf:lfl\": 7\n}" +} {} + +test format_data_lyb_err {Printing in LYB format: expect error due to missing parameter} { + ly_cmd_err "-f lyb $mdir/modleaf.yang $ddir/modleaf.xml" "The LYB format requires the -o" +} {} + +test format_tree_submodule {Print submodule in tree format} { + ly_cmd "-s modsub -f tree $mdir/modinclude.yang" "submodule: modsub" +} {} + +test format_tree_path {Print subtree in tree format} { + ly_cmd "-f tree -P $ipv6_path $mdir/ietf-ip.yang" "\\+--rw address.*\\+--rw prefix-length" +} {} + +test format_tree_path_single_node {Print node in tree format} { + ly_cmd "-f tree -q -P $ipv6_path $mdir/ietf-ip.yang" "\\+--rw address\\* \\\[ip\\\]$" +} {} + +test format_tree_path_single_node_line_length {Print node in the tree format and limit row size} { + ly_cmd "-f tree -L 20 -q -P $ipv6_path $mdir/ietf-ip.yang" "\\+--rw address\\*\n *\\\[ip\\\]$" +} {} + +test format_feature_param_one_module {Show features for one module} { + ly_cmd "-f feature-param $mdir/ietf-ip.yang" " -F ietf-ip:ipv4-non-contiguous-netmasks,ipv6-privacy-autoconf" -ex +} {} + +test format_feature_param_more_modules {Show a mix of modules with and without features} { + + set features " -F modfeature:ftr1,ftr2\ +-F modleaf:\ +-F ietf-ip:ipv4-non-contiguous-netmasks,ipv6-privacy-autoconf" + + ly_cmd "-f feature-param $mdir/modfeature.yang $mdir/modleaf.yang $mdir/ietf-ip.yang" $features -ex +} {} + +cleanupTests diff --git a/tests/yanglint/non-interactive/list.test b/tests/yanglint/non-interactive/list.test new file mode 100644 index 0000000..626d9a1 --- /dev/null +++ b/tests/yanglint/non-interactive/list.test @@ -0,0 +1,26 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}] +namespace import uti::regex_xml_elements uti::regex_json_pairs + +set modules {ietf-yang-library ietf-inet-types} + +test list_basic {} { + ly_cmd "-l" "ietf-yang-types" +} {} + +test list_format_xml {list --format xml} { + ly_cmd "-y -f xml -l" [regex_xml_elements $modules "name"] +} {} + +test list_format_json {list --format json} { + ly_cmd "-y -f json -l" [regex_json_pairs $modules "name"] +} {} + +test list_ietf_yang_library {Error due to missing ietf-yang-library} { + ly_cmd_err "-f xml -l" "Module \"ietf-yang-library\" is not implemented." +} {} + +test list_bad_format {Error due to bad format} { + ly_cmd_err "-f csv -l" "Unknown output format csv" +} {} + +cleanupTests diff --git a/tests/yanglint/non-interactive/ly.tcl b/tests/yanglint/non-interactive/ly.tcl new file mode 100644 index 0000000..f6bb2c7 --- /dev/null +++ b/tests/yanglint/non-interactive/ly.tcl @@ -0,0 +1,8 @@ +# @brief The main source of functions and variables for testing yanglint in the non-interactive mode. + +# For testing yanglint. +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/common.tcl" : "../common.tcl"}] +# For testing any non-interactive tool. +source "$::env(TESTS_DIR)/../tool_ni.tcl" + +# The script continues by defining variables and functions specific to the non-interactive yanglint tool. diff --git a/tests/yanglint/non-interactive/make_implemented.test b/tests/yanglint/non-interactive/make_implemented.test new file mode 100644 index 0000000..40cead9 --- /dev/null +++ b/tests/yanglint/non-interactive/make_implemented.test @@ -0,0 +1,17 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}] + +set mdir $::env(YANG_MODULES_DIR) + +test make_impl_no_set {Import while --make-implemented is not set} { + ly_cmd "-l $mdir/modleafref.yang" "I modleafref\n.*I modleaf" +} {} + +test make_impl_set_once {--make-implemented} { + ly_cmd "-l -i $mdir/modmust.yang" "I modmust\n.*I modleaf" +} {} + +test make_impl_set_twice {-i -i} { + ly_cmd "-l -i -i $mdir/modimp-type.yang" "I modimp-type\n.*I modtypedef" +} {} + +cleanupTests diff --git a/tests/yanglint/non-interactive/modcwd.yang b/tests/yanglint/non-interactive/modcwd.yang new file mode 100644 index 0000000..db33e73 --- /dev/null +++ b/tests/yanglint/non-interactive/modcwd.yang @@ -0,0 +1,4 @@ +module modcwd { + namespace "urn:yanglint:modcwd"; + prefix mc; +} diff --git a/tests/yanglint/non-interactive/path.test b/tests/yanglint/non-interactive/path.test new file mode 100644 index 0000000..bf915ff --- /dev/null +++ b/tests/yanglint/non-interactive/path.test @@ -0,0 +1,9 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}] + +set mdir $env(YANG_MODULES_DIR) + +test path_basic {} { + ly_cmd "-p $::env(TESTS_DIR)/data $::env(YANG_MODULES_DIR)/modimp-path.yang" +} {} + +cleanupTests diff --git a/tests/yanglint/non-interactive/yang_library_file.test b/tests/yanglint/non-interactive/yang_library_file.test new file mode 100644 index 0000000..bd95978 --- /dev/null +++ b/tests/yanglint/non-interactive/yang_library_file.test @@ -0,0 +1,18 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ly.tcl" : "ly.tcl"}] + +set mdir "$::env(YANG_MODULES_DIR)" +set ddir "$::env(TESTS_DIR)/data" + +test ylf_list {apply --yang-library-file and check result by --list} { + ly_cmd "-Y $ddir/modimp_type_ctx.xml -p $mdir -l" "I modimp-type.*i modtypedef" +} {} + +test ylf_make_implemented {apply --yang-library-file and --make-implemented} { + ly_cmd "-Y $ddir/modimp_type_ctx.xml -p $mdir -i -i -l" "I modimp-type.*I modtypedef" +} {} + +test ylf_augment_ctx {Setup context by yang-library-file and augment module} { + ly_cmd "-Y $ddir/modconfig_ctx.xml -p $mdir -f tree $mdir/modconfig.yang $mdir/modconfig-augment.yang" "mca:alf" +} {} + +cleanupTests diff --git a/tests/yangre/CMakeLists.txt b/tests/yangre/CMakeLists.txt new file mode 100644 index 0000000..ce5b39b --- /dev/null +++ b/tests/yangre/CMakeLists.txt @@ -0,0 +1,8 @@ +find_program(PATH_TCLSH NAMES tclsh) +if(NOT PATH_TCLSH) + message(WARNING "'tclsh' not found! The yangre test will not be available.") +else() + add_test(NAME "yangre" COMMAND "tclsh" "${CMAKE_CURRENT_SOURCE_DIR}/all.tcl") + set_property(TEST "yangre" APPEND PROPERTY ENVIRONMENT "TESTS_DIR=${CMAKE_CURRENT_SOURCE_DIR}") + set_property(TEST "yangre" APPEND PROPERTY ENVIRONMENT "YANGRE=${PROJECT_BINARY_DIR}") +endif() diff --git a/tests/yangre/all.tcl b/tests/yangre/all.tcl new file mode 100644 index 0000000..f00563f --- /dev/null +++ b/tests/yangre/all.tcl @@ -0,0 +1,15 @@ +package require tcltest + +# Hook to determine if any of the tests failed. +# Sets a global variable exitCode to 1 if any test fails otherwise it is set to 0. +proc tcltest::cleanupTestsHook {} { + variable numTests + set ::exitCode [expr {$numTests(Failed) > 0}] +} + +if {[info exists ::env(TESTS_DIR)]} { + tcltest::configure -testdir "$env(TESTS_DIR)" +} + +tcltest::runAllTests +exit $exitCode diff --git a/tests/yangre/arg.test b/tests/yangre/arg.test new file mode 100644 index 0000000..821aad1 --- /dev/null +++ b/tests/yangre/arg.test @@ -0,0 +1,19 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/ly.tcl" : "ly.tcl"}] + +test arg_empty {Missing arguments} { + ly_cmd_err "" "missing <string> parameter to process" +} {} + +test arg_wrong {Wrong argument} { + ly_cmd_err "-j" "invalid option" +} {} + +test arg_help {Print help} { + ly_cmd "-h" "Usage:" +} {} + +test arg_version {Print version} { + ly_cmd "-v" "yangre" +} {} + +cleanupTests diff --git a/tests/yangre/file.test b/tests/yangre/file.test new file mode 100644 index 0000000..80ea3f6 --- /dev/null +++ b/tests/yangre/file.test @@ -0,0 +1,37 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/ly.tcl" : "ly.tcl"}] + +set fdir "$::env(TESTS_DIR)/files" + +test file_empty {file is empty} { + ly_cmd "-f $fdir/empty.txt" +} {} + +test file_empty_str {<string> is empty} { + ly_cmd "-f $fdir/empty_str.txt" +} {} + +test file_empty_str_err {empty <string> is not allowed} { + ly_cmd_err "-f $fdir/empty_str_err.txt" "does not conform" +} {} + +test file_one_pattern {one pattern in the file} { + ly_cmd "-f $fdir/one_pattern.txt" +} {} + +test file_two_patterns {two patterns in the file} { + ly_cmd "-f $fdir/two_patterns.txt" +} {} + +test file_two_patterns_err {two patterns and the <string> is wrong} { + ly_cmd_err "-f $fdir/two_patterns_err.txt" "does not conform" +} {} + +test file_two_patterns_invert_match {one pattern is inverted} { + ly_cmd "-f $fdir/two_patterns_invert_match.txt" +} {} + +test file_two_patterns_invert_match_err {one pattern is inverted and the <string> is wrong} { + ly_cmd_err "-f $fdir/two_patterns_invert_match_err.txt" "does not conform to inverted" +} {} + +cleanupTests diff --git a/tests/yangre/files/empty.txt b/tests/yangre/files/empty.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/yangre/files/empty.txt diff --git a/tests/yangre/files/empty_str.txt b/tests/yangre/files/empty_str.txt new file mode 100644 index 0000000..bf9b1b5 --- /dev/null +++ b/tests/yangre/files/empty_str.txt @@ -0,0 +1,2 @@ +"[0-9a-fA-F]*" + diff --git a/tests/yangre/files/empty_str_err.txt b/tests/yangre/files/empty_str_err.txt new file mode 100644 index 0000000..f48a15d --- /dev/null +++ b/tests/yangre/files/empty_str_err.txt @@ -0,0 +1,2 @@ +"[0-9a-fA-F]+" + diff --git a/tests/yangre/files/one_pattern.txt b/tests/yangre/files/one_pattern.txt new file mode 100644 index 0000000..cf9acc5 --- /dev/null +++ b/tests/yangre/files/one_pattern.txt @@ -0,0 +1,3 @@ +"[0-9a-fA-F]*" + +1F diff --git a/tests/yangre/files/two_patterns.txt b/tests/yangre/files/two_patterns.txt new file mode 100644 index 0000000..7d04b2c --- /dev/null +++ b/tests/yangre/files/two_patterns.txt @@ -0,0 +1,4 @@ +"[0-9a-fA-F]*" +'[a-zA-Z0-9\-_.]*' + +1F diff --git a/tests/yangre/files/two_patterns_err.txt b/tests/yangre/files/two_patterns_err.txt new file mode 100644 index 0000000..78f9878 --- /dev/null +++ b/tests/yangre/files/two_patterns_err.txt @@ -0,0 +1,4 @@ +"[0-9a-fA-F]*" +'[a-zA-Z0-9\-_.]*' + +@!@ diff --git a/tests/yangre/files/two_patterns_invert_match.txt b/tests/yangre/files/two_patterns_invert_match.txt new file mode 100644 index 0000000..ffbd835 --- /dev/null +++ b/tests/yangre/files/two_patterns_invert_match.txt @@ -0,0 +1,4 @@ +"[a-z]*" + '[a-f]*' + +gh diff --git a/tests/yangre/files/two_patterns_invert_match_err.txt b/tests/yangre/files/two_patterns_invert_match_err.txt new file mode 100644 index 0000000..f182aab --- /dev/null +++ b/tests/yangre/files/two_patterns_invert_match_err.txt @@ -0,0 +1,4 @@ +"[a-z]*" + '[a-f]*' + +ab diff --git a/tests/yangre/invert_match.test b/tests/yangre/invert_match.test new file mode 100644 index 0000000..707ca9d --- /dev/null +++ b/tests/yangre/invert_match.test @@ -0,0 +1,28 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/ly.tcl" : "ly.tcl"}] + +test invert_match_from_help1 {Test the first pattern from help via invert match} { + ly_cmd_err {-p {"[0-9a-fA-F]*"} -i {1F}} "not conform to inverted" + ly_cmd {-p {"[0-9a-fA-F]*"} -i {GUN}} +} {} + +test invert_match_from_help2 {Test the second pattern from help via invert match} { + ly_cmd_err {-p {'[a-zA-Z0-9\-_.]*'} -i {a-b}} "not conform to inverted" + ly_cmd {-p {'[a-zA-Z0-9\-_.]*'} -i {%@}} +} {} + +test invert_match_from_help3 {Test the third pattern from help via invert match} { + ly_cmd_err {-p {[xX][mM][lL].*} -i {xml-encoding}} "not conform to inverted" + ly_cmd {-p {[xX][mM][lL].*} -i {json}} +} {} + +test invert_match_three_at_once {Test three inverted patterns and once} { + ly_cmd_err {-p {"[0-9a-zA-Z]*"} -i -p {'[a-zA-Z0-9\-_.]*'} -i -p {[xX][mM][lL].*} -i {xml}} "not conform to inverted" + ly_cmd {-p {"[0-9a-zA-Z]*"} -i -p {'[a-zA-Z0-9\-_.]*'} -i -p {[xX][mM][lL].*} -i {%@}} +} {} + +test invert_match_second_is_not {Test three patterns but the second one is not inverted} { + ly_cmd_err {-p {"[0-9a-zA-Z]*"} -i -p {'[a-zA-Z0-9\-_.]*'} -i -p {[xX][mM][lL].*} -i {o_O}} "not conform to inverted" + ly_cmd {-p {"[0-9a-zA-Z]*"} -i -p {'[a-zA-Z0-9\-_.]*'} -p {[xX][mM][lL].*} -i {o_O}} +} {} + +cleanupTests diff --git a/tests/yangre/ly.tcl b/tests/yangre/ly.tcl new file mode 100644 index 0000000..3bb62b5 --- /dev/null +++ b/tests/yangre/ly.tcl @@ -0,0 +1,17 @@ +# @brief The main source of functions and variables for testing yangre. + +package require tcltest +namespace import ::tcltest::test ::tcltest::cleanupTests + +if { ![info exists ::env(TESTS_DIR)] } { + # the script is not run via 'ctest' so paths must be set + set ::env(TESTS_DIR) "./" + set TUT_PATH "../../build" +} else { + # cmake (ctest) already sets ::env variables + set TUT_PATH $::env(YANGRE) +} +set TUT_NAME "yangre" +source "$::env(TESTS_DIR)/../tool_ni.tcl" + +# The script continues by defining variables and functions specific to the yangre tool. diff --git a/tests/yangre/pattern.test b/tests/yangre/pattern.test new file mode 100644 index 0000000..45b7e3b --- /dev/null +++ b/tests/yangre/pattern.test @@ -0,0 +1,19 @@ +source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/ly.tcl" : "ly.tcl"}] + +test pattern_from_help1 {Test the first pattern from help} { + ly_cmd {-p {"[0-9a-fA-F]*"} {1F}} +} {} + +test pattern_from_help2 {Test the second pattern from help} { + ly_cmd {-p {'[a-zA-Z0-9\-_.]*'} {a-b}} +} {} + +test pattern_from_help3 {Test the third pattern from help} { + ly_cmd {-p {[xX][mM][lL].*} {xml-encoding}} +} {} + +test pattern_three_at_once {Test three patterns and once} { + ly_cmd {-p {"[0-9a-zA-Z]*"} -p {'[a-zA-Z0-9\-_.]*'} -p {[xX][mM][lL].*} {xml}} +} {} + +cleanupTests diff --git a/tools/lint/CMakeLists.txt b/tools/lint/CMakeLists.txt index 32cdcbf..14f8b76 100644 --- a/tools/lint/CMakeLists.txt +++ b/tools/lint/CMakeLists.txt @@ -17,6 +17,12 @@ set(lintsrc cmd_load.c cmd_print.c cmd_searchpath.c + cmd_extdata.c + cmd_help.c + cmd_verb.c + cmd_debug.c + yl_opt.c + yl_schema_features.c common.c ) if(YANGLINT_INTERACTIVE) @@ -46,45 +52,3 @@ if(WIN32) target_include_directories(yanglint PRIVATE ${GETOPT_INCLUDE_DIR}) target_link_libraries(yanglint ${GETOPT_LIBRARY}) endif() - -# -# tests -# -function(add_yanglint_test) - cmake_parse_arguments(ADDTEST "" "NAME;VIA;SCRIPT" "" ${ARGN}) - set(TEST_NAME yanglint_${ADDTEST_NAME}) - - if(${ADDTEST_VIA} STREQUAL "bash") - set(WRAPPER /usr/bin/env bash) - elseif(${ADDTEST_VIA} STREQUAL "expect") - set(WRAPPER ${PATH_EXPECT}) - else() - message(FATAL_ERROR "build: unexpected wrapper '${ADDTEST_VIA}'") - endif() - - add_test(NAME ${TEST_NAME} COMMAND ${WRAPPER} ${CMAKE_CURRENT_SOURCE_DIR}/tests/${ADDTEST_SCRIPT}) - set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "YANGLINT=${PROJECT_BINARY_DIR}/yanglint") - set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "YANG_MODULES_DIR=${PROJECT_SOURCE_DIR}/tests/modules/yang") - set_property(TEST ${TEST_NAME} APPEND PROPERTY ENVIRONMENT "CURRENT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}") -endfunction(add_yanglint_test) - -if(ENABLE_TESTS) - # tests of non-interactive mode using shunit2 - find_program(PATH_SHUNIT NAMES shunit2) - if(NOT PATH_SHUNIT) - message(WARNING "'shunit2' not found! The yanglint(1) non-interactive tests will not be available.") - else() - add_yanglint_test(NAME ni_list VIA bash SCRIPT shunit2/list.sh) - add_yanglint_test(NAME ni_feature VIA bash SCRIPT shunit2/feature.sh) - endif() - - # tests of interactive mode using expect - find_program(PATH_EXPECT NAMES expect) - if(NOT PATH_EXPECT) - message(WARNING "'expect' not found! The yanglint(1) interactive tests will not be available.") - elseif(YANGLINT_INTERACTIVE) - add_yanglint_test(NAME in_list VIA expect SCRIPT expect/list.exp) - add_yanglint_test(NAME in_feature VIA expect SCRIPT expect/feature.exp) - add_yanglint_test(NAME in_completion VIA expect SCRIPT expect/completion.exp) - endif() -endif() diff --git a/tools/lint/cmd.c b/tools/lint/cmd.c index 10e7446..344900d 100644 --- a/tools/lint/cmd.c +++ b/tools/lint/cmd.c @@ -2,9 +2,10 @@ * @file cmd.c * @author Michal Vasko <mvasko@cesnet.cz> * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief libyang's yanglint tool general commands * - * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -30,230 +31,80 @@ COMMAND commands[]; extern int done; -#ifndef NDEBUG - -void -cmd_debug_help(void) -{ - printf("Usage: debug (dict | xpath)+\n"); -} - void -cmd_debug(struct ly_ctx **UNUSED(ctx), const char *cmdline) +cmd_free(void) { - int argc = 0; - char **argv = NULL; - int opt, opt_index; - struct option options[] = { - {"help", no_argument, NULL, 'h'}, - {NULL, 0, NULL, 0} - }; - uint32_t dbg_groups = 0; - - if (parse_cmdline(cmdline, &argc, &argv)) { - goto cleanup; - } + uint16_t i; - while ((opt = getopt_long(argc, argv, "h", options, &opt_index)) != -1) { - switch (opt) { - case 'h': - cmd_debug_help(); - goto cleanup; - default: - YLMSG_E("Unknown option.\n"); - goto cleanup; + for (i = 0; commands[i].name; i++) { + if (commands[i].free_func) { + commands[i].free_func(); } } - if (argc == optind) { - /* no argument */ - cmd_debug_help(); - goto cleanup; - } - - for (int i = 0; i < argc - optind; i++) { - if (!strcasecmp("dict", argv[optind + i])) { - dbg_groups |= LY_LDGDICT; - } else if (!strcasecmp("xpath", argv[optind + i])) { - dbg_groups |= LY_LDGXPATH; - } else { - YLMSG_E("Unknown debug group \"%s\"\n", argv[optind + 1]); - goto cleanup; - } - } - - ly_log_dbg_groups(dbg_groups); - -cleanup: - free_cmdline(argv); } -#endif - -void -cmd_verb_help(void) -{ - printf("Usage: verb (error | warning | verbose | debug)\n"); -} - -void -cmd_verb(struct ly_ctx **UNUSED(ctx), const char *cmdline) -{ - int argc = 0; - char **argv = NULL; - int opt, opt_index; - struct option options[] = { - {"help", no_argument, NULL, 'h'}, - {NULL, 0, NULL, 0} - }; - - if (parse_cmdline(cmdline, &argc, &argv)) { - goto cleanup; - } - - while ((opt = getopt_long(argc, argv, "h", options, &opt_index)) != -1) { - switch (opt) { - case 'h': - cmd_verb_help(); - goto cleanup; - default: - YLMSG_E("Unknown option.\n"); - goto cleanup; - } - } - - if (argc - optind > 1) { - YLMSG_E("Only a single verbosity level can be set.\n"); - cmd_verb_help(); - goto cleanup; - } else if (argc == optind) { - /* no argument - print current value */ - LY_LOG_LEVEL level = ly_log_level(LY_LLERR); - - ly_log_level(level); - printf("Current verbosity level: "); - if (level == LY_LLERR) { - printf("error\n"); - } else if (level == LY_LLWRN) { - printf("warning\n"); - } else if (level == LY_LLVRB) { - printf("verbose\n"); - } else if (level == LY_LLDBG) { - printf("debug\n"); - } - goto cleanup; - } - - if (!strcasecmp("error", argv[optind]) || !strcmp("0", argv[optind])) { - ly_log_level(LY_LLERR); - } else if (!strcasecmp("warning", argv[optind]) || !strcmp("1", argv[optind])) { - ly_log_level(LY_LLWRN); - } else if (!strcasecmp("verbose", argv[optind]) || !strcmp("2", argv[optind])) { - ly_log_level(LY_LLVRB); - } else if (!strcasecmp("debug", argv[optind]) || !strcmp("3", argv[optind])) { - ly_log_level(LY_LLDBG); - } else { - YLMSG_E("Unknown verbosity \"%s\"\n", argv[optind]); - goto cleanup; - } - -cleanup: - free_cmdline(argv); -} - -void -cmd_quit(struct ly_ctx **UNUSED(ctx), const char *UNUSED(cmdline)) +int +cmd_quit_exec(struct ly_ctx **UNUSED(ctx), struct yl_opt *UNUSED(yo), const char *UNUSED(posv)) { done = 1; - return; -} - -void -cmd_help_help(void) -{ - printf("Usage: help [cmd ...]\n"); -} - -void -cmd_help(struct ly_ctx **UNUSED(ctx), const char *cmdline) -{ - int argc = 0; - char **argv = NULL; - int opt, opt_index; - struct option options[] = { - {"help", no_argument, NULL, 'h'}, - {NULL, 0, NULL, 0} - }; - - if (parse_cmdline(cmdline, &argc, &argv)) { - goto cleanup; - } - - while ((opt = getopt_long(argc, argv, "h", options, &opt_index)) != -1) { - switch (opt) { - case 'h': - cmd_help_help(); - goto cleanup; - default: - YLMSG_E("Unknown option.\n"); - goto cleanup; - } - } - - if (argc == optind) { -generic_help: - printf("Available commands:\n"); - for (uint16_t i = 0; commands[i].name; i++) { - if (commands[i].helpstring != NULL) { - printf(" %-15s %s\n", commands[i].name, commands[i].helpstring); - } - } - } else { - /* print specific help for the selected command(s) */ - - for (int c = 0; c < argc - optind; ++c) { - int8_t match = 0; - - /* get the command of the specified name */ - for (uint16_t i = 0; commands[i].name; i++) { - if (strcmp(argv[optind + c], commands[i].name) == 0) { - match = 1; - if (commands[i].help_func != NULL) { - commands[i].help_func(); - } else { - printf("%s: %s\n", argv[optind + c], commands[i].helpstring); - } - break; - } - } - if (!match) { - /* if unknown command specified, print the list of commands */ - printf("Unknown command \'%s\'\n", argv[optind + c]); - goto generic_help; - } - } - } - -cleanup: - free_cmdline(argv); + return 0; } +/* Also keep enum COMMAND_INDEX updated. */ COMMAND commands[] = { - {"help", cmd_help, cmd_help_help, "Display commands description"}, - {"add", cmd_add, cmd_add_help, "Add a new module from a specific file"}, - {"load", cmd_load, cmd_load_help, "Load a new schema from the searchdirs"}, - {"print", cmd_print, cmd_print_help, "Print a module"}, - {"data", cmd_data, cmd_data_help, "Load, validate and optionally print instance data"}, - {"list", cmd_list, cmd_list_help, "List all the loaded modules"}, - {"feature", cmd_feature, cmd_feature_help, "Print all features of module(s) with their state"}, - {"searchpath", cmd_searchpath, cmd_searchpath_help, "Print/set the search path(s) for schemas"}, - {"clear", cmd_clear, cmd_clear_help, "Clear the context - remove all the loaded modules"}, - {"verb", cmd_verb, cmd_verb_help, "Change verbosity"}, + { + "help", cmd_help_opt, NULL, cmd_help_exec, NULL, cmd_help_help, NULL, + "Display commands description", "h" + }, + { + "add", cmd_add_opt, cmd_add_dep, cmd_add_exec, NULL, cmd_add_help, NULL, + "Add a new module from a specific file", "DF:hiX" + }, + { + "load", cmd_load_opt, cmd_load_dep, cmd_load_exec, NULL, cmd_load_help, NULL, + "Load a new schema from the searchdirs", "F:hiX" + }, + { + "print", cmd_print_opt, cmd_print_dep, cmd_print_exec, NULL, cmd_print_help, NULL, + "Print a module", "f:hL:o:P:q" + }, + { + "data", cmd_data_opt, cmd_data_dep, cmd_data_store, cmd_data_process, cmd_data_help, NULL, + "Load, validate and optionally print instance data", "d:ef:F:hmo:O:R:r:nt:x:" + }, + { + "list", cmd_list_opt, cmd_list_dep, cmd_list_exec, NULL, cmd_list_help, NULL, + "List all the loaded modules", "f:h" + }, + { + "feature", cmd_feature_opt, cmd_feature_dep, cmd_feature_exec, cmd_feature_fin, cmd_feature_help, NULL, + "Print all features of module(s) with their state", "haf" + }, + { + "searchpath", cmd_searchpath_opt, NULL, cmd_searchpath_exec, NULL, cmd_searchpath_help, NULL, + "Print/set the search path(s) for schemas", "ch" + }, + { + "extdata", cmd_extdata_opt, cmd_extdata_dep, cmd_extdata_exec, NULL, cmd_extdata_help, cmd_extdata_free, + "Set the specific data required by an extension", "ch" + }, + { + "clear", cmd_clear_opt, cmd_clear_dep, cmd_clear_exec, NULL, cmd_clear_help, NULL, + "Clear the context - remove all the loaded modules", "iyhY:" + }, + { + "verb", cmd_verb_opt, cmd_verb_dep, cmd_verb_exec, NULL, cmd_verb_help, NULL, + "Change verbosity", "h" + }, #ifndef NDEBUG - {"debug", cmd_debug, cmd_debug_help, "Display specific debug message groups"}, + { + "debug", cmd_debug_opt, cmd_debug_dep, cmd_debug_store, cmd_debug_setlog, cmd_debug_help, NULL, + "Display specific debug message groups", "h" + }, #endif - {"quit", cmd_quit, NULL, "Quit the program"}, + {"quit", NULL, NULL, cmd_quit_exec, NULL, NULL, NULL, "Quit the program", "h"}, /* synonyms for previous commands */ - {"?", cmd_help, NULL, "Display commands description"}, - {"exit", cmd_quit, NULL, "Quit the program"}, - {NULL, NULL, NULL, NULL} + {"?", NULL, NULL, cmd_help_exec, NULL, NULL, NULL, "Display commands description", "h"}, + {"exit", NULL, NULL, cmd_quit_exec, NULL, NULL, NULL, "Quit the program", "h"}, + {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL} }; diff --git a/tools/lint/cmd.h b/tools/lint/cmd.h index 9f6f88d..bd2f2f2 100644 --- a/tools/lint/cmd.h +++ b/tools/lint/cmd.h @@ -2,9 +2,10 @@ * @file cmd.h * @author Michal Vasko <mvasko@cesnet.cz> * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief libyang's yanglint tool commands header * - * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -18,15 +19,44 @@ #include "libyang.h" +struct yl_opt; + /** * @brief command information + * + * Callback functions are in the order they should be called. + * First, the 'opt_func' should be called, which parses arguments from the command line and sets flags or pointers in + * the struct yl_opt. This type of function is for interactive mode and is optional. + * Then the 'dep_func' callback can check the struct yl_opt settings. Other items that depend on them can also be + * set. There is also an possibility for controlling the number of positional arguments and its implications. + * The most important callback is 'exec_func' where the command itself is executed. This function can even replace the + * entire libyang context. The function parameters are mainly found in the yl_opt structure. Optionally, the function + * can be called with a positional argument obtained from the command line. Some 'exec_func' are adapted to be called + * from non-interactive yanglint mode. + * The 'fun_func' complements the 'exec_func'. In some cases, the command execution must be divided into two stages. + * For example, the 'exec_func' is used to fill some items in the yl_opt structure from the positional + * arguments and then the 'fin_func' is used to perform the final action. */ typedef struct { - char *name; /* User printable name of the function. */ + /* User printable name of the function. */ + char *name; - void (*func)(struct ly_ctx **ctx, const char *); /* Function to call to do the command. */ - void (*help_func)(void); /* Display command help. */ - char *helpstring; /* Documentation for this function. */ + /* Convert command line options to the data struct yl_opt. */ + int (*opt_func)(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + /* Additionally set dependent items and perform error checking. */ + int (*dep_func)(struct yl_opt *yo, int posc); + /* Execute command. */ + int (*exec_func)(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); + /* Finish execution of command. */ + int (*fin_func)(struct ly_ctx *ctx, struct yl_opt *yo); + /* Display command help. */ + void (*help_func)(void); + /* Freeing global variables allocated by the command. */ + void (*free_func)(void); + /* Documentation for this function. */ + char *helpstring; + /* Option characters used in function getopt_long. */ + char *optstring; } COMMAND; /** @@ -34,36 +64,333 @@ typedef struct { */ extern COMMAND commands[]; +/** + * @brief Index for global variable ::commands. + */ +enum COMMAND_INDEX { + CMD_HELP = 0, + CMD_ADD, + CMD_LOAD, + CMD_PRINT, + CMD_DATA, + CMD_LIST, + CMD_FEATURE, + CMD_SEARCHPATH, + CMD_EXTDATA, + CMD_CLEAR, + CMD_VERB, +#ifndef NDEBUG + CMD_DEBUG, +#endif +}; + +/** + * @brief For each cmd, call the COMMAND.free_func in the variable 'commands'. + */ +void cmd_free(void); + /* cmd_add.c */ -void cmd_add(struct ly_ctx **ctx, const char *cmdline); + +/** + * @brief Parse the arguments of an interactive command. + * + * @param[out] yo Context for yanglint. + * @param[in] cmdline String containing command line arguments. + * @param[out] posv Pointer to argv to a section of positional arguments. + * @param[out] posc Number of positional arguments. + * @return 0 on success. + */ +int cmd_add_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @brief Check the options and set dependent items in @p yo. + * + * @param[in,out] yo context for yanglint. + * @param[in] posc number of positional arguments. + * @return 0 on success. + */ +int cmd_add_dep(struct yl_opt *yo, int posc); + +/** + * @brief Parse and compile a new module using filepath. + * + * @param[in,out] ctx Context for libyang. + * @param[in,out] yo Context for yanglint. + * @param[in] posv Path to the file where the new module is located. + * @return 0 on success. + */ +int cmd_add_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); void cmd_add_help(void); /* cmd_clear.c */ -void cmd_clear(struct ly_ctx **ctx, const char *cmdline); + +/** + * @copydoc cmd_add_opt + */ +int cmd_clear_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_clear_dep(struct yl_opt *yo, int posc); + +/** + * @brief Clear libyang context. + * + * @param[in,out] ctx context for libyang that will be replaced with an empty one. + * @param[in,out] yo context for yanglint. + * @param[in] posv not used. + * @return 0 on success. + */ +int cmd_clear_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); void cmd_clear_help(void); /* cmd_data.c */ -void cmd_data(struct ly_ctx **ctx, const char *cmdline); + +/** + * @copydoc cmd_add_opt + */ +int cmd_data_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_data_dep(struct yl_opt *yo, int posc); + +/** + * @brief Store data file for later processing. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Path to the file where the data is located. + * @return 0 on success. + */ +int cmd_data_store(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); + +/** + * @brief Parse, validate and optionally print data instances. + * + * @param[in] ctx Context for libyang. + * @param[in] yo Context of yanglint. All necessary parameters should already be set. + * @return 0 on success. + */ +int cmd_data_process(struct ly_ctx *ctx, struct yl_opt *yo); void cmd_data_help(void); /* cmd_list.c */ -void cmd_list(struct ly_ctx **ctx, const char *cmdline); + +/** + * @copydoc cmd_add_opt + */ +int cmd_list_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_list_dep(struct yl_opt *yo, int posc); + +/** + * @brief Print the list of modules in the current context. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Not used. + * @return 0 on success. + */ +int cmd_list_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); void cmd_list_help(void); /* cmd_feature.c */ -void cmd_feature(struct ly_ctx **ctx, const char *cmdline); + +/** + * @copydoc cmd_add_opt + */ +int cmd_feature_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_feature_dep(struct yl_opt *yo, int posc); + +/** + * @brief Print the features the modules. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Name of the module which features are to be printed. + * @return 0 on success. + */ +int cmd_feature_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); + +/** + * @brief Printing of features ends. + * + * @param[in] ctx context for libyang. Not used. + * @param[in] yo context for yanglint. + * @return 0 on success. + */ +int cmd_feature_fin(struct ly_ctx *ctx, struct yl_opt *yo); void cmd_feature_help(void); /* cmd_load.c */ -void cmd_load(struct ly_ctx **ctx, const char *cmdline); + +/** + * @copydoc cmd_add_opt + */ +int cmd_load_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_load_dep(struct yl_opt *yo, int posc); + +/** + * @brief Parse and compile a new module using module name. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Name of the module to be loaded into the context. + * @return 0 on success. + */ +int cmd_load_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); void cmd_load_help(void); /* cmd_print.c */ -void cmd_print(struct ly_ctx **ctx, const char *cmdline); + +/** + * @copydoc cmd_add_opt + */ +int cmd_print_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_print_dep(struct yl_opt *yo, int posc); + +/** + * @brief Print a schema module. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Name of the module to be printed. Can be NULL in the case of printing a node. + * @return 0 on success. + */ +int cmd_print_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); void cmd_print_help(void); /* cmd_searchpath.c */ -void cmd_searchpath(struct ly_ctx **ctx, const char *cmdline); + +/** + * @copydoc cmd_add_opt + */ +int cmd_searchpath_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @brief Set the paths of directories where to search schema modules. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Path to the directory. Can be NULL in the case of printing a current searchdirs. + * @return 0 on success. + */ +int cmd_searchpath_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); void cmd_searchpath_help(void); +/* cmd_extdata.c */ + +/** + * @copydoc cmd_add_opt + */ +int cmd_extdata_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_extdata_dep(struct yl_opt *yo, int posc); + +/** + * @brief Set path to the file required by the extension. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Path to the directory. Can be NULL in the case of printing a current path. + * @return 0 on success. + */ +int cmd_extdata_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); +void cmd_extdata_help(void); +void cmd_extdata_free(void); + +/* cmd_help.c */ + +/** + * @copydoc cmd_add_opt + */ +int cmd_help_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @brief Print help. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Name of the command which help message is to be printed. Can be NULL. + * @return 0 on success. + */ +int cmd_help_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); +void cmd_help_help(void); + +/* cmd_verb.c */ + +/** + * @copydoc cmd_add_opt + */ +int cmd_verb_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_verb_dep(struct yl_opt *yo, int posc); + +/** + * @brief Set the verbose level. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Name of the verbose level to be set. + * @return 0 on success. + */ +int cmd_verb_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); +void cmd_verb_help(void); + +/* cmd_debug.c */ + +/** + * @copydoc cmd_add_opt + */ +int cmd_debug_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc); + +/** + * @copydoc cmd_add_dep + */ +int cmd_debug_dep(struct yl_opt *yo, int posc); + +/** + * @brief Store the type of debug messages for later processing. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. + * @param[in] posv Name of the debug type to be set. + * @return 0 on success. + */ +int cmd_debug_store(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); + +/** + * @brief Set debug logging. + * + * @param[in,out] ctx context for libyang. + * @param[in,out] yo context for yanglint. All necessary parameters should already be set. + * @return 0 on success. + */ +int cmd_debug_setlog(struct ly_ctx *ctx, struct yl_opt *yo); +void cmd_debug_help(void); + #endif /* COMMANDS_H_ */ diff --git a/tools/lint/cmd_add.c b/tools/lint/cmd_add.c index bbfdfd6..9f10711 100644 --- a/tools/lint/cmd_add.c +++ b/tools/lint/cmd_add.c @@ -2,9 +2,10 @@ * @file cmd_add.c * @author Michal Vasko <mvasko@cesnet.cz> * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief 'add' command of the libyang's yanglint tool. * - * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -17,8 +18,8 @@ #include "cmd.h" +#include <assert.h> #include <getopt.h> -#include <libgen.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> @@ -26,6 +27,8 @@ #include "libyang.h" #include "common.h" +#include "yl_opt.h" +#include "yl_schema_features.h" void cmd_add_help(void) @@ -44,133 +47,164 @@ cmd_add_help(void) " -i, --make-implemented\n" " Make the imported modules \"referenced\" from any loaded\n" " <schema> module also implemented. If specified a second time,\n" - " all the modules are set implemented.\n"); + " all the modules are set implemented.\n" + " -X, --extended-leafref\n" + " Allow usage of deref() XPath function within leafref.\n"); } -void -cmd_add(struct ly_ctx **ctx, const char *cmdline) +int +cmd_add_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) { - int argc = 0; - char **argv = NULL; + int rc = 0, argc = 0; int opt, opt_index; struct option options[] = { {"disable-searchdir", no_argument, NULL, 'D'}, {"features", required_argument, NULL, 'F'}, {"help", no_argument, NULL, 'h'}, {"make-implemented", no_argument, NULL, 'i'}, + {"extended-leafref", no_argument, NULL, 'X'}, {NULL, 0, NULL, 0} }; - uint16_t options_ctx = 0; - const char *all_features[] = {"*", NULL}; - struct ly_set fset = {0}; - if (parse_cmdline(cmdline, &argc, &argv)) { - goto cleanup; + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; } - while ((opt = getopt_long(argc, argv, "D:F:hi", options, &opt_index)) != -1) { + while ((opt = getopt_long(argc, yo->argv, commands[CMD_ADD].optstring, options, &opt_index)) != -1) { switch (opt) { case 'D': /* --disable--search */ - if (options_ctx & LY_CTX_DISABLE_SEARCHDIRS) { - YLMSG_W("The -D option specified too many times.\n"); - } - if (options_ctx & LY_CTX_DISABLE_SEARCHDIR_CWD) { - options_ctx &= ~LY_CTX_DISABLE_SEARCHDIR_CWD; - options_ctx |= LY_CTX_DISABLE_SEARCHDIRS; - } else { - options_ctx |= LY_CTX_DISABLE_SEARCHDIR_CWD; + if (yo->ctx_options & LY_CTX_DISABLE_SEARCHDIRS) { + YLMSG_W("The -D option specified too many times."); } + yo_opt_update_disable_searchdir(yo); break; case 'F': /* --features */ - if (parse_features(optarg, &fset)) { - goto cleanup; + if (parse_features(optarg, &yo->schema_features)) { + return 1; } break; case 'h': cmd_add_help(); - goto cleanup; + return 1; case 'i': /* --make-implemented */ - if (options_ctx & LY_CTX_REF_IMPLEMENTED) { - options_ctx &= ~LY_CTX_REF_IMPLEMENTED; - options_ctx |= LY_CTX_ALL_IMPLEMENTED; - } else { - options_ctx |= LY_CTX_REF_IMPLEMENTED; - } + yo_opt_update_make_implemented(yo); + break; + + case 'X': /* --extended-leafref */ + yo->ctx_options |= LY_CTX_LEAFREF_EXTENDED; break; default: - YLMSG_E("Unknown option.\n"); - goto cleanup; + YLMSG_E("Unknown option."); + return 1; } } - if (argc == optind) { + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_add_dep(struct yl_opt *yo, int posc) +{ + if (yo->interactive && !posc) { /* no argument */ cmd_add_help(); - goto cleanup; + return 1; } - - if (!fset.count) { + if (!yo->schema_features.count) { /* no features, enable all of them */ - options_ctx |= LY_CTX_ENABLE_IMP_FEATURES; + yo->ctx_options |= LY_CTX_ENABLE_IMP_FEATURES; } - if (options_ctx) { - ly_ctx_set_options(*ctx, options_ctx); - } + return 0; +} - for (int i = 0; i < argc - optind; i++) { - /* process the schema module files */ - LY_ERR ret; - uint8_t path_unset = 1; /* flag to unset the path from the searchpaths list (if not already present) */ - char *dir, *module; - const char **features = NULL; - struct ly_in *in = NULL; +static int +store_parsed_module(const char *filepath, struct lys_module *mod, struct yl_opt *yo) +{ + assert(!yo->interactive); - if (parse_schema_path(argv[optind + i], &dir, &module)) { - goto cleanup; + if (yo->schema_out_format || yo->feature_param_format) { + if (ly_set_add(&yo->schema_modules, (void *)mod, 1, NULL)) { + YLMSG_E("Storing parsed schema module (%s) for print failed.", filepath); + return 1; } + } - /* add temporarily also the path of the module itself */ - if (ly_ctx_set_searchdir(*ctx, dirname(dir)) == LY_EEXIST) { - path_unset = 0; - } + return 0; +} - /* get features list for this module */ - if (!fset.count) { - features = all_features; - } else { - get_features(&fset, module, &features); - } +int +cmd_add_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + const char *all_features[] = {"*", NULL}; + LY_ERR ret; + uint8_t path_unset = 1; /* flag to unset the path from the searchpaths list (if not already present) */ + char *dir, *modname = NULL; + const char **features = NULL; + struct ly_in *in = NULL; + struct lys_module *mod; + + assert(posv); - /* temporary cleanup */ - free(dir); - free(module); + if (yo->ctx_options) { + ly_ctx_set_options(*ctx, yo->ctx_options); + } + + if (parse_schema_path(posv, &dir, &modname)) { + return 1; + } - /* prepare input handler */ - ret = ly_in_new_filepath(argv[optind + i], 0, &in); - if (ret) { + if (!yo->interactive) { + /* The module should already be parsed if yang_lib_file was set. */ + if (yo->yang_lib_file && (mod = ly_ctx_get_module_implemented(*ctx, modname))) { + ret = store_parsed_module(posv, mod, yo); goto cleanup; } + /* parse module */ + } + + /* add temporarily also the path of the module itself */ + if (ly_ctx_set_searchdir(*ctx, dir) == LY_EEXIST) { + path_unset = 0; + } + + /* get features list for this module */ + if (!yo->schema_features.count) { + features = all_features; + } else { + get_features(&yo->schema_features, modname, &features); + } + + /* prepare input handler */ + ret = ly_in_new_filepath(posv, 0, &in); + if (ret) { + goto cleanup; + } - /* parse the file */ - ret = lys_parse(*ctx, in, LYS_IN_UNKNOWN, features, NULL); - ly_in_free(in, 1); - ly_ctx_unset_searchdir_last(*ctx, path_unset); + /* parse the file */ + ret = lys_parse(*ctx, in, LYS_IN_UNKNOWN, features, &mod); + ly_in_free(in, 1); + ly_ctx_unset_searchdir_last(*ctx, path_unset); + if (ret) { + goto cleanup; + } - if (ret) { - /* libyang printed the error messages */ + if (!yo->interactive) { + if ((ret = store_parsed_module(posv, mod, yo))) { goto cleanup; } } cleanup: - if (options_ctx) { - ly_ctx_unset_options(*ctx, options_ctx); - } - ly_set_erase(&fset, free_features); - free_cmdline(argv); + free(dir); + free(modname); + + return ret; } diff --git a/tools/lint/cmd_clear.c b/tools/lint/cmd_clear.c index 5eed6ff..4a869af 100644 --- a/tools/lint/cmd_clear.c +++ b/tools/lint/cmd_clear.c @@ -2,9 +2,10 @@ * @file cmd_clear.c * @author Michal Vasko <mvasko@cesnet.cz> * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief 'clear' command of the libyang's yanglint tool. * - * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -24,6 +25,7 @@ #include "libyang.h" #include "common.h" +#include "yl_opt.h" void cmd_clear_help(void) @@ -32,68 +34,139 @@ cmd_clear_help(void) " Replace the current context with an empty one, searchpaths\n" " are not kept.\n\n" " -i, --make-implemented\n" - " Make the imported modules \"referenced\" from any loaded\n" - " module also implemented. If specified a second time, all the\n" - " modules are set implemented.\n" + " When loading a module into the context, the imported 'referenced'\n" + " modules will also be implemented. If specified a second time,\n" + " all the modules will be implemented.\n" " -y, --yang-library\n" " Load and implement internal \"ietf-yang-library\" YANG module.\n" " Note that this module includes definitions of mandatory state\n" - " data that can result in unexpected data validation errors.\n"); -#if 0 - " If <yang-library-data> path specified, load the modules\n" - " according to the provided yang library data.\n" -#endif + " data that can result in unexpected data validation errors.\n" + " -Y FILE, --yang-library-file=FILE\n" + " Parse FILE with \"ietf-yang-library\" data and use them to\n" + " create an exact YANG schema context. Searchpaths defined so far\n" + " are used, but then deleted.\n"); } -void -cmd_clear(struct ly_ctx **ctx, const char *cmdline) +int +cmd_clear_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) { - int argc = 0; - char **argv = NULL; + int rc = 0, argc = 0; int opt, opt_index; struct option options[] = { - {"make-implemented", no_argument, NULL, 'i'}, - {"yang-library", no_argument, NULL, 'y'}, + {"make-implemented", no_argument, NULL, 'i'}, + {"yang-library", no_argument, NULL, 'y'}, + {"yang-library-file", no_argument, NULL, 'Y'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} }; - uint16_t options_ctx = LY_CTX_NO_YANGLIBRARY; - struct ly_ctx *ctx_new; - if (parse_cmdline(cmdline, &argc, &argv)) { - goto cleanup; + yo->ctx_options = LY_CTX_NO_YANGLIBRARY; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; } - while ((opt = getopt_long(argc, argv, "iyh", options, &opt_index)) != -1) { + while ((opt = getopt_long(argc, yo->argv, commands[CMD_CLEAR].optstring, options, &opt_index)) != -1) { switch (opt) { case 'i': - if (options_ctx & LY_CTX_REF_IMPLEMENTED) { - options_ctx &= ~LY_CTX_REF_IMPLEMENTED; - options_ctx |= LY_CTX_ALL_IMPLEMENTED; + if (yo->ctx_options & LY_CTX_REF_IMPLEMENTED) { + yo->ctx_options &= ~LY_CTX_REF_IMPLEMENTED; + yo->ctx_options |= LY_CTX_ALL_IMPLEMENTED; } else { - options_ctx |= LY_CTX_REF_IMPLEMENTED; + yo->ctx_options |= LY_CTX_REF_IMPLEMENTED; } break; case 'y': - options_ctx &= ~LY_CTX_NO_YANGLIBRARY; + yo->ctx_options &= ~LY_CTX_NO_YANGLIBRARY; + break; + case 'Y': + yo->ctx_options &= ~LY_CTX_NO_YANGLIBRARY; + yo->yang_lib_file = optarg; + if (!yo->yang_lib_file) { + YLMSG_E("Memory allocation error."); + return 1; + } break; case 'h': cmd_clear_help(); - goto cleanup; + return 1; default: - YLMSG_E("Unknown option.\n"); - goto cleanup; + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return rc; +} + +int +cmd_clear_dep(struct yl_opt *yo, int posc) +{ + (void) yo; + + if (posc) { + YLMSG_E("No positional arguments are allowed."); + return 1; + } + + return 0; +} + +/** + * @brief Convert searchpaths into single string. + * + * @param[in] ctx Context with searchpaths. + * @param[out] searchpaths Collection of paths in the single string. Paths are delimited by colon ":" + * (on Windows, used semicolon ";" instead). + * @return LY_ERR value. + */ +static LY_ERR +searchpaths_to_str(const struct ly_ctx *ctx, char **searchpaths) +{ + uint32_t i; + int rc = 0; + const char * const *dirs = ly_ctx_get_searchdirs(ctx); + + for (i = 0; dirs[i]; ++i) { + rc = searchpath_strcat(searchpaths, dirs[i]); + if (!rc) { + break; } } - if (ly_ctx_new(NULL, options_ctx, &ctx_new)) { - YLMSG_W("Failed to create context.\n"); - goto cleanup; + return rc; +} + +int +cmd_clear_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + (void) posv; + struct ly_ctx *ctx_new = NULL; + + if (yo->yang_lib_file) { + if (searchpaths_to_str(*ctx, &yo->searchpaths)) { + YLMSG_E("Storing searchpaths failed."); + return 1; + } + if (ly_ctx_new_ylpath(yo->searchpaths, yo->yang_lib_file, LYD_UNKNOWN, yo->ctx_options, &ctx_new)) { + YLMSG_E("Unable to create libyang context with yang-library data."); + return 1; + } + } else { + if (ly_ctx_new(NULL, yo->ctx_options, &ctx_new)) { + YLMSG_W("Failed to create context."); + return 1; + } } + /* Global variables in commands are also deleted. */ + cmd_free(); + ly_ctx_destroy(*ctx); *ctx = ctx_new; -cleanup: - free_cmdline(argv); + return 0; } diff --git a/tools/lint/cmd_data.c b/tools/lint/cmd_data.c index 25449f5..44fb237 100644 --- a/tools/lint/cmd_data.c +++ b/tools/lint/cmd_data.c @@ -2,9 +2,10 @@ * @file cmd_data.c * @author Michal Vasko <mvasko@cesnet.cz> * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief 'data' command of the libyang's yanglint tool. * - * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -17,6 +18,7 @@ #include "cmd.h" +#include <assert.h> #include <errno.h> #include <getopt.h> #include <stdint.h> @@ -27,18 +29,23 @@ #include "libyang.h" #include "common.h" +#include "yl_opt.h" -void -cmd_data_help(void) +static void +cmd_data_help_header(void) { printf("Usage: data [-emn] [-t TYPE]\n" " [-F FORMAT] [-f FORMAT] [-d DEFAULTS] [-o OUTFILE] <data1> ...\n" " data [-n] -t (rpc | notif | reply) [-O FILE]\n" " [-F FORMAT] [-f FORMAT] [-d DEFAULTS] [-o OUTFILE] <data1> ...\n" " data [-en] [-t TYPE] [-F FORMAT] -x XPATH [-o OUTFILE] <data1> ...\n" - " Parse, validate and optionally print data instances\n\n" + " Parse, validate and optionally print data instances\n"); +} - " -t TYPE, --type=TYPE\n" +static void +cmd_data_help_type(void) +{ + printf(" -t TYPE, --type=TYPE\n" " Specify data tree type in the input data file(s):\n" " data - Complete datastore with status data (default type).\n" " config - Configuration datastore (without status data).\n" @@ -47,39 +54,45 @@ cmd_data_help(void) " edit - Content of the NETCONF <edit-config> operation.\n" " rpc - Content of the NETCONF <rpc> message, defined as YANG's\n" " RPC/Action input statement.\n" + " nc-rpc - Similar to 'rpc' but expect and check also the NETCONF\n" + " envelopes <rpc> or <action>.\n" " reply - Reply to the RPC/Action. Note that the reply data are\n" " expected inside a container representing the original\n" " RPC/Action. This is necessary to identify appropriate\n" " data definitions in the schema module.\n" + " nc-reply - Similar to 'reply' but expect and check also the NETCONF\n" + " envelope <rpc-reply> with output data nodes as direct\n" + " descendants. The original RPC/action invocation is expected\n" + " in a separate parameter '-R' and is parsed as 'nc-rpc'.\n" " notif - Notification instance (content of the <notification>\n" - " element without <eventTime>).\n\n" - - " -e, --present Validate only with the schema modules whose data actually\n" - " exist in the provided input data files. Takes effect only\n" - " with the 'data' or 'config' TYPEs. Used to avoid requiring\n" - " mandatory nodes from modules which data are not present in the\n" - " provided input data files.\n" - " -m, --merge Merge input data files into a single tree and validate at\n" - " once.The option has effect only for 'data' and 'config' TYPEs.\n" - " In case of using -x option, the data are always merged.\n" - " -n, --not-strict\n" - " Do not require strict data parsing (silently skip unknown data),\n" - " has no effect for schemas.\n\n" - " -O FILE, --operational=FILE\n" - " Provide optional data to extend validation of the 'rpc',\n" - " 'reply' or 'notif' TYPEs. The FILE is supposed to contain\n" - " the :running configuration datastore and state data\n" - " (operational datastore) referenced from the RPC/Notification.\n\n" + " element without <eventTime>).\n" + " nc-notif - Similar to 'notif' but expect and check also the NETCONF\n" + " envelope <notification> with element <eventTime> and its\n" + " sibling as the actual notification.\n"); +} - " -f FORMAT, --format=FORMAT\n" +static void +cmd_data_help_format(void) +{ + printf(" -f FORMAT, --format=FORMAT\n" " Print the data in one of the following formats:\n" " xml, json, lyb\n" - " Note that the LYB format requires the -o option specified.\n" - " -F FORMAT, --in-format=FORMAT\n" + " Note that the LYB format requires the -o option specified.\n"); +} + +static void +cmd_data_help_in_format(void) +{ + printf(" -F FORMAT, --in-format=FORMAT\n" " Load the data in one of the following formats:\n" " xml, json, lyb\n" - " If input format not specified, it is detected from the file extension.\n" - " -d MODE, --default=MODE\n" + " If input format not specified, it is detected from the file extension.\n"); +} + +static void +cmd_data_help_default(void) +{ + printf(" -d MODE, --default=MODE\n" " Print data with default values, according to the MODE\n" " (to print attributes, ietf-netconf-with-defaults model\n" " must be loaded):\n" @@ -87,24 +100,58 @@ cmd_data_help(void) " all-tagged - Add missing default nodes and mark all the default\n" " nodes with the attribute.\n" " trim - Remove all nodes with a default value.\n" - " implicit-tagged - Add missing nodes and mark them with the attribute.\n" - " -o OUTFILE, --output=OUTFILE\n" - " Write the output to OUTFILE instead of stdout.\n\n" + " implicit-tagged - Add missing nodes and mark them with the attribute.\n"); +} - " -x XPATH, --xpath=XPATH\n" - " Evaluate XPATH expression and print the nodes satysfying the.\n" +static void +cmd_data_help_xpath(void) +{ + printf(" -x XPATH, --xpath=XPATH\n" + " Evaluate XPATH expression and print the nodes satisfying the\n" " expression. The output format is specific and the option cannot\n" " be combined with the -f and -d options. Also all the data\n" " inputs are merged into a single data tree where the expression\n" - " is evaluated, so the -m option is always set implicitly.\n\n"); - + " is evaluated, so the -m option is always set implicitly.\n"); } void -cmd_data(struct ly_ctx **ctx, const char *cmdline) +cmd_data_help(void) +{ + cmd_data_help_header(); + printf("\n"); + cmd_data_help_type(); + printf(" -e, --present Validate only with the schema modules whose data actually\n" + " exist in the provided input data files. Takes effect only\n" + " with the 'data' or 'config' TYPEs. Used to avoid requiring\n" + " mandatory nodes from modules which data are not present in the\n" + " provided input data files.\n" + " -m, --merge Merge input data files into a single tree and validate at\n" + " once.The option has effect only for 'data' and 'config' TYPEs.\n" + " In case of using -x option, the data are always merged.\n" + " -n, --not-strict\n" + " Do not require strict data parsing (silently skip unknown data),\n" + " has no effect for schemas.\n" + " -O FILE, --operational=FILE\n" + " Provide optional data to extend validation of the 'rpc',\n" + " 'reply' or 'notif' TYPEs. The FILE is supposed to contain\n" + " the operational datastore referenced from the operation.\n" + " In case of a nested notification or action, its parent\n" + " existence is also checked in these operational data.\n" + " -R FILE, --reply-rpc=FILE\n" + " Provide source RPC for parsing of the 'nc-reply' TYPE. The FILE\n" + " is supposed to contain the source 'nc-rpc' operation of the reply.\n"); + cmd_data_help_format(); + cmd_data_help_in_format(); + printf(" -o OUTFILE, --output=OUTFILE\n" + " Write the output to OUTFILE instead of stdout.\n"); + cmd_data_help_xpath(); + printf("\n"); +} + +int +cmd_data_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) { - int argc = 0; - char **argv = NULL; + int rc = 0, argc = 0; int opt, opt_index; struct option options[] = { {"defaults", required_argument, NULL, 'd'}, @@ -115,214 +162,525 @@ cmd_data(struct ly_ctx **ctx, const char *cmdline) {"merge", no_argument, NULL, 'm'}, {"output", required_argument, NULL, 'o'}, {"operational", required_argument, NULL, 'O'}, + {"reply-rpc", required_argument, NULL, 'R'}, {"not-strict", no_argument, NULL, 'n'}, {"type", required_argument, NULL, 't'}, {"xpath", required_argument, NULL, 'x'}, {NULL, 0, NULL, 0} }; - uint8_t data_merge = 0; - uint32_t options_print = 0; - uint32_t options_parse = YL_DEFAULT_DATA_PARSE_OPTIONS; - uint32_t options_validate = 0; - enum lyd_type data_type = 0; uint8_t data_type_set = 0; - LYD_FORMAT outformat = LYD_UNKNOWN; - LYD_FORMAT informat = LYD_UNKNOWN; - struct ly_out *out = NULL; - struct cmdline_file *operational = NULL; - struct ly_set inputs = {0}; - struct ly_set xpaths = {0}; - - if (parse_cmdline(cmdline, &argc, &argv)) { - goto cleanup; + + yo->data_parse_options = YL_DEFAULT_DATA_PARSE_OPTIONS; + yo->data_validate_options = YL_DEFAULT_DATA_VALIDATE_OPTIONS; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; } - while ((opt = getopt_long(argc, argv, "d:ef:F:hmo:O:r:nt:x:", options, &opt_index)) != -1) { + while ((opt = getopt_long(argc, yo->argv, commands[CMD_DATA].optstring, options, &opt_index)) != -1) { switch (opt) { case 'd': /* --default */ - if (!strcasecmp(optarg, "all")) { - options_print = (options_print & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL; - } else if (!strcasecmp(optarg, "all-tagged")) { - options_print = (options_print & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL_TAG; - } else if (!strcasecmp(optarg, "trim")) { - options_print = (options_print & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_TRIM; - } else if (!strcasecmp(optarg, "implicit-tagged")) { - options_print = (options_print & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_IMPL_TAG; - } else { - YLMSG_E("Unknown default mode %s\n", optarg); - cmd_data_help(); - goto cleanup; + if (yo_opt_update_data_default(optarg, yo)) { + YLMSG_E("Unknown default mode %s.", optarg); + cmd_data_help_default(); + return 1; } break; case 'f': /* --format */ - if (!strcasecmp(optarg, "xml")) { - outformat = LYD_XML; - } else if (!strcasecmp(optarg, "json")) { - outformat = LYD_JSON; - } else if (!strcasecmp(optarg, "lyb")) { - outformat = LYD_LYB; - } else { - YLMSG_E("Unknown output format %s\n", optarg); - cmd_data_help(); - goto cleanup; + if (yl_opt_update_data_out_format(optarg, yo)) { + cmd_data_help_format(); + return 1; } break; case 'F': /* --in-format */ - if (!strcasecmp(optarg, "xml")) { - informat = LYD_XML; - } else if (!strcasecmp(optarg, "json")) { - informat = LYD_JSON; - } else if (!strcasecmp(optarg, "lyb")) { - informat = LYD_LYB; - } else { - YLMSG_E("Unknown input format %s\n", optarg); - cmd_data_help(); - goto cleanup; + if (yo_opt_update_data_in_format(optarg, yo)) { + YLMSG_E("Unknown input format %s.", optarg); + cmd_data_help_in_format(); + return 1; } break; case 'o': /* --output */ - if (out) { - YLMSG_E("Only a single output can be specified.\n"); - goto cleanup; + if (yo->out) { + YLMSG_E("Only a single output can be specified."); + return 1; } else { - if (ly_out_new_filepath(optarg, &out)) { - YLMSG_E("Unable open output file %s (%s)\n", optarg, strerror(errno)); - goto cleanup; + if (ly_out_new_filepath(optarg, &yo->out)) { + YLMSG_E("Unable open output file %s (%s).", optarg, strerror(errno)); + return 1; } } break; - case 'O': { /* --operational */ - struct ly_in *in; - LYD_FORMAT f; - - if (operational) { - YLMSG_E("The operational datastore (-O) cannot be set multiple times.\n"); - goto cleanup; + case 'O': /* --operational */ + if (yo->data_operational.path) { + YLMSG_E("The operational datastore (-O) cannot be set multiple times."); + return 1; } - if (get_input(optarg, NULL, &f, &in)) { - goto cleanup; + yo->data_operational.path = optarg; + break; + case 'R': /* --reply-rpc */ + if (yo->reply_rpc.path) { + YLMSG_E("The PRC of the reply (-R) cannot be set multiple times."); + return 1; } - operational = fill_cmdline_file(NULL, in, optarg, f); + yo->reply_rpc.path = optarg; break; - } /* case 'O' */ - case 'e': /* --present */ - options_validate |= LYD_VALIDATE_PRESENT; + yo->data_validate_options |= LYD_VALIDATE_PRESENT; break; case 'm': /* --merge */ - data_merge = 1; + yo->data_merge = 1; break; case 'n': /* --not-strict */ - options_parse &= ~LYD_PARSE_STRICT; + yo->data_parse_options &= ~LYD_PARSE_STRICT; break; case 't': /* --type */ if (data_type_set) { - YLMSG_E("The data type (-t) cannot be set multiple times.\n"); - goto cleanup; + YLMSG_E("The data type (-t) cannot be set multiple times."); + return 1; } - if (!strcasecmp(optarg, "config")) { - options_parse |= LYD_PARSE_NO_STATE; - options_validate |= LYD_VALIDATE_NO_STATE; - } else if (!strcasecmp(optarg, "get")) { - options_parse |= LYD_PARSE_ONLY; - } else if (!strcasecmp(optarg, "getconfig") || !strcasecmp(optarg, "get-config") || !strcasecmp(optarg, "edit")) { - options_parse |= LYD_PARSE_ONLY | LYD_PARSE_NO_STATE; - } else if (!strcasecmp(optarg, "rpc") || !strcasecmp(optarg, "action")) { - data_type = LYD_TYPE_RPC_YANG; - } else if (!strcasecmp(optarg, "reply") || !strcasecmp(optarg, "rpcreply")) { - data_type = LYD_TYPE_REPLY_YANG; - } else if (!strcasecmp(optarg, "notif") || !strcasecmp(optarg, "notification")) { - data_type = LYD_TYPE_NOTIF_YANG; - } else if (!strcasecmp(optarg, "data")) { - /* default option */ - } else { - YLMSG_E("Unknown data tree type %s.\n", optarg); - cmd_data_help(); - goto cleanup; + if (yl_opt_update_data_type(optarg, yo)) { + YLMSG_E("Unknown data tree type %s.", optarg); + cmd_data_help_type(); + return 1; } data_type_set = 1; break; case 'x': /* --xpath */ - if (ly_set_add(&xpaths, optarg, 0, NULL)) { - YLMSG_E("Storing XPath \"%s\" failed.\n", optarg); - goto cleanup; + if (ly_set_add(&yo->data_xpath, optarg, 0, NULL)) { + YLMSG_E("Storing XPath \"%s\" failed.", optarg); + return 1; } break; case 'h': /* --help */ cmd_data_help(); - goto cleanup; + return 1; default: - YLMSG_E("Unknown option.\n"); - goto cleanup; + YLMSG_E("Unknown option."); + return 1; } } - if (optind == argc) { - YLMSG_E("Missing the data file to process.\n"); - goto cleanup; + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return rc; +} + +int +cmd_data_dep(struct yl_opt *yo, int posc) +{ + if (yo->interactive && !posc) { + YLMSG_E("Missing the data file to process."); + return 1; } - if (data_merge) { - if (data_type || (options_parse & LYD_PARSE_ONLY)) { + if (yo->data_merge) { + if (yo->data_type || (yo->data_parse_options & LYD_PARSE_ONLY)) { /* switch off the option, incompatible input data type */ - data_merge = 0; + YLMSG_W("The --merge option has effect only for 'data' and 'config' TYPEs."); + yo->data_merge = 0; } else { /* postpone validation after the merge of all the input data */ - options_parse |= LYD_PARSE_ONLY; + yo->data_parse_options |= LYD_PARSE_ONLY; } - } else if (xpaths.count) { - data_merge = 1; + } else if (yo->data_xpath.count) { + yo->data_merge = 1; } - if (xpaths.count && outformat) { - YLMSG_E("The --format option cannot be combined with --xpath option.\n"); - cmd_data_help(); - goto cleanup; + if (yo->data_xpath.count && (yo->schema_out_format || yo->data_out_format)) { + YLMSG_E("The --format option cannot be combined with --xpath option."); + if (yo->interactive) { + cmd_data_help_xpath(); + } + return 1; + } + if (yo->data_xpath.count && (yo->data_print_options & LYD_PRINT_WD_MASK)) { + YLMSG_E("The --default option cannot be combined with --xpath option."); + if (yo->interactive) { + cmd_data_help_xpath(); + } + return 1; + } + + if (yo->data_operational.path && !yo->data_type) { + YLMSG_W("Operational datastore takes effect only with RPCs/Actions/Replies/Notification input data types."); + yo->data_operational.path = NULL; + } + + if (yo->reply_rpc.path && (yo->data_type != LYD_TYPE_REPLY_NETCONF)) { + YLMSG_W("Source RPC is needed only for NETCONF Reply input data type."); + yo->data_operational.path = NULL; + } else if (!yo->reply_rpc.path && (yo->data_type == LYD_TYPE_REPLY_NETCONF)) { + YLMSG_E("Missing source RPC (-R) for NETCONF Reply input data type."); + return 1; + } + + if (!yo->out && (yo->data_out_format == LYD_LYB)) { + YLMSG_E("The LYB format requires the -o option specified."); + return 1; + } + + /* default output stream */ + if (!yo->out) { + if (ly_out_new_file(stdout, &yo->out)) { + YLMSG_E("Unable to set stdout as output."); + return 1; + } + yo->out_stdout = 1; } - if (operational && !data_type) { - YLMSG_W("Operational datastore takes effect only with RPCs/Actions/Replies/Notifications input data types.\n"); - free_cmdline_file(operational); - operational = NULL; + /* process the operational and/or reply RPC content if any */ + if (yo->data_operational.path) { + if (get_input(yo->data_operational.path, NULL, &yo->data_operational.format, &yo->data_operational.in)) { + return -1; + } + } + if (yo->reply_rpc.path) { + if (get_input(yo->reply_rpc.path, NULL, &yo->reply_rpc.format, &yo->reply_rpc.in)) { + return -1; + } } + return 0; +} + +int +cmd_data_store(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + (void) ctx; + struct ly_in *in; + LYD_FORMAT format_data; + + assert(posv); + + format_data = yo->data_in_format; + /* process input data files provided as standalone command line arguments */ - for (int i = 0; i < argc - optind; i++) { - struct ly_in *in; + if (get_input(posv, NULL, &format_data, &in)) { + return 1; + } + + if (!fill_cmdline_file(&yo->data_inputs, in, posv, format_data)) { + ly_in_free(in, 1); + return 1; + } + + return 0; +} + +/** + * @brief Evaluate xpath adn print result. + * + * @param[in] tree Data tree. + * @param[in] xpath Xpath to evaluate. + * @return 0 on success. + */ +static int +evaluate_xpath(const struct lyd_node *tree, const char *xpath) +{ + struct ly_set *set = NULL; + + if (lyd_find_xpath(tree, xpath, &set)) { + return -1; + } + + /* print result */ + printf("XPath \"%s\" evaluation result:\n", xpath); + if (!set->count) { + printf("\tEmpty\n"); + } else { + for (uint32_t u = 0; u < set->count; ++u) { + struct lyd_node *node = (struct lyd_node *)set->objs[u]; + + printf(" %s \"%s\"", lys_nodetype2str(node->schema->nodetype), node->schema->name); + if (node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) { + printf(" (value: \"%s\")\n", lyd_get_value(node)); + } else if (node->schema->nodetype == LYS_LIST) { + printf(" ("); + for (struct lyd_node *key = ((struct lyd_node_inner *)node)->child; key && lysc_is_key(key->schema); key = key->next) { + printf("%s\"%s\": \"%s\";", (key != ((struct lyd_node_inner *)node)->child) ? " " : "", + key->schema->name, lyd_get_value(key)); + } + printf(")\n"); + } else { + printf("\n"); + } + } + } + + ly_set_free(set, NULL); + return 0; +} + +/** + * @brief Checking that a parent data node exists in the datastore for the nested-notification and action. + * + * @param[in] op Operation to check. + * @param[in] oper_tree Data from datastore. + * @param[in] operational_f Operational datastore file information. + * @return LY_ERR value. + */ +static LY_ERR +check_operation_parent(struct lyd_node *op, struct lyd_node *oper_tree, struct cmdline_file *operational_f) +{ + LY_ERR ret; + struct ly_set *set = NULL; + char *path = NULL; + + if (!op || !lyd_parent(op)) { + /* The function is defined only for nested-notification and action. */ + return LY_SUCCESS; + } + + if (!operational_f || (operational_f && !operational_f->in)) { + YLMSG_E("The --operational parameter needed to validate operation \"%s\" is missing.", LYD_NAME(op)); + ret = LY_EVALID; + goto cleanup; + } + + path = lyd_path(lyd_parent(op), LYD_PATH_STD, NULL, 0); + if (!path) { + ret = LY_EMEM; + goto cleanup; + } + + if (!oper_tree) { + YLMSG_W("Operational datastore is empty or contains unknown data."); + YLMSG_E("Operation \"%s\" parent \"%s\" not found in the operational data.", LYD_NAME(op), path); + ret = LY_EVALID; + goto cleanup; + } + if ((ret = lyd_find_xpath(oper_tree, path, &set))) { + goto cleanup; + } + if (!set->count) { + YLMSG_E("Operation \"%s\" parent \"%s\" not found in the operational data.", LYD_NAME(op), path); + ret = LY_EVALID; + goto cleanup; + } + +cleanup: + ly_set_free(set, NULL); + free(path); + + return ret; +} + +/** + * @brief Process the input data files - parse, validate and print according to provided options. + * + * @param[in] ctx libyang context with schema. + * @param[in] type The type of data in the input files. + * @param[in] merge Flag if the data should be merged before validation. + * @param[in] out_format Data format for printing. + * @param[in] out The output handler for printing. + * @param[in] parse_options Parser options. + * @param[in] validate_options Validation options. + * @param[in] print_options Printer options. + * @param[in] operational Optional operational datastore file information for the case of an extended validation of + * operation(s). + * @param[in] reply_rpc Source RPC operation file information for parsing NETCONF rpc-reply. + * @param[in] inputs Set of file informations of input data files. + * @param[in] xpaths The set of XPaths to be evaluated on the processed data tree, basic information about the resulting set + * is printed. Alternative to data printing. + * @return LY_ERR value. + */ +static LY_ERR +process_data(struct ly_ctx *ctx, enum lyd_type type, uint8_t merge, LYD_FORMAT out_format, + struct ly_out *out, uint32_t parse_options, uint32_t validate_options, uint32_t print_options, + struct cmdline_file *operational, struct cmdline_file *reply_rpc, struct ly_set *inputs, + struct ly_set *xpaths) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_node *tree = NULL, *op = NULL, *envp = NULL, *merged_tree = NULL, *oper_tree = NULL; + const char *xpath; + struct ly_set *set = NULL; - if (get_input(argv[optind + i], NULL, &informat, &in)) { + /* additional operational datastore */ + if (operational && operational->in) { + ret = lyd_parse_data(ctx, NULL, operational->in, operational->format, LYD_PARSE_ONLY, 0, &oper_tree); + if (ret) { + YLMSG_E("Failed to parse operational datastore file \"%s\".", operational->path); goto cleanup; } + } + + for (uint32_t u = 0; u < inputs->count; ++u) { + struct cmdline_file *input_f = (struct cmdline_file *)inputs->objs[u]; + + switch (type) { + case LYD_TYPE_DATA_YANG: + ret = lyd_parse_data(ctx, NULL, input_f->in, input_f->format, parse_options, validate_options, &tree); + break; + case LYD_TYPE_RPC_YANG: + case LYD_TYPE_REPLY_YANG: + case LYD_TYPE_NOTIF_YANG: + ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, type, &tree, &op); + break; + case LYD_TYPE_RPC_NETCONF: + case LYD_TYPE_NOTIF_NETCONF: + ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, type, &envp, &op); + + /* adjust pointers */ + for (tree = op; lyd_parent(tree); tree = lyd_parent(tree)) {} + break; + case LYD_TYPE_REPLY_NETCONF: + /* parse source RPC operation */ + assert(reply_rpc && reply_rpc->in); + ret = lyd_parse_op(ctx, NULL, reply_rpc->in, reply_rpc->format, LYD_TYPE_RPC_NETCONF, &envp, &op); + if (ret) { + YLMSG_E("Failed to parse source NETCONF RPC operation file \"%s\".", reply_rpc->path); + goto cleanup; + } + + /* adjust pointers */ + for (tree = op; lyd_parent(tree); tree = lyd_parent(tree)) {} + + /* free input */ + lyd_free_siblings(lyd_child(op)); + + /* we do not care */ + lyd_free_all(envp); + envp = NULL; - if (!fill_cmdline_file(&inputs, in, argv[optind + i], informat)) { - ly_in_free(in, 1); + ret = lyd_parse_op(ctx, op, input_f->in, input_f->format, type, &envp, NULL); + break; + default: + YLMSG_E("Internal error (%s:%d).", __FILE__, __LINE__); + goto cleanup; + } + + if (ret) { + YLMSG_E("Failed to parse input data file \"%s\".", input_f->path); goto cleanup; } + + if (merge) { + /* merge the data so far parsed for later validation and print */ + if (!merged_tree) { + merged_tree = tree; + } else { + ret = lyd_merge_siblings(&merged_tree, tree, LYD_MERGE_DESTRUCT); + if (ret) { + YLMSG_E("Merging %s with previous data failed.", input_f->path); + goto cleanup; + } + } + tree = NULL; + } else if (out_format) { + /* print */ + switch (type) { + case LYD_TYPE_DATA_YANG: + lyd_print_all(out, tree, out_format, print_options); + break; + case LYD_TYPE_RPC_YANG: + case LYD_TYPE_REPLY_YANG: + case LYD_TYPE_NOTIF_YANG: + case LYD_TYPE_RPC_NETCONF: + case LYD_TYPE_NOTIF_NETCONF: + lyd_print_tree(out, tree, out_format, print_options); + break; + case LYD_TYPE_REPLY_NETCONF: + /* just the output */ + lyd_print_tree(out, lyd_child(tree), out_format, print_options); + break; + default: + assert(0); + } + } else { + /* validation of the RPC/Action/reply/Notification with the operational datastore, if any */ + switch (type) { + case LYD_TYPE_DATA_YANG: + /* already validated */ + break; + case LYD_TYPE_RPC_YANG: + case LYD_TYPE_RPC_NETCONF: + ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_RPC_YANG, NULL); + break; + case LYD_TYPE_REPLY_YANG: + case LYD_TYPE_REPLY_NETCONF: + ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_REPLY_YANG, NULL); + break; + case LYD_TYPE_NOTIF_YANG: + case LYD_TYPE_NOTIF_NETCONF: + ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_NOTIF_YANG, NULL); + break; + default: + assert(0); + } + if (ret) { + if (operational->path) { + YLMSG_E("Failed to validate input data file \"%s\" with operational datastore \"%s\".", + input_f->path, operational->path); + } else { + YLMSG_E("Failed to validate input data file \"%s\".", input_f->path); + } + goto cleanup; + } + + if ((ret = check_operation_parent(op, oper_tree, operational))) { + goto cleanup; + } + } + + /* next iter */ + lyd_free_all(tree); + tree = NULL; + lyd_free_all(envp); + envp = NULL; } - /* default output stream */ - if (!out) { - if (ly_out_new_file(stdout, &out)) { - YLMSG_E("Unable to set stdout as output.\n"); + if (merge) { + /* validate the merged result */ + ret = lyd_validate_all(&merged_tree, ctx, validate_options, NULL); + if (ret) { + YLMSG_E("Merged data are not valid."); goto cleanup; } + + if (out_format) { + /* and print it */ + lyd_print_all(out, merged_tree, out_format, print_options); + } + + for (uint32_t u = 0; xpaths && (u < xpaths->count); ++u) { + xpath = (const char *)xpaths->objs[u]; + ly_set_free(set, NULL); + ret = lys_find_xpath(ctx, NULL, xpath, LYS_FIND_NO_MATCH_ERROR, &set); + if (ret || !set->count) { + ret = (ret == LY_SUCCESS) ? LY_EINVAL : ret; + YLMSG_E("The requested xpath failed."); + goto cleanup; + } + if (evaluate_xpath(merged_tree, xpath)) { + goto cleanup; + } + } } +cleanup: + lyd_free_all(tree); + lyd_free_all(envp); + lyd_free_all(merged_tree); + lyd_free_all(oper_tree); + ly_set_free(set, NULL); + return ret; +} + +int +cmd_data_process(struct ly_ctx *ctx, struct yl_opt *yo) +{ /* parse, validate and print data */ - if (process_data(*ctx, data_type, data_merge, outformat, out, options_parse, options_validate, options_print, - operational, NULL, &inputs, &xpaths)) { - goto cleanup; + if (process_data(ctx, yo->data_type, yo->data_merge, yo->data_out_format, yo->out, yo->data_parse_options, + yo->data_validate_options, yo->data_print_options, &yo->data_operational, &yo->reply_rpc, + &yo->data_inputs, &yo->data_xpath)) { + return 1; } -cleanup: - ly_out_free(out, NULL, 0); - ly_set_erase(&inputs, free_cmdline_file); - ly_set_erase(&xpaths, NULL); - free_cmdline_file(operational); - free_cmdline(argv); + return 0; } diff --git a/tools/lint/cmd_debug.c b/tools/lint/cmd_debug.c new file mode 100644 index 0000000..3661bfa --- /dev/null +++ b/tools/lint/cmd_debug.c @@ -0,0 +1,134 @@ +/** + * @file cmd_debug.c + * @author Adam Piecek <piecek@cesnet.cz> + * @brief 'verb' command of the libyang's yanglint tool. + * + * Copyright (c) 2023-2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#ifndef NDEBUG + +#include "cmd.h" + +#include <assert.h> +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> +#include <strings.h> + +#include "libyang.h" + +#include "common.h" +#include "yl_opt.h" + +struct debug_groups { + char *name; + uint32_t flag; +} const dg [] = { + {"dict", LY_LDGDICT}, + {"xpath", LY_LDGXPATH}, + {"dep-sets", LY_LDGDEPSETS}, +}; +#define DG_LENGTH (sizeof dg / sizeof *dg) + +void +cmd_debug_help(void) +{ + uint32_t i; + + printf("Usage: debug ("); + for (i = 0; i < DG_LENGTH; i++) { + if ((i + 1) == DG_LENGTH) { + printf("%s", dg[i].name); + } else { + printf("%s | ", dg[i].name); + } + } + printf(")+\n"); +} + +int +cmd_debug_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) +{ + int rc = 0, argc = 0; + int opt, opt_index; + struct option options[] = { + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; + } + + while ((opt = getopt_long(argc, yo->argv, commands[CMD_DEBUG].optstring, options, &opt_index)) != -1) { + switch (opt) { + case 'h': + cmd_debug_help(); + return 1; + default: + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_debug_dep(struct yl_opt *yo, int posc) +{ + (void) yo; + + if (yo->interactive && !posc) { + /* no argument */ + cmd_debug_help(); + return 1; + } + + return 0; +} + +int +cmd_debug_store(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + (void) ctx; + uint32_t i; + ly_bool set; + + assert(posv); + + set = 0; + for (i = 0; i < DG_LENGTH; i++) { + if (!strcasecmp(posv, dg[i].name)) { + yo->dbg_groups |= dg[i].flag; + set = 1; + break; + } + } + + if (!set) { + YLMSG_E("Unknown debug group \"%s\".", posv); + return 1; + } + + return 0; +} + +int +cmd_debug_setlog(struct ly_ctx *ctx, struct yl_opt *yo) +{ + (void) ctx; + return ly_log_dbg_groups(yo->dbg_groups); +} + +#endif diff --git a/tools/lint/cmd_extdata.c b/tools/lint/cmd_extdata.c new file mode 100644 index 0000000..fc7ac7b --- /dev/null +++ b/tools/lint/cmd_extdata.c @@ -0,0 +1,115 @@ +/** + * @file cmd_extdata.c + * @author Adam Piecek <piecek@cesnet.cz> + * @brief 'extdata' command of the libyang's yanglint tool. + * + * Copyright (c) 2015-2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 200809L /* strdup */ + +#include "cmd.h" + +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> + +#include "libyang.h" + +#include "common.h" +#include "yl_opt.h" + +char *filename; + +void +cmd_extdata_free(void) +{ + free(filename); + filename = NULL; +} + +void +cmd_extdata_help(void) +{ + printf("Usage: extdata [--clear] [<extdata-file-path>]\n" + " File containing the specific data required by an extension. Required by\n" + " the schema-mount extension, for example, when the operational data are\n" + " expected in the file. File format is guessed.\n"); +} + +int +cmd_extdata_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) +{ + int rc = 0, argc = 0; + int opt, opt_index; + struct option options[] = { + {"clear", no_argument, NULL, 'c'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; + } + + while ((opt = getopt_long(argc, yo->argv, commands[CMD_EXTDATA].optstring, options, &opt_index)) != -1) { + switch (opt) { + case 'c': + yo->extdata_unset = 1; + free(filename); + filename = NULL; + break; + case 'h': + cmd_extdata_help(); + return 1; + default: + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_extdata_dep(struct yl_opt *yo, int posc) +{ + if (!yo->extdata_unset && (posc > 1)) { + YLMSG_E("Only one file must be entered."); + return 1; + } + + return 0; +} + +int +cmd_extdata_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + if (yo->extdata_unset) { + ly_ctx_set_ext_data_clb(*ctx, NULL, NULL); + } else if (!yo->extdata_unset && !posv) { + /* no argument - print the current file */ + printf("%s\n", filename ? filename : "No file set."); + } else if (posv) { + /* set callback providing run-time extension instance data */ + free(filename); + filename = strdup(posv); + if (!filename) { + YLMSG_E("Memory allocation error."); + return 1; + } + ly_ctx_set_ext_data_clb(*ctx, ext_data_clb, filename); + } + + return 0; +} diff --git a/tools/lint/cmd_feature.c b/tools/lint/cmd_feature.c index 6b332ab..96d55c1 100644 --- a/tools/lint/cmd_feature.c +++ b/tools/lint/cmd_feature.c @@ -1,9 +1,10 @@ /** * @file cmd_feature.c * @author Michal Vasko <mvasko@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief 'feature' command of the libyang's yanglint tool. * - * Copyright (c) 2015-2021 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -23,6 +24,8 @@ #include "libyang.h" #include "common.h" +#include "yl_opt.h" +#include "yl_schema_features.h" void cmd_feature_help(void) @@ -37,17 +40,11 @@ cmd_feature_help(void) " Print features of all implemented modules.\n"); } -void -cmd_feature(struct ly_ctx **ctx, const char *cmdline) +int +cmd_feature_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) { - int argc = 0; - char **argv = NULL; - char *features_output = NULL; - int opt, opt_index, i; - ly_bool generate_features = 0, print_all = 0; - struct ly_set set = {0}; - const struct lys_module *mod; - struct ly_out *out = NULL; + int rc = 0, argc = 0; + int opt, opt_index; struct option options[] = { {"help", no_argument, NULL, 'h'}, {"all", no_argument, NULL, 'a'}, @@ -55,81 +52,80 @@ cmd_feature(struct ly_ctx **ctx, const char *cmdline) {NULL, 0, NULL, 0} }; - if (parse_cmdline(cmdline, &argc, &argv)) { - goto cleanup; + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; } - while ((opt = getopt_long(argc, argv, "haf", options, &opt_index)) != -1) { + while ((opt = getopt_long(argc, yo->argv, commands[CMD_FEATURE].optstring, options, &opt_index)) != -1) { switch (opt) { case 'h': cmd_feature_help(); - goto cleanup; + return 1; case 'a': - print_all = 1; + yo->feature_print_all = 1; break; case 'f': - generate_features = 1; + yo->feature_param_format = 1; break; default: - YLMSG_E("Unknown option.\n"); - goto cleanup; + YLMSG_E("Unknown option."); + return 1; } } - if (ly_out_new_file(stdout, &out)) { - YLMSG_E("Unable to print to the standard output.\n"); - goto cleanup; - } + *posv = &yo->argv[optind]; + *posc = argc - optind; - if (print_all) { - if (print_all_features(out, *ctx, generate_features, &features_output)) { - YLMSG_E("Printing all features failed.\n"); - goto cleanup; - } - if (generate_features) { - printf("%s\n", features_output); - } - goto cleanup; - } + return 0; +} - if (argc == optind) { - YLMSG_E("Missing modules to print.\n"); - goto cleanup; +int +cmd_feature_dep(struct yl_opt *yo, int posc) +{ + if (ly_out_new_file(stdout, &yo->out)) { + YLMSG_E("Unable to print to the standard output."); + return 1; } + yo->out_stdout = 1; - for (i = 0; i < argc - optind; i++) { - /* always erase the set, so the previous module's features don't carry over to the next module's features */ - ly_set_erase(&set, NULL); + if (yo->interactive && !yo->feature_print_all && !posc) { + YLMSG_E("Missing modules to print."); + return 1; + } - mod = ly_ctx_get_module_latest(*ctx, argv[optind + i]); - if (!mod) { - YLMSG_E("Module \"%s\" not found.\n", argv[optind + i]); - goto cleanup; - } + return 0; +} - /* collect features of the module */ - if (collect_features(mod, &set)) { - goto cleanup; - } +int +cmd_feature_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + const struct lys_module *mod; - if (generate_features) { - if (generate_features_output(mod, &set, &features_output)) { - goto cleanup; - } - /* don't print features and their state of each module if generating features parameter */ - continue; - } + if (yo->feature_print_all) { + print_all_features(yo->out, *ctx, yo->feature_param_format); + return 0; + } - print_features(out, mod, &set); + mod = ly_ctx_get_module_latest(*ctx, posv); + if (!mod) { + YLMSG_E("Module \"%s\" not found.", posv); + return 1; } - if (generate_features) { - printf("%s\n", features_output); + if (yo->feature_param_format) { + print_feature_param(yo->out, mod); + } else { + print_features(yo->out, mod); } -cleanup: - free_cmdline(argv); - ly_out_free(out, NULL, 0); - ly_set_erase(&set, NULL); - free(features_output); + return 0; +} + +int +cmd_feature_fin(struct ly_ctx *ctx, struct yl_opt *yo) +{ + (void) ctx; + + ly_print(yo->out, "\n"); + return 0; } diff --git a/tools/lint/cmd_help.c b/tools/lint/cmd_help.c new file mode 100644 index 0000000..a1ee3f6 --- /dev/null +++ b/tools/lint/cmd_help.c @@ -0,0 +1,107 @@ +/** + * @file cmd_help.c + * @author Adam Piecek <piecek@cesnet.cz> + * @brief 'help' command of the libyang's yanglint tool. + * + * Copyright (c) 2023-2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include "cmd.h" + +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> + +#include "libyang.h" + +#include "common.h" +#include "yl_opt.h" + +void +cmd_help_help(void) +{ + printf("Usage: help [cmd ...]\n"); +} + +int +cmd_help_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) +{ + int rc = 0, argc = 0; + int opt, opt_index; + struct option options[] = { + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; + } + + while ((opt = getopt_long(argc, yo->argv, commands[CMD_HELP].optstring, options, &opt_index)) != -1) { + if (opt == 'h') { + cmd_help_help(); + return 1; + } else { + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return rc; +} + +static void +print_generic_help(void) +{ + printf("Available commands:\n"); + for (uint16_t i = 0; commands[i].name; i++) { + if (commands[i].helpstring != NULL) { + printf(" %-15s %s\n", commands[i].name, commands[i].helpstring); + } + } +} + +int +cmd_help_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + (void)ctx, (void)yo; + + if (!posv) { + print_generic_help(); + } else { + /* print specific help for the selected command(s) */ + + int8_t match = 0; + + /* get the command of the specified name */ + for (uint16_t i = 0; commands[i].name; i++) { + if (strcmp(posv, commands[i].name) == 0) { + match = 1; + if (commands[i].help_func != NULL) { + commands[i].help_func(); + } else { + printf("%s: %s\n", posv, commands[i].helpstring); + } + break; + } + } + if (!match) { + /* if unknown command specified, print the list of commands */ + printf("Unknown command \'%s\'\n", posv); + print_generic_help(); + } + } + + return 0; +} diff --git a/tools/lint/cmd_list.c b/tools/lint/cmd_list.c index ec7a021..166fbfa 100644 --- a/tools/lint/cmd_list.c +++ b/tools/lint/cmd_list.c @@ -2,9 +2,10 @@ * @file cmd_list.c * @author Michal Vasko <mvasko@cesnet.cz> * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief 'list' command of the libyang's yanglint tool. * - * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -24,6 +25,7 @@ #include "libyang.h" #include "common.h" +#include "yl_opt.h" void cmd_list_help(void) @@ -37,53 +39,148 @@ cmd_list_help(void) " modules.\n"); } -void -cmd_list(struct ly_ctx **ctx, const char *cmdline) +int +cmd_list_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) { - int argc = 0; - char **argv = NULL; + int rc = 0, argc = 0; int opt, opt_index; struct option options[] = { {"format", required_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} }; - LYD_FORMAT format = LYD_UNKNOWN; - struct ly_out *out = NULL; - if (parse_cmdline(cmdline, &argc, &argv)) { - goto cleanup; + yo->data_out_format = LYD_UNKNOWN; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; } - while ((opt = getopt_long(argc, argv, "f:h", options, &opt_index)) != -1) { + while ((opt = getopt_long(argc, yo->argv, commands[CMD_LIST].optstring, options, &opt_index)) != -1) { switch (opt) { case 'f': /* --format */ if (!strcasecmp(optarg, "xml")) { - format = LYD_XML; + yo->data_out_format = LYD_XML; } else if (!strcasecmp(optarg, "json")) { - format = LYD_JSON; + yo->data_out_format = LYD_JSON; } else { - YLMSG_E("Unknown output format %s\n", optarg); + YLMSG_E("Unknown output format %s.", optarg); cmd_list_help(); - goto cleanup; + return 1; } break; case 'h': cmd_list_help(); - goto cleanup; + return 1; default: - YLMSG_E("Unknown option.\n"); - goto cleanup; + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_list_dep(struct yl_opt *yo, int posc) +{ + if (posc) { + YLMSG_E("No positional arguments are allowed."); + return 1; + } + if (ly_out_new_file(stdout, &yo->out)) { + YLMSG_E("Unable to print to the standard output."); + return 1; + } + yo->out_stdout = 1; + + return 0; +} + +/** + * @brief Print yang library data. + * + * @param[in] ctx Context for libyang. + * @param[in] data_out_format Output format of printed data. + * @param[in] out Output handler. + * @return 0 on success. + */ +static int +print_yang_lib_data(struct ly_ctx *ctx, LYD_FORMAT data_out_format, struct ly_out *out) +{ + struct lyd_node *ylib; + + if (ly_ctx_get_yanglib_data(ctx, &ylib, "%u", ly_ctx_get_change_count(ctx))) { + YLMSG_E("Getting context info (ietf-yang-library data) failed. If the YANG module is missing or not implemented, " + "use an option to add it internally."); + return 1; + } + + lyd_print_all(out, ylib, data_out_format, 0); + lyd_free_all(ylib); + + return 0; +} + +int +cmd_list_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + (void) posv; + uint32_t idx = 0, has_modules = 0; + const struct lys_module *mod; + + if (yo->data_out_format != LYD_UNKNOWN) { + /* ietf-yang-library data are printed in the specified format */ + if (print_yang_lib_data(*ctx, yo->data_out_format, yo->out)) { + return 1; } + return 0; } - if (!ly_out_new_file(stdout, &out)) { - print_list(out, *ctx, format); - ly_out_free(out, NULL, 0); - } else { - YLMSG_E("Unable to print to the standard output.\n"); + /* iterate schemas in context and provide just the basic info */ + ly_print(yo->out, "List of the loaded models:\n"); + while ((mod = ly_ctx_get_module_iter(*ctx, &idx))) { + has_modules++; + + /* conformance print */ + if (mod->implemented) { + ly_print(yo->out, " I"); + } else { + ly_print(yo->out, " i"); + } + + /* module print */ + ly_print(yo->out, " %s", mod->name); + if (mod->revision) { + ly_print(yo->out, "@%s", mod->revision); + } + + /* submodules print */ + if (mod->parsed && mod->parsed->includes) { + uint64_t u = 0; + + ly_print(yo->out, " ("); + LY_ARRAY_FOR(mod->parsed->includes, u) { + ly_print(yo->out, "%s%s", !u ? "" : ",", mod->parsed->includes[u].name); + if (mod->parsed->includes[u].rev[0]) { + ly_print(yo->out, "@%s", mod->parsed->includes[u].rev); + } + } + ly_print(yo->out, ")"); + } + + /* finish the line */ + ly_print(yo->out, "\n"); + } + + if (!has_modules) { + ly_print(yo->out, "\t(none)\n"); } -cleanup: - free_cmdline(argv); + ly_print_flush(yo->out); + + return 0; } diff --git a/tools/lint/cmd_load.c b/tools/lint/cmd_load.c index f5883e9..808c125 100644 --- a/tools/lint/cmd_load.c +++ b/tools/lint/cmd_load.c @@ -2,9 +2,10 @@ * @file cmd_load.c * @author Michal Vasko <mvasko@cesnet.cz> * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief 'load' command of the libyang's yanglint tool. * - * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -17,6 +18,7 @@ #include "cmd.h" +#include <assert.h> #include <getopt.h> #include <stdint.h> #include <stdio.h> @@ -25,13 +27,15 @@ #include "libyang.h" #include "common.h" +#include "yl_opt.h" +#include "yl_schema_features.h" void cmd_load_help(void) { printf("Usage: load [-i] <module-name1>[@<revision>] [<module-name2>[@revision] ...]\n" " Add a new module of the specified name, yanglint will find\n" - " them in searchpaths. if the <revision> of the module not\n" + " them in searchpaths. If the <revision> of the module not\n" " specified, the latest revision available is loaded.\n\n" " -F FEATURES, --features=FEATURES\n" " Features to support, default all in all implemented modules.\n" @@ -39,101 +43,110 @@ cmd_load_help(void) " -i, --make-implemented\n" " Make the imported modules \"referenced\" from any loaded\n" " <schema> module also implemented. If specified a second time,\n" - " all the modules are set implemented.\n"); + " all the modules are set implemented.\n" + " -X, --extended-leafref\n" + " Allow usage of deref() XPath function within leafref.\n"); } -void -cmd_load(struct ly_ctx **ctx, const char *cmdline) +int +cmd_load_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) { - int argc = 0; - char **argv = NULL; + int rc, argc = 0; int opt, opt_index; struct option options[] = { {"features", required_argument, NULL, 'F'}, {"help", no_argument, NULL, 'h'}, {"make-implemented", no_argument, NULL, 'i'}, + {"extended-leafref", no_argument, NULL, 'X'}, {NULL, 0, NULL, 0} }; - uint16_t options_ctx = 0; - const char *all_features[] = {"*", NULL}; - struct ly_set fset = {0}; - if (parse_cmdline(cmdline, &argc, &argv)) { - goto cleanup; + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; } - while ((opt = getopt_long(argc, argv, "F:hi", options, &opt_index)) != -1) { + while ((opt = getopt_long(argc, yo->argv, commands[CMD_LOAD].optstring, options, &opt_index)) != -1) { switch (opt) { case 'F': /* --features */ - if (parse_features(optarg, &fset)) { - goto cleanup; + if (parse_features(optarg, &yo->schema_features)) { + return 1; } break; case 'h': cmd_load_help(); - goto cleanup; + return 1; case 'i': /* --make-implemented */ - if (options_ctx & LY_CTX_REF_IMPLEMENTED) { - options_ctx &= ~LY_CTX_REF_IMPLEMENTED; - options_ctx |= LY_CTX_ALL_IMPLEMENTED; - } else { - options_ctx |= LY_CTX_REF_IMPLEMENTED; - } + yo_opt_update_make_implemented(yo); + break; + + case 'X': /* --extended-leafref */ + yo->ctx_options |= LY_CTX_LEAFREF_EXTENDED; break; default: - YLMSG_E("Unknown option.\n"); - goto cleanup; + YLMSG_E("Unknown option."); + return 1; } } - if (argc == optind) { + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_load_dep(struct yl_opt *yo, int posc) +{ + if (yo->interactive && !posc) { /* no argument */ - cmd_add_help(); - goto cleanup; + cmd_load_help(); + return 1; } - if (!fset.count) { + if (!yo->schema_features.count) { /* no features, enable all of them */ - options_ctx |= LY_CTX_ENABLE_IMP_FEATURES; + yo->ctx_options |= LY_CTX_ENABLE_IMP_FEATURES; } - if (options_ctx) { - ly_ctx_set_options(*ctx, options_ctx); - } + return 0; +} - for (int i = 0; i < argc - optind; i++) { - /* process the schema module files */ - char *revision; - const char **features = NULL; +int +cmd_load_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + const char *all_features[] = {"*", NULL}; + char *revision; + const char **features = NULL; - /* get revision */ - revision = strchr(argv[optind + i], '@'); - if (revision) { - revision[0] = '\0'; - ++revision; - } + assert(posv); - /* get features list for this module */ - if (!fset.count) { - features = all_features; - } else { - get_features(&fset, argv[optind + i], &features); - } + if (yo->ctx_options) { + ly_ctx_set_options(*ctx, yo->ctx_options); + yo->ctx_options = 0; + } - /* load the module */ - if (!ly_ctx_load_module(*ctx, argv[optind + i], revision, features)) { - /* libyang printed the error messages */ - goto cleanup; - } + /* get revision */ + revision = strchr(posv, '@'); + if (revision) { + revision[0] = '\0'; + ++revision; } -cleanup: - if (options_ctx) { - ly_ctx_unset_options(*ctx, options_ctx); + /* get features list for this module */ + if (!yo->schema_features.count) { + features = all_features; + } else { + get_features(&yo->schema_features, posv, &features); } - ly_set_erase(&fset, free_features); - free_cmdline(argv); + + /* load the module */ + if (!ly_ctx_load_module(*ctx, posv, revision, features)) { + /* libyang printed the error messages */ + return 1; + } + + return 0; } diff --git a/tools/lint/cmd_print.c b/tools/lint/cmd_print.c index c1a5359..ff5fb90 100644 --- a/tools/lint/cmd_print.c +++ b/tools/lint/cmd_print.c @@ -2,9 +2,10 @@ * @file cmd_print.c * @author Michal Vasko <mvasko@cesnet.cz> * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief 'print' command of the libyang's yanglint tool. * - * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -27,6 +28,7 @@ #include "libyang.h" #include "common.h" +#include "yl_opt.h" void cmd_print_help(void) @@ -34,7 +36,8 @@ cmd_print_help(void) printf("Usage: print [-f (yang | yin | tree [-q -P PATH -L LINE_LENGTH ] | info [-q -P PATH])]\n" " [-o OUTFILE] [<module-name1>[@revision]] ...\n" " Print a schema module. The <module-name> is not required\n" - " only in case the -P option is specified.\n\n" + " only in case the -P option is specified. For yang, yin and tree\n" + " formats, a submodule can also be printed.\n\n" " -f FORMAT, --format=FORMAT\n" " Print the module in the specified FORMAT. If format not\n" " specified, the 'tree' format is used.\n" @@ -53,95 +56,10 @@ cmd_print_help(void) " Write the output to OUTFILE instead of stdout.\n"); } -static LY_ERR -cmd_print_submodule(struct ly_out *out, struct ly_ctx **ctx, char *name, char *revision, LYS_OUTFORMAT format, size_t line_length, uint32_t options) -{ - LY_ERR erc; - const struct lysp_submodule *submodule; - - submodule = revision ? - ly_ctx_get_submodule(*ctx, name, revision) : - ly_ctx_get_submodule_latest(*ctx, name); - - erc = submodule ? - lys_print_submodule(out, submodule, format, line_length, options) : - LY_ENOTFOUND; - - return erc; -} - -static LY_ERR -cmd_print_module(struct ly_out *out, struct ly_ctx **ctx, char *name, char *revision, LYS_OUTFORMAT format, size_t line_length, uint32_t options) -{ - LY_ERR erc; - struct lys_module *module; - - module = revision ? - ly_ctx_get_module(*ctx, name, revision) : - ly_ctx_get_module_latest(*ctx, name); - - erc = module ? - lys_print_module(out, module, format, line_length, options) : - LY_ENOTFOUND; - - return erc; -} - -static void -cmd_print_modules(int argc, char **argv, struct ly_out *out, struct ly_ctx **ctx, LYS_OUTFORMAT format, size_t line_length, uint32_t options) -{ - LY_ERR erc; - char *name, *revision; - ly_bool search_submodul; - const int stop = argc - optind; - - for (int i = 0; i < stop; i++) { - name = argv[optind + i]; - /* get revision */ - revision = strchr(name, '@'); - if (revision) { - revision[0] = '\0'; - ++revision; - } - - erc = cmd_print_module(out, ctx, name, revision, format, line_length, options); - - if (erc == LY_ENOTFOUND) { - search_submodul = 1; - erc = cmd_print_submodule(out, ctx, name, revision, format, line_length, options); - } else { - search_submodul = 0; - } - - if (erc == LY_SUCCESS) { - /* for YANG Tree Diagrams printing it's more readable to print a blank line between modules. */ - if ((format == LYS_OUT_TREE) && (i + 1 < stop)) { - ly_print(out, "\n"); - } - continue; - } else if (erc == LY_ENOTFOUND) { - if (revision) { - YLMSG_E("No (sub)module \"%s\" in revision %s found.\n", name, revision); - } else { - YLMSG_E("No (sub)module \"%s\" found.\n", name); - } - break; - } else { - if (search_submodul) { - YLMSG_E("Unable to print submodule %s.\n", name); - } else { - YLMSG_E("Unable to print module %s.\n", name); - } - break; - } - } -} - -void -cmd_print(struct ly_ctx **ctx, const char *cmdline) +int +cmd_print_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) { - int argc = 0; - char **argv = NULL; + int rc = 0, argc = 0; int opt, opt_index; struct option options[] = { {"format", required_argument, NULL, 'f'}, @@ -152,113 +70,230 @@ cmd_print(struct ly_ctx **ctx, const char *cmdline) {"single-node", no_argument, NULL, 'q'}, {NULL, 0, NULL, 0} }; - uint16_t options_print = 0; - const char *node_path = NULL; - LYS_OUTFORMAT format = LYS_OUT_TREE; - struct ly_out *out = NULL; - ly_bool out_stdout = 0; - size_t line_length = 0; - - if (parse_cmdline(cmdline, &argc, &argv)) { - goto cleanup; + + yo->schema_out_format = LYS_OUT_TREE; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; } - while ((opt = getopt_long(argc, argv, "f:hL:o:P:q", options, &opt_index)) != -1) { + while ((opt = getopt_long(argc, yo->argv, commands[CMD_PRINT].optstring, options, &opt_index)) != -1) { switch (opt) { case 'o': /* --output */ - if (out) { - if (ly_out_filepath(out, optarg) != NULL) { - YLMSG_E("Unable to use output file %s for printing output.\n", optarg); - goto cleanup; - } + if (yo->out) { + YLMSG_E("Only a single output can be specified."); + return 1; } else { - if (ly_out_new_filepath(optarg, &out)) { - YLMSG_E("Unable to use output file %s for printing output.\n", optarg); - goto cleanup; + if (ly_out_new_filepath(optarg, &yo->out)) { + YLMSG_E("Unable open output file %s (%s).", optarg, strerror(errno)); + return 1; } } break; case 'f': /* --format */ - if (!strcasecmp(optarg, "yang")) { - format = LYS_OUT_YANG; - } else if (!strcasecmp(optarg, "yin")) { - format = LYS_OUT_YIN; - } else if (!strcasecmp(optarg, "info")) { - format = LYS_OUT_YANG_COMPILED; - } else if (!strcasecmp(optarg, "tree")) { - format = LYS_OUT_TREE; - } else { - YLMSG_E("Unknown output format %s\n", optarg); + if (yl_opt_update_schema_out_format(optarg, yo)) { cmd_print_help(); - goto cleanup; + return 1; } break; case 'L': /* --tree-line-length */ - line_length = atoi(optarg); + yo->line_length = atoi(optarg); break; case 'P': /* --schema-node */ - node_path = optarg; + yo->schema_node_path = optarg; break; case 'q': /* --single-node */ - options_print |= LYS_PRINT_NO_SUBSTMT; + yo->schema_print_options |= LYS_PRINT_NO_SUBSTMT; break; case 'h': cmd_print_help(); - goto cleanup; + return 1; default: - YLMSG_E("Unknown option.\n"); - goto cleanup; + YLMSG_E("Unknown option."); + return 1; } } + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_print_dep(struct yl_opt *yo, int posc) +{ /* file name */ - if ((argc == optind) && !node_path) { - YLMSG_E("Missing the name of the module to print.\n"); - goto cleanup; + if (yo->interactive && !posc && !yo->schema_node_path) { + YLMSG_E("Missing the name of the module to print."); + return 1; } - if ((format != LYS_OUT_TREE) && line_length) { - YLMSG_E("--tree-line-length take effect only in case of the tree output format.\n"); - goto cleanup; + if ((yo->schema_out_format != LYS_OUT_TREE) && yo->line_length) { + YLMSG_W("--tree-line-length take effect only in case of the tree output format."); } - if (!out) { - if (ly_out_new_file(stdout, &out)) { - YLMSG_E("Could not use stdout to print output.\n"); - goto cleanup; + if (!yo->out) { + if (ly_out_new_file(stdout, &yo->out)) { + YLMSG_E("Could not use stdout to print output."); } - out_stdout = 1; + yo->out_stdout = 1; } - if (format == LYS_OUT_TREE) { + if (yo->schema_out_format == LYS_OUT_TREE) { /* print tree from lysc_nodes */ - ly_ctx_set_options(*ctx, LY_CTX_SET_PRIV_PARSED); + yo->ctx_options |= LY_CTX_SET_PRIV_PARSED; } - if (node_path) { - const struct lysc_node *node; + return 0; +} + +static LY_ERR +print_submodule(struct ly_out *out, struct ly_ctx **ctx, char *name, char *revision, LYS_OUTFORMAT format, size_t line_length, uint32_t options) +{ + LY_ERR erc; + const struct lysp_submodule *submodule; - node = find_schema_path(*ctx, node_path); + submodule = revision ? + ly_ctx_get_submodule(*ctx, name, revision) : + ly_ctx_get_submodule_latest(*ctx, name); + + erc = submodule ? + lys_print_submodule(out, submodule, format, line_length, options) : + LY_ENOTFOUND; + + if (!erc) { + return 0; + } else if ((erc == LY_ENOTFOUND) && revision) { + YLMSG_E("No submodule \"%s\" found.", name); + } else { + YLMSG_E("Unable to print submodule %s.", name); + } + + return erc; +} + +static LY_ERR +print_module(struct ly_out *out, struct ly_ctx **ctx, char *name, char *revision, LYS_OUTFORMAT format, size_t line_length, uint32_t options) +{ + LY_ERR erc; + struct lys_module *module; + + module = revision ? + ly_ctx_get_module(*ctx, name, revision) : + ly_ctx_get_module_latest(*ctx, name); + + erc = module ? + lys_print_module(out, module, format, line_length, options) : + LY_ENOTFOUND; + + if (!erc) { + return 0; + } else if ((erc == LY_ENOTFOUND) && revision) { + YLMSG_E("No module \"%s\" found.", name); + } else { + YLMSG_E("Unable to print module %s.", name); + } + + return erc; +} + +static int +cmd_print_module(const char *posv, struct ly_out *out, struct ly_ctx **ctx, LYS_OUTFORMAT format, + size_t line_length, uint32_t options) +{ + LY_ERR erc; + char *name = NULL, *revision; + + name = strdup(posv); + /* get revision */ + revision = strchr(name, '@'); + if (revision) { + revision[0] = '\0'; + ++revision; + } + + erc = print_module(out, ctx, name, revision, format, line_length, options); + + if (erc == LY_ENOTFOUND) { + erc = print_submodule(out, ctx, name, revision, format, line_length, options); + } + + free(name); + return erc; +} + +/** + * @brief Print schema node path. + * + * @param[in] ctx Context for libyang. + * @param[in] yo Context for yanglint. + * @return 0 on success. + */ +static int +print_node(struct ly_ctx *ctx, struct yl_opt *yo) +{ + const struct lysc_node *node; + uint32_t temp_lo = 0; + + if (yo->interactive) { + /* Use the same approach as for completion. */ + node = find_schema_path(ctx, yo->schema_node_path); if (!node) { - YLMSG_E("The requested schema node \"%s\" does not exists.\n", node_path); - goto cleanup; + YLMSG_E("The requested schema node \"%s\" does not exists.", yo->schema_node_path); + return 1; } - - if (lys_print_node(out, node, format, 0, options_print)) { - YLMSG_E("Unable to print schema node %s.\n", node_path); - goto cleanup; + } else { + /* turn off logging so that the message is not repeated */ + ly_temp_log_options(&temp_lo); + /* search operation input */ + node = lys_find_path(ctx, NULL, yo->schema_node_path, 0); + if (!node) { + /* restore logging so an error may be displayed */ + ly_temp_log_options(NULL); + /* search operation output */ + node = lys_find_path(ctx, NULL, yo->schema_node_path, 1); + if (!node) { + YLMSG_E("Invalid schema path."); + return 1; + } } + } + + if (lys_print_node(yo->out, node, yo->schema_out_format, yo->line_length, yo->schema_print_options)) { + YLMSG_E("Unable to print schema node %s.", yo->schema_node_path); + return 1; + } + + return 0; +} + +int +cmd_print_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + int rc = 0; + + if (yo->ctx_options & LY_CTX_SET_PRIV_PARSED) { + /* print tree from lysc_nodes */ + ly_ctx_set_options(*ctx, LY_CTX_SET_PRIV_PARSED); + } + + if (yo->schema_node_path) { + rc = print_node(*ctx, yo); + } else if (!yo->interactive && yo->submodule) { + rc = print_submodule(yo->out, ctx, yo->submodule, NULL, yo->schema_out_format, yo->line_length, + yo->schema_print_options); } else { - cmd_print_modules(argc, argv, out, ctx, format, line_length, options_print); - goto cleanup; + rc = cmd_print_module(posv, yo->out, ctx, yo->schema_out_format, yo->line_length, yo->schema_print_options); + if (!yo->last_one && (yo->schema_out_format == LYS_OUT_TREE)) { + ly_print(yo->out, "\n"); + } } -cleanup: - free_cmdline(argv); - ly_out_free(out, NULL, out_stdout ? 0 : 1); + return rc; } diff --git a/tools/lint/cmd_searchpath.c b/tools/lint/cmd_searchpath.c index 529e05d..a6aeacf 100644 --- a/tools/lint/cmd_searchpath.c +++ b/tools/lint/cmd_searchpath.c @@ -2,9 +2,10 @@ * @file cmd_searchpath.c * @author Michal Vasko <mvasko@cesnet.cz> * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief 'searchpath' command of the libyang's yanglint tool. * - * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -24,51 +25,62 @@ #include "libyang.h" #include "common.h" +#include "yl_opt.h" void cmd_searchpath_help(void) { printf("Usage: searchpath [--clear] [<modules-dir-path> ...]\n" - " Set paths of directories where to search for imports and\n" - " includes of the schema modules. The current working directory\n" - " and the path of the module being added is used implicitly.\n" - " The 'load' command uses these paths to search even for the\n" - " schema modules to be loaded.\n"); + " Set paths of directories where to search for imports and includes\n" + " of the schema modules. Subdirectories are also searched. The current\n" + " working directory and the path of the module being added is used implicitly.\n" + " The 'load' command uses these paths to search even for the schema modules\n" + " to be loaded.\n"); } -void -cmd_searchpath(struct ly_ctx **ctx, const char *cmdline) +int +cmd_searchpath_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) { - int argc = 0; - char **argv = NULL; + int rc = 0, argc = 0; int opt, opt_index; struct option options[] = { {"clear", no_argument, NULL, 'c'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} }; - int8_t cleared = 0; - if (parse_cmdline(cmdline, &argc, &argv)) { - goto cleanup; + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; } - while ((opt = getopt_long(argc, argv, "ch", options, &opt_index)) != -1) { + while ((opt = getopt_long(argc, yo->argv, commands[CMD_SEARCHPATH].optstring, options, &opt_index)) != -1) { switch (opt) { case 'c': - ly_ctx_unset_searchdir(*ctx, NULL); - cleared = 1; + yo->searchdir_unset = 1; break; case 'h': cmd_searchpath_help(); - goto cleanup; + return 1; default: - YLMSG_E("Unknown option.\n"); - goto cleanup; + YLMSG_E("Unknown option."); + return 1; } } - if (!cleared && (argc == optind)) { + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_searchpath_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + int rc = 0; + + if (yo->searchdir_unset) { + ly_ctx_unset_searchdir(*ctx, NULL); + } else if (!yo->searchdir_unset && !posv) { /* no argument - print the paths */ const char * const *dirs = ly_ctx_get_searchdirs(*ctx); @@ -76,15 +88,9 @@ cmd_searchpath(struct ly_ctx **ctx, const char *cmdline) for (uint32_t i = 0; dirs[i]; ++i) { printf(" %s\n", dirs[i]); } - goto cleanup; - } - - for (int i = 0; i < argc - optind; i++) { - if (ly_ctx_set_searchdir(*ctx, argv[optind + i])) { - goto cleanup; - } + } else { + rc = ly_ctx_set_searchdir(*ctx, posv); } -cleanup: - free_cmdline(argv); + return rc; } diff --git a/tools/lint/cmd_verb.c b/tools/lint/cmd_verb.c new file mode 100644 index 0000000..33c8d1e --- /dev/null +++ b/tools/lint/cmd_verb.c @@ -0,0 +1,114 @@ +/** + * @file cmd_verb.c + * @author Adam Piecek <piecek@cesnet.cz> + * @brief 'verb' command of the libyang's yanglint tool. + * + * Copyright (c) 2023-2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#include "cmd.h" + +#include <getopt.h> +#include <stdint.h> +#include <stdio.h> +#include <strings.h> + +#include "libyang.h" + +#include "common.h" +#include "yl_opt.h" + +void +cmd_verb_help(void) +{ + printf("Usage: verb (error | warning | verbose | debug)\n"); +} + +int +cmd_verb_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) +{ + int rc = 0, argc = 0; + int opt, opt_index; + struct option options[] = { + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + if ((rc = parse_cmdline(cmdline, &argc, &yo->argv))) { + return rc; + } + + while ((opt = getopt_long(argc, yo->argv, commands[CMD_VERB].optstring, options, &opt_index)) != -1) { + if (opt == 'h') { + cmd_verb_help(); + return 1; + } else { + YLMSG_E("Unknown option."); + return 1; + } + } + + *posv = &yo->argv[optind]; + *posc = argc - optind; + + return 0; +} + +int +cmd_verb_dep(struct yl_opt *yo, int posc) +{ + (void) yo; + + if (posc > 1) { + YLMSG_E("Only a single verbosity level can be set."); + cmd_verb_help(); + return 1; + } + + return 0; +} + +int +cmd_verb_exec(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv) +{ + (void) ctx, (void) yo; + + if (!posv) { + /* no argument - print current value */ + LY_LOG_LEVEL level = ly_log_level(LY_LLERR); + + ly_log_level(level); + printf("Current verbosity level: "); + if (level == LY_LLERR) { + printf("error\n"); + } else if (level == LY_LLWRN) { + printf("warning\n"); + } else if (level == LY_LLVRB) { + printf("verbose\n"); + } else if (level == LY_LLDBG) { + printf("debug\n"); + } + return 0; + } else { + if (!strcasecmp("error", posv) || !strcmp("0", posv)) { + ly_log_level(LY_LLERR); + } else if (!strcasecmp("warning", posv) || !strcmp("1", posv)) { + ly_log_level(LY_LLWRN); + } else if (!strcasecmp("verbose", posv) || !strcmp("2", posv)) { + ly_log_level(LY_LLVRB); + } else if (!strcasecmp("debug", posv) || !strcmp("3", posv)) { + ly_log_level(LY_LLDBG); + } else { + YLMSG_E("Unknown verbosity \"%s\".", posv); + return 1; + } + } + + return 0; +} diff --git a/tools/lint/common.c b/tools/lint/common.c index fc9b1cd..d86c54f 100644 --- a/tools/lint/common.c +++ b/tools/lint/common.c @@ -1,9 +1,10 @@ /** * @file common.c * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief libyang's yanglint tool - common functions for both interactive and non-interactive mode. * - * Copyright (c) 2020 CESNET, z.s.p.o. + * Copyright (c) 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -19,7 +20,7 @@ #include <assert.h> #include <errno.h> -#include <getopt.h> +#include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -27,6 +28,21 @@ #include "compat.h" #include "libyang.h" +#include "plugins_exts.h" +#include "yl_opt.h" + +void +yl_log(ly_bool err, const char *format, ...) +{ + char msg[256]; + va_list ap; + + va_start(ap, format); + vsnprintf(msg, 256, format, ap); + va_end(ap); + + fprintf(stderr, "YANGLINT[%c]: %s\n", err ? 'E' : 'W', msg); +} int parse_schema_path(const char *path, char **dir, char **module) @@ -38,6 +54,7 @@ parse_schema_path(const char *path, char **dir, char **module) /* split the path to dirname and basename for further work */ *dir = strdup(path); + /* FIXME: this is broken on Windows */ *module = strrchr(*dir, '/'); if (!(*module)) { *module = *dir; @@ -65,722 +82,99 @@ get_input(const char *filepath, LYS_INFORMAT *format_schema, LYD_FORMAT *format_ /* check that the filepath exists and is a regular file */ if (stat(filepath, &st) == -1) { - YLMSG_E("Unable to use input filepath (%s) - %s.\n", filepath, strerror(errno)); + YLMSG_E("Unable to use input filepath (%s) - %s.", filepath, strerror(errno)); return -1; } if (!S_ISREG(st.st_mode)) { - YLMSG_E("Provided input file (%s) is not a regular file.\n", filepath); + YLMSG_E("Provided input file (%s) is not a regular file.", filepath); return -1; } - if ((format_schema && !*format_schema) || (format_data && !*format_data)) { - /* get the file format */ - if (get_format(filepath, format_schema, format_data)) { - return -1; - } - } - - if (ly_in_new_filepath(filepath, 0, in)) { - YLMSG_E("Unable to process input file.\n"); + if (get_format(filepath, format_schema, format_data)) { return -1; } - return 0; -} - -void -free_features(void *flist) -{ - struct schema_features *rec = (struct schema_features *)flist; - - if (rec) { - free(rec->mod_name); - if (rec->features) { - for (uint32_t u = 0; rec->features[u]; ++u) { - free(rec->features[u]); - } - free(rec->features); - } - free(rec); - } -} - -void -get_features(struct ly_set *fset, const char *module, const char ***features) -{ - /* get features list for this module */ - for (uint32_t u = 0; u < fset->count; ++u) { - struct schema_features *sf = (struct schema_features *)fset->objs[u]; - - if (!strcmp(module, sf->mod_name)) { - /* matched module - explicitly set features */ - *features = (const char **)sf->features; - sf->applied = 1; - return; - } - } - - /* features not set so disable all */ - *features = NULL; -} - -int -parse_features(const char *fstring, struct ly_set *fset) -{ - struct schema_features *rec; - uint32_t count; - char *p, **fp; - - rec = calloc(1, sizeof *rec); - if (!rec) { - YLMSG_E("Unable to allocate features information record (%s).\n", strerror(errno)); + if (in && ly_in_new_filepath(filepath, 0, in)) { + YLMSG_E("Unable to process input file."); return -1; } - if (ly_set_add(fset, rec, 1, NULL)) { - YLMSG_E("Unable to store features information (%s).\n", strerror(errno)); - free(rec); - return -1; - } - - /* fill the record */ - p = strchr(fstring, ':'); - if (!p) { - YLMSG_E("Invalid format of the features specification (%s).\n", fstring); - return -1; - } - rec->mod_name = strndup(fstring, p - fstring); - - count = 0; - while (p) { - size_t len = 0; - char *token = p + 1; - - p = strchr(token, ','); - if (!p) { - /* the last item, if any */ - len = strlen(token); - } else { - len = p - token; - } - - if (len) { - fp = realloc(rec->features, (count + 1) * sizeof *rec->features); - if (!fp) { - YLMSG_E("Unable to store features list information (%s).\n", strerror(errno)); - return -1; - } - rec->features = fp; - fp = &rec->features[count++]; /* array item to set */ - (*fp) = strndup(token, len); - } - } - - /* terminating NULL */ - fp = realloc(rec->features, (count + 1) * sizeof *rec->features); - if (!fp) { - YLMSG_E("Unable to store features list information (%s).\n", strerror(errno)); - return -1; - } - rec->features = fp; - rec->features[count++] = NULL; return 0; } -struct cmdline_file * -fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format) +LYS_INFORMAT +get_schema_format(const char *filename) { - struct cmdline_file *rec; - - rec = malloc(sizeof *rec); - if (!rec) { - YLMSG_E("Allocating memory for data file information failed.\n"); - return NULL; - } - rec->in = in; - rec->path = path; - rec->format = format; - - if (set && ly_set_add(set, rec, 1, NULL)) { - free(rec); - YLMSG_E("Storing data file information failed.\n"); - return NULL; - } - - return rec; -} - -int -collect_features(const struct lys_module *mod, struct ly_set *set) -{ - struct lysp_feature *f = NULL; - uint32_t idx = 0; - - while ((f = lysp_feature_next(f, mod->parsed, &idx))) { - if (ly_set_add(set, (void *)f->name, 1, NULL)) { - YLMSG_E("Memory allocation failed.\n"); - ly_set_erase(set, NULL); - return 1; - } - } - - return 0; -} - -void -print_features(struct ly_out *out, const struct lys_module *mod, const struct ly_set *set) -{ - size_t max_len; - uint32_t j; - const char *name; - - /* header */ - ly_print(out, "%s:\n", mod->name); - - /* no features */ - if (!set->count) { - ly_print(out, "\t(none)\n\n"); - return; - } - - /* get max len, so the statuses of all the features will be aligned */ - max_len = 0; - for (j = 0; j < set->count; ++j) { - name = set->objs[j]; - if (strlen(name) > max_len) { - max_len = strlen(name); - } - } - - /* print features */ - for (j = 0; j < set->count; ++j) { - name = set->objs[j]; - ly_print(out, "\t%-*s (%s)\n", (int)max_len, name, lys_feature_value(mod, name) ? "off" : "on"); - } - - ly_print(out, "\n"); -} - -int -generate_features_output(const struct lys_module *mod, const struct ly_set *set, char **features_param) -{ - uint32_t j; - /* - * features_len - length of all the features in the current module - * added_len - length of a string to be added, = features_len + extra necessary length - * param_len - length of the parameter before appending new string - */ - size_t features_len, added_len, param_len; - char *tmp; - - features_len = 0; - for (j = 0; j < set->count; j++) { - features_len += strlen(set->objs[j]); - } - - if (j == 0) { - /* no features */ - added_len = strlen("-F ") + strlen(mod->name) + strlen(":"); - } else { - /* j = comma count, -1 because of trailing comma */ - added_len = strlen("-F ") + strlen(mod->name) + strlen(":") + features_len + j - 1; - } - - /* to avoid strlen(NULL) if this is the first call */ - param_len = 0; - if (*features_param) { - param_len = strlen(*features_param); - } - - /* +1 because of white space at the beginning */ - tmp = realloc(*features_param, param_len + added_len + 1 + 1); - if (!tmp) { - goto error; - } else { - *features_param = tmp; - } - sprintf(*features_param + param_len, " -F %s:", mod->name); - - for (j = 0; j < set->count; j++) { - strcat(*features_param, set->objs[j]); - /* no trailing comma */ - if (j != (set->count - 1)) { - strcat(*features_param, ","); - } - } - - return 0; - -error: - YLMSG_E("Memory allocation failed (%s:%d, %s).\n", __FILE__, __LINE__, strerror(errno)); - return 1; -} - -int -print_all_features(struct ly_out *out, const struct ly_ctx *ctx, ly_bool generate_features, char **features_param) -{ - int ret = 0; - uint32_t i = 0; - struct lys_module *mod; - struct ly_set set = {0}; - - while ((mod = ly_ctx_get_module_iter(ctx, &i)) != NULL) { - /* only care about implemented modules */ - if (!mod->implemented) { - continue; - } - - /* always erase the set, so the previous module's features don't carry over to the next module's features */ - ly_set_erase(&set, NULL); - - if (collect_features(mod, &set)) { - ret = 1; - goto cleanup; - } - - if (generate_features && generate_features_output(mod, &set, features_param)) { - ret = 1; - goto cleanup; - } - print_features(out, mod, &set); - } - -cleanup: - ly_set_erase(&set, NULL); - return ret; -} - -void -free_cmdline_file(void *cmdline_file) -{ - struct cmdline_file *rec = (struct cmdline_file *)cmdline_file; - - if (rec) { - ly_in_free(rec->in, 1); - free(rec); - } -} - -void -free_cmdline(char *argv[]) -{ - if (argv) { - free(argv[0]); - free(argv); - } -} - -int -parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[]) -{ - int count; - char **vector; char *ptr; - char qmark = 0; - - assert(cmdline); - assert(argc_p); - assert(argv_p); - - /* init */ - optind = 0; /* reinitialize getopt() */ - count = 1; - vector = malloc((count + 1) * sizeof *vector); - vector[0] = strdup(cmdline); - - /* command name */ - strtok(vector[0], " "); - - /* arguments */ - while ((ptr = strtok(NULL, " "))) { - size_t len; - void *r; - - len = strlen(ptr); - - if (qmark) { - /* still in quotated text */ - /* remove NULL termination of the previous token since it is not a token, - * but a part of the quotation string */ - ptr[-1] = ' '; - - if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) { - /* end of quotation */ - qmark = 0; - /* shorten the argument by the terminating quotation mark */ - ptr[len - 1] = '\0'; - } - continue; - } - /* another token in cmdline */ - ++count; - r = realloc(vector, (count + 1) * sizeof *vector); - if (!r) { - YLMSG_E("Memory allocation failed (%s:%d, %s).\n", __FILE__, __LINE__, strerror(errno)); - free(vector); - return -1; - } - vector = r; - vector[count - 1] = ptr; - - if ((ptr[0] == '"') || (ptr[0] == '\'')) { - /* remember the quotation mark to identify end of quotation */ - qmark = ptr[0]; - - /* move the remembered argument after the quotation mark */ - ++vector[count - 1]; - - /* check if the quotation is terminated within this token */ - if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) { - /* end of quotation */ - qmark = 0; - /* shorten the argument by the terminating quotation mark */ - ptr[len - 1] = '\0'; - } + if ((ptr = strrchr(filename, '.')) != NULL) { + ++ptr; + if (!strcmp(ptr, "yang")) { + return LYS_IN_YANG; + } else if (!strcmp(ptr, "yin")) { + return LYS_IN_YIN; + } else { + return LYS_IN_UNKNOWN; } + } else { + return LYS_IN_UNKNOWN; } - vector[count] = NULL; - - *argc_p = count; - *argv_p = vector; - - return 0; } -int -get_format(const char *filename, LYS_INFORMAT *schema, LYD_FORMAT *data) +LYD_FORMAT +get_data_format(const char *filename) { char *ptr; - LYS_INFORMAT informat_s; - LYD_FORMAT informat_d; - /* get the file format */ if ((ptr = strrchr(filename, '.')) != NULL) { ++ptr; - if (!strcmp(ptr, "yang")) { - informat_s = LYS_IN_YANG; - informat_d = 0; - } else if (!strcmp(ptr, "yin")) { - informat_s = LYS_IN_YIN; - informat_d = 0; - } else if (!strcmp(ptr, "xml")) { - informat_s = 0; - informat_d = LYD_XML; + if (!strcmp(ptr, "xml")) { + return LYD_XML; } else if (!strcmp(ptr, "json")) { - informat_s = 0; - informat_d = LYD_JSON; + return LYD_JSON; } else if (!strcmp(ptr, "lyb")) { - informat_s = 0; - informat_d = LYD_LYB; + return LYD_LYB; } else { - YLMSG_E("Input file \"%s\" in an unknown format \"%s\".\n", filename, ptr); - return 0; + return LYD_UNKNOWN; } } else { - YLMSG_E("Input file \"%s\" without file extension - unknown format.\n", filename); - return 1; + return LYD_UNKNOWN; } - - if (informat_d) { - if (!data) { - YLMSG_E("Input file \"%s\" not expected to contain data instances (unexpected format).\n", filename); - return 2; - } - (*data) = informat_d; - } else if (informat_s) { - if (!schema) { - YLMSG_E("Input file \"%s\" not expected to contain schema definition (unexpected format).\n", filename); - return 3; - } - (*schema) = informat_s; - } - - return 0; } int -print_list(struct ly_out *out, struct ly_ctx *ctx, LYD_FORMAT outformat) +get_format(const char *filepath, LYS_INFORMAT *schema_form, LYD_FORMAT *data_form) { - struct lyd_node *ylib; - uint32_t idx = 0, has_modules = 0; - const struct lys_module *mod; - - if (outformat != LYD_UNKNOWN) { - if (ly_ctx_get_yanglib_data(ctx, &ylib, "%u", ly_ctx_get_change_count(ctx))) { - YLMSG_E("Getting context info (ietf-yang-library data) failed. If the YANG module is missing or not implemented, use an option to add it internally.\n"); - return 1; - } - - lyd_print_all(out, ylib, outformat, 0); - lyd_free_all(ylib); - return 0; - } - - /* iterate schemas in context and provide just the basic info */ - ly_print(out, "List of the loaded models:\n"); - while ((mod = ly_ctx_get_module_iter(ctx, &idx))) { - has_modules++; - - /* conformance print */ - if (mod->implemented) { - ly_print(out, " I"); - } else { - ly_print(out, " i"); - } - - /* module print */ - ly_print(out, " %s", mod->name); - if (mod->revision) { - ly_print(out, "@%s", mod->revision); - } + LYS_INFORMAT schema; + LYD_FORMAT data; - /* submodules print */ - if (mod->parsed && mod->parsed->includes) { - uint64_t u = 0; + schema = !schema_form || !*schema_form ? LYS_IN_UNKNOWN : *schema_form; + data = !data_form || !*data_form ? LYD_UNKNOWN : *data_form; - ly_print(out, " ("); - LY_ARRAY_FOR(mod->parsed->includes, u) { - ly_print(out, "%s%s", !u ? "" : ",", mod->parsed->includes[u].name); - if (mod->parsed->includes[u].rev[0]) { - ly_print(out, "@%s", mod->parsed->includes[u].rev); - } - } - ly_print(out, ")"); - } - - /* finish the line */ - ly_print(out, "\n"); + if (!schema) { + schema = get_schema_format(filepath); } - - if (!has_modules) { - ly_print(out, "\t(none)\n"); + if (!data) { + data = get_data_format(filepath); } - ly_print_flush(out); - return 0; -} - -int -evaluate_xpath(const struct lyd_node *tree, const char *xpath) -{ - struct ly_set *set = NULL; - - if (lyd_find_xpath(tree, xpath, &set)) { + if (!schema && !data) { + YLMSG_E("Input schema format for %s file not recognized.", filepath); + return -1; + } else if (!data && !schema) { + YLMSG_E("Input data format for %s file not recognized.", filepath); return -1; } + assert(schema || data); - /* print result */ - printf("XPath \"%s\" evaluation result:\n", xpath); - if (!set->count) { - printf("\tEmpty\n"); - } else { - for (uint32_t u = 0; u < set->count; ++u) { - struct lyd_node *node = (struct lyd_node *)set->objs[u]; - - printf(" %s \"%s\"", lys_nodetype2str(node->schema->nodetype), node->schema->name); - if (node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST)) { - printf(" (value: \"%s\")\n", lyd_get_value(node)); - } else if (node->schema->nodetype == LYS_LIST) { - printf(" ("); - for (struct lyd_node *key = ((struct lyd_node_inner *)node)->child; key && lysc_is_key(key->schema); key = key->next) { - printf("%s\"%s\": \"%s\";", (key != ((struct lyd_node_inner *)node)->child) ? " " : "", - key->schema->name, lyd_get_value(key)); - } - printf(")\n"); - } - } - } - - ly_set_free(set, NULL); - return 0; -} - -LY_ERR -process_data(struct ly_ctx *ctx, enum lyd_type data_type, uint8_t merge, LYD_FORMAT format, struct ly_out *out, - uint32_t options_parse, uint32_t options_validate, uint32_t options_print, struct cmdline_file *operational_f, - struct cmdline_file *rpc_f, struct ly_set *inputs, struct ly_set *xpaths) -{ - LY_ERR ret = LY_SUCCESS; - struct lyd_node *tree = NULL, *op = NULL, *envp = NULL, *merged_tree = NULL, *oper_tree = NULL; - char *path = NULL; - struct ly_set *set = NULL; - - /* additional operational datastore */ - if (operational_f && operational_f->in) { - ret = lyd_parse_data(ctx, NULL, operational_f->in, operational_f->format, LYD_PARSE_ONLY, 0, &oper_tree); - if (ret) { - YLMSG_E("Failed to parse operational datastore file \"%s\".\n", operational_f->path); - goto cleanup; - } - } - - for (uint32_t u = 0; u < inputs->count; ++u) { - struct cmdline_file *input_f = (struct cmdline_file *)inputs->objs[u]; - - switch (data_type) { - case LYD_TYPE_DATA_YANG: - ret = lyd_parse_data(ctx, NULL, input_f->in, input_f->format, options_parse, options_validate, &tree); - break; - case LYD_TYPE_RPC_YANG: - case LYD_TYPE_REPLY_YANG: - case LYD_TYPE_NOTIF_YANG: - ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, data_type, &tree, &op); - break; - case LYD_TYPE_RPC_NETCONF: - case LYD_TYPE_NOTIF_NETCONF: - ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, data_type, &envp, &op); - - /* adjust pointers */ - for (tree = op; lyd_parent(tree); tree = lyd_parent(tree)) {} - break; - case LYD_TYPE_REPLY_NETCONF: - /* parse source RPC operation */ - assert(rpc_f && rpc_f->in); - ret = lyd_parse_op(ctx, NULL, rpc_f->in, rpc_f->format, LYD_TYPE_RPC_NETCONF, &envp, &op); - if (ret) { - YLMSG_E("Failed to parse source NETCONF RPC operation file \"%s\".\n", rpc_f->path); - goto cleanup; - } - - /* adjust pointers */ - for (tree = op; lyd_parent(tree); tree = lyd_parent(tree)) {} - - /* free input */ - lyd_free_siblings(lyd_child(op)); - - /* we do not care */ - lyd_free_all(envp); - envp = NULL; - - ret = lyd_parse_op(ctx, op, input_f->in, input_f->format, data_type, &envp, NULL); - break; - default: - YLMSG_E("Internal error (%s:%d).\n", __FILE__, __LINE__); - goto cleanup; - } - - if (ret) { - YLMSG_E("Failed to parse input data file \"%s\".\n", input_f->path); - goto cleanup; - } - - if (merge) { - /* merge the data so far parsed for later validation and print */ - if (!merged_tree) { - merged_tree = tree; - } else { - ret = lyd_merge_siblings(&merged_tree, tree, LYD_MERGE_DESTRUCT); - if (ret) { - YLMSG_E("Merging %s with previous data failed.\n", input_f->path); - goto cleanup; - } - } - tree = NULL; - } else if (format) { - /* print */ - switch (data_type) { - case LYD_TYPE_DATA_YANG: - lyd_print_all(out, tree, format, options_print); - break; - case LYD_TYPE_RPC_YANG: - case LYD_TYPE_REPLY_YANG: - case LYD_TYPE_NOTIF_YANG: - case LYD_TYPE_RPC_NETCONF: - case LYD_TYPE_NOTIF_NETCONF: - lyd_print_tree(out, tree, format, options_print); - break; - case LYD_TYPE_REPLY_NETCONF: - /* just the output */ - lyd_print_tree(out, lyd_child(tree), format, options_print); - break; - default: - assert(0); - } - } else { - /* validation of the RPC/Action/reply/Notification with the operational datastore, if any */ - switch (data_type) { - case LYD_TYPE_DATA_YANG: - /* already validated */ - break; - case LYD_TYPE_RPC_YANG: - case LYD_TYPE_RPC_NETCONF: - ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_RPC_YANG, NULL); - break; - case LYD_TYPE_REPLY_YANG: - case LYD_TYPE_REPLY_NETCONF: - ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_REPLY_YANG, NULL); - break; - case LYD_TYPE_NOTIF_YANG: - case LYD_TYPE_NOTIF_NETCONF: - ret = lyd_validate_op(tree, oper_tree, LYD_TYPE_NOTIF_YANG, NULL); - break; - default: - assert(0); - } - if (ret) { - if (operational_f->path) { - YLMSG_E("Failed to validate input data file \"%s\" with operational datastore \"%s\".\n", - input_f->path, operational_f->path); - } else { - YLMSG_E("Failed to validate input data file \"%s\".\n", input_f->path); - } - goto cleanup; - } - - if (op && oper_tree && lyd_parent(op)) { - /* check operation parent existence */ - path = lyd_path(lyd_parent(op), LYD_PATH_STD, NULL, 0); - if (!path) { - ret = LY_EMEM; - goto cleanup; - } - if ((ret = lyd_find_xpath(oper_tree, path, &set))) { - goto cleanup; - } - if (!set->count) { - YLMSG_E("Operation \"%s\" parent \"%s\" not found in the operational data.\n", LYD_NAME(op), path); - ret = LY_EVALID; - goto cleanup; - } - } - } - - /* next iter */ - lyd_free_all(tree); - tree = NULL; - lyd_free_all(envp); - envp = NULL; + if (schema_form) { + *schema_form = schema; } - - if (merge) { - /* validate the merged result */ - ret = lyd_validate_all(&merged_tree, ctx, LYD_VALIDATE_PRESENT, NULL); - if (ret) { - YLMSG_E("Merged data are not valid.\n"); - goto cleanup; - } - - if (format) { - /* and print it */ - lyd_print_all(out, merged_tree, format, options_print); - } - - for (uint32_t u = 0; xpaths && (u < xpaths->count); ++u) { - if (evaluate_xpath(merged_tree, (const char *)xpaths->objs[u])) { - goto cleanup; - } - } + if (data_form) { + *data_form = data; } -cleanup: - lyd_free_all(tree); - lyd_free_all(envp); - lyd_free_all(merged_tree); - lyd_free_all(oper_tree); - free(path); - ly_set_free(set, NULL); - return ret; + return 0; } const struct lysc_node * @@ -812,7 +206,7 @@ find_schema_path(const struct ly_ctx *ctx, const char *schema_path) /* - 1 because module_name_end points to ':' */ module_name = strndup(schema_path + 1, module_name_end - schema_path - 1); if (!module_name) { - YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno)); + YLMSG_E("Memory allocation failed (%s:%d, %s).", __FILE__, __LINE__, strerror(errno)); parent_node = NULL; goto cleanup; } @@ -866,3 +260,42 @@ cleanup: free(module_name); return parent_node; } + +LY_ERR +ext_data_clb(const struct lysc_ext_instance *ext, void *user_data, void **ext_data, ly_bool *ext_data_free) +{ + struct ly_ctx *ctx; + struct lyd_node *data = NULL; + + ctx = ext->module->ctx; + if (user_data) { + lyd_parse_data_path(ctx, user_data, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, &data); + } + + *ext_data = data; + *ext_data_free = 1; + return LY_SUCCESS; +} + +LY_ERR +searchpath_strcat(char **searchpaths, const char *path) +{ + uint64_t len; + char *new; + + if (!(*searchpaths)) { + *searchpaths = strdup(path); + return LY_SUCCESS; + } + + len = strlen(*searchpaths) + strlen(path) + strlen(PATH_SEPARATOR); + new = realloc(*searchpaths, sizeof(char) * len + 1); + if (!new) { + return LY_EMEM; + } + strcat(new, PATH_SEPARATOR); + strcat(new, path); + *searchpaths = new; + + return LY_SUCCESS; +} diff --git a/tools/lint/common.h b/tools/lint/common.h index 7c6a8ad..7c50e72 100644 --- a/tools/lint/common.h +++ b/tools/lint/common.h @@ -1,9 +1,10 @@ /** * @file common.h * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief libyang's yanglint tool - common functions and definitions for both interactive and non-interactive mode. * - * Copyright (c) 2020 CESNET, z.s.p.o. + * Copyright (c) 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -33,16 +34,21 @@ #define YL_DEFAULT_DATA_PARSE_OPTIONS LYD_PARSE_STRICT /** + * @brief Default data validation flags. + */ +#define YL_DEFAULT_DATA_VALIDATE_OPTIONS LYD_VALIDATE_MULTI_ERROR + +/** * @brief log error message */ #define YLMSG_E(...) \ - fprintf(stderr, "YANGLINT[E]: " __VA_ARGS__) + yl_log(1, __VA_ARGS__); /** * @brief log warning message */ #define YLMSG_W(...) \ - fprintf(stderr, "YANGLINT[W]: " __VA_ARGS__) + yl_log(0, __VA_ARGS__); #ifndef _WIN32 # define PATH_SEPARATOR ":" @@ -50,90 +56,16 @@ # define PATH_SEPARATOR ";" #endif -/** - * @brief Storage for the list of the features (their names) in a specific YANG module. - */ -struct schema_features { - char *mod_name; - char **features; - ly_bool applied; -}; - -/** - * @brief Data connected with a file provided on a command line as a file path. - */ -struct cmdline_file { - struct ly_in *in; - const char *path; - LYD_FORMAT format; -}; - -/** - * @brief Free the schema features list (struct schema_features *) - * @param[in,out] flist The (struct schema_features *) to free. - */ -void free_features(void *flist); - -/** - * @brief Get the list of features connected with the specific YANG module. - * - * @param[in] fset The set of features information (struct schema_features *). - * @param[in] module Name of the YANG module which features should be found. - * @param[out] features Pointer to the list of features being returned. - */ -void get_features(struct ly_set *fset, const char *module, const char ***features); - -/** - * @brief Parse features being specified for the specific YANG module. - * - * Format of the input @p fstring is as follows: <module_name>:[<feature>,]* - * - * @param[in] fstring Input string to be parsed. - * @param[in, out] fset Features information set (of struct schema_features *). The set is being filled. - */ -int parse_features(const char *fstring, struct ly_set *fset); - -/** - * @brief Collect all features of a module. - * - * @param[in] mod Module to be searched for features. - * @param[out] set Set in which the features will be stored. - * @return 0 on success. - * @return 1 on error. - */ -int collect_features(const struct lys_module *mod, struct ly_set *set); - -/** - * @brief Print all features of a single module. - * - * @param[in] out The output handler for printing. - * @param[in] mod Module which contains the features. - * @param[in] set Set which holds the features. - */ -void print_features(struct ly_out *out, const struct lys_module *mod, const struct ly_set *set); - -/** - * @brief Generate a string, which will contain features paramater. - * - * @param[in] mod Module, for which the string will be generated. - * @param[in] set Set containing the features. - * @param[out] features_param String which will contain the output. - * @return 0 on success. - * @return 1 on error. - */ -int generate_features_output(const struct lys_module *mod, const struct ly_set *set, char **features_param); +struct cmdline_file; /** - * @brief Print all features of all implemented modules. + * @brief Log a yanglint message. * - * @param[in] out The output handler for printing. - * @param[in] ctx Libyang context. - * @param[in] generate_features Flag expressing whether to generate features parameter. - * @param[out] features_param String, which will contain the output if the above flag is set. - * @return 0 on success. - * @return 1 on error. + * @param[in] err Whether the message is an error or a warning. + * @param[in] format Message format. + * @param[in] ... Format arguments. */ -int print_all_features(struct ly_out *out, const struct ly_ctx *ctx, ly_bool generate_features, char **features_param); +void yl_log(ly_bool err, const char *format, ...); /** * @brief Parse path of a schema module file into the directory and module name. @@ -141,7 +73,7 @@ int print_all_features(struct ly_out *out, const struct ly_ctx *ctx, ly_bool gen * @param[in] path Schema module file path to be parsed. * @param[out] dir Pointer to the directory path where the file resides. Caller is expected to free the returned string. * @param[out] module Pointer to the name of the module (without file suffixes or revision information) specified by the - * @path. Caller is expected to free the returned string. + * @p path. Caller is expected to free the returned string. * @return 0 on success * @return -1 on error */ @@ -158,100 +90,69 @@ int parse_schema_path(const char *path, char **dir, char **module); * prohibited and such files are refused. * @param[out] format_data Format of the data detected from the file name. If NULL specified, the data formats are * prohibited and such files are refused. - * @param[out] in Created input handler referring the file behind the @p filepath. + * @param[out] in Created input handler referring the file behind the @p filepath. Can be NULL. * @return 0 on success. * @return -1 on failure. */ int get_input(const char *filepath, LYS_INFORMAT *format_schema, LYD_FORMAT *format_data, struct ly_in **in); /** - * @brief Free the command line file data (struct cmdline_file *) - * @param[in,out] cmdline_file The (struct cmdline_file *) to free. - */ -void free_cmdline_file(void *cmdline_file); - -/** - * @brief Create and fill the command line file data (struct cmdline_file *). - * @param[in] set Optional parameter in case the record is supposed to be added into a set. - * @param[in] in Input file handler. - * @param[in] path Filepath of the file. - * @param[in] format Format of the data file. - * @return The created command line file structure. - * @return NULL on failure - */ -struct cmdline_file *fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format); - -/** - * @brief Helper function to prepare argc, argv pair from a command line string. + * @brief Get schema format of the @p filename's content according to the @p filename's suffix. * - * @param[in] cmdline Complete command line string. - * @param[out] argc_p Pointer to store argc value. - * @param[out] argv_p Pointer to store argv vector. - * @return 0 on success, non-zero on failure. + * @param[in] filename Name of the file to examine. + * @return Detected schema input format. */ -int parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[]); +LYS_INFORMAT get_schema_format(const char *filename); /** - * @brief Destructor for the argument vector prepared by ::parse_cmdline(). + * @brief Get data format of the @p filename's content according to the @p filename's suffix. * - * @param[in,out] argv Argument vector to destroy. + * @param[in] filename Name of the file to examine. + * @return Detected data input format. */ -void free_cmdline(char *argv[]); +LYD_FORMAT get_data_format(const char *filename); /** - * @brief Get expected format of the @p filename's content according to the @p filename's suffix. - * @param[in] filename Name of the file to examine. - * @param[out] schema Pointer to a variable to store the expected input schema format. Do not provide the pointer in case a - * schema format is not expected. - * @param[out] data Pointer to a variable to store the expected input data format. Do not provide the pointer in case a data - * format is not expected. + * @brief Get format of the @p filename's content according to the @p filename's suffix. + * + * Either the @p schema or @p data parameter is set. + * + * @param[in] filepath Name of the file to examine. + * @param[out] schema_form Pointer to a variable to store the input schema format. + * @param[out] data_form Pointer to a variable to store the expected input data format. * @return zero in case a format was successfully detected. * @return nonzero in case it is not possible to get valid format from the @p filename. */ -int get_format(const char *filename, LYS_INFORMAT *schema, LYD_FORMAT *data); +int get_format(const char *filepath, LYS_INFORMAT *schema_form, LYD_FORMAT *data_form); /** - * @brief Print list of schemas in the context. + * @brief Get the node specified by the path. * - * @param[in] out Output handler where to print. - * @param[in] ctx Context to print. - * @param[in] outformat Optional output format. If not specified (:LYD_UNKNOWN), a simple list with single module per line - * is printed. Otherwise, the ietf-yang-library data are printed in the specified format. - * @return zero in case the data successfully printed. - * @return nonzero in case of error. + * @param[in] ctx libyang context with schema. + * @param[in] schema_path Path to the wanted node. + * @return Pointer to the schema node specified by the path on success, NULL otherwise. */ -int print_list(struct ly_out *out, struct ly_ctx *ctx, LYD_FORMAT outformat); +const struct lysc_node *find_schema_path(const struct ly_ctx *ctx, const char *schema_path); /** - * @brief Process the input data files - parse, validate and print according to provided options. + * @brief General callback providing run-time extension instance data. * - * @param[in] ctx libyang context with schema. - * @param[in] data_type The type of data in the input files. - * @param[in] merge Flag if the data should be merged before validation. - * @param[in] format Data format for printing. - * @param[in] out The output handler for printing. - * @param[in] options_parse Parser options. - * @param[in] options_validate Validation options. - * @param[in] options_print Printer options. - * @param[in] operational_f Optional operational datastore file information for the case of an extended validation of - * operation(s). - * @param[in] rpc_f Source RPC operation file information for parsing NETCONF rpc-reply. - * @param[in] inputs Set of file informations of input data files. - * @param[in] xpath The set of XPaths to be evaluated on the processed data tree, basic information about the resulting set - * is printed. Alternative to data printing. + * @param[in] ext Compiled extension instance. + * @param[in] user_data User-supplied callback data. + * @param[out] ext_data Provided extension instance data. + * @param[out] ext_data_free Whether the extension instance should free @p ext_data or not. * @return LY_ERR value. */ -LY_ERR process_data(struct ly_ctx *ctx, enum lyd_type data_type, uint8_t merge, LYD_FORMAT format, struct ly_out *out, - uint32_t options_parse, uint32_t options_validate, uint32_t options_print, struct cmdline_file *operational_f, - struct cmdline_file *rpc_f, struct ly_set *inputs, struct ly_set *xpaths); +LY_ERR ext_data_clb(const struct lysc_ext_instance *ext, void *user_data, void **ext_data, ly_bool *ext_data_free); /** - * @brief Get the node specified by the path. + * @brief Concatenation of paths into one string. * - * @param[in] ctx libyang context with schema. - * @param[in] schema_path Path to the wanted node. - * @return Pointer to the schema node specified by the path on success, NULL otherwise. + * @param[in,out] searchpaths Collection of paths in the single string. Paths are delimited by colon ":" + * (on Windows, used semicolon ";" instead). + * @param[in] path Path to add. + * @return LY_ERR value. */ -const struct lysc_node * find_schema_path(const struct ly_ctx *ctx, const char *schema_path); +LY_ERR searchpath_strcat(char **searchpaths, const char *path); #endif /* COMMON_H_ */ diff --git a/tools/lint/completion.c b/tools/lint/completion.c index 9843816..67c6b68 100644 --- a/tools/lint/completion.c +++ b/tools/lint/completion.c @@ -43,14 +43,18 @@ cmd_completion_add_match(const char *match, char ***matches, unsigned int *match { void *p; - ++(*match_count); - p = realloc(*matches, *match_count * sizeof **matches); + p = realloc(*matches, (*match_count + 1) * sizeof **matches); if (!p) { - YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno)); + YLMSG_E("Memory allocation failed (%s:%d, %s).", __FILE__, __LINE__, strerror(errno)); return; } *matches = p; - (*matches)[*match_count - 1] = strdup(match); + (*matches)[*match_count] = strdup(match); + if (!((*matches)[*match_count])) { + YLMSG_E("Memory allocation failed (%s:%d, %s).", __FILE__, __LINE__, strerror(errno)); + return; + } + ++(*match_count); } /** @@ -76,6 +80,34 @@ get_cmd_completion(const char *hint, char ***matches, unsigned int *match_count) } /** + * @brief Provides completion for arguments. + * + * @param[in] hint User input. + * @param[in] args Array of all possible arguments. The last element must be NULL. + * @param[out] matches Matches provided to the user as a completion hint. + * @param[out] match_count Number of matches. + */ +static void +get_arg_completion(const char *hint, const char **args, char ***matches, unsigned int *match_count) +{ + int i; + + *match_count = 0; + *matches = NULL; + + for (i = 0; args[i]; i++) { + if (!strncmp(hint, args[i], strlen(hint))) { + cmd_completion_add_match(args[i], matches, match_count); + } + } + if (*match_count == 0) { + for (i = 0; args[i]; i++) { + cmd_completion_add_match(args[i], matches, match_count); + } + } +} + +/** * @brief Provides completion for module names. * * @param[in] hint User input. @@ -108,21 +140,21 @@ get_model_completion(const char *hint, char ***matches, unsigned int *match_coun /** * @brief Add all child nodes of a single node to the completion hint. * - * @param[in] last_node Node of which children will be added to the hint. - * @param matches[out] Matches provided to the user as a completion hint. - * @param match_count[out] Number of matches. + * @param[in] parent Node of which children will be added to the hint. + * @param[out] matches Matches provided to the user as a completion hint. + * @param[out] match_count Number of matches. */ static void -single_hint_add_children(const struct lysc_node *last_node, char ***matches, unsigned int *match_count) +single_hint_add_children(const struct lysc_node *parent, char ***matches, unsigned int *match_count) { const struct lysc_node *node = NULL; char *match; - if (!last_node) { + if (!parent) { return; } - while ((node = lys_getnext(node, last_node, NULL, LYS_GETNEXT_WITHCASE | LYS_GETNEXT_WITHCHOICE))) { + while ((node = lys_getnext(node, parent, NULL, LYS_GETNEXT_WITHCASE | LYS_GETNEXT_WITHCHOICE))) { match = lysc_path(node, LYSC_PATH_LOG, NULL, 0); cmd_completion_add_match(match, matches, match_count); free(match); @@ -157,13 +189,13 @@ add_all_children_nodes(const struct lysc_module *module, const struct lysc_node if (parent && (node->module != parent->module)) { /* augmented node */ if (asprintf(&node_name, "%s:%s", node->module->name, node->name) == -1) { - YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno)); + YLMSG_E("Memory allocation failed (%s:%d, %s).", __FILE__, __LINE__, strerror(errno)); break; } } else { node_name = strdup(node->name); if (!node_name) { - YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno)); + YLMSG_E("Memory allocation failed (%s:%d, %s).", __FILE__, __LINE__, strerror(errno)); break; } } @@ -229,7 +261,7 @@ get_schema_completion(const char *hint, char ***matches, unsigned int *match_cou /* module name known */ module_name = strndup(start, end - start); if (!module_name) { - YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno)); + YLMSG_E("Memory allocation failed (%s:%d, %s).", __FILE__, __LINE__, strerror(errno)); rc = 1; goto cleanup; } @@ -255,7 +287,7 @@ get_schema_completion(const char *hint, char ***matches, unsigned int *match_cou /* get rid of stuff after the last '/' to obtain the parent node */ path = strndup(hint, start - hint); if (!path) { - YLMSG_E("Memory allocation failed (%s:%d, %s)", __FILE__, __LINE__, strerror(errno)); + YLMSG_E("Memory allocation failed (%s:%d, %s).", __FILE__, __LINE__, strerror(errno)); rc = 1; goto cleanup; } @@ -277,6 +309,90 @@ cleanup: } /** + * @brief Get all possible argument hints for option. + * + * @param[in] hint User input. + * @param[out] matches Matches provided to the user as a completion hint. + * @param[out] match_count Number of matches. + */ +static void +get_print_format_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"yang", "yin", "tree", "info", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_data_type_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"data", "config", "get", "getconfig", "edit", "rpc", "reply", "notif", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_data_in_format_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"xml", "json", "lyb", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_data_default_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"all", "all-tagged", "trim", "implicit-tagged", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_list_format_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"xml", "json", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +/** + * @copydoc get_print_format_arg + */ +static void +get_verb_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"error", "warning", "verbose", "debug", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +#ifndef NDEBUG +/** + * @copydoc get_print_format_arg + */ +static void +get_debug_arg(const char *hint, char ***matches, unsigned int *match_count) +{ + const char *args[] = {"dict", "xpath", "dep-sets", NULL}; + + get_arg_completion(hint, args, matches, match_count); +} + +#endif + +/** * @brief Get the string before the hint, which autocompletion is for. * * @param[in] buf Complete user input. @@ -315,22 +431,37 @@ void complete_cmd(const char *buf, const char *hint, linenoiseCompletions *lc) { struct autocomplete { - const char *cmd; /**< command */ - const char *opt; /**< optional option */ - int last_opt; /**< whether to autocomplete even if an option is last in the hint */ - + enum COMMAND_INDEX ci; /**< command index to global variable 'commands' */ + const char *opt; /**< optional option */ void (*ln_cb)(const char *, const char *, linenoiseCompletions *); /**< linenoise callback to call */ void (*yl_cb)(const char *, char ***, unsigned int *); /**< yanglint callback to call */ } ac[] = { - {"add", NULL, 1, linenoisePathCompletion, NULL}, - {"searchpath", NULL, 0, linenoisePathCompletion, NULL}, - {"data", NULL, 0, linenoisePathCompletion, NULL}, - {"print", NULL, 0, NULL, get_model_completion}, - {"feature", NULL, 0, NULL, get_model_completion}, - {"print", "-P", 1, NULL, get_schema_completion}, + {CMD_ADD, NULL, linenoisePathCompletion, NULL}, + {CMD_PRINT, "-f", NULL, get_print_format_arg}, + {CMD_PRINT, "-P", NULL, get_schema_completion}, + {CMD_PRINT, "-o", linenoisePathCompletion, NULL}, + {CMD_PRINT, NULL, NULL, get_model_completion}, + {CMD_SEARCHPATH, NULL, linenoisePathCompletion, NULL}, + {CMD_EXTDATA, NULL, linenoisePathCompletion, NULL}, + {CMD_CLEAR, "-Y", linenoisePathCompletion, NULL}, + {CMD_DATA, "-t", NULL, get_data_type_arg}, + {CMD_DATA, "-O", linenoisePathCompletion, NULL}, + {CMD_DATA, "-R", linenoisePathCompletion, NULL}, + {CMD_DATA, "-f", NULL, get_data_in_format_arg}, + {CMD_DATA, "-F", NULL, get_data_in_format_arg}, + {CMD_DATA, "-d", NULL, get_data_default_arg}, + {CMD_DATA, "-o", linenoisePathCompletion, NULL}, + {CMD_DATA, NULL, linenoisePathCompletion, NULL}, + {CMD_LIST, NULL, NULL, get_list_format_arg}, + {CMD_FEATURE, NULL, NULL, get_model_completion}, + {CMD_VERB, NULL, NULL, get_verb_arg}, +#ifndef NDEBUG + {CMD_DEBUG, NULL, NULL, get_debug_arg}, +#endif }; - size_t cmd_len; - const char *last; + size_t name_len; + const char *last, *name, *getoptstr; + char opt[3] = {'\0', ':', '\0'}; char **matches = NULL; unsigned int match_count = 0, i; @@ -340,24 +471,27 @@ complete_cmd(const char *buf, const char *hint, linenoiseCompletions *lc) } else { for (i = 0; i < (sizeof ac / sizeof *ac); ++i) { - cmd_len = strlen(ac[i].cmd); - if (strncmp(buf, ac[i].cmd, cmd_len) || (buf[cmd_len] != ' ')) { + /* Find the right command. */ + name = commands[ac[i].ci].name; + name_len = strlen(name); + if (strncmp(buf, name, name_len) || (buf[name_len] != ' ')) { /* not this command */ continue; } + /* Select based on the right option. */ last = get_last_str(buf, hint); - if (ac[i].opt && strncmp(ac[i].opt, last, strlen(ac[i].opt))) { - /* autocompletion for (another) option */ + opt[0] = (last[0] == '-') && last[1] ? last[1] : '\0'; + getoptstr = commands[ac[i].ci].optstring; + if (!ac[i].opt && opt[0] && strstr(getoptstr, opt)) { + /* completion for the argument must be defined */ continue; - } - if (!ac[i].last_opt && (last[0] == '-')) { - /* autocompletion for the command, not an option */ + } else if (ac[i].opt && opt[0] && strncmp(ac[i].opt, last, strlen(ac[i].opt))) { + /* completion for (another) option */ + continue; + } else if (ac[i].opt && !opt[0]) { + /* completion is defined for option */ continue; - } - if ((last != buf) && (last[0] != '-')) { - /* autocompleted */ - return; } /* callback */ diff --git a/tools/lint/configuration.c b/tools/lint/configuration.c index 86179fa..e3db668 100644 --- a/tools/lint/configuration.c +++ b/tools/lint/configuration.c @@ -37,14 +37,14 @@ get_yanglint_dir(void) char *user_home, *yl_dir; if (!(pw = getpwuid(getuid()))) { - YLMSG_E("Determining home directory failed (%s).\n", strerror(errno)); + YLMSG_E("Determining home directory failed (%s).", strerror(errno)); return NULL; } user_home = pw->pw_dir; yl_dir = malloc(strlen(user_home) + 1 + strlen(YL_DIR) + 1); if (!yl_dir) { - YLMSG_E("Memory allocation failed (%s).\n", strerror(errno)); + YLMSG_E("Memory allocation failed (%s).", strerror(errno)); return NULL; } sprintf(yl_dir, "%s/%s", user_home, YL_DIR); @@ -53,17 +53,17 @@ get_yanglint_dir(void) if (ret == -1) { if (errno == ENOENT) { /* directory does not exist */ - YLMSG_W("Configuration directory \"%s\" does not exist, creating it.\n", yl_dir); + YLMSG_W("Configuration directory \"%s\" does not exist, creating it.", yl_dir); if (mkdir(yl_dir, 00700)) { if (errno != EEXIST) { /* parallel execution, yay */ - YLMSG_E("Configuration directory \"%s\" cannot be created (%s).\n", yl_dir, strerror(errno)); + YLMSG_E("Configuration directory \"%s\" cannot be created (%s).", yl_dir, strerror(errno)); free(yl_dir); return NULL; } } } else { - YLMSG_E("Configuration directory \"%s\" exists but cannot be accessed (%s).\n", yl_dir, strerror(errno)); + YLMSG_E("Configuration directory \"%s\" exists but cannot be accessed (%s).", yl_dir, strerror(errno)); free(yl_dir); return NULL; } @@ -83,16 +83,16 @@ load_config(void) history_file = malloc(strlen(yl_dir) + 9); if (!history_file) { - YLMSG_E("Memory allocation failed (%s).\n", strerror(errno)); + YLMSG_E("Memory allocation failed (%s).", strerror(errno)); free(yl_dir); return; } sprintf(history_file, "%s/history", yl_dir); if (access(history_file, F_OK) && (errno == ENOENT)) { - YLMSG_W("No saved history.\n"); + YLMSG_W("No saved history."); } else if (linenoiseHistoryLoad(history_file)) { - YLMSG_E("Failed to load history.\n"); + YLMSG_E("Failed to load history."); } free(history_file); @@ -110,14 +110,14 @@ store_config(void) history_file = malloc(strlen(yl_dir) + 9); if (!history_file) { - YLMSG_E("Memory allocation failed (%s).\n", strerror(errno)); + YLMSG_E("Memory allocation failed (%s).", strerror(errno)); free(yl_dir); return; } sprintf(history_file, "%s/history", yl_dir); if (linenoiseHistorySave(history_file)) { - YLMSG_E("Failed to save history.\n"); + YLMSG_E("Failed to save history."); } free(history_file); diff --git a/tools/lint/examples/README.md b/tools/lint/examples/README.md index 604591c..93d3c2a 100644 --- a/tools/lint/examples/README.md +++ b/tools/lint/examples/README.md @@ -33,11 +33,11 @@ combination with the command name you are interested in: ``` > help searchpath Usage: searchpath [--clear] [<modules-dir-path> ...] - Set paths of directories where to search for imports and - includes of the schema modules. The current working directory - and the path of the module being added is used implicitly. - The 'load' command uses these paths to search even for the - schema modules to be loaded. + Set paths of directories where to search for imports and includes + of the schema modules. Subdirectories are also searched. The current + working directory and the path of the module being added is used implicitly. + The 'load' command uses these paths to search even for the schema modules + to be loaded. ``` The input files referred in this document are available together with this @@ -469,3 +469,68 @@ ietf-ip features: } } ``` + +## YANG modules with the Schema Mount extension + +In these examples the non-interactive `yanglint` is used to simplify creating the context, a `yang-library` data file is +used. The working directory is `libyang/tools/lint/examples` and *libyang* must be installed. + +**Print tree output of a model with Schema Mount** + +Command and its output: + +``` +$ yanglint -f tree -p . -Y sm-context-main.xml -x sm-context-extension.xml sm-main.yang +module: sm-main + +--mp root* [node] + | +--rw node string + +--mp root2 + +--rw root3 + +--mp my-list* [name] + +--rw things/* [name] + | +--rw name -> /if:interfaces/if:interface/if:name + | +--rw attribute? uint32 + +--rw not-compiled/ + | +--rw first? string + | +--rw second? string + +--rw interfaces@ + | +--rw interface* [name] + | +--rw name string + | +--rw type identityref + +--rw name string +``` + +**Validating and printing mounted data** + +Command and its output: + +``` +$ yanglint -f json -t config -p . -Y sm-context-main.xml -x sm-context-extension.xml sm-data.xml +{ + "ietf-interfaces:interfaces": { + "interface": [ + { + "name": "eth0", + "type": "iana-if-type:ethernetCsmacd" + }, + { + "name": "eth1", + "type": "iana-if-type:ethernetCsmacd" + } + ] + }, + "sm-main:root3": { + "my-list": [ + { + "name": "list item 1", + "sm-extension:things": [ + { + "name": "eth0", + "attribute": 1 + } + ] + } + ] + } +} +``` diff --git a/tools/lint/main.c b/tools/lint/main.c index 9f0d027..43b90c8 100644 --- a/tools/lint/main.c +++ b/tools/lint/main.c @@ -1,9 +1,10 @@ /** * @file main.c * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief libyang's yanglint tool * - * Copyright (c) 2015-2020 CESNET, z.s.p.o. + * Copyright (c) 2015-2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -27,6 +28,7 @@ #include "completion.h" #include "configuration.h" #include "linenoise/linenoise.h" +#include "yl_opt.h" int done; struct ly_ctx *ctx = NULL; @@ -37,25 +39,32 @@ int main_ni(int argc, char *argv[]); int main(int argc, char *argv[]) { - char *cmdline; - int cmdlen; + int cmdlen, posc, i, j; + struct yl_opt yo = {0}; + char *empty = NULL, *cmdline; + char **posv; + uint8_t cmd_found; if (argc > 1) { /* run in non-interactive mode */ return main_ni(argc, argv); } + yo.interactive = 1; /* continue in interactive mode */ linenoiseSetCompletionCallback(complete_cmd); load_config(); if (ly_ctx_new(NULL, YL_DEFAULT_CTX_OPTIONS, &ctx)) { - YLMSG_E("Failed to create context.\n"); + YLMSG_E("Failed to create context."); return 1; } while (!done) { - uint8_t executed = 0; + cmd_found = 0; + + posv = ∅ + posc = 0; /* get the command from user */ cmdline = linenoise(PROMPT); @@ -76,25 +85,48 @@ main(int argc, char *argv[]) for (cmdlen = 0; cmdline[cmdlen] && (cmdline[cmdlen] != ' '); cmdlen++) {} /* execute the command if any valid specified */ - for (uint16_t i = 0; commands[i].name; i++) { + for (i = 0; commands[i].name; i++) { if (strncmp(cmdline, commands[i].name, (size_t)cmdlen) || (commands[i].name[cmdlen] != '\0')) { continue; } - commands[i].func(&ctx, cmdline); - executed = 1; + cmd_found = 1; + if (commands[i].opt_func && commands[i].opt_func(&yo, cmdline, &posv, &posc)) { + break; + } + if (commands[i].dep_func && commands[i].dep_func(&yo, posc)) { + break; + } + if (posc) { + for (j = 0; j < posc; j++) { + yo.last_one = (j + 1) == posc; + if (commands[i].exec_func(&ctx, &yo, posv[j])) { + break; + } + } + } else { + commands[i].exec_func(&ctx, &yo, NULL); + } + if (commands[i].fin_func) { + commands[i].fin_func(ctx, &yo); + } + break; } - if (!executed) { + if (!cmd_found) { /* if unknown command specified, tell it to user */ - YLMSG_E("Unknown command \"%.*s\", type 'help' for more information.\n", cmdlen, cmdline); + YLMSG_E("Unknown command \"%.*s\", type 'help' for more information.", cmdlen, cmdline); } linenoiseHistoryAdd(cmdline); free(cmdline); + yl_opt_erase(&yo); } + /* Global variables in commands are freed. */ + cmd_free(); + store_config(); ly_ctx_destroy(ctx); diff --git a/tools/lint/main_ni.c b/tools/lint/main_ni.c index 04c2340..c08acc3 100644 --- a/tools/lint/main_ni.c +++ b/tools/lint/main_ni.c @@ -2,9 +2,10 @@ * @file main_ni.c * @author Radek Krejci <rkrejci@cesnet.cz> * @author Michal Vasko <mvasko@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief libyang's yanglint tool - non-interactive code * - * Copyright (c) 2020 - 2022 CESNET, z.s.p.o. + * Copyright (c) 2020 - 2023 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. @@ -25,107 +26,13 @@ #include <sys/stat.h> #include "libyang.h" -#include "plugins_exts.h" +#include "cmd.h" #include "common.h" #include "out.h" #include "tools/config.h" - -/** - * @brief Context structure to hold and pass variables in a structured form. - */ -struct context { - /* libyang context for the run */ - const char *yang_lib_file; - uint16_t ctx_options; - struct ly_ctx *ctx; - - /* prepared output (--output option or stdout by default) */ - struct ly_out *out; - - char *searchpaths; - - /* options flags */ - uint8_t list; /* -l option to print list of schemas */ - - /* line length for 'tree' format */ - size_t line_length; /* --tree-line-length */ - - /* - * schema - */ - /* set schema modules' features via --features option (struct schema_features *) */ - struct ly_set schema_features; - - /* set of loaded schema modules (struct lys_module *) */ - struct ly_set schema_modules; - - /* options to parse and print schema modules */ - uint32_t schema_parse_options; - uint32_t schema_print_options; - - /* specification of printing schema node subtree, option --schema-node */ - const char *schema_node_path; - const struct lysc_node *schema_node; - const char *submodule; - - /* name of file containing explicit context passed to callback - * for schema-mount extension. This also causes a callback to - * be registered. - */ - char *schema_context_filename; - - /* value of --format in case of schema format */ - LYS_OUTFORMAT schema_out_format; - ly_bool feature_param_format; - - /* - * data - */ - /* various options based on --type option */ - enum lyd_type data_type; - uint32_t data_parse_options; - uint32_t data_validate_options; - uint32_t data_print_options; - - /* flag for --merge option */ - uint8_t data_merge; - - /* value of --format in case of data format */ - LYD_FORMAT data_out_format; - - /* input data files (struct cmdline_file *) */ - struct ly_set data_inputs; - - /* storage for --operational */ - struct cmdline_file data_operational; - - /* storage for --reply-rpc */ - struct cmdline_file reply_rpc; -}; - -static void -erase_context(struct context *c) -{ - /* data */ - ly_set_erase(&c->data_inputs, free_cmdline_file); - ly_in_free(c->data_operational.in, 1); - - /* schema */ - ly_set_erase(&c->schema_features, free_features); - ly_set_erase(&c->schema_modules, NULL); - - /* context */ - free(c->searchpaths); - c->searchpaths = NULL; - - ly_out_free(c->out, NULL, 0); - ly_ctx_destroy(c->ctx); - - if (c->schema_context_filename) { - free(c->schema_context_filename); - } -} +#include "yl_opt.h" +#include "yl_schema_features.h" static void version(void) @@ -146,7 +53,8 @@ help(int shortout) " printing them in the specified format.\n\n" " yanglint -t (nc-)rpc/notif [-O <operational-file>] <schema>... <file>\n" " Validates the YANG/NETCONF RPC/notification <file> according to the <schema>(s) using\n" - " <operational-file> with possible references to the operational datastore data.\n\n" + " <operational-file> with possible references to the operational datastore data.\n" + " To validate nested-notification or action, the <operational-file> is required.\n\n" " yanglint -t nc-reply -R <rpc-file> [-O <operational-file>] <schema>... <file>\n" " Validates the NETCONF rpc-reply <file> of RPC <rpc-file> according to the <schema>(s)\n" " using <operational-file> with possible references to the operational datastore data.\n\n" @@ -169,10 +77,16 @@ help(int shortout) " yang, yin, tree, info and feature-param for schemas,\n" " xml, json, and lyb for data.\n\n"); + printf(" -I FORMAT, --in-format=FORMAT\n" + " Load the data in one of the following formats:\n" + " xml, json, lyb\n" + " If input format not specified, it is detected from the file extension.\n\n"); + printf(" -p PATH, --path=PATH\n" " Search path for schema (YANG/YIN) modules. The option can be\n" " used multiple times. The current working directory and the\n" - " path of the module being added is used implicitly.\n\n"); + " path of the module being added is used implicitly. Subdirectories\n" + " are also searched\n\n"); printf(" -D, --disable-searchdir\n" " Do not implicitly search in current working directory for\n" @@ -251,6 +165,13 @@ help(int shortout) " trim - Remove all nodes with a default value.\n" " implicit-tagged - Add missing nodes and mark them with the attribute.\n\n"); + printf(" -E XPATH, --data-xpath=XPATH\n" + " Evaluate XPATH expression over the data and print the nodes satisfying\n" + " the expression. The output format is specific and the option cannot\n" + " be combined with the -f and -d options. Also all the data\n" + " inputs are merged into a single data tree where the expression\n" + " is evaluated, so the -m option is always set implicitly.\n\n"); + printf(" -l, --list Print info about the loaded schemas.\n" " (i - imported module, I - implemented module)\n" " In case the '-f' option with data encoding is specified,\n" @@ -267,8 +188,8 @@ help(int shortout) " Provide optional data to extend validation of the '(nc-)rpc',\n" " '(nc-)reply' or '(nc-)notif' TYPEs. The FILE is supposed to contain\n" " the operational datastore referenced from the operation.\n" - " In case of a nested operation, its parent existence is also\n" - " checked in these operational data.\n\n"); + " In case of a nested notification or action, its parent existence\n" + " is also checked in these operational data.\n\n"); printf(" -R FILE, --reply-rpc=FILE\n" " Provide source RPC for parsing of the 'nc-reply' TYPE. The FILE\n" @@ -287,6 +208,9 @@ help(int shortout) " create an exact YANG schema context. If specified, the '-F'\n" " parameter (enabled features) is ignored.\n\n"); + printf(" -X, --extended-leafref\n" + " Allow usage of deref() XPath function within leafref\n\n"); + #ifndef NDEBUG printf(" -G GROUPS, --debug=GROUPS\n" " Enable printing of specific debugging message group\n" @@ -321,11 +245,11 @@ libyang_verbclb(LY_LOG_LEVEL level, const char *msg, const char *path) } } -static struct schema_features * +static struct yl_schema_features * get_features_not_applied(const struct ly_set *fset) { for (uint32_t u = 0; u < fset->count; ++u) { - struct schema_features *sf = fset->objs[u]; + struct yl_schema_features *sf = fset->objs[u]; if (!sf->applied) { return sf; @@ -335,190 +259,166 @@ get_features_not_applied(const struct ly_set *fset) return NULL; } -static LY_ERR -ext_data_clb(const struct lysc_ext_instance *ext, void *user_data, void **ext_data, ly_bool *ext_data_free) -{ - struct ly_ctx *ctx; - struct lyd_node *data = NULL; - - ctx = ext->module->ctx; - if (user_data) { - lyd_parse_data_path(ctx, user_data, LYD_XML, LYD_PARSE_STRICT, LYD_VALIDATE_PRESENT, &data); - } - - *ext_data = data; - *ext_data_free = 1; - return LY_SUCCESS; -} - -static LY_ERR -searchpath_strcat(char **searchpaths, const char *path) -{ - uint64_t len; - char *new; - - if (!(*searchpaths)) { - *searchpaths = strdup(path); - return LY_SUCCESS; - } - - len = strlen(*searchpaths) + strlen(path) + strlen(PATH_SEPARATOR); - new = realloc(*searchpaths, sizeof(char) * len + 1); - if (!new) { - return LY_EMEM; - } - strcat(new, PATH_SEPARATOR); - strcat(new, path); - *searchpaths = new; - - return LY_SUCCESS; -} - +/** + * @brief Create the libyang context. + * + * @param[in] yang_lib_file Context can be defined in yang library file. + * @param[in] searchpaths Directories in which modules are searched. + * @param[in,out] schema_features Set of features. + * @param[in,out] ctx_options Options for libyang context. + * @param[out] ctx Context for libyang. + * @return 0 on success. + */ static int -fill_context_inputs(int argc, char *argv[], struct context *c) +create_ly_context(const char *yang_lib_file, const char *searchpaths, struct ly_set *schema_features, + uint16_t *ctx_options, struct ly_ctx **ctx) { - struct ly_in *in = NULL; - struct schema_features *sf; - struct lys_module *mod; - const char *all_features[] = {"*", NULL}; - char *dir = NULL, *module = NULL; - - /* Create libyang context. */ - if (c->yang_lib_file) { + if (yang_lib_file) { /* ignore features */ - ly_set_erase(&c->schema_features, free_features); + ly_set_erase(schema_features, yl_schema_features_free); - if (ly_ctx_new_ylpath(c->searchpaths, c->yang_lib_file, LYD_UNKNOWN, c->ctx_options, &c->ctx)) { - YLMSG_E("Unable to modify libyang context with yang-library data.\n"); + if (ly_ctx_new_ylpath(searchpaths, yang_lib_file, LYD_UNKNOWN, *ctx_options, ctx)) { + YLMSG_E("Unable to modify libyang context with yang-library data."); return -1; } } else { /* set imp feature flag if all should be enabled */ - c->ctx_options |= !c->schema_features.count ? LY_CTX_ENABLE_IMP_FEATURES : 0; + (*ctx_options) |= !schema_features->count ? LY_CTX_ENABLE_IMP_FEATURES : 0; - if (ly_ctx_new(c->searchpaths, c->ctx_options, &c->ctx)) { - YLMSG_E("Unable to create libyang context\n"); + if (ly_ctx_new(searchpaths, *ctx_options, ctx)) { + YLMSG_E("Unable to create libyang context."); return -1; } } - /* set callback providing run-time extension instance data */ - if (c->schema_context_filename) { - ly_ctx_set_ext_data_clb(c->ctx, ext_data_clb, c->schema_context_filename); - } + return 0; +} - /* process the operational and/or reply RPC content if any */ - if (c->data_operational.path) { - if (get_input(c->data_operational.path, NULL, &c->data_operational.format, &c->data_operational.in)) { - return -1; +/** + * @brief Implement module if some feature has not been applied. + * + * @param[in] schema_features Set of features. + * @param[in,out] ctx Context for libyang. + * @return 0 on success. + */ +static int +apply_features(struct ly_set *schema_features, struct ly_ctx *ctx) +{ + struct yl_schema_features *sf; + struct lys_module *mod; + + /* check that all specified features were applied, apply now if possible */ + while ((sf = get_features_not_applied(schema_features))) { + /* try to find implemented or the latest revision of this module */ + mod = ly_ctx_get_module_implemented(ctx, sf->mod_name); + if (!mod) { + mod = ly_ctx_get_module_latest(ctx, sf->mod_name); } - } - if (c->reply_rpc.path) { - if (get_input(c->reply_rpc.path, NULL, &c->reply_rpc.format, &c->reply_rpc.in)) { - return -1; + if (!mod) { + YLMSG_E("Specified features not applied, module \"%s\" not loaded.", sf->mod_name); + return 1; } - } - for (int i = 0; i < argc - optind; i++) { - LYS_INFORMAT format_schema = LYS_IN_UNKNOWN; - LYD_FORMAT format_data = LYD_UNKNOWN; - - if (get_input(argv[optind + i], &format_schema, &format_data, &in)) { - goto error; + /* we have the module, implement it if needed and enable the specific features */ + if (lys_set_implemented(mod, (const char **)sf->features)) { + YLMSG_E("Implementing module \"%s\" failed.", mod->name); + return 1; } + sf->applied = 1; + } - if (format_schema) { - LY_ERR ret; - uint8_t path_unset = 1; /* flag to unset the path from the searchpaths list (if not already present) */ - const char **features; + return 0; +} - /* parse the input */ - if (parse_schema_path(argv[optind + i], &dir, &module)) { - goto error; - } +/** + * @brief Parse and compile modules, data are only stored for later processing. + * + * @param[in] argc Number of strings in @p argv. + * @param[in] argv Strings from command line. + * @param[in] optind Index to the first input file in @p argv. + * @param[in] data_in_format Specified input data format. + * @param[in,out] ctx Context for libyang. + * @param[in,out] yo Options for yanglint. + * @return 0 on success. + */ +static int +fill_context_inputs(int argc, char *argv[], int optind, LYD_FORMAT data_in_format, struct ly_ctx *ctx, + struct yl_opt *yo) +{ + char *filepath = NULL; + LYS_INFORMAT format_schema; + LYD_FORMAT format_data; - if (c->yang_lib_file) { - /* just get the module, it should already be parsed */ - mod = ly_ctx_get_module_implemented(c->ctx, module); - if (!mod) { - YLMSG_E("Schema module \"%s\" not implemented in yang-library data.\n", module); - goto error; - } - } else { - /* add temporarily also the path of the module itself */ - if (ly_ctx_set_searchdir(c->ctx, dir) == LY_EEXIST) { - path_unset = 0; - } + for (int i = 0; i < argc - optind; i++) { + format_schema = LYS_IN_UNKNOWN; + format_data = data_in_format; - /* get features list for this module */ - if (!c->schema_features.count) { - features = all_features; - } else { - get_features(&c->schema_features, module, &features); - } + filepath = argv[optind + i]; - /* parse module */ - ret = lys_parse(c->ctx, in, format_schema, features, &mod); - ly_ctx_unset_searchdir_last(c->ctx, path_unset); - if (ret) { - YLMSG_E("Parsing schema module \"%s\" failed.\n", argv[optind + i]); - goto error; - } - } + if (!filepath) { + return -1; + } + if (get_format(filepath, &format_schema, &format_data)) { + return -1; + } - /* temporary cleanup */ - free(dir); - dir = NULL; - free(module); - module = NULL; - - if (c->schema_out_format || c->feature_param_format) { - /* module will be printed */ - if (ly_set_add(&c->schema_modules, (void *)mod, 1, NULL)) { - YLMSG_E("Storing parsed schema module (%s) for print failed.\n", argv[optind + i]); - goto error; - } + if (format_schema) { + if (cmd_add_exec(&ctx, yo, filepath)) { + return -1; } - } else if (format_data) { - if (!fill_cmdline_file(&c->data_inputs, in, argv[optind + i], format_data)) { - goto error; + } else { + if (cmd_data_store(&ctx, yo, filepath)) { + return -1; } - in = NULL; } + } - ly_in_free(in, 1); - in = NULL; + /* Check that all specified features were applied, apply now if possible. */ + if (apply_features(&yo->schema_features, ctx)) { + return -1; } - /* check that all specified features were applied, apply now if possible */ - while ((sf = get_features_not_applied(&c->schema_features))) { - /* try to find implemented or the latest revision of this module */ - mod = ly_ctx_get_module_implemented(c->ctx, sf->mod_name); - if (!mod) { - mod = ly_ctx_get_module_latest(c->ctx, sf->mod_name); - } - if (!mod) { - YLMSG_E("Specified features not applied, module \"%s\" not loaded.\n", sf->mod_name); - goto error; - } + return 0; +} - /* we have the module, implement it if needed and enable the specific features */ - if (lys_set_implemented(mod, (const char **)sf->features)) { - YLMSG_E("Implementing module \"%s\" failed.\n", mod->name); - goto error; +#ifndef NDEBUG +/** + * @brief Enable specific debugging messages. + * + * @param[in] groups String in the form "<group>[,group>]*". + * @param[in,out] yo Options for yanglint. + * return 0 on success. + */ +static int +set_debug_groups(char *groups, struct yl_opt *yo) +{ + int rc; + char *str, *end; + + /* Process all debug arguments except the last one. */ + for (str = groups; (end = strchr(str, ',')); str = end + 1) { + /* Temporary modify input string. */ + *end = '\0'; + rc = cmd_debug_store(NULL, yo, str); + *end = ','; + if (rc) { + return -1; } - sf->applied = 1; + } + /* Process single/last debug argument. */ + if (cmd_debug_store(NULL, yo, str)) { + return -1; + } + /* All debug arguments are valid, so they can apply. */ + if (cmd_debug_setlog(NULL, yo)) { + return -1; } return 0; - -error: - ly_in_free(in, 1); - free(dir); - free(module); - return -1; } +#endif + /** * @brief Process command line options and store the settings into the context. * @@ -527,10 +427,8 @@ error: * return 1 in case of success, but expect to exit. */ static int -fill_context(int argc, char *argv[], struct context *c) +fill_context(int argc, char *argv[], struct yl_opt *yo, struct ly_ctx **ctx) { - int ret; - int opt, opt_index; struct option options[] = { {"help", no_argument, NULL, 'h'}, @@ -542,6 +440,7 @@ fill_context(int argc, char *argv[], struct context *c) {"disable-searchdir", no_argument, NULL, 'D'}, {"features", required_argument, NULL, 'F'}, {"make-implemented", no_argument, NULL, 'i'}, + {"in-format", required_argument, NULL, 'I'}, {"schema-node", required_argument, NULL, 'P'}, {"single-node", no_argument, NULL, 'q'}, {"submodule", required_argument, NULL, 's'}, @@ -550,6 +449,7 @@ fill_context(int argc, char *argv[], struct context *c) {"present", no_argument, NULL, 'e'}, {"type", required_argument, NULL, 't'}, {"default", required_argument, NULL, 'd'}, + {"data-xpath", required_argument, NULL, 'E'}, {"list", no_argument, NULL, 'l'}, {"tree-line-length", required_argument, NULL, 'L'}, {"output", required_argument, NULL, 'o'}, @@ -558,6 +458,7 @@ fill_context(int argc, char *argv[], struct context *c) {"merge", no_argument, NULL, 'm'}, {"yang-library", no_argument, NULL, 'y'}, {"yang-library-file", required_argument, NULL, 'Y'}, + {"extended-leafref", no_argument, NULL, 'X'}, #ifndef NDEBUG {"debug", required_argument, NULL, 'G'}, #endif @@ -565,15 +466,16 @@ fill_context(int argc, char *argv[], struct context *c) }; uint8_t data_type_set = 0; - c->ctx_options = YL_DEFAULT_CTX_OPTIONS; - c->data_parse_options = YL_DEFAULT_DATA_PARSE_OPTIONS; - c->line_length = 0; + yo->ctx_options = YL_DEFAULT_CTX_OPTIONS; + yo->data_parse_options = YL_DEFAULT_DATA_PARSE_OPTIONS; + yo->data_validate_options = YL_DEFAULT_DATA_VALIDATE_OPTIONS; + yo->line_length = 0; opterr = 0; #ifndef NDEBUG - while ((opt = getopt_long(argc, argv, "hvVQf:p:DF:iP:qs:net:d:lL:o:O:R:myY:x:G:", options, &opt_index)) != -1) + while ((opt = getopt_long(argc, argv, "hvVQf:I:p:DF:iP:qs:neE:t:d:lL:o:O:R:myY:Xx:G:", options, &opt_index)) != -1) #else - while ((opt = getopt_long(argc, argv, "hvVQf:p:DF:iP:qs:net:d:lL:o:O:R:myY:x:", options, &opt_index)) != -1) + while ((opt = getopt_long(argc, argv, "hvVQf:I:p:DF:iP:qs:neE:t:d:lL:o:O:R:myY:Xx:", options, &opt_index)) != -1) #endif { switch (opt) { @@ -609,138 +511,77 @@ fill_context(int argc, char *argv[], struct context *c) } /* case 'Q' */ case 'f': /* --format */ - if (!strcasecmp(optarg, "yang")) { - c->schema_out_format = LYS_OUT_YANG; - c->data_out_format = 0; - } else if (!strcasecmp(optarg, "yin")) { - c->schema_out_format = LYS_OUT_YIN; - c->data_out_format = 0; - } else if (!strcasecmp(optarg, "info")) { - c->schema_out_format = LYS_OUT_YANG_COMPILED; - c->data_out_format = 0; - } else if (!strcasecmp(optarg, "tree")) { - c->schema_out_format = LYS_OUT_TREE; - c->data_out_format = 0; - } else if (!strcasecmp(optarg, "xml")) { - c->schema_out_format = 0; - c->data_out_format = LYD_XML; - } else if (!strcasecmp(optarg, "json")) { - c->schema_out_format = 0; - c->data_out_format = LYD_JSON; - } else if (!strcasecmp(optarg, "lyb")) { - c->schema_out_format = 0; - c->data_out_format = LYD_LYB; - } else if (!strcasecmp(optarg, "feature-param")) { - c->feature_param_format = 1; - } else { - YLMSG_E("Unknown output format %s\n", optarg); + if (yl_opt_update_out_format(optarg, yo)) { help(1); return -1; } break; - case 'p': { /* --path */ - struct stat st; - - if (stat(optarg, &st) == -1) { - YLMSG_E("Unable to use search path (%s) - %s.\n", optarg, strerror(errno)); - return -1; - } - if (!S_ISDIR(st.st_mode)) { - YLMSG_E("Provided search path is not a directory.\n"); + case 'I': /* --in-format */ + if (yo_opt_update_data_in_format(optarg, yo)) { + YLMSG_E("Unknown input format %s.", optarg); + help(1); return -1; } + break; - if (searchpath_strcat(&c->searchpaths, optarg)) { - YLMSG_E("Storing searchpath failed.\n"); + case 'p': /* --path */ + if (searchpath_strcat(&yo->searchpaths, optarg)) { + YLMSG_E("Storing searchpath failed."); return -1; } - break; - } /* case 'p' */ + /* case 'p' */ - case 'D': /* --disable-search */ - if (c->ctx_options & LY_CTX_DISABLE_SEARCHDIRS) { - YLMSG_W("The -D option specified too many times.\n"); - } - if (c->ctx_options & LY_CTX_DISABLE_SEARCHDIR_CWD) { - c->ctx_options &= ~LY_CTX_DISABLE_SEARCHDIR_CWD; - c->ctx_options |= LY_CTX_DISABLE_SEARCHDIRS; - } else { - c->ctx_options |= LY_CTX_DISABLE_SEARCHDIR_CWD; + case 'D': /* --disable-searchdir */ + if (yo->ctx_options & LY_CTX_DISABLE_SEARCHDIRS) { + YLMSG_W("The -D option specified too many times."); } + yo_opt_update_disable_searchdir(yo); break; case 'F': /* --features */ - if (parse_features(optarg, &c->schema_features)) { + if (parse_features(optarg, &yo->schema_features)) { return -1; } break; case 'i': /* --make-implemented */ - if (c->ctx_options & LY_CTX_REF_IMPLEMENTED) { - c->ctx_options &= ~LY_CTX_REF_IMPLEMENTED; - c->ctx_options |= LY_CTX_ALL_IMPLEMENTED; - } else { - c->ctx_options |= LY_CTX_REF_IMPLEMENTED; - } + yo_opt_update_make_implemented(yo); break; case 'P': /* --schema-node */ - c->schema_node_path = optarg; + yo->schema_node_path = optarg; break; case 'q': /* --single-node */ - c->schema_print_options |= LYS_PRINT_NO_SUBSTMT; + yo->schema_print_options |= LYS_PRINT_NO_SUBSTMT; break; case 's': /* --submodule */ - c->submodule = optarg; + yo->submodule = optarg; break; case 'x': /* --ext-data */ - c->schema_context_filename = strdup(optarg); + yo->schema_context_filename = optarg; break; case 'n': /* --not-strict */ - c->data_parse_options &= ~LYD_PARSE_STRICT; + yo->data_parse_options &= ~LYD_PARSE_STRICT; break; case 'e': /* --present */ - c->data_validate_options |= LYD_VALIDATE_PRESENT; + yo->data_validate_options |= LYD_VALIDATE_PRESENT; break; case 't': /* --type */ if (data_type_set) { - YLMSG_E("The data type (-t) cannot be set multiple times.\n"); + YLMSG_E("The data type (-t) cannot be set multiple times."); return -1; } - if (!strcasecmp(optarg, "config")) { - c->data_parse_options |= LYD_PARSE_NO_STATE; - c->data_validate_options |= LYD_VALIDATE_NO_STATE; - } else if (!strcasecmp(optarg, "get")) { - c->data_parse_options |= LYD_PARSE_ONLY; - } else if (!strcasecmp(optarg, "getconfig") || !strcasecmp(optarg, "get-config")) { - c->data_parse_options |= LYD_PARSE_ONLY | LYD_PARSE_NO_STATE; - } else if (!strcasecmp(optarg, "edit")) { - c->data_parse_options |= LYD_PARSE_ONLY; - } else if (!strcasecmp(optarg, "rpc")) { - c->data_type = LYD_TYPE_RPC_YANG; - } else if (!strcasecmp(optarg, "nc-rpc")) { - c->data_type = LYD_TYPE_RPC_NETCONF; - } else if (!strcasecmp(optarg, "reply")) { - c->data_type = LYD_TYPE_REPLY_YANG; - } else if (!strcasecmp(optarg, "nc-reply")) { - c->data_type = LYD_TYPE_REPLY_NETCONF; - } else if (!strcasecmp(optarg, "notif")) { - c->data_type = LYD_TYPE_NOTIF_YANG; - } else if (!strcasecmp(optarg, "nc-notif")) { - c->data_type = LYD_TYPE_NOTIF_NETCONF; - } else if (!strcasecmp(optarg, "data")) { - /* default option */ - } else { - YLMSG_E("Unknown data tree type %s\n", optarg); + if (yl_opt_update_data_type(optarg, yo)) { + YLMSG_E("Unknown data tree type %s.", optarg); help(1); return -1; } @@ -749,184 +590,128 @@ fill_context(int argc, char *argv[], struct context *c) break; case 'd': /* --default */ - if (!strcasecmp(optarg, "all")) { - c->data_print_options = (c->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL; - } else if (!strcasecmp(optarg, "all-tagged")) { - c->data_print_options = (c->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL_TAG; - } else if (!strcasecmp(optarg, "trim")) { - c->data_print_options = (c->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_TRIM; - } else if (!strcasecmp(optarg, "implicit-tagged")) { - c->data_print_options = (c->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_IMPL_TAG; - } else { - YLMSG_E("Unknown default mode %s\n", optarg); + if (yo_opt_update_data_default(optarg, yo)) { + YLMSG_E("Unknown default mode %s.", optarg); help(1); return -1; } break; + case 'E': /* --data-xpath */ + if (ly_set_add(&yo->data_xpath, optarg, 0, NULL)) { + YLMSG_E("Storing XPath \"%s\" failed.", optarg); + return -1; + } + break; + case 'l': /* --list */ - c->list = 1; + yo->list = 1; break; case 'L': /* --tree-line-length */ - c->line_length = atoi(optarg); + yo->line_length = atoi(optarg); break; case 'o': /* --output */ - if (c->out) { - YLMSG_E("Only a single output can be specified.\n"); + if (yo->out) { + YLMSG_E("Only a single output can be specified."); return -1; } else { - if (ly_out_new_filepath(optarg, &c->out)) { - YLMSG_E("Unable open output file %s (%s)\n", optarg, strerror(errno)); + if (ly_out_new_filepath(optarg, &yo->out)) { + YLMSG_E("Unable open output file %s (%s).", optarg, strerror(errno)); return -1; } } break; case 'O': /* --operational */ - if (c->data_operational.path) { - YLMSG_E("The operational datastore (-O) cannot be set multiple times.\n"); + if (yo->data_operational.path) { + YLMSG_E("The operational datastore (-O) cannot be set multiple times."); return -1; } - c->data_operational.path = optarg; + yo->data_operational.path = optarg; break; case 'R': /* --reply-rpc */ - if (c->reply_rpc.path) { - YLMSG_E("The PRC of the reply (-R) cannot be set multiple times.\n"); + if (yo->reply_rpc.path) { + YLMSG_E("The PRC of the reply (-R) cannot be set multiple times."); return -1; } - c->reply_rpc.path = optarg; + yo->reply_rpc.path = optarg; break; case 'm': /* --merge */ - c->data_merge = 1; + yo->data_merge = 1; break; case 'y': /* --yang-library */ - c->ctx_options &= ~LY_CTX_NO_YANGLIBRARY; + yo->ctx_options &= ~LY_CTX_NO_YANGLIBRARY; break; case 'Y': /* --yang-library-file */ - c->ctx_options &= ~LY_CTX_NO_YANGLIBRARY; - c->yang_lib_file = optarg; + yo->ctx_options &= ~LY_CTX_NO_YANGLIBRARY; + yo->yang_lib_file = optarg; break; -#ifndef NDEBUG - case 'G': { /* --debug */ - uint32_t dbg_groups = 0; - const char *ptr = optarg; - - while (ptr[0]) { - if (!strncasecmp(ptr, "dict", sizeof "dict" - 1)) { - dbg_groups |= LY_LDGDICT; - ptr += sizeof "dict" - 1; - } else if (!strncasecmp(ptr, "xpath", sizeof "xpath" - 1)) { - dbg_groups |= LY_LDGXPATH; - ptr += sizeof "xpath" - 1; - } else if (!strncasecmp(ptr, "dep-sets", sizeof "dep-sets" - 1)) { - dbg_groups |= LY_LDGDEPSETS; - ptr += sizeof "dep-sets" - 1; - } + case 'X': /* --extended-leafref */ + yo->ctx_options |= LY_CTX_LEAFREF_EXTENDED; + break; - if (ptr[0]) { - if (ptr[0] != ',') { - YLMSG_E("Unknown debug group string \"%s\"\n", optarg); - return -1; - } - ++ptr; - } +#ifndef NDEBUG + case 'G': /* --debug */ + if (set_debug_groups(optarg, yo)) { + return -1; } - ly_log_dbg_groups(dbg_groups); break; - } /* case 'G' */ + /* case 'G' */ #endif default: - YLMSG_E("Invalid option or missing argument: -%c\n", optopt); + YLMSG_E("Invalid option or missing argument: -%c.", optopt); return -1; } /* switch */ } /* additional checks for the options combinations */ - if (!c->list && (optind >= argc)) { + if (!yo->list && (optind >= argc)) { help(1); - YLMSG_E("Missing <schema> to process.\n"); + YLMSG_E("Missing <schema> to process."); return 1; } - if (c->data_merge) { - if (c->data_type || (c->data_parse_options & LYD_PARSE_ONLY)) { - /* switch off the option, incompatible input data type */ - c->data_merge = 0; - } else { - /* postpone validation after the merge of all the input data */ - c->data_parse_options |= LYD_PARSE_ONLY; - } - } - - if (c->data_operational.path && !c->data_type) { - YLMSG_E("Operational datastore takes effect only with RPCs/Actions/Replies/Notification input data types.\n"); - c->data_operational.path = NULL; - } - - if (c->reply_rpc.path && (c->data_type != LYD_TYPE_REPLY_NETCONF)) { - YLMSG_E("Source RPC is needed only for NETCONF Reply input data type.\n"); - c->data_operational.path = NULL; - } else if (!c->reply_rpc.path && (c->data_type == LYD_TYPE_REPLY_NETCONF)) { - YLMSG_E("Missing source RPC (-R) for NETCONF Reply input data type.\n"); + if (cmd_data_dep(yo, 0)) { return -1; } - - if ((c->schema_out_format != LYS_OUT_TREE) && c->line_length) { - YLMSG_E("--tree-line-length take effect only in case of the tree output format.\n"); + if (cmd_print_dep(yo, 0)) { + return -1; } - /* default output stream */ - if (!c->out) { - if (ly_out_new_file(stdout, &c->out)) { - YLMSG_E("Unable to set stdout as output.\n"); - return -1; - } + /* Create the libyang context. */ + if (create_ly_context(yo->yang_lib_file, yo->searchpaths, &yo->schema_features, &yo->ctx_options, ctx)) { + return -1; } - if (c->schema_out_format == LYS_OUT_TREE) { - /* print tree from lysc_nodes */ - c->ctx_options |= LY_CTX_SET_PRIV_PARSED; + /* Set callback providing run-time extension instance data. */ + if (yo->schema_context_filename) { + ly_ctx_set_ext_data_clb(*ctx, ext_data_clb, yo->schema_context_filename); } - /* process input files provided as standalone command line arguments, - * schema modules are parsed and inserted into the context, - * data files are just checked and prepared into internal structures for further processing */ - ret = fill_context_inputs(argc, argv, c); - if (ret) { - return ret; + /* Schema modules and data files are just checked and prepared into internal structures for further processing. */ + if (fill_context_inputs(argc, argv, optind, yo->data_in_format, *ctx, yo)) { + return -1; } /* the second batch of checks */ - if (c->schema_print_options && !c->schema_out_format) { - YLMSG_W("Schema printer options specified, but the schema output format is missing.\n"); + if (yo->schema_print_options && !yo->schema_out_format) { + YLMSG_W("Schema printer options specified, but the schema output format is missing."); } - if (c->schema_parse_options && !c->schema_modules.count) { - YLMSG_W("Schema parser options specified, but no schema input file provided.\n"); + if (yo->schema_parse_options && !yo->schema_modules.count) { + YLMSG_W("Schema parser options specified, but no schema input file provided."); } - if (c->data_print_options && !c->data_out_format) { - YLMSG_W("data printer options specified, but the data output format is missing.\n"); + if (yo->data_print_options && !yo->data_out_format) { + YLMSG_W("data printer options specified, but the data output format is missing."); } - if (((c->data_parse_options != YL_DEFAULT_DATA_PARSE_OPTIONS) || c->data_type) && !c->data_inputs.count) { - YLMSG_W("Data parser options specified, but no data input file provided.\n"); - } - - if (c->schema_node_path) { - c->schema_node = lys_find_path(c->ctx, NULL, c->schema_node_path, 0); - if (!c->schema_node) { - c->schema_node = lys_find_path(c->ctx, NULL, c->schema_node_path, 1); - - if (!c->schema_node) { - YLMSG_E("Invalid schema path.\n"); - return -1; - } - } + if (((yo->data_parse_options != YL_DEFAULT_DATA_PARSE_OPTIONS) || yo->data_type) && !yo->data_inputs.count) { + YLMSG_W("Data parser options specified, but no data input file provided."); } return 0; @@ -936,7 +721,8 @@ int main_ni(int argc, char *argv[]) { int ret = EXIT_SUCCESS, r; - struct context c = {0}; + struct yl_opt yo = {0}; + struct ly_ctx *ctx = NULL; char *features_output = NULL; struct ly_set set = {0}; uint32_t u; @@ -944,7 +730,7 @@ main_ni(int argc, char *argv[]) /* set callback for printing libyang messages */ ly_set_log_clb(libyang_verbclb, 1); - r = fill_context(argc, argv, &c); + r = fill_context(argc, argv, &yo, &ctx); if (r < 0) { ret = EXIT_FAILURE; } @@ -954,72 +740,46 @@ main_ni(int argc, char *argv[]) /* do the required job - parse, validate, print */ - if (c.list) { + if (yo.list) { /* print the list of schemas */ - print_list(c.out, c.ctx, c.data_out_format); - } else { - if (c.feature_param_format) { - for (u = 0; u < c.schema_modules.count; u++) { - if (collect_features(c.schema_modules.objs[u], &set)) { - YLMSG_E("Unable to read features from a module.\n"); - goto cleanup; - } - if (generate_features_output(c.schema_modules.objs[u], &set, &features_output)) { - YLMSG_E("Unable to generate feature command output.\n"); - goto cleanup; - } - ly_set_erase(&set, NULL); - } - ly_print(c.out, "%s\n", features_output); - } else if (c.schema_out_format) { - if (c.schema_node) { - ret = lys_print_node(c.out, c.schema_node, c.schema_out_format, 0, c.schema_print_options); - if (ret) { - YLMSG_E("Unable to print schema node %s.\n", c.schema_node_path); - goto cleanup; - } - } else if (c.submodule) { - const struct lysp_submodule *submod = ly_ctx_get_submodule_latest(c.ctx, c.submodule); - - if (!submod) { - YLMSG_E("Unable to find submodule %s.\n", c.submodule); - goto cleanup; - } - - ret = lys_print_submodule(c.out, submod, c.schema_out_format, c.line_length, c.schema_print_options); - if (ret) { - YLMSG_E("Unable to print submodule %s.\n", submod->name); - goto cleanup; - } - } else { - for (u = 0; u < c.schema_modules.count; ++u) { - ret = lys_print_module(c.out, (struct lys_module *)c.schema_modules.objs[u], c.schema_out_format, - c.line_length, c.schema_print_options); - /* for YANG Tree Diagrams printing it's more readable to print a blank line between modules. */ - if ((c.schema_out_format == LYS_OUT_TREE) && (u + 1 < c.schema_modules.count)) { - ly_print(c.out, "\n"); - } - if (ret) { - YLMSG_E("Unable to print module %s.\n", ((struct lys_module *)c.schema_modules.objs[u])->name); - goto cleanup; - } - } + ret = cmd_list_exec(&ctx, &yo, NULL); + goto cleanup; + } + if (yo.feature_param_format) { + for (u = 0; u < yo.schema_modules.count; u++) { + if ((ret = cmd_feature_exec(&ctx, &yo, ((struct lys_module *)yo.schema_modules.objs[u])->name))) { + goto cleanup; } } - - /* do the data validation despite the schema was printed */ - if (c.data_inputs.size) { - ret = process_data(c.ctx, c.data_type, c.data_merge, c.data_out_format, c.out, c.data_parse_options, - c.data_validate_options, c.data_print_options, &c.data_operational, &c.reply_rpc, &c.data_inputs, NULL); - if (ret) { + cmd_feature_fin(ctx, &yo); + } else if (yo.schema_out_format && yo.schema_node_path) { + if ((ret = cmd_print_exec(&ctx, &yo, NULL))) { + goto cleanup; + } + } else if (yo.schema_out_format && yo.submodule) { + if ((ret = cmd_print_exec(&ctx, &yo, yo.submodule))) { + goto cleanup; + } + } else if (yo.schema_out_format) { + for (u = 0; u < yo.schema_modules.count; ++u) { + yo.last_one = (u + 1) == yo.schema_modules.count; + if ((ret = cmd_print_exec(&ctx, &yo, ((struct lys_module *)yo.schema_modules.objs[u])->name))) { goto cleanup; } } } + /* do the data validation despite the schema was printed */ + if (yo.data_inputs.size) { + if ((ret = cmd_data_process(ctx, &yo))) { + goto cleanup; + } + } + cleanup: /* cleanup */ - erase_context(&c); + yl_opt_erase(&yo); + ly_ctx_destroy(ctx); free(features_output); ly_set_erase(&set, NULL); diff --git a/tools/lint/tests/expect/common.exp b/tools/lint/tests/expect/common.exp deleted file mode 100644 index 0381e6c..0000000 --- a/tools/lint/tests/expect/common.exp +++ /dev/null @@ -1,68 +0,0 @@ -# detect the path to the yanglint binary -if { [info exists ::env(YANGLINT)] } { - set yanglint "$env(YANGLINT)" -} else { - set yanglint "../../../../build/yanglint" -} - -# set the timeout to 1 second -set timeout 1 - -# expect a single line of anchored regex output -proc expect_output {output} { - expect { - -re "^${output}$" {} - timeout {exit 1} - } -} - -# send a command and either expect some anchored regex output if specified or just an empty line -proc expect_command {command has_output output} { - send -- "${command}\r" - - if ($has_output==1) { - expect { - -re "^${command}\r\n${output}$" {} - timeout {exit 1} - } - } else { - # input echoes - expect { - -re "^${command}\r\n$" {} - timeout {exit 1} - } - expect { - -re "^> $" {} - timeout {exit 1} - } - } -} - -# send a completion request and check if the anchored regex output matches -proc expect_completion {input output} { - send -- "${input}\t" - - expect { - # expecting echoing input, output and 10 terminal control characters - -re "^${input}\r> ${output}.*\r.*$" {} - timeout {exit 1} - } -} - -# send a completion request and check if the anchored regex hint options match -proc expect_hint {input prev_input hints} { - set output {} - foreach i $hints { - # each element might have some number of spaces and CRLF around it - append output "${i} *(?:\\r\\n)?" - } - - send -- "${input}\t" - - expect { - # expecting the hints, previous input from which the hints were generated - # and some number of terminal control characters - -re "^\r\n${output}\r> ${prev_input}.*\r.*$" {} - timeout {exit 1} - } -} diff --git a/tools/lint/tests/expect/completion.exp b/tools/lint/tests/expect/completion.exp deleted file mode 100755 index ed4f6bd..0000000 --- a/tools/lint/tests/expect/completion.exp +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/expect -f - -if { [info exists ::env(CURRENT_SOURCE_DIR)] } { - source "$env(CURRENT_SOURCE_DIR)/tests/expect/common.exp" - set yang_dir "$env(CURRENT_SOURCE_DIR)/examples" -} else { - source "common.exp" - set yang_dir "../../examples" -} - -spawn $yanglint - -# skip no dir and/or no history warnings -expect_output "(YANGLINT.*)*> " - -expect_command "clear -ii" 0 "" - -expect_command "add ${yang_dir}/ietf-ip.yang" 0 "" - -expect_completion "print -f info -P " "print -f info -P /ietf-" - -set hints {"/ietf-yang-schema-mount:schema-mounts" "/ietf-interfaces:interfaces" "/ietf-interfaces:interfaces-state"} - -expect_hint "" "print -f info -P /ietf-" $hints - -expect_completion "i" "print -f info -P /ietf-interfaces:interfaces" - -expect_completion "/" "print -f info -P /ietf-interfaces:interfaces/interface" - -set hints {"/ietf-interfaces:interfaces/interface" -"/ietf-interfaces:interfaces/interface/name" "/ietf-interfaces:interfaces/interface/description" -"/ietf-interfaces:interfaces/interface/type" "/ietf-interfaces:interfaces/interface/enabled" -"/ietf-interfaces:interfaces/interface/link-up-down-trap-enable" -"/ietf-interfaces:interfaces/interface/ietf-ip:ipv4" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv6"} - -expect_hint "" "print -f info -P /ietf-interfaces:interfaces/interface" $hints - -expect_completion "/i" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv" - -expect_completion "4" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv4" - -set hints { "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/enabled" -"/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/forwarding" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/mtu" -"/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/address" "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/neighbor" -} - -expect_hint "\t" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv" $hints - -expect_completion "/e" "print -f info -P /ietf-interfaces:interfaces/interface/ietf-ip:ipv4/enabled " - -send -- "\r" - -expect { - -re ".*\r\n> " {} - timeout {exit 1} -} - -send -- "exit\r" - -expect eof diff --git a/tools/lint/tests/expect/feature.exp b/tools/lint/tests/expect/feature.exp deleted file mode 100755 index 37680b0..0000000 --- a/tools/lint/tests/expect/feature.exp +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/expect -f - -if { [info exists ::env(CURRENT_SOURCE_DIR)] } { - source "$env(CURRENT_SOURCE_DIR)/tests/expect/common.exp" - set yang_dir "$env(CURRENT_SOURCE_DIR)/examples" -} else { - source "common.exp" - set yang_dir "../../examples" -} - -spawn $yanglint - -# skip no dir and/or no history warnings -expect_output "(YANGLINT.*)*> " - -expect_command "feature -a" 1 "yang:\r\n\t\\(none\\)\r\n\r\nietf-yang-schema-mount:\r\n\t\\(none\\)\r\n\r\n> " - -send -- "exit\r" - -expect eof diff --git a/tools/lint/tests/expect/list.exp b/tools/lint/tests/expect/list.exp deleted file mode 100755 index ec3cdba..0000000 --- a/tools/lint/tests/expect/list.exp +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/expect -f - -if { [info exists ::env(CURRENT_SOURCE_DIR)] } { - source "$env(CURRENT_SOURCE_DIR)/tests/expect/common.exp" - set yang_dir "$env(CURRENT_SOURCE_DIR)/examples" -} else { - source "common.exp" - set yang_dir "../../examples" -} - -spawn $yanglint - -# skip no dir and/or no history warnings -expect_output "(YANGLINT.*)*> " - -expect_command "list" 1 "List of the loaded models:\r\n *i ietf-yang-metadata@2016-08-05\r\n *I yang@2022-06-16\r\n *i ietf-inet-types@2013-07-15\r\n *i ietf-yang-types@2013-07-15\r\n *I ietf-yang-schema-mount@2019-01-14\r\n *i ietf-yang-structure-ext@2020-06-17\r\n> " - -send -- "exit\r" - -expect eof diff --git a/tools/lint/tests/shunit2/feature.sh b/tools/lint/tests/shunit2/feature.sh deleted file mode 100755 index fb2ee88..0000000 --- a/tools/lint/tests/shunit2/feature.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -testFeature() { - models=( "iana-if-type@2014-05-08.yang" "ietf-netconf@2011-06-01.yang" "ietf-netconf-with-defaults@2011-06-01.yang" - "sm.yang" "ietf-interfaces@2014-05-08.yang" "ietf-netconf-acm@2018-02-14.yang" "ietf-origin@2018-02-14.yang" - "ietf-ip@2014-06-16.yang" "ietf-restconf@2017-01-26.yang" ) - features=( " -F iana-if-type:" - " -F ietf-netconf:writable-running,candidate,confirmed-commit,rollback-on-error,validate,startup,url,xpath" - " -F ietf-netconf-with-defaults:" " -F sm:" " -F ietf-interfaces:arbitrary-names,pre-provisioning,if-mib" - " -F ietf-netconf-acm:" " -F ietf-origin:" " -F ietf-ip:ipv4-non-contiguous-netmasks,ipv6-privacy-autoconf" - " -F ietf-restconf:" ) - - for i in ${!models[@]}; do - output=`${YANGLINT} -f feature-param ${YANG_MODULES_DIR}/${models[$i]}` - assertEquals "Unexpected features of module ${models[$i]}." "${features[$i]}" "${output}" - done -} - -. shunit2 diff --git a/tools/lint/tests/shunit2/list.sh b/tools/lint/tests/shunit2/list.sh deleted file mode 100755 index d64503a..0000000 --- a/tools/lint/tests/shunit2/list.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash - -LIST_BASE="List of the loaded models: - i ietf-yang-metadata@2016-08-05 - I yang@2022-06-16 - i ietf-inet-types@2013-07-15 - i ietf-yang-types@2013-07-15 - I ietf-yang-schema-mount@2019-01-14 - i ietf-yang-structure-ext@2020-06-17" - -testListEmptyContext() { - output=`${YANGLINT} -l` - assertEquals "Unexpected list of modules in empty context." "${LIST_BASE}" "${output}" -} - -testListAllImplemented() { - LIST_BASE_ALLIMPLEMENTED="List of the loaded models: - I ietf-yang-metadata@2016-08-05 - I yang@2022-06-16 - I ietf-inet-types@2013-07-15 - I ietf-yang-types@2013-07-15 - I ietf-yang-schema-mount@2019-01-14 - I ietf-yang-structure-ext@2020-06-17" - output=`${YANGLINT} -lii` - assertEquals "Unexpected list of modules in empty context with -ii." "${LIST_BASE_ALLIMPLEMENTED}" "${output}" -} - -. shunit2 diff --git a/tools/lint/yl_opt.c b/tools/lint/yl_opt.c new file mode 100644 index 0000000..7cd855f --- /dev/null +++ b/tools/lint/yl_opt.c @@ -0,0 +1,344 @@ +/** + * @file yl_opt.c + * @author Adam Piecek <piecek@cesnet.cz> + * @brief Settings options for the libyang context. + * + * Copyright (c) 2020 - 2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include <assert.h> +#include <errno.h> +#include <getopt.h> +#include <strings.h> + +#include "in.h" /* ly_in_free */ + +#include "common.h" +#include "yl_opt.h" +#include "yl_schema_features.h" + +struct cmdline_file * +fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format) +{ + struct cmdline_file *rec; + + rec = malloc(sizeof *rec); + if (!rec) { + YLMSG_E("Allocating memory for data file information failed."); + return NULL; + } + rec->in = in; + rec->path = path; + rec->format = format; + + if (set && ly_set_add(set, rec, 1, NULL)) { + free(rec); + YLMSG_E("Storing data file information failed."); + return NULL; + } + + return rec; +} + +void +free_cmdline_file_items(struct cmdline_file *rec) +{ + if (rec && rec->in) { + ly_in_free(rec->in, 1); + } +} + +void +free_cmdline_file(void *cmdline_file) +{ + struct cmdline_file *rec = (struct cmdline_file *)cmdline_file; + + if (rec) { + free_cmdline_file_items(rec); + free(rec); + } +} + +void +yl_opt_erase(struct yl_opt *yo) +{ + ly_bool interactive; + + interactive = yo->interactive; + + /* data */ + ly_set_erase(&yo->data_inputs, free_cmdline_file); + ly_in_free(yo->data_operational.in, 1); + ly_set_erase(&yo->data_xpath, NULL); + + /* schema */ + ly_set_erase(&yo->schema_features, yl_schema_features_free); + ly_set_erase(&yo->schema_modules, NULL); + + /* context */ + free(yo->searchpaths); + + /* --reply-rpc */ + ly_in_free(yo->reply_rpc.in, 1); + + ly_out_free(yo->out, NULL, yo->out_stdout ? 0 : 1); + + free_cmdline(yo->argv); + + *yo = (const struct yl_opt) { + 0 + }; + yo->interactive = interactive; +} + +int +yl_opt_update_schema_out_format(const char *arg, struct yl_opt *yo) +{ + if (!strcasecmp(arg, "yang")) { + yo->schema_out_format = LYS_OUT_YANG; + yo->data_out_format = 0; + } else if (!strcasecmp(arg, "yin")) { + yo->schema_out_format = LYS_OUT_YIN; + yo->data_out_format = 0; + } else if (!strcasecmp(arg, "info")) { + yo->schema_out_format = LYS_OUT_YANG_COMPILED; + yo->data_out_format = 0; + } else if (!strcasecmp(arg, "tree")) { + yo->schema_out_format = LYS_OUT_TREE; + yo->data_out_format = 0; + } else { + return 1; + } + + return 0; +} + +int +yl_opt_update_data_out_format(const char *arg, struct yl_opt *yo) +{ + if (!strcasecmp(arg, "xml")) { + yo->schema_out_format = 0; + yo->data_out_format = LYD_XML; + } else if (!strcasecmp(arg, "json")) { + yo->schema_out_format = 0; + yo->data_out_format = LYD_JSON; + } else if (!strcasecmp(arg, "lyb")) { + yo->schema_out_format = 0; + yo->data_out_format = LYD_LYB; + } else { + return 1; + } + + return 0; +} + +static int +yl_opt_update_other_out_format(const char *arg, struct yl_opt *yo) +{ + if (!strcasecmp(arg, "feature-param")) { + yo->feature_param_format = 1; + } else { + return 1; + } + + return 0; +} + +int +yl_opt_update_out_format(const char *arg, struct yl_opt *yo) +{ + if (!yl_opt_update_schema_out_format(arg, yo)) { + return 0; + } + if (!yl_opt_update_data_out_format(arg, yo)) { + return 0; + } + if (!yl_opt_update_other_out_format(arg, yo)) { + return 0; + } + + YLMSG_E("Unknown output format %s.", arg); + return 1; +} + +int +yl_opt_update_data_type(const char *arg, struct yl_opt *yo) +{ + if (!strcasecmp(arg, "config")) { + yo->data_parse_options |= LYD_PARSE_NO_STATE; + yo->data_validate_options |= LYD_VALIDATE_NO_STATE; + } else if (!strcasecmp(arg, "get")) { + yo->data_parse_options |= LYD_PARSE_ONLY; + } else if (!strcasecmp(arg, "getconfig") || !strcasecmp(arg, "get-config") || !strcasecmp(arg, "edit")) { + yo->data_parse_options |= LYD_PARSE_ONLY | LYD_PARSE_NO_STATE; + } else if (!strcasecmp(arg, "rpc") || !strcasecmp(arg, "action")) { + yo->data_type = LYD_TYPE_RPC_YANG; + } else if (!strcasecmp(arg, "nc-rpc")) { + yo->data_type = LYD_TYPE_RPC_NETCONF; + } else if (!strcasecmp(arg, "reply") || !strcasecmp(arg, "rpcreply")) { + yo->data_type = LYD_TYPE_REPLY_YANG; + } else if (!strcasecmp(arg, "nc-reply")) { + yo->data_type = LYD_TYPE_REPLY_NETCONF; + } else if (!strcasecmp(arg, "notif") || !strcasecmp(arg, "notification")) { + yo->data_type = LYD_TYPE_NOTIF_YANG; + } else if (!strcasecmp(arg, "nc-notif")) { + yo->data_type = LYD_TYPE_NOTIF_NETCONF; + } else if (!strcasecmp(arg, "data")) { + /* default option */ + } else { + return 1; + } + + return 0; +} + +int +yo_opt_update_data_default(const char *arg, struct yl_opt *yo) +{ + if (!strcasecmp(arg, "all")) { + yo->data_print_options = (yo->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL; + } else if (!strcasecmp(arg, "all-tagged")) { + yo->data_print_options = (yo->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_ALL_TAG; + } else if (!strcasecmp(arg, "trim")) { + yo->data_print_options = (yo->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_TRIM; + } else if (!strcasecmp(arg, "implicit-tagged")) { + yo->data_print_options = (yo->data_print_options & ~LYD_PRINT_WD_MASK) | LYD_PRINT_WD_IMPL_TAG; + } else { + return 1; + } + + return 0; +} + +int +yo_opt_update_data_in_format(const char *arg, struct yl_opt *yo) +{ + if (!strcasecmp(arg, "xml")) { + yo->data_in_format = LYD_XML; + } else if (!strcasecmp(arg, "json")) { + yo->data_in_format = LYD_JSON; + } else if (!strcasecmp(arg, "lyb")) { + yo->data_in_format = LYD_LYB; + } else { + return 1; + } + + return 0; +} + +void +yo_opt_update_make_implemented(struct yl_opt *yo) +{ + if (yo->ctx_options & LY_CTX_REF_IMPLEMENTED) { + yo->ctx_options &= ~LY_CTX_REF_IMPLEMENTED; + yo->ctx_options |= LY_CTX_ALL_IMPLEMENTED; + } else { + yo->ctx_options |= LY_CTX_REF_IMPLEMENTED; + } +} + +void +yo_opt_update_disable_searchdir(struct yl_opt *yo) +{ + if (yo->ctx_options & LY_CTX_DISABLE_SEARCHDIR_CWD) { + yo->ctx_options &= ~LY_CTX_DISABLE_SEARCHDIR_CWD; + yo->ctx_options |= LY_CTX_DISABLE_SEARCHDIRS; + } else { + yo->ctx_options |= LY_CTX_DISABLE_SEARCHDIR_CWD; + } +} + +void +free_cmdline(char *argv[]) +{ + if (argv) { + free(argv[0]); + free(argv); + } +} + +int +parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[]) +{ + int count; + char **vector; + char *ptr; + char qmark = 0; + + assert(cmdline); + assert(argc_p); + assert(argv_p); + + /* init */ + optind = 0; /* reinitialize getopt() */ + count = 1; + vector = malloc((count + 1) * sizeof *vector); + vector[0] = strdup(cmdline); + + /* command name */ + strtok(vector[0], " "); + + /* arguments */ + while ((ptr = strtok(NULL, " "))) { + size_t len; + void *r; + + len = strlen(ptr); + + if (qmark) { + /* still in quotated text */ + /* remove NULL termination of the previous token since it is not a token, + * but a part of the quotation string */ + ptr[-1] = ' '; + + if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) { + /* end of quotation */ + qmark = 0; + /* shorten the argument by the terminating quotation mark */ + ptr[len - 1] = '\0'; + } + continue; + } + + /* another token in cmdline */ + ++count; + r = realloc(vector, (count + 1) * sizeof *vector); + if (!r) { + YLMSG_E("Memory allocation failed (%s:%d, %s).", __FILE__, __LINE__, strerror(errno)); + free(vector); + return -1; + } + vector = r; + vector[count - 1] = ptr; + + if ((ptr[0] == '"') || (ptr[0] == '\'')) { + /* remember the quotation mark to identify end of quotation */ + qmark = ptr[0]; + + /* move the remembered argument after the quotation mark */ + ++vector[count - 1]; + + /* check if the quotation is terminated within this token */ + if ((ptr[len - 1] == qmark) && (ptr[len - 2] != '\\')) { + /* end of quotation */ + qmark = 0; + /* shorten the argument by the terminating quotation mark */ + ptr[len - 1] = '\0'; + } + } + } + vector[count] = NULL; + + *argc_p = count; + *argv_p = vector; + + return 0; +} diff --git a/tools/lint/yl_opt.h b/tools/lint/yl_opt.h new file mode 100644 index 0000000..d66ae4d --- /dev/null +++ b/tools/lint/yl_opt.h @@ -0,0 +1,237 @@ +/** + * @file yl_opt.h + * @author Adam Piecek <piecek@cesnet.cz> + * @brief Settings options for the libyang context. + * + * Copyright (c) 2020 - 2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#ifndef YL_OPT_H_ +#define YL_OPT_H_ + +#include "parser_data.h" /* enum lyd_type */ +#include "printer_schema.h" /* LYS_OUTFORMAT */ +#include "set.h" /* ly_set */ + +/** + * @brief Data connected with a file provided on a command line as a file path. + */ +struct cmdline_file { + struct ly_in *in; + const char *path; + LYD_FORMAT format; +}; + +/** + * @brief Create and fill the command line file data (struct cmdline_file *). + * @param[in] set Optional parameter in case the record is supposed to be added into a set. + * @param[in] in Input file handler. + * @param[in] path Filepath of the file. + * @param[in] format Format of the data file. + * @return The created command line file structure. + * @return NULL on failure + */ +struct cmdline_file *fill_cmdline_file(struct ly_set *set, struct ly_in *in, const char *path, LYD_FORMAT format); + +/** + * @brief Free the command line file data items. + * @param[in,out] rec record to free. + */ +void free_cmdline_file_items(struct cmdline_file *rec); + +/** + * @brief Free the command line file data (struct cmdline_file *). + * @param[in,out] cmdline_file The (struct cmdline_file *) to free. + */ +void free_cmdline_file(void *cmdline_file); + +/** + * @brief Context structure to hold and pass variables in a structured form. + */ +struct yl_opt { + /* Set to 1 if yanglint running in the interactive mode */ + ly_bool interactive; + ly_bool last_one; + + /* libyang context for the run */ + char *yang_lib_file; + uint16_t ctx_options; + + /* prepared output (--output option or stdout by default) */ + ly_bool out_stdout; + struct ly_out *out; + + char *searchpaths; + ly_bool searchdir_unset; + + /* options flags */ + uint8_t list; /* -l option to print list of schemas */ + + /* line length for 'tree' format */ + size_t line_length; /* --tree-line-length */ + + uint32_t dbg_groups; + + /* + * schema + */ + /* set schema modules' features via --features option (struct schema_features *) */ + struct ly_set schema_features; + + /* set of loaded schema modules (struct lys_module *) */ + struct ly_set schema_modules; + + /* options to parse and print schema modules */ + uint32_t schema_parse_options; + uint32_t schema_print_options; + + /* specification of printing schema node subtree, option --schema-node */ + char *schema_node_path; + char *submodule; + + /* name of file containing explicit context passed to callback + * for schema-mount extension. This also causes a callback to + * be registered. + */ + char *schema_context_filename; + ly_bool extdata_unset; + + /* value of --format in case of schema format */ + LYS_OUTFORMAT schema_out_format; + ly_bool feature_param_format; + ly_bool feature_print_all; + + /* + * data + */ + /* various options based on --type option */ + enum lyd_type data_type; + uint32_t data_parse_options; + uint32_t data_validate_options; + uint32_t data_print_options; + + /* flag for --merge option */ + uint8_t data_merge; + + /* value of --format in case of data format */ + LYD_FORMAT data_out_format; + + /* value of --in-format in case of data format */ + LYD_FORMAT data_in_format; + + /* input data files (struct cmdline_file *) */ + struct ly_set data_inputs; + + /* storage for --operational */ + struct cmdline_file data_operational; + + /* storage for --reply-rpc */ + struct cmdline_file reply_rpc; + + /* storage for --data-xpath */ + struct ly_set data_xpath; + + char **argv; +}; + +/** + * @brief Erase all values in @p opt. + * + * The yl_opt.interactive item is not deleted. + * + * @param[in,out] yo Option context to erase. + */ +void yl_opt_erase(struct yl_opt *yo); + +/** + * @brief Update @p yo according to the @p arg of the schema --format parameter. + * + * @param[in] arg Format parameter argument (for example yang, yin, ...). + * @param[out] yo yanglint options used to update. + * @return 0 on success. + */ +int yl_opt_update_schema_out_format(const char *arg, struct yl_opt *yo); + +/** + * @brief Update @p yo according to the @p arg of the data --format parameter. + * + * @param[in] arg Format parameter argument (for example xml, json, ...). + * @param[out] yo yanglint options used to update. + * @return 0 on success. + */ +int yl_opt_update_data_out_format(const char *arg, struct yl_opt *yo); + +/** + * @brief Update @p yo according to the @p arg of the general --format parameter. + * + * @param[in] arg Format parameter argument (for example yang, xml, ...). + * @param[out] yo yanglint options used to update. + * @return 0 on success. + */ +int yl_opt_update_out_format(const char *arg, struct yl_opt *yo); + +/** + * @brief Update @p yo according to the @p arg of the data --type parameter. + * + * @param[in] arg Format parameter argument (for example config, rpc, ...). + * @param[out] yo yanglint options used to update. + * @return 0 on success. + */ +int yl_opt_update_data_type(const char *arg, struct yl_opt *yo); + +/** + * @brief Update @p yo according to the @p arg of the data --default parameter. + * + * @param[in] arg Format parameter argument (for example all, trim, ...). + * @param[out] yo yanglint options used to update. + * @return 0 on success. + */ +int yo_opt_update_data_default(const char *arg, struct yl_opt *yo); + +/** + * @brief Update @p yo according to the @p arg of the data --in-format parameter. + * + * @param[in] arg Format parameter argument (for example xml, json, ...). + * @param[out] yo yanglint options used to update. + * @return 0 on success. + */ +int yo_opt_update_data_in_format(const char *arg, struct yl_opt *yo); + +/** + * @brief Update @p yo according to the --make-implemented parameter. + * + * @param[in,out] yo yanglint options used to update. + */ +void yo_opt_update_make_implemented(struct yl_opt *yo); + +/** + * @brief Update @p yo according to the --disable-searchdir parameter. + * + * @param[in,out] yo yanglint options used to update. + */ +void yo_opt_update_disable_searchdir(struct yl_opt *yo); + +/** + * @brief Helper function to prepare argc, argv pair from a command line string. + * + * @param[in] cmdline Complete command line string. + * @param[out] argc_p Pointer to store argc value. + * @param[out] argv_p Pointer to store argv vector. + * @return 0 on success, non-zero on failure. + */ +int parse_cmdline(const char *cmdline, int *argc_p, char **argv_p[]); + +/** + * @brief Destructor for the argument vector prepared by ::parse_cmdline(). + * + * @param[in,out] argv Argument vector to destroy. + */ +void free_cmdline(char *argv[]); + +#endif /* YL_OPT_H_ */ diff --git a/tools/lint/yl_schema_features.c b/tools/lint/yl_schema_features.c new file mode 100644 index 0000000..74f88b9 --- /dev/null +++ b/tools/lint/yl_schema_features.c @@ -0,0 +1,212 @@ +/** + * @file yl_schema_features.c + * @author Adam Piecek <piecek@cesnet.cz> + * @brief Control features for the schema. + * + * Copyright (c) 2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#define _GNU_SOURCE + +#include <errno.h> +#include <stdlib.h> /* calloc */ +#include <string.h> /* strcmp */ + +#include "compat.h" /* strndup */ +#include "set.h" /* ly_set */ + +#include "common.h" +#include "yl_schema_features.h" + +void +yl_schema_features_free(void *flist) +{ + struct yl_schema_features *rec = (struct yl_schema_features *)flist; + + if (rec) { + free(rec->mod_name); + if (rec->features) { + for (uint32_t u = 0; rec->features[u]; ++u) { + free(rec->features[u]); + } + free(rec->features); + } + free(rec); + } +} + +void +get_features(const struct ly_set *fset, const char *module, const char ***features) +{ + /* get features list for this module */ + for (uint32_t u = 0; u < fset->count; ++u) { + struct yl_schema_features *sf = (struct yl_schema_features *)fset->objs[u]; + + if (!strcmp(module, sf->mod_name)) { + /* matched module - explicitly set features */ + *features = (const char **)sf->features; + sf->applied = 1; + return; + } + } + + /* features not set so disable all */ + *features = NULL; +} + +int +parse_features(const char *fstring, struct ly_set *fset) +{ + struct yl_schema_features *rec = NULL; + uint32_t count; + char *p, **fp; + + rec = calloc(1, sizeof *rec); + if (!rec) { + YLMSG_E("Unable to allocate features information record (%s).", strerror(errno)); + goto error; + } + + /* fill the record */ + p = strchr(fstring, ':'); + if (!p) { + YLMSG_E("Invalid format of the features specification (%s).", fstring); + goto error; + } + rec->mod_name = strndup(fstring, p - fstring); + + count = 0; + while (p) { + size_t len = 0; + char *token = p + 1; + + p = strchr(token, ','); + if (!p) { + /* the last item, if any */ + len = strlen(token); + } else { + len = p - token; + } + + if (len) { + fp = realloc(rec->features, (count + 1) * sizeof *rec->features); + if (!fp) { + YLMSG_E("Unable to store features list information (%s).", strerror(errno)); + goto error; + } + rec->features = fp; + fp = &rec->features[count++]; /* array item to set */ + (*fp) = strndup(token, len); + } + } + + /* terminating NULL */ + fp = realloc(rec->features, (count + 1) * sizeof *rec->features); + if (!fp) { + YLMSG_E("Unable to store features list information (%s).", strerror(errno)); + goto error; + } + rec->features = fp; + rec->features[count++] = NULL; + + /* Store record to the output set. */ + if (ly_set_add(fset, rec, 1, NULL)) { + YLMSG_E("Unable to store features information (%s).", strerror(errno)); + goto error; + } + rec = NULL; + + return 0; + +error: + yl_schema_features_free(rec); + return -1; +} + +void +print_features(struct ly_out *out, const struct lys_module *mod) +{ + struct lysp_feature *f; + uint32_t idx; + size_t max_len, len; + + ly_print(out, "%s:\n", mod->name); + + /* get max len, so the statuses of all the features will be aligned */ + max_len = 0, idx = 0, f = NULL; + while ((f = lysp_feature_next(f, mod->parsed, &idx))) { + len = strlen(f->name); + max_len = (max_len > len) ? max_len : len; + } + if (!max_len) { + ly_print(out, "\t(none)\n"); + return; + } + + /* print features */ + idx = 0, f = NULL; + while ((f = lysp_feature_next(f, mod->parsed, &idx))) { + ly_print(out, "\t%-*s (%s)\n", (int)max_len, f->name, lys_feature_value(mod, f->name) ? "off" : "on"); + } +} + +void +print_feature_param(struct ly_out *out, const struct lys_module *mod) +{ + struct lysp_feature *f = NULL; + uint32_t idx = 0; + uint8_t first = 1; + + ly_print(out, " -F %s:", mod->name); + while ((f = lysp_feature_next(f, mod->parsed, &idx))) { + if (first) { + ly_print(out, "%s", f->name); + first = 0; + } else { + ly_print(out, ",%s", f->name); + } + } +} + +void +print_all_features(struct ly_out *out, const struct ly_ctx *ctx, uint8_t feature_param) +{ + uint32_t i; + struct lys_module *mod; + uint8_t first; + + /* Print features for all implemented modules. */ + first = 1; + i = 0; + while ((mod = ly_ctx_get_module_iter(ctx, &i)) != NULL) { + if (!mod->implemented) { + continue; + } + if (first) { + print_features(out, mod); + first = 0; + } else { + ly_print(out, "\n"); + print_features(out, mod); + } + } + + if (!feature_param) { + return; + } + ly_print(out, "\n"); + + /* Print features for all implemented modules in 'feature-param' format. */ + i = 0; + while ((mod = ly_ctx_get_module_iter(ctx, &i)) != NULL) { + if (mod->implemented) { + print_feature_param(out, mod); + } + } +} diff --git a/tools/lint/yl_schema_features.h b/tools/lint/yl_schema_features.h new file mode 100644 index 0000000..7bfe9fd --- /dev/null +++ b/tools/lint/yl_schema_features.h @@ -0,0 +1,84 @@ +/** + * @file yl_schema_features.h + * @author Adam Piecek <piecek@cesnet.cz> + * @brief Control features for the schema. + * + * Copyright (c) 2023 CESNET, z.s.p.o. + * + * This source code is licensed under BSD 3-Clause License (the "License"). + * You may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/BSD-3-Clause + */ + +#ifndef YL_SCHEMA_FEATURES_H_ +#define YL_SCHEMA_FEATURES_H_ + +#include <stdint.h> + +struct ly_set; +struct lys_module; +struct ly_out; +struct ly_ctx; + +/** + * @brief Storage for the list of the features (their names) in a specific YANG module. + */ +struct yl_schema_features { + char *mod_name; + char **features; + uint8_t applied; +}; + +/** + * @brief Free the schema features list (struct schema_features *) + * @param[in,out] flist The (struct schema_features *) to free. + */ +void yl_schema_features_free(void *flist); + +/** + * @brief Get the list of features connected with the specific YANG module. + * + * @param[in] fset The set of features information (struct schema_features *). + * @param[in] module Name of the YANG module which features should be found. + * @param[out] features Pointer to the list of features being returned. + */ +void get_features(const struct ly_set *fset, const char *module, const char ***features); + +/** + * @brief Parse features being specified for the specific YANG module. + * + * Format of the input @p fstring is as follows: "<module_name>:[<feature>,]*" + * + * @param[in] fstring Input string to be parsed. + * @param[in, out] fset Features information set (of struct schema_features *). The set is being filled. + */ +int parse_features(const char *fstring, struct ly_set *fset); + +/** + * @brief Print all features of a single module. + * + * @param[in] out The output handler for printing. + * @param[in] mod Module which can contains the features. + */ +void print_features(struct ly_out *out, const struct lys_module *mod); + +/** + * @brief Print all features in the 'feature-param' format. + * + * @param[in] out The output handler for printing. + * @param[in] mod Module which can contains the features. + */ +void print_feature_param(struct ly_out *out, const struct lys_module *mod); + +/** + * @brief Print all features of all implemented modules. + * + * @param[in] out The output handler for printing. + * @param[in] ctx Libyang context. + * @param[in] feature_param Flag expressing whether to print features parameter. + */ +void print_all_features(struct ly_out *out, const struct ly_ctx *ctx, uint8_t feature_param); + +#endif /* YL_SCHEMA_FEATURES_H_ */ diff --git a/tools/re/main.c b/tools/re/main.c index 2292b2a..5e33536 100644 --- a/tools/re/main.c +++ b/tools/re/main.c @@ -1,6 +1,7 @@ /** * @file main.c * @author Radek Krejci <rkrejci@cesnet.cz> + * @author Adam Piecek <piecek@cesnet.cz> * @brief libyang's YANG Regular Expression tool * * Copyright (c) 2017 CESNET, z.s.p.o. @@ -26,6 +27,11 @@ #include "compat.h" #include "tools/config.h" +struct yr_pattern { + char *expr; + ly_bool invert; +}; + void help(void) { @@ -34,7 +40,9 @@ help(void) fprintf(stdout, " yangre [-hv]\n"); fprintf(stdout, " yangre [-V] -p <regexp1> [-i] [-p <regexp2> [-i] ...] <string>\n"); fprintf(stdout, " yangre [-V] -f <file>\n"); - fprintf(stdout, "Returns 0 if string matches the pattern(s), 1 if not and -1 on error.\n\n"); + fprintf(stdout, "Returns 0 if string matches the pattern(s) or if otherwise successful.\n"); + fprintf(stdout, "Returns 1 on error.\n"); + fprintf(stdout, "Returns 2 if string does not match the pattern(s).\n\n"); fprintf(stdout, "Options:\n" " -h, --help Show this help message and exit.\n" " -v, --version Show version number and exit.\n" @@ -74,43 +82,248 @@ pattern_error(LY_LOG_LEVEL level, const char *msg, const char *path) } } -static const char *module_start = "module yangre {" - "yang-version 1.1;" - "namespace urn:cesnet:libyang:yangre;" - "prefix re;" - "leaf pattern {" - " type string {"; -static const char *module_invertmatch = " { modifier invert-match; }"; -static const char *module_match = ";"; -static const char *module_end = "}}}"; - static int -add_pattern(char ***patterns, int **inverts, int *counter, char *pattern) +add_pattern(struct yr_pattern **patterns, int *counter, char *pattern) { - void *reallocated1, *reallocated2; + void *reallocated; + int orig_counter; + /* Store the original number of items. */ + orig_counter = *counter; + + /* Reallocate 'patterns' memory with additional space. */ + reallocated = realloc(*patterns, (orig_counter + 1) * sizeof **patterns); + if (!reallocated) { + goto error; + } + (*patterns) = reallocated; + /* Allocated memory is now larger. */ (*counter)++; - reallocated1 = realloc(*patterns, *counter * sizeof **patterns); - reallocated2 = realloc(*inverts, *counter * sizeof **inverts); - if (!reallocated1 || !reallocated2) { - fprintf(stderr, "yangre error: memory allocation error.\n"); - free(reallocated1); - free(reallocated2); - return EXIT_FAILURE; + /* Copy the pattern and store it to the additonal space. */ + (*patterns)[orig_counter].expr = strdup(pattern); + if (!(*patterns)[orig_counter].expr) { + goto error; + } + (*patterns)[orig_counter].invert = 0; + + return 0; + +error: + fprintf(stderr, "yangre error: memory allocation error.\n"); + return 1; +} + +static int +create_empty_string(char **str) +{ + free(*str); + *str = malloc(sizeof(char)); + if (!(*str)) { + fprintf(stderr, "yangre error: memory allocation failed.\n"); + return 1; + } + (*str)[0] = '\0'; + + return 0; +} + +static ly_bool +file_is_empty(FILE *fp) +{ + int c; + + c = fgetc(fp); + if (c == EOF) { + return 1; + } else { + ungetc(c, fp); + return 0; + } +} + +/** + * @brief Open the @p filepath, parse patterns and given string-argument. + * + * @param[in] filepath File to parse. Contains patterns and string. + * @param[out] infile The file descriptor of @p filepath. + * @param[out] patterns Storage of patterns. + * @param[out] patterns_count Number of items in @p patterns. + * @param[out] strarg The string-argument to check. + * @return 0 on success. + */ +static int +parse_patterns_file(const char *filepath, FILE **infile, struct yr_pattern **patterns, int *patterns_count, char **strarg) +{ + int blankline = 0; + char *str = NULL; + size_t len = 0; + ssize_t l; + + *infile = fopen(filepath, "rb"); + if (!(*infile)) { + fprintf(stderr, "yangre error: unable to open input file %s (%s).\n", optarg, strerror(errno)); + goto error; + } + if (file_is_empty(*infile)) { + if (create_empty_string(strarg)) { + goto error; + } + return 0; + } + + while ((l = getline(&str, &len, *infile)) != -1) { + if (!blankline && ((str[0] == '\n') || ((str[0] == '\r') && (str[1] == '\n')))) { + /* blank line */ + blankline = 1; + continue; + } + if ((str[0] != '\n') && (str[0] != '\r') && (str[l - 1] == '\n')) { + /* remove ending newline */ + if ((l > 1) && (str[l - 2] == '\r') && (str[l - 1] == '\n')) { + str[l - 2] = '\0'; + } else { + str[l - 1] = '\0'; + } + } + if (blankline) { + /* done - str is now the string to check */ + blankline = 0; + *strarg = str; + break; + /* else read the patterns */ + } else if (add_pattern(patterns, patterns_count, (str[0] == ' ') ? &str[1] : str)) { + goto error; + } + if (str[0] == ' ') { + /* set invert-match */ + (*patterns)[*patterns_count - 1].invert = 1; + } + } + if (!str || (blankline && (str[0] != '\0'))) { + /* corner case, no input after blankline meaning the pattern to check is empty */ + if (create_empty_string(&str)) { + goto error; + } + } + *strarg = str; + + return 0; + +error: + free(str); + if (*infile) { + fclose(*infile); + *infile = NULL; + } + *strarg = NULL; + + return 1; +} + +static char * +modstr_init(void) +{ + const char *module_start = "module yangre {" + "yang-version 1.1;" + "namespace urn:cesnet:libyang:yangre;" + "prefix re;" + "leaf pattern {" + " type string {"; + + return strdup(module_start); +} + +static char * +modstr_add_pattern(char **modstr, const struct yr_pattern *pattern) +{ + char *new; + const char *module_invertmatch = " { modifier invert-match; }"; + const char *module_match = ";"; + + if (asprintf(&new, "%s pattern %s%s", *modstr, pattern->expr, + pattern->invert ? module_invertmatch : module_match) == -1) { + fprintf(stderr, "yangre error: memory allocation failed.\n"); + return NULL; + } + free(*modstr); + *modstr = NULL; + + return new; +} + +static char * +modstr_add_ending(char **modstr) +{ + char *new; + static const char *module_end = "}}}"; + + if (asprintf(&new, "%s%s", *modstr, module_end) == -1) { + fprintf(stderr, "yangre error: memory allocation failed.\n"); + return NULL; } - (*patterns) = reallocated1; - (*patterns)[*counter - 1] = strdup(pattern); - (*inverts) = reallocated2; - (*inverts)[*counter - 1] = 0; + free(*modstr); + *modstr = NULL; + + return new; +} + +static int +create_module(struct yr_pattern *patterns, int patterns_count, char **mod) +{ + int i; + char *new = NULL, *modstr; + + if (!(modstr = modstr_init())) { + goto error; + } + + for (i = 0; i < patterns_count; i++) { + if (!(new = modstr_add_pattern(&modstr, &patterns[i]))) { + goto error; + } + modstr = new; + } + + if (!(new = modstr_add_ending(&modstr))) { + goto error; + } + + *mod = new; + + return 0; + +error: + *mod = NULL; + free(new); + free(modstr); - return EXIT_SUCCESS; + return 1; +} + +static void +print_verbose(struct ly_ctx *ctx, struct yr_pattern *patterns, int patterns_count, char *str, LY_ERR match) +{ + int i; + + for (i = 0; i < patterns_count; i++) { + fprintf(stdout, "pattern %d: %s\n", i + 1, patterns[i].expr); + fprintf(stdout, "matching %d: %s\n", i + 1, patterns[i].invert ? "inverted" : "regular"); + } + fprintf(stdout, "string : %s\n", str); + if (match == LY_SUCCESS) { + fprintf(stdout, "result : matching\n"); + } else if (match == LY_EVALID) { + fprintf(stdout, "result : not matching\n"); + } else { + fprintf(stdout, "result : error (%s)\n", ly_errmsg(ctx)); + } } int main(int argc, char *argv[]) { LY_ERR match; - int i, opt_index = 0, ret = -1, verbose = 0, blankline = 0; + int i, opt_index = 0, ret = 1, verbose = 0; struct option options[] = { {"help", no_argument, NULL, 'h'}, {"file", required_argument, NULL, 'f'}, @@ -120,21 +333,20 @@ main(int argc, char *argv[]) {"verbose", no_argument, NULL, 'V'}, {NULL, 0, NULL, 0} }; - char **patterns = NULL, *str = NULL, *modstr = NULL, *s; - int *invert_match = NULL; + struct yr_pattern *patterns = NULL; + char *str = NULL, *modstr = NULL; int patterns_count = 0; struct ly_ctx *ctx = NULL; struct lys_module *mod; FILE *infile = NULL; - size_t len = 0; - ssize_t l; + ly_bool info_printed = 0; opterr = 0; while ((i = getopt_long(argc, argv, "hf:ivVp:", options, &opt_index)) != -1) { switch (i) { case 'h': help(); - ret = -2; /* continue to allow printing version and help at once */ + info_printed = 1; break; case 'f': if (infile) { @@ -146,52 +358,17 @@ main(int argc, char *argv[]) fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n"); goto cleanup; } - infile = fopen(optarg, "rb"); - if (!infile) { - fprintf(stderr, "yangre error: unable to open input file %s (%s).\n", optarg, strerror(errno)); + if (parse_patterns_file(optarg, &infile, &patterns, &patterns_count, &str)) { goto cleanup; } - - while ((l = getline(&str, &len, infile)) != -1) { - if (!blankline && (str[0] == '\n')) { - /* blank line */ - blankline = 1; - continue; - } - if ((str[0] != '\n') && (str[l - 1] == '\n')) { - /* remove ending newline */ - str[l - 1] = '\0'; - } - if (blankline) { - /* done - str is now the string to check */ - blankline = 0; - break; - /* else read the patterns */ - } else if (add_pattern(&patterns, &invert_match, &patterns_count, - (str[0] == ' ') ? &str[1] : str)) { - goto cleanup; - } - if (str[0] == ' ') { - /* set invert-match */ - invert_match[patterns_count - 1] = 1; - } - } - if (blankline) { - /* corner case, no input after blankline meaning the pattern to check is empty */ - if (str != NULL) { - free(str); - } - str = malloc(sizeof(char)); - str[0] = '\0'; - } break; case 'i': - if (!patterns_count || invert_match[patterns_count - 1]) { + if (!patterns_count || patterns[patterns_count - 1].invert) { help(); fprintf(stderr, "yangre error: invert-match option must follow some pattern.\n"); goto cleanup; } - invert_match[patterns_count - 1] = 1; + patterns[patterns_count - 1].invert = 1; break; case 'p': if (infile) { @@ -199,13 +376,13 @@ main(int argc, char *argv[]) fprintf(stderr, "yangre error: command line patterns cannot be mixed with file input.\n"); goto cleanup; } - if (add_pattern(&patterns, &invert_match, &patterns_count, optarg)) { + if (add_pattern(&patterns, &patterns_count, optarg)) { goto cleanup; } break; case 'v': version(); - ret = -2; /* continue to allow printing version and help at once */ + info_printed = 1; break; case 'V': verbose = 1; @@ -221,7 +398,8 @@ main(int argc, char *argv[]) } } - if (ret == -2) { + if (info_printed) { + ret = 0; goto cleanup; } @@ -239,24 +417,9 @@ main(int argc, char *argv[]) str = argv[optind]; } - for (modstr = (char *)module_start, i = 0; i < patterns_count; i++) { - if (asprintf(&s, "%s pattern %s%s", modstr, patterns[i], invert_match[i] ? module_invertmatch : module_match) == -1) { - fprintf(stderr, "yangre error: memory allocation failed.\n"); - goto cleanup; - } - if (modstr != module_start) { - free(modstr); - } - modstr = s; - } - if (asprintf(&s, "%s%s", modstr, module_end) == -1) { - fprintf(stderr, "yangre error: memory allocation failed.\n"); + if (create_module(patterns, patterns_count, &modstr)) { goto cleanup; } - if (modstr != module_start) { - free(modstr); - } - modstr = s; if (ly_ctx_new(NULL, 0, &ctx)) { goto cleanup; @@ -271,34 +434,24 @@ main(int argc, char *argv[]) match = lyd_value_validate(ctx, mod->compiled->data, str, strlen(str), NULL, NULL, NULL); if (verbose) { - for (i = 0; i < patterns_count; i++) { - fprintf(stdout, "pattern %d: %s\n", i + 1, patterns[i]); - fprintf(stdout, "matching %d: %s\n", i + 1, invert_match[i] ? "inverted" : "regular"); - } - fprintf(stdout, "string : %s\n", str); - if (match == LY_SUCCESS) { - fprintf(stdout, "result : matching\n"); - } else if (match == LY_EVALID) { - fprintf(stdout, "result : not matching\n"); - } else { - fprintf(stdout, "result : error (%s)\n", ly_errmsg(ctx)); - } + print_verbose(ctx, patterns, patterns_count, str, match); } if (match == LY_SUCCESS) { ret = 0; } else if (match == LY_EVALID) { - ret = 1; + ret = 2; } else { - ret = -1; + ret = 1; } cleanup: ly_ctx_destroy(ctx); for (i = 0; i < patterns_count; i++) { - free(patterns[i]); + free(patterns[i].expr); + } + if (patterns_count) { + free(patterns); } - free(patterns); - free(invert_match); free(modstr); if (infile) { fclose(infile); diff --git a/uncrustify.cfg b/uncrustify.cfg index ec52e71..80d95e9 100644 --- a/uncrustify.cfg +++ b/uncrustify.cfg @@ -1,4 +1,4 @@ -# Uncrustify-0.76.0_f +# Uncrustify-0.77.1_f # # General options @@ -210,6 +210,11 @@ sp_before_ptr_star = force # ignore/add/remove/force # variable name. If set to ignore, sp_before_ptr_star is used instead. sp_before_unnamed_ptr_star = ignore # ignore/add/remove/force +# Add or remove space between a qualifier and a pointer star '*' that isn't +# followed by a variable name, as in '(char const *)'. If set to ignore, +# sp_before_ptr_star is used instead. +sp_qualifier_unnamed_ptr_star = ignore # ignore/add/remove/force/not_defined + # Add or remove space between pointer stars '*'. sp_between_ptr_star = remove # ignore/add/remove/force @@ -250,10 +255,20 @@ sp_ptr_star_paren = remove # ignore/add/remove/force # prototype or function definition. sp_before_ptr_star_func = force # ignore/add/remove/force +# Add or remove space between a qualifier and a pointer star '*' followed by +# the name of the function in a function prototype or definition, as in +# 'char const *foo()`. If set to ignore, sp_before_ptr_star is used instead. +sp_qualifier_ptr_star_func = ignore # ignore/add/remove/force/not_defined + # Add or remove space before a pointer star '*' in the trailing return of a # function prototype or function definition. sp_before_ptr_star_trailing = ignore # ignore/add/remove/force/not_defined +# Add or remove space between a qualifier and a pointer star '*' in the +# trailing return of a function prototype or function definition, as in +# 'auto foo() -> char const *'. +sp_qualifier_ptr_star_trailing = force # ignore/add/remove/force/not_defined + # Add or remove space before a reference sign '&'. sp_before_byref = ignore # ignore/add/remove/force @@ -611,6 +626,16 @@ sp_inside_fparens = remove # ignore/add/remove/force # Add or remove space inside function '(' and ')'. sp_inside_fparen = remove # ignore/add/remove/force +# Add or remove space inside user functor '(' and ')'. +sp_func_call_user_inside_rparen = ignore # ignore/add/remove/force/not_defined + +# Add or remove space inside empty functor '()'. +# Overrides sp_after_angle unless use_sp_after_angle_always is set to true. +sp_inside_rparens = ignore # ignore/add/remove/force/not_defined + +# Add or remove space inside functor '(' and ')'. +sp_inside_rparen = ignore # ignore/add/remove/force/not_defined + # Add or remove space inside the first parentheses in a function type, as in # 'void (*x)(...)'. sp_inside_tparen = remove # ignore/add/remove/force @@ -951,6 +976,14 @@ sp_extern_paren = ignore # ignore/add/remove/force # i.e. '// A' vs. '//A'. sp_cmt_cpp_start = force # ignore/add/remove/force +# remove space after the '//' and the pvs command '-V1234', +# only works with sp_cmt_cpp_start set to add or force. +sp_cmt_cpp_pvs = false # true/false + +# remove space after the '//' and the command 'lint', +# only works with sp_cmt_cpp_start set to add or force. +sp_cmt_cpp_lint = false # true/false + # Add or remove space in a C++ region marker comment, as in '// <here> BEGIN'. # A region marker is defined as a comment which is not preceded by other text # (i.e. the comment is the first non-whitespace on the line), and which starts @@ -2804,9 +2837,19 @@ align_single_line_brace_gap = 1 # unsigned number # 0: Don't align (default). align_oc_msg_spec_span = 0 # unsigned number -# Whether to align macros wrapped with a backslash and a newline. This will -# not work right if the macro contains a multi-line comment. -align_nl_cont = false # true/false +# 0: Do nothing (default) +# 1: Align the backslashes in the column at the end of the longest line +# 2: Align with the backslash that is farthest to the left, or, if that +# backslash is farther left than the end of the longest line, at the end of +# the longest line +# 3: Align with the backslash that is farthest to the right +align_nl_cont = 0 # unsigned number + +# The minimum number of spaces between the end of a line and its continuation +# backslash. Requires align_nl_cont. +# +# Default: 1 +align_nl_cont_spaces = 1 # unsigned number # Whether to align macro functions and variables together. align_pp_define_together = false # true/false @@ -3117,6 +3160,12 @@ mod_remove_extra_semicolon = true # true/false # Whether to remove duplicate include. mod_remove_duplicate_include = true # true/false +# the following options (mod_XX_closebrace_comment) use different comment, +# depending of the setting of the next option. +# false: Use the c comment (default) +# true : Use the cpp comment +mod_add_force_c_closebrace_comment = false # true/false + # If a function body exceeds the specified number of newlines and doesn't have # a comment after the close brace, a comment will be added. mod_add_long_function_closebrace_comment = 0 # unsigned number |