diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 147c94884..14fdd551e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,9 +13,25 @@ env: DEFAULT_PACKAGES: libcmocka-dev zlib1g-dev libssh-dev libssl-dev jobs: + git-branch: + name: Get git branch + runs-on: ubuntu-18.04 + outputs: + branch-name: ${{ steps.get-git-branch.outputs.branch-name }} + steps: + - id: get-git-branch + run: | + if ${{ github.event_name == 'push' }} + then export GIT_BRANCH=`echo ${{ github.ref }} | cut -d'/' -f 3` + else + export GIT_BRANCH=${{ github.base_ref }} + fi + echo "::set-output name=branch-name::$GIT_BRANCH" + build: name: ${{ matrix.config.name }} runs-on: ${{ matrix.config.os }} + needs: git-branch strategy: fail-fast: false matrix: @@ -24,47 +40,77 @@ jobs: name: "Release, Ubuntu 18.04, gcc", os: "ubuntu-18.04", build-type: "Release", + dep-build-type: "Release", cc: "gcc", options: "", - packages: "" + packages: "", + snaps: "", + make-prepend: "", + make-target: "" } - { name: "Release, Ubuntu 18.04, clang", os: "ubuntu-18.04", build-type: "Release", + dep-build-type: "Release", cc: "clang", options: "", - packages: "" + packages: "", + snaps: "", + make-prepend: "", + make-target: "" } - { - name: "Debug, Ubuntu 18.04, gcc", + name: "Debug, Ubuntu 18.04, gcc, with URL", os: "ubuntu-18.04", build-type: "Debug", + dep-build-type: "Debug", cc: "gcc", options: "", - packages: "valgrind" + packages: "libcurl4-openssl-dev valgrind", + snaps: "", + make-prepend: "", + make-target: "" } - { name: "Debug, Ubuntu 18.04, clang", os: "ubuntu-18.04", build-type: "Debug", + dep-build-type: "Debug", cc: "clang", options: "", - packages: "valgrind" + packages: "valgrind", + snaps: "", + make-prepend: "", + make-target: "" } - { name: "ASAN and UBSAN", os: "ubuntu-18.04", build-type: "Debug", + dep-build-type: "Debug", cc: "clang", options: "-DCMAKE_C_FLAGS=-fsanitize=address,undefined -DENABLE_VALGRIND_TESTS=OFF", - packages: "" + packages: "", + snaps: "", + make-prepend: "", + make-target: "" } steps: - uses: actions/checkout@v2 - - name: Uncrustify + - name: Deps-packages + shell: bash + run: | + sudo add-apt-repository ppa:kedazo/libssh-0.7.x -y + sudo apt-get update + sudo apt-get install $DEFAULT_PACKAGES ${{ matrix.config.packages }} + if ${{ matrix.config.snaps != '' }} + then sudo snap install ${{ matrix.config.snaps }} + fi + + - name: Deps-uncrustify shell: bash working-directory: ${{ github.workspace }} run: | @@ -77,44 +123,42 @@ jobs: sudo make install if: ${{ matrix.config.name == 'Debug, Ubuntu 18.04, gcc' }} - - name: Dependencies + - name: Deps-libyang shell: bash run: | - sudo add-apt-repository ppa:kedazo/libssh-0.7.x -y - sudo apt-get update - sudo apt-get install $DEFAULT_PACKAGES ${{ matrix.config.packages }} - - if ${{ github.event_name == 'push' }} - then GIT_BRANCH=`echo ${{ github.ref }} | cut -d'/' -f 3` - else - GIT_BRANCH=${{ github.base_ref }} - fi - - git clone -b $GIT_BRANCH https://github.com/CESNET/libyang.git + git clone -b ${{needs.git-branch.outputs.branch-name}} https://github.com/CESNET/libyang.git cd libyang mkdir build cd build - CC=${{ matrix.config.cc }} cmake -DCMAKE_BUILD_TYPE=${{ matrix.config.build-type }} -DENABLE_BUILD_TESTS=OFF .. + CC=${{ matrix.config.cc }} cmake -DCMAKE_BUILD_TYPE=${{ matrix.config.dep-build-type }} -DENABLE_TESTS=OFF .. make -j2 sudo make install - git clone -b $GIT_BRANCH https://github.com/sysrepo/sysrepo.git + - name: Deps-sysrepo + shell: bash + run: | + git clone -b ${{needs.git-branch.outputs.branch-name}} https://github.com/sysrepo/sysrepo.git cd sysrepo mkdir build cd build - CC=${{ matrix.config.cc }} cmake -DCMAKE_BUILD_TYPE=${{ matrix.config.build-type }} -DENABLE_TESTS=OFF .. + CC=${{ matrix.config.cc }} cmake -DCMAKE_BUILD_TYPE=${{ matrix.config.build-type }} -DSYSREPO_SUPERUSER_UID=`id -u` -DENABLE_TESTS=OFF .. make -j2 sudo make install - git clone -b $GIT_BRANCH https://github.com/CESNET/libnetconf2.git + - name: Deps-libnetconf2 + shell: bash + run: | + git clone -b ${{needs.git-branch.outputs.branch-name}} https://github.com/CESNET/libnetconf2.git cd libnetconf2 mkdir build cd build - CC=${{ matrix.config.cc }} cmake -DCMAKE_BUILD_TYPE=${{ matrix.config.build-type }} -DENABLE_BUILD_TESTS=OFF .. + CC=${{ matrix.config.cc }} cmake -DCMAKE_BUILD_TYPE=${{ matrix.config.build-type }} -DENABLE_TESTS=OFF .. make -j2 sudo make install - sudo ldconfig + - name: Deps-update-ld-cache + shell: bash + run: sudo ldconfig - name: Configure shell: bash @@ -127,7 +171,10 @@ jobs: - name: Build shell: bash working-directory: ${{ github.workspace }}/build - run: make + run: | + export LC_ALL=C.UTF-8 + export PATH=/snap/bin:${{ github.workspace }}/coverity-tools/bin:$PATH + ${{ matrix.config.make-prepend }} make ${{ matrix.config.make-target }} - name: Test shell: bash diff --git a/.github/workflows/devel-push.yml b/.github/workflows/devel-push.yml new file mode 100644 index 000000000..868b1e35b --- /dev/null +++ b/.github/workflows/devel-push.yml @@ -0,0 +1,162 @@ +name: netopeer2 devel push +on: + push: + branches: + - devel + +env: + DEFAULT_PACKAGES: libcmocka-dev zlib1g-dev libssh-dev libssl-dev + COVERITY_PROJECT: CESNET%2FNetopeer2 + +jobs: + git-branch: + name: Get git branch + runs-on: ubuntu-18.04 + outputs: + branch-name: ${{ steps.get-git-branch.outputs.branch-name }} + steps: + - id: get-git-branch + run: | + if ${{ github.event_name == 'push' }} + then export GIT_BRANCH=`echo ${{ github.ref }} | cut -d'/' -f 3` + else + export GIT_BRANCH=${{ github.base_ref }} + fi + echo "::set-output name=branch-name::$GIT_BRANCH" + + build: + name: ${{ matrix.config.name }} + runs-on: ${{ matrix.config.os }} + needs: git-branch + strategy: + fail-fast: false + matrix: + config: + - { + name: "Coverity", + os: "ubuntu-latest", + build-type: "Debug", + dep-build-type: "Debug", + cc: "clang", + options: "", + packages: "", + snaps: "", + make-prepend: "cov-build --dir cov-int", + make-target: "" + } + - { + name: "Codecov", + os: "ubuntu-latest", + build-type: "Debug", + dep-build-type: "Debug", + cc: "gcc", + options: "-DENABLE_COVERAGE=ON", + packages: "libcmocka-dev lcov", + snaps: "", + make-prepend: "", + make-target: "" + } + + steps: + - uses: actions/checkout@v2 + + - name: Deps-packages + shell: bash + run: | + sudo add-apt-repository ppa:kedazo/libssh-0.7.x -y + sudo apt-get update + sudo apt-get install $DEFAULT_PACKAGES ${{ matrix.config.packages }} + if ${{ matrix.config.snaps != '' }} + then sudo snap install ${{ matrix.config.snaps }} + fi + + - name: Deps-coverity + shell: bash + working-directory: ${{ github.workspace }} + run: | + wget -q https://scan.coverity.com/download/linux64 --post-data "token=$TOKEN&project=$COVERITY_PROJECT" -O coverity-tools.tar.gz + mkdir coverity-tools + tar xzf coverity-tools.tar.gz --strip 1 -C coverity-tools + env: + TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} + if: ${{ matrix.config.name == 'Coverity' }} + + - name: Deps-libyang + shell: bash + run: | + git clone -b ${{needs.git-branch.outputs.branch-name}} https://github.com/CESNET/libyang.git + cd libyang + mkdir build + cd build + CC=${{ matrix.config.cc }} cmake -DCMAKE_BUILD_TYPE=${{ matrix.config.dep-build-type }} -DENABLE_BUILD_TESTS=OFF .. + make -j2 + sudo make install + + - name: Deps-sysrepo + shell: bash + run: | + git clone -b ${{needs.git-branch.outputs.branch-name}} https://github.com/sysrepo/sysrepo.git + cd sysrepo + mkdir build + cd build + CC=${{ matrix.config.cc }} cmake -DCMAKE_BUILD_TYPE=${{ matrix.config.build-type }} -DENABLE_TESTS=OFF .. + make -j2 + sudo make install + + - name: Deps-libnetconf2 + shell: bash + run: | + git clone -b ${{needs.git-branch.outputs.branch-name}} https://github.com/CESNET/libnetconf2.git + cd libnetconf2 + mkdir build + cd build + CC=${{ matrix.config.cc }} cmake -DCMAKE_BUILD_TYPE=${{ matrix.config.build-type }} -DENABLE_BUILD_TESTS=OFF .. + make -j2 + sudo make install + + - name: Deps-update-ld-cache + shell: bash + run: sudo ldconfig + + - name: Configure + shell: bash + working-directory: ${{ github.workspace }} + run: | + mkdir build + cd build + CC=${{ matrix.config.cc }} cmake -DCMAKE_BUILD_TYPE=${{ matrix.config.build-type }} ${{ matrix.config.options }} .. + + - name: Build + shell: bash + working-directory: ${{ github.workspace }}/build + run: | + export LC_ALL=C.UTF-8 + export PATH=/snap/bin:${{ github.workspace }}/coverity-tools/bin:$PATH + ${{ matrix.config.make-prepend }} make ${{ matrix.config.make-target }} + + - name: Test + shell: bash + working-directory: ${{ github.workspace }}/build + run: ctest --output-on-failure + + - name: Upload to Coverity.com + shell: bash + working-directory: ${{ github.workspace }}/build + run: | + tar czvf netopeer2.tgz cov-int + curl \ + --form token=$TOKEN \ + --form email=mvasko@cesnet.cz \ + --form file=@netopeer2.tgz \ + --form version="`./netopeer2-server -V | head -n1 | cut -d' ' -f 2`" \ + --form description="netopeer2 NETCONF suite" \ + https://scan.coverity.com/builds?project=$COVERITY_PROJECT + env: + TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} + if: ${{ matrix.config.name == 'Coverity' }} + + - name: Upload to Codecov.io + shell: bash + working-directory: ${{ github.workspace }}/build + run: bash <(curl -s https://codecov.io/bash) + if: ${{ matrix.config.name == 'Codecov' }} diff --git a/CMakeLists.txt b/CMakeLists.txt index ca8598270..d06e227fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,15 +11,11 @@ include(CheckFunctionExists) include(CheckIncludeFile) include(UseCompat) include(SourceFormat) +include(GenCoverage) if(POLICY CMP0075) cmake_policy(SET CMP0075 NEW) endif() -# check the supported platform -if(NOT UNIX) - message(FATAL_ERROR "Only *nix like systems are supported.") -endif() - # set default build type if not specified by user and normalize it if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) @@ -35,21 +31,35 @@ elseif("${BUILD_TYPE_UPPER}" STREQUAL "RELWITHDEBUG") set(CMAKE_BUILD_TYPE "RelWithDebug" CACHE STRING "Build Type" FORCE) endif() -# compilation flags -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -std=gnu99") -set(CMAKE_C_FLAGS_RELEASE "-DNDEBUG -O2") -set(CMAKE_C_FLAGS_DEBUG "-g -O0") +# check the supported platform +if(NOT UNIX) + message(FATAL_ERROR "Only *nix like systems are supported.") +endif() # Version of the project # Generic version of not only the library. Major version is reserved for really big changes of the project, # minor version changes with added functionality (new tool, functionality of the tool or library, ...) and # micro version is changed with a set of small changes or bugfixes anywhere in the project. -set(NP2SRV_VERSION 2.0.0) +set(NP2SRV_VERSION 2.0.28) # libyang required SO version set(LIBYANG_DEP_SOVERSION_MAJOR 2) -# build options +# Version of sysrepo that this netopeer2 version depends on +set(SYSREPO_DEP_VERSION 2.0.33) +set(SYSREPO_DEP_SOVERSION 6.4.0) +set(SYSREPO_DEP_SOVERSION_MAJOR 6) + +# +# compilation flags +# +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -std=gnu99") +set(CMAKE_C_FLAGS_RELEASE "-DNDEBUG -O2") +set(CMAKE_C_FLAGS_DEBUG "-g -O0") + +# +# options +# if(("${BUILD_TYPE_UPPER}" STREQUAL "DEBUG") OR ("${BUILD_TYPE_UPPER}" STREQUAL "RELWITHDEBINFO")) option(ENABLE_TESTS "Build tests" ON) option(ENABLE_VALGRIND_TESTS "Build tests with valgrind" ON) @@ -57,6 +67,7 @@ else() option(ENABLE_TESTS "Build tests" OFF) option(ENABLE_VALGRIND_TESTS "Build tests with valgrind" OFF) endif() +option(ENABLE_COVERAGE "Build code coverage report from tests" OFF) option(BUILD_CLI "Build and install neotpeer2-cli" ON) option(ENABLE_URL "Enable URL capability" ON) set(THREAD_COUNT 5 CACHE STRING "Number of threads accepting new sessions and handling requests") @@ -99,7 +110,37 @@ if(NOT NP2SRV_SSH_AUTHORIZED_KEYS_PATTERN MATCHES "^[^%]*%s[^%]*$") message(FATAL_ERROR "Wrong format string given for NP2SRV_SSH_AUTHORIZED_KEYS_PATTERN: exactly one '%s' expected.") endif() -# check that lnc2 supports np2srv thread count +# +# sources +# +set(SERVER_SRC + src/common.c + src/netconf.c + src/netconf_monitoring.c + src/netconf_acm.c + src/netconf_nmda.c + src/netconf_subscribed_notifications.c + src/subscribed_notifications.c + src/yang_push.c + src/log.c + src/err_netconf.c) + +# source files to be covered by the 'format' target +set(FORMAT_SRC + compat/*.c + compat/*.h* + src/*.c + src/*.h + cli/*.c + cli/*.h + tests/*.c + tests/*.h) + +# +# checks +# + +# lnc2 support for np2srv thread count find_package(PkgConfig) if(PKG_CONFIG_FOUND) execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} "--variable=LNC2_MAX_THREAD_COUNT" "libnetconf2" OUTPUT_VARIABLE LNC2_THREAD_COUNT) @@ -117,6 +158,36 @@ else() message(STATUS "pkg-config not found, so it was not possible to check if libnetconf2 supports ${THREAD_COUNT} threads") endif() +if(ENABLE_VALGRIND_TESTS) + find_program(VALGRIND_FOUND valgrind) + if(NOT VALGRIND_FOUND) + message(WARNING "valgrind executable not found! Disabling memory leaks tests.") + set(ENABLE_VALGRIND_TESTS OFF) + else() + set(ENABLE_TESTS ON) + endif() +endif() + +if(ENABLE_TESTS) + find_package(CMocka 1.0.0) + if(NOT CMOCKA_FOUND) + message(STATUS "Disabling tests because of missing CMocka") + set(ENABLE_TESTS OFF) + endif() +endif() + +if(ENABLE_COVERAGE) + gen_coverage_enable(${ENABLE_TESTS}) +endif() + +if ("${BUILD_TYPE_UPPER}" STREQUAL "DEBUG") + source_format_enable() +endif() + +# +# targets +# + # put all binaries into one directory (even from subprojects) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) @@ -140,30 +211,6 @@ include_directories(${LIBNETCONF2_INCLUDE_DIRS}) list(APPEND CMAKE_REQUIRED_INCLUDES ${LIBNETCONF2_INCLUDE_DIRS}) list(APPEND CMAKE_REQUIRED_LIBRARIES ${LIBNETCONF2_LIBRARIES}) -# source files -set(SERVER_SRC - src/common.c - src/netconf.c - src/netconf_monitoring.c - src/netconf_acm.c - src/netconf_nmda.c - src/netconf_subscribed_notifications.c - src/subscribed_notifications.c - src/yang_push.c - src/log.c - src/err_netconf.c) - -# source files to be covered by the 'format' target -set(FORMAT_SRC - compat/*.c - compat/*.h* - src/*.c - src/*.h - cli/*.c - cli/*.h - tests/*.c - tests/*.h) - # at least some remote transport is enabled if(LIBNETCONF2_ENABLED_SSH OR LIBNETCONF2_ENABLED_TLS) list(APPEND SERVER_SRC src/netconf_server.c) @@ -186,22 +233,26 @@ use_compat() add_library(serverobj OBJECT ${SERVER_SRC}) add_executable(netopeer2-server $ src/main.c ${compatsrc}) -# dependencies - librt (not required on OSX or QNX) +# +# dependencies +# + +# librt (not required on OSX or QNX) find_library(LIBRT rt) if(LIBRT) target_link_libraries(netopeer2-server ${LIBRT}) endif() -# dependencies - libnetconf2 (was already found) +# libnetconf2 (was already found) target_link_libraries(netopeer2-server ${LIBNETCONF2_LIBRARIES}) -# dependencies - libssh (was already found, if exists) +# libssh (was already found, if exists) if(LIBSSH_FOUND AND LIBNETCONF2_ENABLED_SSH) target_link_libraries(netopeer2-server ${LIBSSH_LIBRARIES}) include_directories(${LIBSSH_INCLUDE_DIRS}) endif() -# dependencies - libcurl +# libcurl if(ENABLE_URL) find_package(CURL) if(CURL_FOUND) @@ -214,21 +265,21 @@ if(ENABLE_URL) endif() endif() -# dependencies - pthread +# pthread set(CMAKE_THREAD_PREFER_PTHREAD TRUE) find_package(Threads REQUIRED) target_link_libraries(netopeer2-server ${CMAKE_THREAD_LIBS_INIT}) list(APPEND CMAKE_REQUIRED_FLAGS ${CMAKE_THREAD_LIBS_INIT}) -# dependencies - libyang +# libyang find_package(LibYANG ${LIBYANG_DEP_SOVERSION_MAJOR} REQUIRED) target_link_libraries(netopeer2-server ${LIBYANG_LIBRARIES}) include_directories(${LIBYANG_INCLUDE_DIRS}) list(APPEND CMAKE_REQUIRED_INCLUDES ${LIBYANG_INCLUDE_DIRS}) list(APPEND CMAKE_REQUIRED_LIBRARIES ${LIBYANG_LIBRARIES}) -# dependencies - sysrepo -find_package(Sysrepo REQUIRED) +# sysrepo +find_package(Sysrepo ${SYSREPO_DEP_SOVERSION} REQUIRED) target_link_libraries(netopeer2-server ${SYSREPO_LIBRARIES}) include_directories(${SYSREPO_INCLUDE_DIRS}) list(APPEND CMAKE_REQUIRED_INCLUDES ${SYSREPO_INCLUDE_DIRS}) @@ -238,26 +289,6 @@ list(APPEND CMAKE_REQUIRED_LIBRARIES ${SYSREPO_LIBRARIES}) configure_file("${PROJECT_SOURCE_DIR}/src/config.h.in" "${PROJECT_BINARY_DIR}/config.h" ESCAPE_QUOTES @ONLY) include_directories(${PROJECT_BINARY_DIR}) -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() -endif() - -# source files to be covered by the 'format' target and a test with 'format-check' target -source_format(${FORMAT_SRC}) - -# tests -if(ENABLE_TESTS) - find_package(CMocka 1.0.0) - if(CMOCKA_FOUND) - enable_testing() - add_subdirectory(tests) - else() - message(STATUS "Disabling tests because of missing CMocka.") - set(ENABLE_TESTS NO) - endif() -endif() - # set script dir set(SCRIPT_DIR "${PROJECT_SOURCE_DIR}/scripts/") @@ -300,11 +331,23 @@ if(MERGE_LISTEN_CONFIG) ") endif() +# tests +if(ENABLE_TESTS) + enable_testing() + add_subdirectory(tests) +endif() + +# create coverage target for generating coverage reports +gen_coverage("test_.*" "test_.*_valgrind") + # cli if(BUILD_CLI) add_subdirectory(cli) endif() +# source files to be covered by the 'format' target and a test with 'format-check' target +source_format(${FORMAT_SRC}) + # clean cmake cache add_custom_target(cleancache COMMAND make clean diff --git a/CMakeModules/GenCoverage.cmake b/CMakeModules/GenCoverage.cmake new file mode 100644 index 000000000..59004ae19 --- /dev/null +++ b/CMakeModules/GenCoverage.cmake @@ -0,0 +1,118 @@ +# generate test code coverage report + +# check that coverage tools are available - always use before GEN_COVERAGE +macro(GEN_COVERAGE_ENABLE ENABLE_TESTS) + # make into normal variable + set(TESTS_ENABLED ${ENABLE_TESTS}) + + set(GEN_COVERAGE_ENABLED ON) + if(NOT TESTS_ENABLED) + message(WARNING "You cannot generate coverage when tests are disabled. Enable test by additing parameter -DENABLE_BUILD_TESTS=ON or run cmake with Debug build target.") + set(GEN_COVERAGE_ENABLED OFF) + endif() + + if(GEN_COVERAGE_ENABLED) + find_program(PATH_GCOV NAMES gcov) + if(NOT PATH_GCOV) + message(WARNING "gcov executable not found! Disabling building code coverage report.") + set(GEN_COVERAGE_ENABLED OFF) + endif() + endif() + + if(GEN_COVERAGE_ENABLED) + find_program(PATH_LCOV NAMES lcov) + if(NOT PATH_LCOV) + message(WARNING "lcov executable not found! Disabling building code coverage report.") + set(GEN_COVERAGE_ENABLED OFF) + endif() + endif() + + if(GEN_COVERAGE_ENABLED) + find_program(PATH_GENHTML NAMES genhtml) + if(NOT PATH_GENHTML) + message(WARNING "genhtml executable not found! Disabling building code coverage report.") + set(GEN_COVERAGE_ENABLED OFF) + endif() + endif() + + if(GEN_COVERAGE_ENABLED) + if(NOT CMAKE_COMPILER_IS_GNUCC) + message(WARNING "Compiler is not gcc! Coverage may break the tests!") + endif() + + execute_process( + COMMAND bash "-c" "${CMAKE_C_COMPILER} --version | head -n1 | sed \"s/.* (.*) \\([0-9]\\+.[0-9]\\+.[0-9]\\+ .*\\)/\\1/\"" + OUTPUT_VARIABLE GCC_VERSION_FULL + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + execute_process( + COMMAND bash "-c" "${PATH_GCOV} --version | head -n1 | sed \"s/.* (.*) \\([0-9]\\+.[0-9]\\+.[0-9]\\+ .*\\)/\\1/\"" + OUTPUT_VARIABLE GCOV_VERSION_FULL + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(NOT GCC_VERSION_FULL STREQUAL GCOV_VERSION_FULL) + message(WARNING "gcc and gcov versions do not match! Generating coverage may fail with errors.") + endif() + + # add specific required compile flags + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage -fprofile-arcs -ftest-coverage") + endif() +endmacro() + +# tests are always expected to be in ${CMAKE_SOURCE_DIR}/tests +function(GEN_COVERAGE MATCH_TEST_REGEX EXCLUDE_TEST_REGEX) + if(NOT GEN_COVERAGE_ENABLED) + return() + endif() + + # destination + set(COVERAGE_DIR "${CMAKE_BINARY_DIR}/code_coverage/") + set(COVERAGE_FILE_RAW "${CMAKE_BINARY_DIR}/coverage_raw.info") + set(COVERAGE_FILE_CLEAN "${CMAKE_BINARY_DIR}/coverage_clean.info") + + # test match/exclude + if(MATCH_TEST_REGEX) + set(MATCH_TEST_ARGS -R \"${MATCH_TEST_REGEX}\") + endif() + if(EXCLUDE_TEST_REGEX) + set(EXCLUDE_TEST_ARGS -E \"${EXCLUDE_TEST_REGEX}\") + endif() + + # coverage target + add_custom_target(coverage + COMMENT "Generating code coverage..." + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + # Cleanup code counters + COMMAND "${PATH_LCOV}" --directory . --zerocounters --quiet + + # Run tests + COMMAND "${CMAKE_CTEST_COMMAND}" --quiet ${MATCH_TEST_ARGS} ${EXCLUDE_TEST_ARGS} + + # Capture the counters + COMMAND "${PATH_LCOV}" + --directory . + --rc lcov_branch_coverage=1 + --rc 'lcov_excl_line=assert' + --capture --quiet + --output-file "${COVERAGE_FILE_RAW}" + # Remove coverage of tests, system headers, etc. + COMMAND "${PATH_LCOV}" + --remove "${COVERAGE_FILE_RAW}" '${CMAKE_SOURCE_DIR}/tests/*' '${CMAKE_SOURCE_DIR}/compat/*' '*libyang*' + --rc lcov_branch_coverage=1 + --quiet --output-file "${COVERAGE_FILE_CLEAN}" + # Generate HTML report + COMMAND "${PATH_GENHTML}" + --branch-coverage --function-coverage --quiet --title "${PROJECT_NAME}" + --legend --show-details --output-directory "${COVERAGE_DIR}" + "${COVERAGE_FILE_CLEAN}" + # Delete the counters + COMMAND "${CMAKE_COMMAND}" -E remove + ${COVERAGE_FILE_RAW} ${COVERAGE_FILE_CLEAN} + ) + + add_custom_command(TARGET coverage POST_BUILD + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/tests" + COMMENT "To see the code coverage report, open ${COVERAGE_DIR}index.html" + COMMAND ; + ) +endfunction() diff --git a/README.md b/README.md index 15131ea44..ff1b3b7ae 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ [![BSD license](https://img.shields.io/badge/License-BSD-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) [![Build Status](https://secure.travis-ci.org/CESNET/Netopeer2.png?branch=master)](http://travis-ci.org/CESNET/Netopeer2) -[![Coverity Scan Build Status](https://scan.coverity.com/projects/8416/badge.svg)](https://scan.coverity.com/projects/8416) +[![Coverity](https://scan.coverity.com/projects/8416/badge.svg)](https://scan.coverity.com/projects/8416) +[![Codecov](https://codecov.io/gh/CESNET/netopeer2/branch/master/graph/badge.svg?token=ue4DTHDcuq)](https://codecov.io/gh/CESNET/netopeer2) [![Ohloh Project Status](https://www.openhub.net/p/Netopeer2/widgets/project_thin_badge.gif)](https://www.openhub.net/p/Netopeer2) **Netopeer2** is a server for implementing network configuration management based @@ -34,6 +35,14 @@ and it occurs on the `master` branch, the **first response will likely be** to u * [libnetconf2](https://github.com/CESNET/libnetconf2) * [sysrepo](https://github.com/sysrepo/sysrepo) +### Optional + +* cmocka >= 1.0.0 (for [tests](#Tests)) +* valgrind (for enhanced testing) +* gcov (for code coverage) +* lcov (for code coverage) +* genhtml (for code coverage) + ## RFC Compliance * [RFC 5277](https://www.rfc-editor.org/rfc/rfc5277.html) NETCONF Event Notifications @@ -63,7 +72,7 @@ $ make ### Compilation options The `netopeer2-server` requires *ietf-netconf-server* and all connected YANG modules to be installed in *sysrepo* -to work correctly. This is performed autmatically during the installation process. Moreover, default +to work correctly. This is performed automatically during the installation process. Moreover, default SSH configuration listening on all IPv4 interfaces and a newly generated SSH host key are imported so that it can be connected to the server out-of-the-box. However, it may not always be desired to perform all these steps even though the executed scripts check whether the modules/some configuration @@ -114,6 +123,42 @@ adjusted by an option: BUILD_CLI:ON ``` +### Tests + +There are several tests included and built with [cmocka](https://cmocka.org/). The tests +can be found in `tests` subdirectory and they are designed for checking library +functionality after code changes. + +The tests are by default built in the `Debug` build mode by running +``` +$ make +``` + +In case of the `Release` mode, the tests are not built by default (it requires +additional dependency), but they can be enabled via cmake option: +``` +$ cmake -DENABLE_TESTS=ON .. +``` + +Note that if the necessary [cmocka](https://cmocka.org/) headers are not present +in the system include paths, tests are not available despite the build mode or +cmake's options. + +Tests can be run by the make's `test` target: +``` +$ make test +``` + +### Code Coverage + +Based on the tests run, it is possible to generate code coverage report. But +it must be enabled and these commands are needed to generate the report: +``` +$ cmake -DENABLE_COVERAGE=ON .. +$ make +$ make coverage +``` + ## NACM This NETCONF server implements full *ietf-netconf-acm* access control that **bypasses** *sysrepo* @@ -144,7 +189,8 @@ connect using `client.crt` certificate and `client.key` private key and having ` set as trusted. These example certificates can be found in `example_configuration/tls_certs`. *netopeer2-cli* can easily be configured this way and the TLS connection tested. -Once connected, the client will be identified with `tls-test` NETCONF username. +To pass server identity check, the client must be connecting to `localhost`, which is the default +server domain if left empty. Once connected, the client will be identified with `tls-test` NETCONF username. ### TLS Call Home diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt index bcf516a8f..b3be0ab12 100644 --- a/cli/CMakeLists.txt +++ b/cli/CMakeLists.txt @@ -1,9 +1,13 @@ +if(NOT PROJECT_NAME) + message(FATAL_ERROR "Please use the root CMakeLists file instead.") +endif() + include(CheckFunctionExists) project(netopeer2-cli C) # set version -set(NP2CLI_VERSION 2.0.62) +set(NP2CLI_VERSION 2.0.64) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cli_version.h.in" "${PROJECT_BINARY_DIR}/cli_version.h" ESCAPE_QUOTES @ONLY) include_directories(${PROJECT_BINARY_DIR}) diff --git a/cli/commands.c b/cli/commands.c index caa47b66f..6d8931088 100644 --- a/cli/commands.c +++ b/cli/commands.c @@ -434,6 +434,8 @@ cli_send_recv(struct nc_rpc *rpc, FILE *output, NC_WD_MODE wd_mode, int timeout_ while (!lyd_find_sibling_opaq_next(info, "error-info", &info)) { fprintf(output, "\tinfo:\n"); lyd_print_file(stdout, lyd_child(info), LYD_XML, LYD_PRINT_WITHSIBLINGS); + + info = info->next; } fprintf(output, "\n"); } @@ -462,7 +464,7 @@ static char * trim_top_elem(char *data, const char *top_elem, const char *top_elem_ns) { char *ptr, *prefix = NULL, *buf; - int pref_len = 0, state = 0, quote; + int pref_len = 0, state = 0, quote, rc; /* state: -2 - syntax error, * -1 - top_elem not found, @@ -589,9 +591,12 @@ trim_top_elem(char *data, const char *top_elem, const char *top_elem_ns) /* ... but also its ending tag */ if (prefix) { - asprintf(&buf, "", pref_len, prefix, top_elem); + rc = asprintf(&buf, "", pref_len, prefix, top_elem); } else { - asprintf(&buf, "", top_elem); + rc = asprintf(&buf, "", top_elem); + } + if (rc == -1) { + return NULL; } ptr = strstr(data, buf); @@ -1474,7 +1479,9 @@ cmd_knownhosts(const char *arg, char **UNUSED(tmp_config_file)) return EXIT_FAILURE; } - asprintf(&kh_file, "%s/.ssh/known_hosts", pwd->pw_dir); + if (asprintf(&kh_file, "%s/.ssh/known_hosts", pwd->pw_dir) == -1) { + return EXIT_FAILURE; + } if ((file = fopen(kh_file, "r+")) == NULL) { ERROR("knownhosts", "Cannot open \"%s\" (%s)", kh_file, strerror(errno)); @@ -1764,10 +1771,12 @@ cp(const char *to, const char *from) buf = malloc(from_len); if (read(fd_from, buf, from_len) < from_len) { + free(buf); goto out_error; } if (write(fd_to, buf, from_len) < from_len) { + free(buf); goto out_error; } @@ -2008,7 +2017,10 @@ cmd_cert(const char *arg, char **UNUSED(tmp_config_file)) none = 0; name = strdup(d->d_name); name[strlen(name) - 4] = '\0'; - asprintf(&path, "%s/%s", trusted_dir, d->d_name); + if (asprintf(&path, "%s/%s", trusted_dir, d->d_name) == -1) { + free(name); + break; + } parse_cert(name, path); free(name); free(path); @@ -2280,7 +2292,10 @@ cmd_crl(const char *arg, char **UNUSED(tmp_config_file)) none = 0; name = strdup(d->d_name); name[strlen(name) - 4] = '\0'; - asprintf(&path, "%s/%s", crl_dir, d->d_name); + if (asprintf(&path, "%s/%s", crl_dir, d->d_name) == -1) { + free(name); + break; + } parse_crl(name, path); free(name); free(path); @@ -2438,10 +2453,14 @@ cmd_connect_listen_tls(struct arglist *cmd, int is_connect) } break; case 'c': - asprintf(&cert, "%s", optarg); + if (asprintf(&cert, "%s", optarg) == -1) { + return EXIT_FAILURE; + } break; case 'k': - asprintf(&key, "%s", optarg); + if (asprintf(&key, "%s", optarg) == -1) { + return EXIT_FAILURE; + } break; case 'r': trusted_store = optarg; @@ -4967,7 +4986,9 @@ cmd_getdata(const char *arg, char **tmp_config_file) break; case 'O': origin = realloc(origin, (origin_count + 1) * sizeof *origin); - asprintf(&origin[origin_count], "ietf-origin:%s", optarg); + if (asprintf(&origin[origin_count], "ietf-origin:%s", optarg) == -1) { + goto fail; + } ++origin_count; break; case 'n': diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..28bd779eb --- /dev/null +++ b/codecov.yml @@ -0,0 +1,32 @@ +comment: + layout: header, changes, diff + +coverage: + precision: 2 + round: nearest + + ignore: + - compat/.* + - tests/.* + - examples/.* + + status: + project: + default: + target: auto + if_no_uploads: error + + patch: + default: + if_no_uploads: error + + changes: true + + parsers: + gcov: + branch_detection: + macro: no + loop: no + conditional: no + method: no + diff --git a/example_configuration/tls_callhome.xml b/example_configuration/tls_callhome.xml index d9f5bb4ec..312fe0f89 100644 --- a/example_configuration/tls_callhome.xml +++ b/example_configuration/tls_callhome.xml @@ -28,7 +28,7 @@ 1 - 02:E9:38:1F:F6:8B:62:DE:0A:0B:C5:03:81:A8:03:49:A0:00:7F:8B:F3 + 02:20:E1:AD:CC:92:71:E9:EA:6A:85:DF:A7:FF:8C:BB:B9:D5:E4:EE:74 x509c2n:specified tls-test diff --git a/example_configuration/tls_certs/ca.key b/example_configuration/tls_certs/ca.key index fb005ce17..414233570 100644 --- a/example_configuration/tls_certs/ca.key +++ b/example_configuration/tls_certs/ca.key @@ -1,27 +1,30 @@ -----BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEArD3TDHPAMT2Z84orK4lMlarbgooIUCcRZyLe+QM+8KY8Hn+m -GaxPEOTSL3ywszqefB/Utm2hPKLHX684iRC14ID9WDGHxPjvoPArhgFhfV+qnPfx -KTgxZC12uOj4u1V9y+SkTCocFbRfXVBGpojrBuDHXkDMDEWNvr8/52YCv7bGaiBw -UHolcLCUbmtKILCG0RNJyTaJpXQdAeq5Z1SJotpbfYFFtAXB32hVoLug1dzl2tjG -9sb1wq3QaDExcbC5w6P65qOkNoyym9ne6QlQagCqVDyFn3vcqkRaTjvZmxauCeUx -XgJoXkyWcm0lM1KMHdoTArmchw2Dz0yHHSyDAQIDAQABAoIBAFH65y3hFhQZxuHU -3LFPG0WNWgdq3YQQ5EaboVcSRW3TIYA+r3c+vS9ESgpSJeRYvUBFAkCGM50huRWA -177dVkPyASNuB7on5h5K0dxpYdaDpzgpBv7ggRm2TfC66lB343UdcVnTHSTzggRv -BgGT35GZgSsKWlRo8otcifUAZ8SJWRv6UxmX0zuvqSj3Q49ucevb57/CmYdgGxCP -5flP/fqAdKen8/A03KPaltMERdo5xXbs5f7iBjcCZM1427Ta5cjiBW1zDWU4zbzn -9+unwWPEuuPaGGtS6500qBJy6mIgM/9nYP9LKz9sJMEVJJZLZc1/2pjbJSNEiNJV -SlJrvbECgYEA2u+xyfMsc2iE+dC8913NvIJnK7v7ixh5eu86SjJoYvflBvnEpPoX -XxWlWUkO5jR1Hk2v1Z4U4hD/OR1kUwAqbN3MdCDd00hkhgVns8AgZtH0aXmdz+xq -M0CKGXU7L/XS5mpiI8g24im1+1/rQjLxxUQjv0nfMxixa/ENmmtuisUCgYEAyWZ2 -CzAROlhxjaCbe+WopjG0AevCcrAPAeRgqIOm9sJ51q0cg2B6E/Zn27CvzKxsFzgM -+Vu3MoC0vVMK+Dc3o7idaQ0ew7kY5KO6LY8wDu5s3EGiS0KGJum2iIStE3lee/dd -TDcX6yE/3WYvvNf7w7uN7nme3s3EuSDDKpPFCw0CgYAaSnpxI/CMk1qUnUpz8iHI -p2g4SkS0uWWtK5k2W8NJTzeDlO7WWOoBkxneFPXjEx2VXALnhio/04aylyL7DKQL -mr74mxHIU4MuzOtdHI9HiaLuH5qh42QFb5Sl5fwLkFuZK+FJJrvggN3HqAcaVf/O -jpY0XGyfODHmInZdutT1eQKBgBAVoMPP+PBB8/+tnf1NICT1vzyQCZ2DNg+en6GV -shXu/jAI70gGwnkpqq2+9KtR8egAz/hyPLVJ1iVwpmWgc08eBWRIafaTp8tK0Cmn -T91BaWxFyaJdE72z2KIahoARp1wbK1ZU6BIdO66A5LsePLsrFXDAQdHleRqX5T5X -QttZAoGASjKxjcbfZN9Q4jfvof2tacpdKbof9K03tbcRRkcBBF/mtiK4ER+c7dPU -YcGJwOCT9YofASM0Qnq3F118Ic7DJAB332R1/UMY1krkCLAGfjAndL5XguFLpDQx -c3dqfZ38rGw4GIr/rGJsPInYzkLxTfoYit/9dZjLLoxxvwlLTLs= +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,BEF56FA6E8D1D119 + +hfFpAKP0pDKQ+JxK0venmTfMTJHmTBTkhwOmedO6GDu/EZjSjXYn+VZn7/qniuBg +kgeH1nuksXdH157csVG5xGkq2PKiLRfh18pc3/8RjTmsE/7B7BHMck9wRlEmg9Eu +VWl1qJ4waIxjOgiKeeXQBj1jwgN0G96L1B08jZTs/+BytEc8pJ/1VH+omG8632A1 +3MaOkIjZht3udpunSGiC4SKMQGtqiBxJj+gNbt12R5ESyuJd9JuDRkAXhytJOgY9 +GCAizmE7DEipqvlg+b8AaeSB0jHwu5FQmApNDmtIufB6/BuyKr1cjAs2gXQGlK8x +Brm/tDXGEJ77hn6o3sH5YmPLCSYQKuBAuw6sHlyQEEXj7b0ZvQ4Vs6nLdeBBtQFn +rWcGmPkXPLK+9u/1XrQcoPYKVk+lXSg32q+FTDFlybWhUjNU7dKZH0PU+zIqYyyR +B7WY7iYaXdT/ebxXRTeMkCatlgekyKP9lltH7388kYqfmD85jX0JPUtQW663S6nl +BHBwLKmD58AypfTkalbo9AcZYBVHrFUEfW4HVXrk6aBoUpqdJBRS/CZ8fRJMlKPs +sY3FFxhGmcE0MtStBBhOilVOow5kC9vKHJXXhtIDGwB+2SkgSthVdF86FaDGEBRF +REoiwB8nVw/1CTrUqgIahkIWSZg6i4yPIMvBcPRtHKfqtavn1N2qm6dFMQLDGMYJ ++IIxhcaw3N2mvJzPFPNT1yzTj4PJC75d/dEHopn0pjBz3+vKpAMr15hnC+s2JLw6 +PacgqZ6kq7a+ghK5CYrekxfG0M9qRZL8Y1rvc7RjiTyXD2tstfRbyKUt1lYbx8c+ +N0eHhk9KoSuSkDDs5tP6hoN0YQWY0D8SSEEI8eNAPME0d3j0JJ+DkknX2KQi/tFC +6yUsMoYPe869hvvearkUF0qkxSVblVOZ1mMsfoW8WGjbjxOGXyR9zIC4Sbn7zNm/ +zPwPblFFN9oFpiStUNzr0oYdYqO0UZuThAIkG+Yc508Lw/kUMzUaZdFykhgLUFdU +PsHN5NCQLatfUd+EoSc2W/+g1CeVJ5VA/rSdgfi7t7M5p991UX/9Ay7CtjbbMuQm +tj91vywjm9ECmmWD/8DtDxCfaWNTXPUUq/Z7UHFSOckz8VJcuKRZ0lbNfnb3r7GY +JHErIr6R0HpuPj7uTeehSUu7quqmurpRTJf27pNT21OWz28j1kOb3VhniKBJRD/u +xg8gV38lwyAiilLGXGRRoTY7Yl7sKz/MPlEsnQcL1id1/8Kx2XCja9mXoWni6mzQ +/qEdM+fTaLTVklVN+eZN/7iHiofsuMSRSLEvSCUCLB8FPlNNuVb5ntAabgvy2Ql9 +zUQflHhbFn5M76nDzA9kVi4fzTsr2+rjz/ndR4voEYeqv2WOqnFRUIzkUs1zTRlD +xIC3VlmO7n+/z9bXa03s8UPiu2cMOirKrB7ivaeY+GiOMhk8qLOmgP1rKZOpxgoU ++Bd9hSSfw5+sHvD3TJUO+XA/uuNrHcDDWS5N2EuS7ob9QlC8UsQWsviFc6/IzQTD +V2w+OC5K2NzkVTywrDn+FEzi0nBxrF8jjphTpqnSv0/gvPFSZFvyk1HzVORN+3EC -----END RSA PRIVATE KEY----- diff --git a/example_configuration/tls_certs/ca.pem b/example_configuration/tls_certs/ca.pem index 62593ab7c..b9f5a6f6c 100644 --- a/example_configuration/tls_certs/ca.pem +++ b/example_configuration/tls_certs/ca.pem @@ -1,24 +1,24 @@ -----BEGIN CERTIFICATE----- -MIID7TCCAtWgAwIBAgIJAMtE1NGAR5KoMA0GCSqGSIb3DQEBBQUAMIGMMQswCQYD -VQQGEwJDWjEWMBQGA1UECAwNU291dGggTW9yYXZpYTENMAsGA1UEBwwEQnJubzEP -MA0GA1UECgwGQ0VTTkVUMQwwCgYDVQQLDANUTUMxEzARBgNVBAMMCmV4YW1wbGUg -Q0ExIjAgBgkqhkiG9w0BCQEWE2V4YW1wbGVjYUBsb2NhbGhvc3QwHhcNMTQwNzI0 -MTQxOTAyWhcNMjQwNzIxMTQxOTAyWjCBjDELMAkGA1UEBhMCQ1oxFjAUBgNVBAgM -DVNvdXRoIE1vcmF2aWExDTALBgNVBAcMBEJybm8xDzANBgNVBAoMBkNFU05FVDEM -MAoGA1UECwwDVE1DMRMwEQYDVQQDDApleGFtcGxlIENBMSIwIAYJKoZIhvcNAQkB -FhNleGFtcGxlY2FAbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -CgKCAQEArD3TDHPAMT2Z84orK4lMlarbgooIUCcRZyLe+QM+8KY8Hn+mGaxPEOTS -L3ywszqefB/Utm2hPKLHX684iRC14ID9WDGHxPjvoPArhgFhfV+qnPfxKTgxZC12 -uOj4u1V9y+SkTCocFbRfXVBGpojrBuDHXkDMDEWNvr8/52YCv7bGaiBwUHolcLCU -bmtKILCG0RNJyTaJpXQdAeq5Z1SJotpbfYFFtAXB32hVoLug1dzl2tjG9sb1wq3Q -aDExcbC5w6P65qOkNoyym9ne6QlQagCqVDyFn3vcqkRaTjvZmxauCeUxXgJoXkyW -cm0lM1KMHdoTArmchw2Dz0yHHSyDAQIDAQABo1AwTjAdBgNVHQ4EFgQUc1YQIqjZ -sHVwlea0AB4N+ilNI2gwHwYDVR0jBBgwFoAUc1YQIqjZsHVwlea0AB4N+ilNI2gw -DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAI/1KH60qnw9Xs2RGfi0/ -IKf5EynXt4bQX8EIyVKwSkYKe04zZxYfLIl/Q2HOPYoFmm3daj5ddr0ZS1i4p4fT -UhstjsYWvXs3W/HhVmFUslakkn3PrswhP77fCk6eEJLxdfyJ1C7Uudq2m1isZbKi -h+XF0mG1LxJaDMocSz4eAya7M5brwjy8DoOmA1TnLQFCVcpn+sCr7VC4wE/JqxyV -hBCk/MuGqqM3B1j90bGFZ112ZOecyE0EDSr6IbiRBtmeNbEwOFjKXhNLYdxpBZ9D -8A/368OckZkCrVLGuJNxK9UwCVTe8IhotHUqU9EqFDmxdV8oIdU/OzUwwNPA/Bd/ -9g== +MIIEAzCCAuugAwIBAgIURc4sipHvJSlNrQIhRhZilBvV4RowDQYJKoZIhvcNAQEL +BQAwgZAxCzAJBgNVBAYTAkNaMRYwFAYDVQQIDA1Tb3V0aCBNb3JhdmlhMQ0wCwYD +VQQHDARCcm5vMRgwFgYDVQQKDA9DRVNORVQgei5zLnAuby4xDDAKBgNVBAsMA1RN +QzETMBEGA1UEAwwKZXhhbXBsZSBDQTEdMBsGCSqGSIb3DQEJARYOY2FAZXhhbXBs +ZS5vcmcwHhcNMjEwOTAzMTAyMTAxWhcNMzEwOTAxMTAyMTAxWjCBkDELMAkGA1UE +BhMCQ1oxFjAUBgNVBAgMDVNvdXRoIE1vcmF2aWExDTALBgNVBAcMBEJybm8xGDAW +BgNVBAoMD0NFU05FVCB6LnMucC5vLjEMMAoGA1UECwwDVE1DMRMwEQYDVQQDDApl +eGFtcGxlIENBMR0wGwYJKoZIhvcNAQkBFg5jYUBleGFtcGxlLm9yZzCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAN4Ld3JDDocyy9KXNJhEUPeZpQW3UdUN +Xloeh5n/bxasgThkBuQ7oF/nKyVUe517U1CJA993ZIc0jhIWThAnqXkz70DX5EZ7 +ancPd01MidA6T8k1RYYJWr+vyIRYYBYzK7LSnU6wMWqPTgzZB+KMWwb065ooLEB5 +XwqAeTIMPLRqM1Galewl4ZSuRJnrXxRjfF3AWNyC9dZw6wIg8cppvoLdBGQiFJQf +9SgiVy+HyedAytFEixqKAAIgQUJwhCgbEd6jGFbeaL8HT4MFp1VmaaUBQMkZj/Gn +KBwCk5BEMu76EN1pzHc4Dd6DabQNGCnsqOPe31yhQGmNFy9R6zNnWZMCAwEAAaNT +MFEwHQYDVR0OBBYEFM7w/pO8vk5oowvWPoCKo0RW/JcnMB8GA1UdIwQYMBaAFM7w +/pO8vk5oowvWPoCKo0RW/JcnMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggEBAG/xfYuRKnCyiwYC/K7kAjHmCNnLCr1mx8P1ECsSJPme3OThDTNeGf8i +N2952tGmMFDa+DaAwPc6Gt3cWTb/NYMTLWlt2yj5rJAcLXxIU0SMafBf+F7E/R8A +b/HDDjs0pQaJ0EJhQJVkMdfj3Wq9l0dJT5iEBUrUQflDufiMdEJEIGKZh86MgzEL +bcn1QX8dlLc91M2OifWStqLzXPicG+jjuoPUceC0flMQDb2qx03sxvJKfYfS5ArA +CqvdWyXLoP7DI9THJrMI/vBHJKpl4Wtmsh2OLn9VHauFMzPSGke5GwjXCpbXGepj +9qWN8Gd/FWgSDH2OBvZ6aHdB1pPjN9k= -----END CERTIFICATE----- diff --git a/example_configuration/tls_certs/client.crt b/example_configuration/tls_certs/client.crt index 8e52dacfd..3f5a03d15 100644 --- a/example_configuration/tls_certs/client.crt +++ b/example_configuration/tls_certs/client.crt @@ -1,24 +1,22 @@ -----BEGIN CERTIFICATE----- -MIIECTCCAvGgAwIBAgIBBzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCQ1ox -FjAUBgNVBAgMDVNvdXRoIE1vcmF2aWExDTALBgNVBAcMBEJybm8xDzANBgNVBAoM -BkNFU05FVDEMMAoGA1UECwwDVE1DMRMwEQYDVQQDDApleGFtcGxlIENBMSIwIAYJ -KoZIhvcNAQkBFhNleGFtcGxlY2FAbG9jYWxob3N0MB4XDTE1MDczMDA3MjcxOFoX -DTM1MDcyNTA3MjcxOFowgYUxCzAJBgNVBAYTAkNaMRYwFAYDVQQIDA1Tb3V0aCBN -b3JhdmlhMQ8wDQYDVQQKDAZDRVNORVQxDDAKBgNVBAsMA1RNQzEXMBUGA1UEAwwO -ZXhhbXBsZSBjbGllbnQxJjAkBgkqhkiG9w0BCQEWF2V4YW1wbGVjbGllbnRAbG9j -YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAueCQaNQWoNmF -K6LKu1p8U8ZWdWg/PvDdLsJyzfzl/Qw4UA68SfFNaY06zZl8QB9W02nr5kWeeMY0 -VA3adrPgOlvfx3oWlFbkETnMaN4OT3WTQ0Wt6jAWZDzVfopwpJPAzRPxACDftIqF -GagYcF32hZlVNqqnVdbXh0S0EViweqp/dbG4VDUHSNVbglc+u4UbEzNIFXMdEFsJ -ZpkynOmSiTsIATqIhb+2srkVgLwhfkC2qkuHQwAHdubuB07ObM2z01UhyEdDvEYG -HwtYAGDBL2TAcsI0oGeVkRyuOkV0QY0UN7UEFI1yTYw+xZ42HgFx3uGwApCImxhb -j69GBYWFqwIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVu -U1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUXGpLeLnh2cSDARAV -A7KrBxGYpo8wHwYDVR0jBBgwFoAUc1YQIqjZsHVwlea0AB4N+ilNI2gwDQYJKoZI -hvcNAQELBQADggEBAJPV3RTXFRtNyOU4rjPpYeBAIAFp2aqGc4t2J1c7oPp/1n+l -ZvjnwtlJpZHxMM783e2ryDQ6dkvXDf8kpwKlg3U3mkJ3xKkDdWrM4QwghXdCN519 -aa9qmu0zdFL+jUAaWlQ5tsceOrvbusCcbMqiFGk/QfpHqPv52SVWbYyUx7IX7DE+ -UjgsLHycfV/tlcx4ZE6soTzl9VdgSL/zmzG3rjsr58J80rXckLgBhvijgBlIAJvW -fC7D0vaouvBInSFXymdPVoUDZ30cdGLf+hI/i/TfsEMOinLrXVdkSGNo6FXAHKSv -XeB9oFKSzhQ7OPyRyqvEPycUSw/qD6FVr80oDDc= +MIIDqTCCApECFEf4LBXF80V6bTWn6kJ/RuccpJ/AMA0GCSqGSIb3DQEBCwUAMIGQ +MQswCQYDVQQGEwJDWjEWMBQGA1UECAwNU291dGggTW9yYXZpYTENMAsGA1UEBwwE +QnJubzEYMBYGA1UECgwPQ0VTTkVUIHoucy5wLm8uMQwwCgYDVQQLDANUTUMxEzAR +BgNVBAMMCmV4YW1wbGUgQ0ExHTAbBgkqhkiG9w0BCQEWDmNhQGV4YW1wbGUub3Jn +MB4XDTIxMDkwMzEwMjgxOFoXDTMxMDkwMTEwMjgxOFowgZAxCzAJBgNVBAYTAkNa +MRYwFAYDVQQIDA1Tb3V0aCBNb3JhdmlhMQ0wCwYDVQQHDARCcm5vMRgwFgYDVQQK +DA9DRVNORVQgei5zLnAuby4xDDAKBgNVBAsMA1RNQzEPMA0GA1UEAwwGY2xpZW50 +MSEwHwYJKoZIhvcNAQkBFhJjbGllbnRAZXhhbXBsZS5vcmcwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC8zbCEJ0MSbFN+KjWhQKbGVkG+TKKtxMatpKMJ +5lYDmB1dwWBHc+jj3+htAtc43BUTxQs/7Cb28VVp7kB1dGOd0b/HGL9n8cBmqRcx +e+NEhiIFWANy7dzSNF1uKbBLzjMpgjKBcZ4c4O80pBww0U0Q1DPZ/9AhU9GKgUHi +wfHKe8/it7T6lhQ2yXqClkW0xnzTiipYC6T3QNvTHWOCfzsUuHcVcA4seqZYMOOi +PqMujgUws2jJ2y0lDXg2bcayDwtahX2oZP+Fil9ifO5CLNv9mw+ze8RxXeDrtFEJ +zzZfmMp5cmj8LmvdzOszrxfdMoG28gxOGYXXunv3+e4vK32RAgMBAAEwDQYJKoZI +hvcNAQELBQADggEBANwq9tqHYiet73ZdZcsUA2Aoa7mdjrYvmTeb4rrRbW5KGxoV +hRJYAG/8OTZ3P8liorlPtWDci6iXBk/Ev8f1kqlC35xpRaidXSNAancAj4gS30sd +CAAj8KzuWmtYTWT5aR0eAV1UTA4mo2N7OidRk+vVWs5tbbRmFmE/aJj8+Ol8rCgq +RW08uLHy4pGljCDJWEiaIpPNflomui35r2F4bXp+7aoYmqL63XVkvlWyZBVQfN6p +05l2bTTn2N5IrqPq2r7PF0jZtnEnbFc5Rc7gW25EFym70JQQ2kXOft4++G/NK3Jc +Ck3IHwl7tNN7ZVChbMpbVH5TEV8QXR7DqYG0O7Y= -----END CERTIFICATE----- diff --git a/example_configuration/tls_certs/client.key b/example_configuration/tls_certs/client.key index 7ccdab10c..549fcaae7 100644 --- a/example_configuration/tls_certs/client.key +++ b/example_configuration/tls_certs/client.key @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAueCQaNQWoNmFK6LKu1p8U8ZWdWg/PvDdLsJyzfzl/Qw4UA68 -SfFNaY06zZl8QB9W02nr5kWeeMY0VA3adrPgOlvfx3oWlFbkETnMaN4OT3WTQ0Wt -6jAWZDzVfopwpJPAzRPxACDftIqFGagYcF32hZlVNqqnVdbXh0S0EViweqp/dbG4 -VDUHSNVbglc+u4UbEzNIFXMdEFsJZpkynOmSiTsIATqIhb+2srkVgLwhfkC2qkuH -QwAHdubuB07ObM2z01UhyEdDvEYGHwtYAGDBL2TAcsI0oGeVkRyuOkV0QY0UN7UE -FI1yTYw+xZ42HgFx3uGwApCImxhbj69GBYWFqwIDAQABAoIBAQCZN9kR8DGu6V7y -t0Ax68asL8O5B/OKaHWKQ9LqpVrXmikZJOxkbzoGldow/CIFoU+q+Zbwu9aDa65a -0wiP7Hoa4Py3q5XNNUrOQDyU/OYC7cI0I83WS0lJ2zOJGYj8wKae5Z81IeQFKGHK -4lsy1OGPAvPRGh7RjUUgRavA2MCwe07rWRuDb/OJFe4Oh56UMEjwMiNBtMNtncog -j1vr/qgRJdf9tf0zlJmLvUJ9+HSFFV9I/97LJyFhb95gAfHkjdVroLVgT3Cho+4P -WtZaKCIGD0OwfOG2nLV4leXvRUk62/LMlB8NI9+JF7Xm+HCKbaWHNWC7mvWSLV58 -Zl4AbUWRAoGBANyJ6SFHFRHSPDY026SsdMzXR0eUxBAK7G70oSBKKhY+O1j0ocLE -jI2krHJBhHbLlnvJVyMUaCUOTS5m0uDw9hgSsAqeSL3hL38kxVZw+KNG9Ouno1Fl -KnE/xXHlPQyeGs/P8nAMzHZxQtEsQdQayJEhK2XXHTsy7Q3MxDisfVJ1AoGBANfD -34gB+OMx6pwj7zk3qWbYXSX8xjCZMR0ciko+h4xeMP2N8B0oyoqC+v1ABMAtJ3wG -sGZd0hV9gwM7OUM3SEwkn6oeg1GemWLcn4rlSmTnZc4aeVwrEWlnSNFX3s4g9l4u -k8Ugu4MVJYqH8HuDQ5Ggl6/QAwPzMSEdCW0O+jOfAoGAIBRbegC5+t6m7Yegz4Ja -dxV1g98K6f58x+MDsQu4tYWV4mmrQgaPH2dtwizvlMwmdpkh+LNWNtWuumowkJHc -akIFo3XExQIFg6wYnGtQb4e5xrGa2xMpKlIJaXjb+YLiCYqJDG2ALFZrTrvuU2kV -9a5qfqTc1qigvNolTM0iaaUCgYApmrZWhnLUdEKV2wP813PNxfioI4afxlpHD8LG -sCn48gymR6E+Lihn7vuwq5B+8fYEH1ISWxLwW+RQUjIneNhy/jjfV8TgjyFqg7or -0Sy4KjpiNI6kLBXOakELRNNMkeSPopGR2E7v5rr3bGD9oAD+aqX1G7oJH/KgPPYd -Vl7+ZwKBgQDcHyWYrimjyUgKaQD2GmoO9wdcJYQ59ke9K+OuGlp4ti5arsi7N1tP -B4f09aeELM2ASIuk8Q/Mx0jQFnm8lzRFXdewgvdPoZW/7VufM9O7dGPOc41cm2Dh -yrTcXx/VmUBb+/fnXVEgCv7gylp/wtdTGHQBQJHR81jFBz0lnLj+gg== +MIIEpgIBAAKCAQEAvM2whCdDEmxTfio1oUCmxlZBvkyircTGraSjCeZWA5gdXcFg +R3Po49/obQLXONwVE8ULP+wm9vFVae5AdXRjndG/xxi/Z/HAZqkXMXvjRIYiBVgD +cu3c0jRdbimwS84zKYIygXGeHODvNKQcMNFNENQz2f/QIVPRioFB4sHxynvP4re0 ++pYUNsl6gpZFtMZ804oqWAuk90Db0x1jgn87FLh3FXAOLHqmWDDjoj6jLo4FMLNo +ydstJQ14Nm3Gsg8LWoV9qGT/hYpfYnzuQizb/ZsPs3vEcV3g67RRCc82X5jKeXJo +/C5r3czrM68X3TKBtvIMThmF17p79/nuLyt9kQIDAQABAoIBAQCOWLoj+QINqtSM +Q8CpcfgLg08P7fGc98YfdwhhV2M0VISXgktXs+E7pT40qjagLPZLMH2Z1S9PcYbH +VhUNORI+E7z2nAb7lH5OKGBPM6uWp1aRFtmK1iFt7oMeopnDnZRfUEVJ6OKfvUs8 +Mhr7B2KGNKdfTgqahfpu5aNKFpV45fGaPFhIeKJeTFExVQNmcHwxzQPqeocOHuEF +XaOzLidXM9KT9gGpsf0Fo0rv6K9oZGO3C5GRnf2jkWL0PpoJLWjR7fNGWZp1nsGa +oG8m9wjb6KYkQwrrvcVmzjPcmwvfcG0FBUMLwMNKiJ5/nispbR0TpL7nt01fG2P1 +rwyunbVtAoGBAO8RjNHJt7FFOi82xKcJNe6SwD6p8TRJNW9j1MxhOS/yaq9DDyVR +pwFHZo7pK/TU4HBc6DYyFN9W5+4Tn/cWmuu9YfimhNquoFO531qF3ltJ6ZY+wRs3 +cYzQouC7uww68PdLGGE2vxJLXuvVrmsbevh3+VKtLdqXO8v618KCc+3/AoGBAMos +zgJ0Rjxm+VObvYjJz+fzaLCNNORJO4C47F9woy4inSUOO2P30QEBRC+UPjM5AJiz +Up8X1NCOW43o3zfAmBhtDLIwhrW8fWc/OQeqDt7GM9Q2a8yyXPACqDqsBDxTeu3M +hEwACzXuHRscQyIRV8HHCWph3xwTwLGG9ZIEcLRvAoGBAIbsNbh0ispuUo8w7r2C +skBp7Duxd6LVqmWqRv/t4vOPcexmAVdDhOhw3o3LRPaRafWgSaHElAkUKCMySjaO +OHLRWEiX2iT9Jxj5rveM09hbl4wm8J8mpFwfp70D1mXpofM/G4xJ9H4jsXeSCjUC +tl0igMDLYjSa47GUaU6qhzkLAoGBAMID9T7NrolQmHvvvReD9Ay3vgOPvu5EiOGi +lNOSGEax2PQykDQDIYNBX9n4/SfS0Au6KtOZ3xS1SI8Kpwutu0fVfpWRk/TbicyH +E4eTXunScvJ3t0Oc9yssoZyMbxQlWJbT6TG16Qw8EZpuqM4Mrpa7FwIMIjujiQvU +Y91YfX/pAoGBANtGPuVorE8U7PmAfTvEUKwvpDUz2QWZCOYOazBMrXNVsWLSdaaI +6ZQosKgQ9ThVYwItqE1dBp5vjPQKwjBGW2EbCHl2XUJXh9cH87P3kyYAc9sqeQGf ++dSOkNEWsmFl1VwaCUt6L+jqhfBGQQK1bAHmjdRfsaj1gZyE9OoHfvk2 -----END RSA PRIVATE KEY----- diff --git a/example_configuration/tls_certs/server.crt b/example_configuration/tls_certs/server.crt index c0e03a3f0..b129e9c34 100644 --- a/example_configuration/tls_certs/server.crt +++ b/example_configuration/tls_certs/server.crt @@ -1,24 +1,22 @@ -----BEGIN CERTIFICATE----- -MIIECTCCAvGgAwIBAgIBCDANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCQ1ox -FjAUBgNVBAgMDVNvdXRoIE1vcmF2aWExDTALBgNVBAcMBEJybm8xDzANBgNVBAoM -BkNFU05FVDEMMAoGA1UECwwDVE1DMRMwEQYDVQQDDApleGFtcGxlIENBMSIwIAYJ -KoZIhvcNAQkBFhNleGFtcGxlY2FAbG9jYWxob3N0MB4XDTE1MDczMDA3MjU1MFoX -DTM1MDcyNTA3MjU1MFowgYUxCzAJBgNVBAYTAkNaMRYwFAYDVQQIDA1Tb3V0aCBN -b3JhdmlhMQ8wDQYDVQQKDAZDRVNORVQxDDAKBgNVBAsMA1RNQzEXMBUGA1UEAwwO -ZXhhbXBsZSBzZXJ2ZXIxJjAkBgkqhkiG9w0BCQEWF2V4YW1wbGVzZXJ2ZXJAbG9j -YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsdI1TBjzX1Pg -QXFuPCw5/kQwU7qkrhirMcFAXhI8EoXepPa9fKAVuMjHW32P6nNzDpnhFe0YGdNl -oIEN3hJJ87cVOqj4o7zZMbq3zVG2L8As7MTA8tYXm2fSC/0rIxxRRemcGUXM0q+4 -LEACjZj2pOKonaivF5VbhgNjPCO1Jj/TamUc0aViE577C9L9EiObGM+bGbabWk/K -WKLsvxUc+sKZXaJ7psTVgpggJAkUszlmwOQgFiMSR53E9/CAkQYhzGVCmH44Vs6H -zs3RZjOTbce4wr4ongiA5LbPeSNSCFjy9loKpaE1rtOjkNBVdiNPCQTmLuODXUTK -gkeL+9v/OwIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVu -U1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU83qEtQDFzDvLoaII -vqiU6k7j1uswHwYDVR0jBBgwFoAUc1YQIqjZsHVwlea0AB4N+ilNI2gwDQYJKoZI -hvcNAQELBQADggEBAJ+QOLi4gPWGofMkLTqSsbv5xRvTw0xa/sJnEeiejtygAu3o -McAsyevSH9EYVPCANxzISPzd9SFaO56HxWgcxLn9vi8ZNvo2wIp9zucNu285ced1 -K/2nDZfBmvBxXnj/n7spwqOyuoIc8sR7P7YyI806Qsfhk3ybNZE5UHJFZKDRQKvR -J1t4nk9saeo87kIuNEDfYNdwYZzRfXoGJ5qIJQK+uJJv9noaIhfFowDW/G14Ji5p -Vh/YtvnOPh7aBjOj8jmzk8MqzK+TZgT7GWu48Nd/NaV8g/DNg9hlN047LaNsJly3 -NX3+VBlpMnA4rKwl1OnmYSirIVh9RJqNwqe6k/k= +MIIDrDCCApQCFEf4LBXF80V6bTWn6kJ/RuccpJ/BMA0GCSqGSIb3DQEBCwUAMIGQ +MQswCQYDVQQGEwJDWjEWMBQGA1UECAwNU291dGggTW9yYXZpYTENMAsGA1UEBwwE +QnJubzEYMBYGA1UECgwPQ0VTTkVUIHoucy5wLm8uMQwwCgYDVQQLDANUTUMxEzAR +BgNVBAMMCmV4YW1wbGUgQ0ExHTAbBgkqhkiG9w0BCQEWDmNhQGV4YW1wbGUub3Jn +MB4XDTIxMDkwMzEwMzAyMloXDTMxMDkwMTEwMzAyMlowgZMxCzAJBgNVBAYTAkNa +MRYwFAYDVQQIDA1Tb3V0aCBNb3JhdmlhMQ0wCwYDVQQHDARCcm5vMRgwFgYDVQQK +DA9DRVNORVQgei5zLnAuby4xDDAKBgNVBAsMA1RNQzESMBAGA1UEAwwJbG9jYWxo +b3N0MSEwHwYJKoZIhvcNAQkBFhJzZXJ2ZXJAZXhhbXBsZS5vcmcwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDdWuAUE5z+Rk4AMMUSzd2GS08nIH4Q+RFI +PUSdMO2WytHTAbseAAD6B2gTBtvZyonFgfmGFR5zRqUNDiXs9R73JzDov31mwmxF +MACVwPQmGmA+eAC5fbg1WpAO/sKDljHcE5L5DSk7oYbK46/YNzND0Etg8BQCV1oA +hprjbhhoknihQ1tLvoXCpOz5YNpa2SpBECo9r/3ODiFuLIJClNs816xTkZxO5ZCm +65anDVmpdErAn3aRhE0YJRie0ljBJ0qeG6SQQdjExhV1/BobfEWY8IMrLk5UEmbC +7NZkLsoesIrvJPMZnv+LMBZv7cnOHBiqHOr9kXXt5lYL8kPJSQEvAgMBAAEwDQYJ +KoZIhvcNAQELBQADggEBAGIBnIrbkvRThfXyhWwf4522fTer4lP0znXCNuQ8/86i +rl6ZWMF2H0HuUha2g1mqAUjJPeM5x2PHr0Ulm3greKXmHl9Oto4SXPOO8tRD/tE0 +9KRGiV/fk+MzFJqbMQj4Dq9P8yOxMMAj95RKN+fH2cIwOKSe8s3gPjldYDPOwIHZ +hWw3g0LCu/XVhNV2Os3wcWMx/ZWAvSfBUlldc5/dFW3vCy9UpcNj0lfs2LOcAx7R +8mdd7wz/9iIZ6mE0OIh1alqKELAbf2hxX6WLoNCYZeVxixsbKhwgAG0XateRjkGj +Cy7V0q1dpOIs95stxRKNuOlFHXXyCBG1JzpPTfV+RL0= -----END CERTIFICATE----- diff --git a/example_configuration/tls_certs/server.key b/example_configuration/tls_certs/server.key index d61c77bdf..08178b642 100644 --- a/example_configuration/tls_certs/server.key +++ b/example_configuration/tls_certs/server.key @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAsdI1TBjzX1PgQXFuPCw5/kQwU7qkrhirMcFAXhI8EoXepPa9 -fKAVuMjHW32P6nNzDpnhFe0YGdNloIEN3hJJ87cVOqj4o7zZMbq3zVG2L8As7MTA -8tYXm2fSC/0rIxxRRemcGUXM0q+4LEACjZj2pOKonaivF5VbhgNjPCO1Jj/TamUc -0aViE577C9L9EiObGM+bGbabWk/KWKLsvxUc+sKZXaJ7psTVgpggJAkUszlmwOQg -FiMSR53E9/CAkQYhzGVCmH44Vs6Hzs3RZjOTbce4wr4ongiA5LbPeSNSCFjy9loK -paE1rtOjkNBVdiNPCQTmLuODXUTKgkeL+9v/OwIDAQABAoIBAG/4MG1JbL4C/7vV -pBcpth7Aaznd1eJ2UB4VVOWnT8JOH2L6p1h5KRRhAP9AMkXsCnAQPyZiVAG3FlAZ -01SZaY2YJDr6uQ3JVW4155TWtgSdWux//Ass+lJ17lJ0SRxjsV13ez6CsDWeRjc+ -2xy0S+KJgqk71XzhJG9fZLYyuddp3U/i3xFPUAcQM9xXKxcaD7g6LJf+a9pt6rim -Eqq/pjJxDgTsRLARsazYuxrlOB445mvnLiYhOf2/MvI80jIUKaj8BeAhg49UIg/k -mIh0xdevkcxBFer/BjBjscWaFjx14D6nkFMw7vtCum5KfalLN2edZKAzByOudGD4 -5KnRp3ECgYEA6vnSoNGg9Do80JOpXRGYWhcR1lIDO5yRW5rVagncCcW5Pn/GMtNd -x2q6k1ks8mXKR9CxZrxZGqeYObZ9a/5SLih7ZkpiVWXG8ZiBIPhP6lnwm5OeIqLa -hr0BYWcRfrGg1phj5uySZgsVBE+D8jH42O9ccdvrWv1OiryAHfKIcwMCgYEAwbs+ -HfQtvHOQXSYNhtOeA7IetkGy3cKVg2oILNcROvI96hS0MZKt1Rko0UAapx96eCIr -el7vfdT0eUzNqt2wTKp1zmiG+SnX3fMDJNzMwu/jb/b4wQ20IHWNDnqcqTUVRUnL -iksLFoHbTxsN5NpEQExcSt/zzP4qi1W2Bmo18WkCgYEAnhrk16LVux9ohiulHONW -8N9u+BeM51JtGAcxrDzgGo85Gs2czdwc0K6GxdiN/rfxCKtqgqcfCWlVaxfYgo7I -OxiwF17blXx7BVrJICcUlqpX1Ebac5HCmkCYqjJQuj/I6jv1lI7/3rt8M79RF+j5 -+PXt7Qq97SZd78nwJrZni4MCgYAiPjZ8lOyAouyhilhZvI3xmUpUbMhw6jQDRnqr -clhZUvgeqAoxuPuA7zGHywzq/WVoVqHYv28Vjs6noiu4R/chlf+8vD0fTYYadRnZ -Ki4HRt+sqrrNZN6x3hVQudt3DSr1VFXl293Z3JonIWETUoE93EFz+qHdWg+rETtb -ZuqiAQKBgD+HI/syLECyO8UynuEaDD7qPl87PJ/CmZLMxa2/ZZUjhaXAW7CJMaS6 -9PIzsLk33y3O4Qer0wx/tEdfnxMTBJrgGt/lFFdAKhSJroZ45l5apiavg1oZYp89 -jSd0lVxWSmrBjBZLnqOl336gzaBVkBD5ND+XUPdR1UuVQExJlem4 +MIIEogIBAAKCAQEA3VrgFBOc/kZOADDFEs3dhktPJyB+EPkRSD1EnTDtlsrR0wG7 +HgAA+gdoEwbb2cqJxYH5hhUec0alDQ4l7PUe9ycw6L99ZsJsRTAAlcD0JhpgPngA +uX24NVqQDv7Cg5Yx3BOS+Q0pO6GGyuOv2DczQ9BLYPAUAldaAIaa424YaJJ4oUNb +S76FwqTs+WDaWtkqQRAqPa/9zg4hbiyCQpTbPNesU5GcTuWQpuuWpw1ZqXRKwJ92 +kYRNGCUYntJYwSdKnhukkEHYxMYVdfwaG3xFmPCDKy5OVBJmwuzWZC7KHrCK7yTz +GZ7/izAWb+3JzhwYqhzq/ZF17eZWC/JDyUkBLwIDAQABAoIBAHWhVFD290fc/ph1 +UlUi12UFYkPNrZDBeyCjhnHuTWQD1itG0TQpFlvIUdNCotSDIGG4J2zMjkj+MrnU +We0pedInnoMhN7fC/BxsXPM3/ca934Vy6heoqpqXzNRbJ+0bhNWKBWGaT94jgWkS +RCEnfHO+HkCedFOmLer3nRndKNVwe+bHoXqnsXdOflc3mb71/BM/qfJI3GZBDTZa +odTsS2LkuW4DTSNkxGqfZ0Bu1LyYm4mQ50/3nej9ILoT/ejnd17SPNpoBNnEyVJb +Exrk9pEoFPPKxQ3rCve7FE7y+ILmB4BdiSZm5lOyyJl1Y2Had6fZDubaNKACNWHM +kdJ1GokCgYEA8dV97LUZbtgpMyKdzvAfSCXmGyRtucp2FXy5/9UXYU8MXMcOgbMp +yrZLbfEwk0YMxKOw8nI4gCaSvIJb0Pek/agjaXIajoYvVIOhvDEq3+2JeT0eQh+9 +ue5bVOq65FHorDyRV64wwEucBN5CTCaEHdtEJY5WFkOzbrXVipjQxBsCgYEA6lJI +m/Yzn92GQw/44XweoWtATtBwDDMvlWWQ8I7U5Rl6ZItPAGuaMmzDolAZjlRPoEPm +xPpLKXYgCahkNnfPuuMgI8wA/65uZAqKfOuiGoIyj9oObJ/xb2zI3s4V8EwtfUE+ +lgg4D29/Cy33V9G6gHgl+CHjT00AZuvHTBsfwH0CgYAJyTXbSkjJL34bT59LLHRX +mxEAsCywg/zbSbzNGXZkvaomZvezT+i1B0NuI4BvtTn3Cxix9uVKakUt06ibgCnx +CcjFD5T7h3qK1PjKgMLXZOlXOp3q1xX6XCbd/NGrQ5VCwwCup6HZZjXeDJBqPHTE +MIdFbckWBY9RP5JwlVZ9WQKBgCy/8iX26v0I7W85SaqmbaMePHXQ0NVDoT7C2t9W +J8ppBzrUcA4Afr5Kj0IcUgUgjORqk1PjCR+t84hkpF7SmtVyMt0jRL2Prn1klfYt +ehPd8ZIPbtnH4fAJsoL6kK4HnlhhcXZts2cfP//+k1IuN5P5Xib5MdQfPIhrVvBt +7a5xAoGAH59S+aygEEIVf5pO8QmqQUpe3WbbuDvlAx3agP2jkeH5A7ZlxFpH7VJS +GAoPIsX+nlixUH1P3Esw9ch7ByJ/JRFUX5+G5coTBQj+PAkyGKtmBmnBFxZydMpT +dRMyDrKNKpeMZv7yn05YwnbZdS74L49mkY3pFrjRMDH1ltBxobk= -----END RSA PRIVATE KEY----- diff --git a/example_configuration/tls_keystore.xml b/example_configuration/tls_keystore.xml index f91f58eef..add1877e5 100644 --- a/example_configuration/tls_keystore.xml +++ b/example_configuration/tls_keystore.xml @@ -3,63 +3,12 @@ serverkey rsa2048 - MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsdI1TBjzX1PgQXFuPCw5 -/kQwU7qkrhirMcFAXhI8EoXepPa9fKAVuMjHW32P6nNzDpnhFe0YGdNloIEN3hJJ -87cVOqj4o7zZMbq3zVG2L8As7MTA8tYXm2fSC/0rIxxRRemcGUXM0q+4LEACjZj2 -pOKonaivF5VbhgNjPCO1Jj/TamUc0aViE577C9L9EiObGM+bGbabWk/KWKLsvxUc -+sKZXaJ7psTVgpggJAkUszlmwOQgFiMSR53E9/CAkQYhzGVCmH44Vs6Hzs3RZjOT -bce4wr4ongiA5LbPeSNSCFjy9loKpaE1rtOjkNBVdiNPCQTmLuODXUTKgkeL+9v/ -OwIDAQAB - MIIEowIBAAKCAQEAsdI1TBjzX1PgQXFuPCw5/kQwU7qkrhirMcFAXhI8EoXepPa9 -fKAVuMjHW32P6nNzDpnhFe0YGdNloIEN3hJJ87cVOqj4o7zZMbq3zVG2L8As7MTA -8tYXm2fSC/0rIxxRRemcGUXM0q+4LEACjZj2pOKonaivF5VbhgNjPCO1Jj/TamUc -0aViE577C9L9EiObGM+bGbabWk/KWKLsvxUc+sKZXaJ7psTVgpggJAkUszlmwOQg -FiMSR53E9/CAkQYhzGVCmH44Vs6Hzs3RZjOTbce4wr4ongiA5LbPeSNSCFjy9loK -paE1rtOjkNBVdiNPCQTmLuODXUTKgkeL+9v/OwIDAQABAoIBAG/4MG1JbL4C/7vV -pBcpth7Aaznd1eJ2UB4VVOWnT8JOH2L6p1h5KRRhAP9AMkXsCnAQPyZiVAG3FlAZ -01SZaY2YJDr6uQ3JVW4155TWtgSdWux//Ass+lJ17lJ0SRxjsV13ez6CsDWeRjc+ -2xy0S+KJgqk71XzhJG9fZLYyuddp3U/i3xFPUAcQM9xXKxcaD7g6LJf+a9pt6rim -Eqq/pjJxDgTsRLARsazYuxrlOB445mvnLiYhOf2/MvI80jIUKaj8BeAhg49UIg/k -mIh0xdevkcxBFer/BjBjscWaFjx14D6nkFMw7vtCum5KfalLN2edZKAzByOudGD4 -5KnRp3ECgYEA6vnSoNGg9Do80JOpXRGYWhcR1lIDO5yRW5rVagncCcW5Pn/GMtNd -x2q6k1ks8mXKR9CxZrxZGqeYObZ9a/5SLih7ZkpiVWXG8ZiBIPhP6lnwm5OeIqLa -hr0BYWcRfrGg1phj5uySZgsVBE+D8jH42O9ccdvrWv1OiryAHfKIcwMCgYEAwbs+ -HfQtvHOQXSYNhtOeA7IetkGy3cKVg2oILNcROvI96hS0MZKt1Rko0UAapx96eCIr -el7vfdT0eUzNqt2wTKp1zmiG+SnX3fMDJNzMwu/jb/b4wQ20IHWNDnqcqTUVRUnL -iksLFoHbTxsN5NpEQExcSt/zzP4qi1W2Bmo18WkCgYEAnhrk16LVux9ohiulHONW -8N9u+BeM51JtGAcxrDzgGo85Gs2czdwc0K6GxdiN/rfxCKtqgqcfCWlVaxfYgo7I -OxiwF17blXx7BVrJICcUlqpX1Ebac5HCmkCYqjJQuj/I6jv1lI7/3rt8M79RF+j5 -+PXt7Qq97SZd78nwJrZni4MCgYAiPjZ8lOyAouyhilhZvI3xmUpUbMhw6jQDRnqr -clhZUvgeqAoxuPuA7zGHywzq/WVoVqHYv28Vjs6noiu4R/chlf+8vD0fTYYadRnZ -Ki4HRt+sqrrNZN6x3hVQudt3DSr1VFXl293Z3JonIWETUoE93EFz+qHdWg+rETtb -ZuqiAQKBgD+HI/syLECyO8UynuEaDD7qPl87PJ/CmZLMxa2/ZZUjhaXAW7CJMaS6 -9PIzsLk33y3O4Qer0wx/tEdfnxMTBJrgGt/lFFdAKhSJroZ45l5apiavg1oZYp89 -jSd0lVxWSmrBjBZLnqOl336gzaBVkBD5ND+XUPdR1UuVQExJlem4 + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3VrgFBOc/kZOADDFEs3dhktPJyB+EPkRSD1EnTDtlsrR0wG7HgAA+gdoEwbb2cqJxYH5hhUec0alDQ4l7PUe9ycw6L99ZsJsRTAAlcD0JhpgPngAuX24NVqQDv7Cg5Yx3BOS+Q0pO6GGyuOv2DczQ9BLYPAUAldaAIaa424YaJJ4oUNbS76FwqTs+WDaWtkqQRAqPa/9zg4hbiyCQpTbPNesU5GcTuWQpuuWpw1ZqXRKwJ92kYRNGCUYntJYwSdKnhukkEHYxMYVdfwaG3xFmPCDKy5OVBJmwuzWZC7KHrCK7yTzGZ7/izAWb+3JzhwYqhzq/ZF17eZWC/JDyUkBLwIDAQAB + MIIEogIBAAKCAQEA3VrgFBOc/kZOADDFEs3dhktPJyB+EPkRSD1EnTDtlsrR0wG7HgAA+gdoEwbb2cqJxYH5hhUec0alDQ4l7PUe9ycw6L99ZsJsRTAAlcD0JhpgPngAuX24NVqQDv7Cg5Yx3BOS+Q0pO6GGyuOv2DczQ9BLYPAUAldaAIaa424YaJJ4oUNbS76FwqTs+WDaWtkqQRAqPa/9zg4hbiyCQpTbPNesU5GcTuWQpuuWpw1ZqXRKwJ92kYRNGCUYntJYwSdKnhukkEHYxMYVdfwaG3xFmPCDKy5OVBJmwuzWZC7KHrCK7yTzGZ7/izAWb+3JzhwYqhzq/ZF17eZWC/JDyUkBLwIDAQABAoIBAHWhVFD290fc/ph1UlUi12UFYkPNrZDBeyCjhnHuTWQD1itG0TQpFlvIUdNCotSDIGG4J2zMjkj+MrnUWe0pedInnoMhN7fC/BxsXPM3/ca934Vy6heoqpqXzNRbJ+0bhNWKBWGaT94jgWkSRCEnfHO+HkCedFOmLer3nRndKNVwe+bHoXqnsXdOflc3mb71/BM/qfJI3GZBDTZaodTsS2LkuW4DTSNkxGqfZ0Bu1LyYm4mQ50/3nej9ILoT/ejnd17SPNpoBNnEyVJbExrk9pEoFPPKxQ3rCve7FE7y+ILmB4BdiSZm5lOyyJl1Y2Had6fZDubaNKACNWHMkdJ1GokCgYEA8dV97LUZbtgpMyKdzvAfSCXmGyRtucp2FXy5/9UXYU8MXMcOgbMpyrZLbfEwk0YMxKOw8nI4gCaSvIJb0Pek/agjaXIajoYvVIOhvDEq3+2JeT0eQh+9ue5bVOq65FHorDyRV64wwEucBN5CTCaEHdtEJY5WFkOzbrXVipjQxBsCgYEA6lJIm/Yzn92GQw/44XweoWtATtBwDDMvlWWQ8I7U5Rl6ZItPAGuaMmzDolAZjlRPoEPmxPpLKXYgCahkNnfPuuMgI8wA/65uZAqKfOuiGoIyj9oObJ/xb2zI3s4V8EwtfUE+lgg4D29/Cy33V9G6gHgl+CHjT00AZuvHTBsfwH0CgYAJyTXbSkjJL34bT59LLHRXmxEAsCywg/zbSbzNGXZkvaomZvezT+i1B0NuI4BvtTn3Cxix9uVKakUt06ibgCnxCcjFD5T7h3qK1PjKgMLXZOlXOp3q1xX6XCbd/NGrQ5VCwwCup6HZZjXeDJBqPHTEMIdFbckWBY9RP5JwlVZ9WQKBgCy/8iX26v0I7W85SaqmbaMePHXQ0NVDoT7C2t9WJ8ppBzrUcA4Afr5Kj0IcUgUgjORqk1PjCR+t84hkpF7SmtVyMt0jRL2Prn1klfYtehPd8ZIPbtnH4fAJsoL6kK4HnlhhcXZts2cfP//+k1IuN5P5Xib5MdQfPIhrVvBt7a5xAoGAH59S+aygEEIVf5pO8QmqQUpe3WbbuDvlAx3agP2jkeH5A7ZlxFpH7VJSGAoPIsX+nlixUH1P3Esw9ch7ByJ/JRFUX5+G5coTBQj+PAkyGKtmBmnBFxZydMpTdRMyDrKNKpeMZv7yn05YwnbZdS74L49mkY3pFrjRMDH1ltBxobk= servercert - MIIECTCCAvGgAwIBAgIBCDANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCQ1ox -FjAUBgNVBAgMDVNvdXRoIE1vcmF2aWExDTALBgNVBAcMBEJybm8xDzANBgNVBAoM -BkNFU05FVDEMMAoGA1UECwwDVE1DMRMwEQYDVQQDDApleGFtcGxlIENBMSIwIAYJ -KoZIhvcNAQkBFhNleGFtcGxlY2FAbG9jYWxob3N0MB4XDTE1MDczMDA3MjU1MFoX -DTM1MDcyNTA3MjU1MFowgYUxCzAJBgNVBAYTAkNaMRYwFAYDVQQIDA1Tb3V0aCBN -b3JhdmlhMQ8wDQYDVQQKDAZDRVNORVQxDDAKBgNVBAsMA1RNQzEXMBUGA1UEAwwO -ZXhhbXBsZSBzZXJ2ZXIxJjAkBgkqhkiG9w0BCQEWF2V4YW1wbGVzZXJ2ZXJAbG9j -YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsdI1TBjzX1Pg -QXFuPCw5/kQwU7qkrhirMcFAXhI8EoXepPa9fKAVuMjHW32P6nNzDpnhFe0YGdNl -oIEN3hJJ87cVOqj4o7zZMbq3zVG2L8As7MTA8tYXm2fSC/0rIxxRRemcGUXM0q+4 -LEACjZj2pOKonaivF5VbhgNjPCO1Jj/TamUc0aViE577C9L9EiObGM+bGbabWk/K -WKLsvxUc+sKZXaJ7psTVgpggJAkUszlmwOQgFiMSR53E9/CAkQYhzGVCmH44Vs6H -zs3RZjOTbce4wr4ongiA5LbPeSNSCFjy9loKpaE1rtOjkNBVdiNPCQTmLuODXUTK -gkeL+9v/OwIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVu -U1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU83qEtQDFzDvLoaII -vqiU6k7j1uswHwYDVR0jBBgwFoAUc1YQIqjZsHVwlea0AB4N+ilNI2gwDQYJKoZI -hvcNAQELBQADggEBAJ+QOLi4gPWGofMkLTqSsbv5xRvTw0xa/sJnEeiejtygAu3o -McAsyevSH9EYVPCANxzISPzd9SFaO56HxWgcxLn9vi8ZNvo2wIp9zucNu285ced1 -K/2nDZfBmvBxXnj/n7spwqOyuoIc8sR7P7YyI806Qsfhk3ybNZE5UHJFZKDRQKvR -J1t4nk9saeo87kIuNEDfYNdwYZzRfXoGJ5qIJQK+uJJv9noaIhfFowDW/G14Ji5p -Vh/YtvnOPh7aBjOj8jmzk8MqzK+TZgT7GWu48Nd/NaV8g/DNg9hlN047LaNsJly3 -NX3+VBlpMnA4rKwl1OnmYSirIVh9RJqNwqe6k/k= + MIIDrDCCApQCFEf4LBXF80V6bTWn6kJ/RuccpJ/BMA0GCSqGSIb3DQEBCwUAMIGQMQswCQYDVQQGEwJDWjEWMBQGA1UECAwNU291dGggTW9yYXZpYTENMAsGA1UEBwwEQnJubzEYMBYGA1UECgwPQ0VTTkVUIHoucy5wLm8uMQwwCgYDVQQLDANUTUMxEzARBgNVBAMMCmV4YW1wbGUgQ0ExHTAbBgkqhkiG9w0BCQEWDmNhQGV4YW1wbGUub3JnMB4XDTIxMDkwMzEwMzAyMloXDTMxMDkwMTEwMzAyMlowgZMxCzAJBgNVBAYTAkNaMRYwFAYDVQQIDA1Tb3V0aCBNb3JhdmlhMQ0wCwYDVQQHDARCcm5vMRgwFgYDVQQKDA9DRVNORVQgei5zLnAuby4xDDAKBgNVBAsMA1RNQzESMBAGA1UEAwwJbG9jYWxob3N0MSEwHwYJKoZIhvcNAQkBFhJzZXJ2ZXJAZXhhbXBsZS5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDdWuAUE5z+Rk4AMMUSzd2GS08nIH4Q+RFIPUSdMO2WytHTAbseAAD6B2gTBtvZyonFgfmGFR5zRqUNDiXs9R73JzDov31mwmxFMACVwPQmGmA+eAC5fbg1WpAO/sKDljHcE5L5DSk7oYbK46/YNzND0Etg8BQCV1oAhprjbhhoknihQ1tLvoXCpOz5YNpa2SpBECo9r/3ODiFuLIJClNs816xTkZxO5ZCm65anDVmpdErAn3aRhE0YJRie0ljBJ0qeG6SQQdjExhV1/BobfEWY8IMrLk5UEmbC7NZkLsoesIrvJPMZnv+LMBZv7cnOHBiqHOr9kXXt5lYL8kPJSQEvAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGIBnIrbkvRThfXyhWwf4522fTer4lP0znXCNuQ8/86irl6ZWMF2H0HuUha2g1mqAUjJPeM5x2PHr0Ulm3greKXmHl9Oto4SXPOO8tRD/tE09KRGiV/fk+MzFJqbMQj4Dq9P8yOxMMAj95RKN+fH2cIwOKSe8s3gPjldYDPOwIHZhWw3g0LCu/XVhNV2Os3wcWMx/ZWAvSfBUlldc5/dFW3vCy9UpcNj0lfs2LOcAx7R8mdd7wz/9iIZ6mE0OIh1alqKELAbf2hxX6WLoNCYZeVxixsbKhwgAG0XateRjkGjCy7V0q1dpOIs95stxRKNuOlFHXXyCBG1JzpPTfV+RL0= diff --git a/example_configuration/tls_listen.xml b/example_configuration/tls_listen.xml index d320e159c..8317e987d 100644 --- a/example_configuration/tls_listen.xml +++ b/example_configuration/tls_listen.xml @@ -25,7 +25,7 @@ 1 - 02:E9:38:1F:F6:8B:62:DE:0A:0B:C5:03:81:A8:03:49:A0:00:7F:8B:F3 + 02:20:E1:AD:CC:92:71:E9:EA:6A:85:DF:A7:FF:8C:BB:B9:D5:E4:EE:74 x509c2n:specified tls-test diff --git a/example_configuration/tls_truststore.xml b/example_configuration/tls_truststore.xml index 856c6c57b..de132389e 100644 --- a/example_configuration/tls_truststore.xml +++ b/example_configuration/tls_truststore.xml @@ -3,56 +3,14 @@ clientcerts clientcert - MIIECTCCAvGgAwIBAgIBBzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCQ1ox -FjAUBgNVBAgMDVNvdXRoIE1vcmF2aWExDTALBgNVBAcMBEJybm8xDzANBgNVBAoM -BkNFU05FVDEMMAoGA1UECwwDVE1DMRMwEQYDVQQDDApleGFtcGxlIENBMSIwIAYJ -KoZIhvcNAQkBFhNleGFtcGxlY2FAbG9jYWxob3N0MB4XDTE1MDczMDA3MjcxOFoX -DTM1MDcyNTA3MjcxOFowgYUxCzAJBgNVBAYTAkNaMRYwFAYDVQQIDA1Tb3V0aCBN -b3JhdmlhMQ8wDQYDVQQKDAZDRVNORVQxDDAKBgNVBAsMA1RNQzEXMBUGA1UEAwwO -ZXhhbXBsZSBjbGllbnQxJjAkBgkqhkiG9w0BCQEWF2V4YW1wbGVjbGllbnRAbG9j -YWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAueCQaNQWoNmF -K6LKu1p8U8ZWdWg/PvDdLsJyzfzl/Qw4UA68SfFNaY06zZl8QB9W02nr5kWeeMY0 -VA3adrPgOlvfx3oWlFbkETnMaN4OT3WTQ0Wt6jAWZDzVfopwpJPAzRPxACDftIqF -GagYcF32hZlVNqqnVdbXh0S0EViweqp/dbG4VDUHSNVbglc+u4UbEzNIFXMdEFsJ -ZpkynOmSiTsIATqIhb+2srkVgLwhfkC2qkuHQwAHdubuB07ObM2z01UhyEdDvEYG -HwtYAGDBL2TAcsI0oGeVkRyuOkV0QY0UN7UEFI1yTYw+xZ42HgFx3uGwApCImxhb -j69GBYWFqwIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVu -U1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUXGpLeLnh2cSDARAV -A7KrBxGYpo8wHwYDVR0jBBgwFoAUc1YQIqjZsHVwlea0AB4N+ilNI2gwDQYJKoZI -hvcNAQELBQADggEBAJPV3RTXFRtNyOU4rjPpYeBAIAFp2aqGc4t2J1c7oPp/1n+l -ZvjnwtlJpZHxMM783e2ryDQ6dkvXDf8kpwKlg3U3mkJ3xKkDdWrM4QwghXdCN519 -aa9qmu0zdFL+jUAaWlQ5tsceOrvbusCcbMqiFGk/QfpHqPv52SVWbYyUx7IX7DE+ -UjgsLHycfV/tlcx4ZE6soTzl9VdgSL/zmzG3rjsr58J80rXckLgBhvijgBlIAJvW -fC7D0vaouvBInSFXymdPVoUDZ30cdGLf+hI/i/TfsEMOinLrXVdkSGNo6FXAHKSv -XeB9oFKSzhQ7OPyRyqvEPycUSw/qD6FVr80oDDc= + MIIDqTCCApECFEf4LBXF80V6bTWn6kJ/RuccpJ/AMA0GCSqGSIb3DQEBCwUAMIGQMQswCQYDVQQGEwJDWjEWMBQGA1UECAwNU291dGggTW9yYXZpYTENMAsGA1UEBwwEQnJubzEYMBYGA1UECgwPQ0VTTkVUIHoucy5wLm8uMQwwCgYDVQQLDANUTUMxEzARBgNVBAMMCmV4YW1wbGUgQ0ExHTAbBgkqhkiG9w0BCQEWDmNhQGV4YW1wbGUub3JnMB4XDTIxMDkwMzEwMjgxOFoXDTMxMDkwMTEwMjgxOFowgZAxCzAJBgNVBAYTAkNaMRYwFAYDVQQIDA1Tb3V0aCBNb3JhdmlhMQ0wCwYDVQQHDARCcm5vMRgwFgYDVQQKDA9DRVNORVQgei5zLnAuby4xDDAKBgNVBAsMA1RNQzEPMA0GA1UEAwwGY2xpZW50MSEwHwYJKoZIhvcNAQkBFhJjbGllbnRAZXhhbXBsZS5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8zbCEJ0MSbFN+KjWhQKbGVkG+TKKtxMatpKMJ5lYDmB1dwWBHc+jj3+htAtc43BUTxQs/7Cb28VVp7kB1dGOd0b/HGL9n8cBmqRcxe+NEhiIFWANy7dzSNF1uKbBLzjMpgjKBcZ4c4O80pBww0U0Q1DPZ/9AhU9GKgUHiwfHKe8/it7T6lhQ2yXqClkW0xnzTiipYC6T3QNvTHWOCfzsUuHcVcA4seqZYMOOiPqMujgUws2jJ2y0lDXg2bcayDwtahX2oZP+Fil9ifO5CLNv9mw+ze8RxXeDrtFEJzzZfmMp5cmj8LmvdzOszrxfdMoG28gxOGYXXunv3+e4vK32RAgMBAAEwDQYJKoZIhvcNAQELBQADggEBANwq9tqHYiet73ZdZcsUA2Aoa7mdjrYvmTeb4rrRbW5KGxoVhRJYAG/8OTZ3P8liorlPtWDci6iXBk/Ev8f1kqlC35xpRaidXSNAancAj4gS30sdCAAj8KzuWmtYTWT5aR0eAV1UTA4mo2N7OidRk+vVWs5tbbRmFmE/aJj8+Ol8rCgqRW08uLHy4pGljCDJWEiaIpPNflomui35r2F4bXp+7aoYmqL63XVkvlWyZBVQfN6p05l2bTTn2N5IrqPq2r7PF0jZtnEnbFc5Rc7gW25EFym70JQQ2kXOft4++G/NK3JcCk3IHwl7tNN7ZVChbMpbVH5TEV8QXR7DqYG0O7Y= cacerts cacert - MIID7TCCAtWgAwIBAgIJAMtE1NGAR5KoMA0GCSqGSIb3DQEBBQUAMIGMMQswCQYD -VQQGEwJDWjEWMBQGA1UECAwNU291dGggTW9yYXZpYTENMAsGA1UEBwwEQnJubzEP -MA0GA1UECgwGQ0VTTkVUMQwwCgYDVQQLDANUTUMxEzARBgNVBAMMCmV4YW1wbGUg -Q0ExIjAgBgkqhkiG9w0BCQEWE2V4YW1wbGVjYUBsb2NhbGhvc3QwHhcNMTQwNzI0 -MTQxOTAyWhcNMjQwNzIxMTQxOTAyWjCBjDELMAkGA1UEBhMCQ1oxFjAUBgNVBAgM -DVNvdXRoIE1vcmF2aWExDTALBgNVBAcMBEJybm8xDzANBgNVBAoMBkNFU05FVDEM -MAoGA1UECwwDVE1DMRMwEQYDVQQDDApleGFtcGxlIENBMSIwIAYJKoZIhvcNAQkB -FhNleGFtcGxlY2FAbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -CgKCAQEArD3TDHPAMT2Z84orK4lMlarbgooIUCcRZyLe+QM+8KY8Hn+mGaxPEOTS -L3ywszqefB/Utm2hPKLHX684iRC14ID9WDGHxPjvoPArhgFhfV+qnPfxKTgxZC12 -uOj4u1V9y+SkTCocFbRfXVBGpojrBuDHXkDMDEWNvr8/52YCv7bGaiBwUHolcLCU -bmtKILCG0RNJyTaJpXQdAeq5Z1SJotpbfYFFtAXB32hVoLug1dzl2tjG9sb1wq3Q -aDExcbC5w6P65qOkNoyym9ne6QlQagCqVDyFn3vcqkRaTjvZmxauCeUxXgJoXkyW -cm0lM1KMHdoTArmchw2Dz0yHHSyDAQIDAQABo1AwTjAdBgNVHQ4EFgQUc1YQIqjZ -sHVwlea0AB4N+ilNI2gwHwYDVR0jBBgwFoAUc1YQIqjZsHVwlea0AB4N+ilNI2gw -DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAI/1KH60qnw9Xs2RGfi0/ -IKf5EynXt4bQX8EIyVKwSkYKe04zZxYfLIl/Q2HOPYoFmm3daj5ddr0ZS1i4p4fT -UhstjsYWvXs3W/HhVmFUslakkn3PrswhP77fCk6eEJLxdfyJ1C7Uudq2m1isZbKi -h+XF0mG1LxJaDMocSz4eAya7M5brwjy8DoOmA1TnLQFCVcpn+sCr7VC4wE/JqxyV -hBCk/MuGqqM3B1j90bGFZ112ZOecyE0EDSr6IbiRBtmeNbEwOFjKXhNLYdxpBZ9D -8A/368OckZkCrVLGuJNxK9UwCVTe8IhotHUqU9EqFDmxdV8oIdU/OzUwwNPA/Bd/ -9g== + MIIEAzCCAuugAwIBAgIURc4sipHvJSlNrQIhRhZilBvV4RowDQYJKoZIhvcNAQELBQAwgZAxCzAJBgNVBAYTAkNaMRYwFAYDVQQIDA1Tb3V0aCBNb3JhdmlhMQ0wCwYDVQQHDARCcm5vMRgwFgYDVQQKDA9DRVNORVQgei5zLnAuby4xDDAKBgNVBAsMA1RNQzETMBEGA1UEAwwKZXhhbXBsZSBDQTEdMBsGCSqGSIb3DQEJARYOY2FAZXhhbXBsZS5vcmcwHhcNMjEwOTAzMTAyMTAxWhcNMzEwOTAxMTAyMTAxWjCBkDELMAkGA1UEBhMCQ1oxFjAUBgNVBAgMDVNvdXRoIE1vcmF2aWExDTALBgNVBAcMBEJybm8xGDAWBgNVBAoMD0NFU05FVCB6LnMucC5vLjEMMAoGA1UECwwDVE1DMRMwEQYDVQQDDApleGFtcGxlIENBMR0wGwYJKoZIhvcNAQkBFg5jYUBleGFtcGxlLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN4Ld3JDDocyy9KXNJhEUPeZpQW3UdUNXloeh5n/bxasgThkBuQ7oF/nKyVUe517U1CJA993ZIc0jhIWThAnqXkz70DX5EZ7ancPd01MidA6T8k1RYYJWr+vyIRYYBYzK7LSnU6wMWqPTgzZB+KMWwb065ooLEB5XwqAeTIMPLRqM1Galewl4ZSuRJnrXxRjfF3AWNyC9dZw6wIg8cppvoLdBGQiFJQf9SgiVy+HyedAytFEixqKAAIgQUJwhCgbEd6jGFbeaL8HT4MFp1VmaaUBQMkZj/GnKBwCk5BEMu76EN1pzHc4Dd6DabQNGCnsqOPe31yhQGmNFy9R6zNnWZMCAwEAAaNTMFEwHQYDVR0OBBYEFM7w/pO8vk5oowvWPoCKo0RW/JcnMB8GA1UdIwQYMBaAFM7w/pO8vk5oowvWPoCKo0RW/JcnMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAG/xfYuRKnCyiwYC/K7kAjHmCNnLCr1mx8P1ECsSJPme3OThDTNeGf8iN2952tGmMFDa+DaAwPc6Gt3cWTb/NYMTLWlt2yj5rJAcLXxIU0SMafBf+F7E/R8Ab/HDDjs0pQaJ0EJhQJVkMdfj3Wq9l0dJT5iEBUrUQflDufiMdEJEIGKZh86MgzELbcn1QX8dlLc91M2OifWStqLzXPicG+jjuoPUceC0flMQDb2qx03sxvJKfYfS5ArACqvdWyXLoP7DI9THJrMI/vBHJKpl4Wtmsh2OLn9VHauFMzPSGke5GwjXCpbXGepj9qWN8Gd/FWgSDH2OBvZ6aHdB1pPjN9k= diff --git a/scripts/setup.sh b/scripts/setup.sh index 242ab02b2..3601f0162 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -72,6 +72,21 @@ UPDATE_MODULE() { fi } +CHANGE_PERMS() { + CMD="'$SYSREPOCTL' -c $1 -p '$PERMS' -v2" + if [ ! -z ${OWNER} ]; then + CMD="$CMD -o '$OWNER'" + fi + if [ ! -z ${GROUP} ]; then + CMD="$CMD -g '$GROUP'" + fi + eval $CMD + local rc=$? + if [ $rc -ne 0 ]; then + exit $rc + fi +} + ENABLE_FEATURE() { "$SYSREPOCTL" -a -c $1 -e $2 -v2 local rc=$? @@ -101,6 +116,14 @@ for i in "${MODULES[@]}"; do UPDATE_MODULE "$file" fi + sctl_owner=`echo "$SCTL_MODULE" | sed 's/\([^|]*|\)\{3\} \([^:]*\).*/\2/'` + sctl_group=`echo "$SCTL_MODULE" | sed 's/\([^|]*|\)\{3\}[^:]*:\([^ ]*\).*/\2/'` + sctl_perms=`echo "$SCTL_MODULE" | sed 's/\([^|]*|\)\{4\} \([^ ]*\).*/\2/'` + if [ "$sctl_perms" != "$PERMS" ] || [ ! -z ${OWNER} -a "$sctl_owner" != "$OWNER" ] || [ ! -z ${GROUP} -a "$sctl_group" != "$GROUP" ]; then + # change permissions/owner + CHANGE_PERMS "$name" + fi + # parse sysrepoctl features and add extra space at the end for easier matching sctl_features="`echo "$SCTL_MODULE" | sed 's/\([^|]*|\)\{6\}\(.*\)/\2/'` " # parse features we want to enable diff --git a/src/common.c b/src/common.c index 19139e7ec..f2e5e008d 100644 --- a/src/common.c +++ b/src/common.c @@ -57,17 +57,20 @@ np_sleep(uint32_t ms) } struct timespec -np_gettimespec(void) +np_gettimespec(int force_real) { struct timespec ts; - clock_gettime(NP_CLOCK_ID, &ts); + if (force_real) { + clock_gettime(CLOCK_REALTIME, &ts); + } else { + clock_gettime(NP_CLOCK_ID, &ts); + } return ts; } -/* ts1 < ts2 -> +, ts1 > ts2 -> -, returns milliseconds */ -int32_t +int64_t np_difftimespec(const struct timespec *ts1, const struct timespec *ts2) { int64_t nsec_diff = 0; @@ -113,40 +116,59 @@ np_modtimespec(const struct timespec *ts, uint32_t msec) return ret; } -struct nc_session * -np_get_nc_sess_by_sr_id(uint32_t sr_id) +int +np_get_nc_sess_by_id(uint32_t sr_id, uint32_t nc_id, struct nc_session **nc_sess) { uint32_t i; - struct nc_session *ncs; + struct nc_session *ncs = NULL; struct np2_user_sess *user_sess; + assert((sr_id && !nc_id) || (!sr_id && nc_id)); + for (i = 0; (ncs = nc_ps_get_session(np2srv.nc_ps, i)); ++i) { - user_sess = nc_session_get_data(ncs); - if (sr_session_get_id(user_sess->sess) == sr_id) { - break; + if (sr_id) { + user_sess = nc_session_get_data(ncs); + if (sr_session_get_id(user_sess->sess) == sr_id) { + break; + } + } else { + if (nc_session_get_id(ncs) == nc_id) { + break; + } + } + } + + if (!ncs) { + if (nc_id) { + ERR("Failed to find NETCONF session with NC ID %u.", nc_id); } + return SR_ERR_INTERNAL; } - return ncs; + *nc_sess = ncs; + return SR_ERR_OK; } int np_get_user_sess(sr_session_ctx_t *ev_sess, struct nc_session **nc_sess, struct np2_user_sess **user_sess) { struct np2_user_sess *us; - uint32_t i, *nc_id, size; + const char *orig_name; + uint32_t *nc_id, size; struct nc_session *ncs; + int rc; + + orig_name = sr_session_get_orig_name(ev_sess); + if (!orig_name || strcmp(orig_name, "netopeer2")) { + ERR("Unknown originator name \"%s\" in event session.", orig_name); + return SR_ERR_INTERNAL; + } sr_session_get_orig_data(ev_sess, 0, &size, (const void **)&nc_id); - for (i = 0; (ncs = nc_ps_get_session(np2srv.nc_ps, i)); ++i) { - if (nc_session_get_id(ncs) == *nc_id) { - break; - } - } - if (!ncs) { - ERR("Failed to find NETCONF session SID %u.", *nc_id); - return SR_ERR_INTERNAL; + rc = np_get_nc_sess_by_id(0, *nc_id, &ncs); + if (rc) { + return rc; } /* NETCONF session */ @@ -331,7 +353,7 @@ np2srv_url_setcap(void) } if (!sup_prot) { /* no protocols supported */ - return EXIT_SUCCESS; + return 0; } /* get max capab string size and allocate it */ @@ -342,7 +364,7 @@ np2srv_url_setcap(void) cpblt = malloc(j); if (!cpblt) { EMEM; - return EXIT_FAILURE; + return -1; } /* main capability */ @@ -358,7 +380,7 @@ np2srv_url_setcap(void) nc_server_set_capability(cpblt); free(cpblt); - return EXIT_SUCCESS; + return 0; } struct np2srv_url_mem { @@ -392,6 +414,13 @@ url_readdata(void *ptr, size_t size, size_t nmemb, void *userdata) return copied; } +/** + * @brief Open specific URL using curl. + * + * @param[in] url URL to open. + * @return FD with the URL contents; + * @return -1 on error. + */ static int url_open(const char *url) { @@ -441,6 +470,7 @@ op_parse_url(const char *url, uint32_t parse_options, int *rc, sr_session_ctx_t { struct lyd_node *config, *data; struct ly_ctx *ly_ctx; + struct lyd_node_opaq *opaq; int fd; ly_ctx = (struct ly_ctx *)sr_get_context(np2srv.sr_conn); @@ -452,20 +482,35 @@ op_parse_url(const char *url, uint32_t parse_options, int *rc, sr_session_ctx_t return NULL; } - /* do not validate the whole context, we just want to load the config anyxml */ - if (lyd_parse_data_fd(ly_ctx, fd, LYD_XML, LYD_PARSE_ONLY | LYD_PARSE_OPAQ | LYD_PARSE_NO_STATE, 0, &config)) { + /* load the whole config element */ + if (lyd_parse_data_fd(ly_ctx, fd, LYD_XML, parse_options, 0, &config)) { *rc = SR_ERR_LY; sr_session_set_error_message(sr_sess, ly_errmsg(ly_ctx)); return NULL; } - data = op_parse_config((struct lyd_node_any *)config, parse_options, rc, sr_sess); - lyd_free_siblings(config); + if (!config || config->schema) { + *rc = SR_ERR_UNSUPPORTED; + sr_session_set_error_message(sr_sess, "Missing top-level \"config\" element in URL data."); + return NULL; + } + + opaq = (struct lyd_node_opaq *)config; + if (strcmp(opaq->name.name, "config") || strcmp(opaq->name.module_ns, "urn:ietf:params:xml:ns:netconf:base:1.0")) { + *rc = SR_ERR_UNSUPPORTED; + sr_session_set_error_message(sr_sess, "Invalid top-level element in URL data, expected \"config\" with " + "namespace \"urn:ietf:params:xml:ns:netconf:base:1.0\"."); + return NULL; + } + + data = opaq->child; + lyd_unlink_siblings(data); + lyd_free_tree(config); return data; } int -op_export_url(const char *url, struct lyd_node *data, int options, int *rc, sr_session_ctx_t *sr_sess) +op_export_url(const char *url, struct lyd_node *data, uint32_t print_options, int *rc, sr_session_ctx_t *sr_sess) { CURL *curl; CURLcode res; @@ -477,15 +522,18 @@ op_export_url(const char *url, struct lyd_node *data, int options, int *rc, sr_s ly_ctx = (struct ly_ctx *)sr_get_context(np2srv.sr_conn); /* print the config as expected by the other end */ - if (lyd_new_path2(NULL, ly_ctx, "/ietf-netconf:config", data, 0, data ? LYD_ANYDATA_DATATREE : 0, 0, NULL, &config)) { + if (lyd_new_opaq2(NULL, ly_ctx, "config", NULL, NULL, "urn:ietf:params:xml:ns:netconf:base:1.0", &config)) { *rc = SR_ERR_LY; sr_session_set_error_message(sr_sess, ly_errmsg(ly_ctx)); return -1; } - lyd_print_mem(&str_data, config, LYD_XML, options); + if (data) { + lyd_insert_child(config, data); + } + lyd_print_mem(&str_data, config, LYD_XML, print_options); /* do not free data */ - ((struct lyd_node_any *)config)->value.tree = NULL; + lyd_unlink_siblings(data); lyd_free_tree(config); DBG("Uploading file to URL: %s (via curl)", url); @@ -536,6 +584,8 @@ op_parse_config(struct lyd_node_any *config, uint32_t parse_options, int *rc, sr struct lyd_node *root = NULL; LY_ERR lyrc; + assert(config && config->schema && (config->schema->nodetype & LYD_NODE_ANY)); + if (!config->value.str) { /* nothing to do, no data */ return NULL; @@ -567,6 +617,13 @@ op_parse_config(struct lyd_node_any *config, uint32_t parse_options, int *rc, sr return root; } +/** + * @brief Learn whether a string is white-space-only. + * + * @param[in] str String to examine. + * @return 1 if there are only white-spaces in @p str; + * @return 0 otherwise. + */ static int strws(const char *str) { @@ -580,6 +637,15 @@ strws(const char *str) return 1; } +/** + * @brief Add another XPath filter into NP2 filter structure. + * + * @param[in] new_filter New XPath filter to add. + * @param[in] selection Whether @p new_filter is selection or content filter. + * @param[in,out] filter NP2 filter structure to add to. + * @return 0 on success; + * @return -1 on error. + */ static int op_filter_xpath_add_filter(const char *new_filter, int selection, struct np2_filter *filter) { @@ -598,6 +664,17 @@ op_filter_xpath_add_filter(const char *new_filter, int selection, struct np2_fil return 0; } +/** + * @brief Append subtree filter metadata to XPath filter string buffer. + * + * Handles metadata. + * + * @param[in] meta Subtree filter node metadata. + * @param[in,out] buf Current XPath filter buffer. + * @param[in] size Current @p buf size. + * @return New @p buf size; + * @return -1 on error. + */ static int filter_xpath_buf_append_attrs(const struct lyd_meta *meta, char **buf, int size) { @@ -621,7 +698,14 @@ filter_xpath_buf_append_attrs(const struct lyd_meta *meta, char **buf, int size) return size; } -/* top-level content node with namespace and optional attributes */ +/** + * @brief Process a subtree top-level content node with namespace and optional attributes. + * + * @param[in] node Subtree filter node. + * @param[in,out] filter NP2 filter structure to add to. + * @return 0 on success. + * @return -1 on error. + */ static int filter_xpath_buf_add_top_content(const struct lyd_node *node, struct np2_filter *filter) { @@ -641,7 +725,7 @@ filter_xpath_buf_add_top_content(const struct lyd_node *node, struct np2_filter size = filter_xpath_buf_append_attrs(node->meta, &buf, size); if (size < 1) { free(buf); - return size; + return -1; } if (op_filter_xpath_add_filter(buf, 0, filter)) { @@ -653,7 +737,75 @@ filter_xpath_buf_add_top_content(const struct lyd_node *node, struct np2_filter return 0; } -/* content node with optional namespace and attributes */ +/** + * @brief Get the module to print for a node if needed based on JSON instid module inheritence. + * + * @param[in] node Node that is printed. + * @return Module to print; + * @return NULL if no module needs to be printed. + */ +static const struct lys_module * +filter_xpath_print_node_module(const struct lyd_node *node) +{ + const struct lys_module *mod; + const struct lyd_node *parent; + const struct lyd_node_opaq *opaq, *opaq2; + + parent = lyd_parent(node); + + if (!parent) { + /* print the module */ + } else if (node->schema && parent->schema) { + /* 2 data nodes */ + if (node->schema->module == parent->schema->module) { + return NULL; + } + } else if (node->schema || parent->schema) { + /* 1 data node, 1 opaque node */ + mod = node->schema ? node->schema->module : parent->schema->module; + opaq = node->schema ? (struct lyd_node_opaq *)parent : (struct lyd_node_opaq *)node; + assert(opaq->format == LY_VALUE_XML); + + /* in dict */ + if (mod->ns == opaq->name.module_ns) { + return NULL; + } + } else { + /* 2 opaque nodes */ + opaq = (struct lyd_node_opaq *)node; + opaq2 = (struct lyd_node_opaq *)parent; + + /* in dict */ + if (opaq->name.module_ns == opaq2->name.module_ns) { + return NULL; + } + } + + /* module will be printed, get it */ + mod = NULL; + if (node->schema) { + mod = node->schema->module; + } else { + opaq = (struct lyd_node_opaq *)node; + if (opaq->name.module_ns) { + mod = ly_ctx_get_module_implemented_ns(LYD_CTX(node), opaq->name.module_ns); + } + } + + return mod; +} + +/** + * @brief Append subtree filter node to XPath filter string buffer. + * + * Handles content nodes with optional namespace and attributes. + * + * @param[in] node Subtree filter node. + * @param[in,out] buf Current XPath filter buffer. + * @param[in] size Current @p buf size. + * @return New @p buf size; + * @return -1 on error. + */ static int filter_xpath_buf_append_content(const struct lyd_node *node, char **buf, int size) { @@ -661,12 +813,10 @@ filter_xpath_buf_append_content(const struct lyd_node *node, char **buf, int siz int new_size; char *buf_new, quot; - assert(node->schema && (node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST))); + assert(!node->schema || (node->schema->nodetype & (LYS_LEAF | LYS_LEAFLIST))); /* do we print the module name? */ - if (!node->parent || (lyd_parent(node)->schema->module != node->schema->module)) { - mod = node->schema->module; - } + mod = filter_xpath_print_node_module(node); new_size = size + 1 + (mod ? strlen(mod->name) + 1 : 0) + strlen(LYD_NAME(node)); buf_new = realloc(*buf, new_size); @@ -701,36 +851,26 @@ filter_xpath_buf_append_content(const struct lyd_node *node, char **buf, int siz return new_size; } -/* containment/selection node with namespace and optional attributes */ +/** + * @brief Append subtree filter node to XPath filter string buffer. + * + * Handles containment/selection nodes with namespace and optional attributes. + * + * @param[in] node Subtree filter node. + * @param[in,out] buf Current XPath filter buffer. + * @param[in] size Current @p buf size. + * @return New @p buf size; + * @return -1 on error. + */ static int filter_xpath_buf_append_node(const struct lyd_node *node, char **buf, int size) { const struct lys_module *mod = NULL; - const struct lyd_node_opaq *opaq; int new_size; char *buf_new; - assert(node->schema || !((struct lyd_node_opaq *)node)->value || strws(((struct lyd_node_opaq *)node)->value)); - - /* do we print the module? */ - if (node->schema && (!node->parent || (lyd_parent(node)->schema->module != node->schema->module))) { - mod = node->schema->module; - } else if (!node->schema) { - opaq = (struct lyd_node_opaq *)node; - if (!opaq->name.module_ns) { - /* no namespace, will not match anything */ - return 0; - } - - mod = ly_ctx_get_module_implemented_ns(LYD_CTX(node), opaq->name.module_ns); - if (!mod) { - /* unknown namespace, will not match anything */ - } - - if (lyd_parent(node)->schema->module == mod) { - mod = NULL; - } - } + /* do we print the module name? */ + mod = filter_xpath_print_node_module(node); new_size = size + 1 + (mod ? strlen(mod->name) + 1 : 0) + strlen(LYD_NAME(node)); buf_new = realloc(*buf, new_size); @@ -751,6 +891,17 @@ filter_xpath_buf_append_node(const struct lyd_node *node, char **buf, int size) return size; } +/** + * @brief Process a subtree filter node by constructing an XPath filter string and adding it + * to an NP2 filter structure, recursively. + * + * @param[in] node Subtree filter node. + * @param[in,out] buf Current XPath filter buffer. + * @param[in] size Current @p buf size. + * @param[in,out] filter NP2 filter structure to add to. + * @return 0 on success; + * @return -1 on error. + */ static int filter_xpath_buf_add_r(const struct lyd_node *node, char **buf, int size, struct np2_filter *filter) { @@ -760,7 +911,7 @@ filter_xpath_buf_add_r(const struct lyd_node *node, char **buf, int size, struct /* containment node or selection node */ size = filter_xpath_buf_append_node(node, buf, size); if (size < 1) { - return size; + return -1; } if (!lyd_child(node)) { @@ -774,11 +925,11 @@ filter_xpath_buf_add_r(const struct lyd_node *node, char **buf, int size, struct /* append child content match nodes */ only_content_match = 1; LY_LIST_FOR(lyd_child(node), child) { - if (child->schema && lyd_get_value(child) && !strws(lyd_get_value(child))) { + if (lyd_get_value(child) && !strws(lyd_get_value(child))) { /* there is a content filter, append all of them */ size = filter_xpath_buf_append_content(child, buf, size); if (size < 1) { - return size; + return -1; } } else { /* can no longer be just a content match */ @@ -808,7 +959,7 @@ filter_xpath_buf_add_r(const struct lyd_node *node, char **buf, int size, struct if (!s) { continue; } else if (s < 0) { - return s; + return -1; } if (op_filter_xpath_add_filter(*buf, 1, filter)) { @@ -837,6 +988,8 @@ op_filter_subtree2xpath(const struct lyd_node *node, struct np2_filter *filter) if (filter_xpath_buf_add_r(iter, &buf, 1, filter)) { goto error; } + } else { + WRN("Skipping unsupported top-level filter node \"%s\".", LYD_NAME(iter)); } } @@ -862,6 +1015,14 @@ op_filter_erase(struct np2_filter *filter) filter->count = 0; } +/** + * @brief Append string to another string by enlarging it. + * + * @param[in] str String to append. + * @param[in,out] ret String to append to, is enlarged. + * @return 0 on success; + * @return -1 on error. + */ static int np_append_str(const char *str, char **ret) { diff --git a/src/common.h b/src/common.h index 173fe79d0..84447b56c 100644 --- a/src/common.h +++ b/src/common.h @@ -37,6 +37,9 @@ #define NP_IGNORE_RPC(session, event) (!sr_session_get_orig_name(session) || \ strcmp(sr_session_get_orig_name(session), "netopeer2") || (event == SR_EV_ABORT)) +/* macro to check if SR callback was originated by netopeer2 */ +#define NP_IS_ORIG_NP(session) (sr_session_get_orig_name(session) && !strcmp(sr_session_get_orig_name(session), "netopeer2")) + /* user session structure assigned as data of NC sessions */ struct np2_user_sess { sr_session_ctx_t *sess; @@ -64,62 +67,212 @@ struct np2srv { extern struct np2srv np2srv; extern ATOMIC_T skip_nacm_nc_sid; +/** + * @brief Sleep in milliseconds. + * + * @param[in] ms Milliseconds to sleep. + * @return 0 on success; + * @return -1 on error. + */ int np_sleep(uint32_t ms); -struct timespec np_gettimespec(void); +/** + * @brief Get current real or monotonic time, monotic preferred if available. + * + * @param[in] force_real If set, realtime is always returned. + * @return Current time in timespec. + */ +struct timespec np_gettimespec(int force_real); -int32_t np_difftimespec(const struct timespec *ts1, const struct timespec *ts2); +/** + * @brief Get the difference between 2 timestamps in milliseconds. + * + * @param[in] ts1 First timestamp. + * @param[in] ts2 Second timestamp. + * @return Difference in milliseconds, positivne if @p ts1 < @p ts2, negative if @p ts1 > @p ts2. + */ +int64_t np_difftimespec(const struct timespec *ts1, const struct timespec *ts2); +/** + * @brief Add milliseconds to a timestamp. + * + * @param[in] ts Timestamp to modify. + * @param[in] msec Milliseconds to add to @p ts. + */ void np_addtimespec(struct timespec *ts, uint32_t msec); +/** + * @brief Get the remainder (operation modulo) after dividing timestamp by milliseconds. + * + * @param[in] ts Timestamp to divide. + * @param[in] msec Interval in milliseconds to divide @p ts by. + * @return Result of @p ts % @p msec in milliseconds. + */ struct timespec np_modtimespec(const struct timespec *ts, uint32_t msec); -struct nc_session *np_get_nc_sess_by_sr_id(uint32_t sr_id); +/** + * @brief Get NC session by SR or NC session ID. + * + * @param[in] sr_id Search by sysrepo SID, set only if @p nc_id is 0. + * @param[in] nc_id Search by NETCONF SID, set only if @p sr_id is 0. + * @param[out] nc_sess Found NETCONF session. + * @return SR error value, logs only if @p nc_id is set. + */ +int np_get_nc_sess_by_id(uint32_t sr_id, uint32_t nc_id, struct nc_session **nc_sess); +/** + * @brief Get NC session and/or SR user session based on SR event session. + * + * Increases refcount of the session, needs ::np_release_user_sess() call to decrease. + * + * @param[in] ev_sess Sysrepo event session. + * @param[out] nc_sess Optional found NC session. + * @param[out] user_sess Sysrepo user session. + * @return SR error value. + */ int np_get_user_sess(sr_session_ctx_t *ev_sess, struct nc_session **nc_sess, struct np2_user_sess **user_sess); +/** + * @brief Release SR user session, free if no longer referenced. + * + * Decreases refcount after ::np_get_user_sess() call. + * + * @param[in] user_sess Sysrepo user session. + */ void np_release_user_sess(struct np2_user_sess *user_sess); +/** + * @brief Learn whether a module includes any notification definitions. + * + * @param[in] mod Module to examine. + * @return non-zero if there are some notifications; + * @return 0 if there are no notifications. + */ int np_ly_mod_has_notif(const struct lys_module *mod); +/** + * @brief Learn whether a module includes any data node definitions. + * + * @param[in] mod Module to examine. + * @param[in] config_mask Required config mask of the data nodes. + * @return non-zero if there are some data; + * @return 0 if there are no data. + */ int np_ly_mod_has_data(const struct lys_module *mod, uint32_t config_mask); +/** + * @brief NP2 callback for a new session creation. + * + * @param[in] client_name CH client name, unused. + * @param[in] new_session Created NC session. + * @return 0 on success; + * @return -1 on error. + */ int np2srv_new_session_cb(const char *client_name, struct nc_session *new_session); +/** + * @brief Set URL capability to be advertised for new NETCONF sessions. + * + * @return 0 on success; + * @return -1 on error. + */ int np2srv_url_setcap(void); #ifdef NP2SRV_URL_CAPAB +/** + * @brief Parse YANG data found at an URL (encapsulated in `config` element). + * + * @param[in] url URL to access. + * @param[in] parse_options Options for parsing the data. + * @param[out] rc SR error value. + * @param[in,out] sr_sess SR session to set error on. + * @return Parsed data. + */ struct lyd_node *op_parse_url(const char *url, uint32_t parse_options, int *rc, sr_session_ctx_t *sr_sess); -int op_export_url(const char *url, struct lyd_node *data, int options, int *rc, sr_session_ctx_t *sr_sess); +/** + * @brief Upload YANG data to an URL (encapsulated in `config` element). + * + * @param[in] url URL to upload to. + * @param[in] data Data to upload. + * @param[in] print_options Options for printing the data. + * @param[out] rc SR error value. + * @param[in,out] sr_sess SR session to set error on. + * @return 0 on success; + * @return -1 on error. + */ +int op_export_url(const char *url, struct lyd_node *data, uint32_t print_options, int *rc, sr_session_ctx_t *sr_sess); #endif +/** + * @brief Parse YANG data in a `config` YANG anyxml node. + * + * @param[in] config Config node with the data. + * @param[in] parse_options Options for parsing the data. + * @param[out] rc SR error value. + * @param[in,out] sr_sess SR session to set error on. + * @return Parsed data. + */ struct lyd_node *op_parse_config(struct lyd_node_any *config, uint32_t parse_options, int *rc, sr_session_ctx_t *sr_sess); struct np2_filter { struct { - char *str; + char *str; /**< filter string */ int selection; /**< selection or content filter */ } *filters; uint32_t count; }; +/** + * @brief Transform subtree filter into NP2 filter structure. + * + * @param[in] node Subtree filter. + * @param[out] filter Generated NP2 filter. + * @return 0 on success; + * @return -1 on error. + */ int op_filter_subtree2xpath(const struct lyd_node *node, struct np2_filter *filter); +/** + * @brief Erase all members of an NP2 filter structure. + * + * @param[in] filter NP2 filter to erase. + */ void op_filter_erase(struct np2_filter *filter); +/** + * @brief Transform NP2 filter structure into XPath filter. + * + * @param[in] filter NP2 filter structure. + * @param[out] xpath Generated XPath filter. + * @return SR error value. + */ int op_filter_filter2xpath(const struct np2_filter *filter, char **xpath); /** - * @brief Get all data matching the selection filters. + * @brief Get all data matching the NP2 filter. + * + * @param[in] session SR session to get the data on. + * @param[in] max_depth Max depth fo the retrieved data. + * @param[in] get_opts SR get options to use. + * @param[in] filter NP2 filter to use. + * @param[in,out] ev_sess SR event session to set the error on. + * @param[out] data Retrieved data. + * @return SR error value. */ int op_filter_data_get(sr_session_ctx_t *session, uint32_t max_depth, sr_get_oper_options_t get_opts, const struct np2_filter *filter, sr_session_ctx_t *ev_sess, struct lyd_node **data); /** - * @brief Filter out only the data matching the content filters. + * @brief Filter out only the data matching the NP2 filter. + * + * @param[in,out] data Input data to filter. May be used and set to NULL. + * @param[in] filter NP2 filter to use. + * @param[in] with_selection Whether to apply even selection filters in @p filter. + * @param[out] filtered_data Data from @p data selected by @p filter. + * @return SR error value. */ int op_filter_data_filter(struct lyd_node **data, const struct np2_filter *filter, int with_selection, struct lyd_node **filtered_data); diff --git a/src/err_netconf.c b/src/err_netconf.c index 2e9ea0b24..810b043ce 100644 --- a/src/err_netconf.c +++ b/src/err_netconf.c @@ -88,8 +88,169 @@ np_err_sr2nc_lock_denied(sr_session_ctx_t *ev_sess, const sr_error_info_t *err_i if (!ptr) { return; } - nc_sess = np_get_nc_sess_by_sr_id(atoi(ptr + strlen(str))); + np_get_nc_sess_by_id(atoi(ptr + strlen(str)), 0, &nc_sess); sprintf(buf, "%" PRIu32, nc_sess ? nc_session_get_id(nc_sess) : 0); sr_session_push_error_data(ev_sess, strlen(buf) + 1, buf); } + +void +np_err_sr2nc_in_use(sr_session_ctx_t *ev_sess, const sr_error_info_t *err_info) +{ + struct nc_session *nc_sess; + const char *str, *ptr; + char buf[11]; + + /* error format */ + sr_session_set_error_format(ev_sess, "NETCONF"); + + /* error-type */ + str = "protocol"; + sr_session_push_error_data(ev_sess, strlen(str) + 1, str); + + /* error-tag */ + str = "in-use"; + sr_session_push_error_data(ev_sess, strlen(str) + 1, str); + + /* error-message */ + str = "The request requires a resource that already is in use."; + sr_session_push_error_data(ev_sess, strlen(str) + 1, str); + + /* error-app-tag */ + sr_session_push_error_data(ev_sess, 1, ""); + + /* error-path */ + sr_session_push_error_data(ev_sess, 1, ""); + + /* error-info */ + str = "session-id"; + sr_session_push_error_data(ev_sess, strlen(str) + 1, str); + + str = "DS-locked by session "; + ptr = strstr(err_info->err[0].message, str); + if (!ptr) { + return; + } + np_get_nc_sess_by_id(atoi(ptr + strlen(str)), 0, &nc_sess); + + sprintf(buf, "%" PRIu32, nc_sess ? nc_session_get_id(nc_sess) : 0); + sr_session_push_error_data(ev_sess, strlen(buf) + 1, buf); +} + +void +np_err_sr2nc_same_ds(sr_session_ctx_t *ev_sess, const char *err_msg) +{ + const char *str; + + /* error format */ + sr_session_set_error_format(ev_sess, "NETCONF"); + + /* error-type */ + str = "application"; + sr_session_push_error_data(ev_sess, strlen(str) + 1, str); + + /* error-tag */ + str = "invalid-value"; + sr_session_push_error_data(ev_sess, strlen(str) + 1, str); + + /* error-message */ + sr_session_push_error_data(ev_sess, strlen(err_msg) + 1, err_msg); + + /* error-app-tag */ + sr_session_push_error_data(ev_sess, 1, ""); + + /* error-path */ + sr_session_push_error_data(ev_sess, 1, ""); +} + +void +np_err_missing_element(sr_session_ctx_t *ev_sess, const char *elem_name) +{ + const char *str; + + /* error format */ + sr_session_set_error_format(ev_sess, "NETCONF"); + + /* error-type */ + str = "protocol"; + sr_session_push_error_data(ev_sess, strlen(str) + 1, str); + + /* error-tag */ + str = "missing-element"; + sr_session_push_error_data(ev_sess, strlen(str) + 1, str); + + /* error-message */ + str = "An expected element is missing."; + sr_session_push_error_data(ev_sess, strlen(str) + 1, str); + + /* error-app-tag */ + sr_session_push_error_data(ev_sess, 1, ""); + + /* error-path */ + sr_session_push_error_data(ev_sess, 1, ""); + + /* error-info */ + str = "bad-element"; + sr_session_push_error_data(ev_sess, strlen(str) + 1, str); + + sr_session_push_error_data(ev_sess, strlen(elem_name) + 1, elem_name); +} + +void +np_err_bad_element(sr_session_ctx_t *ev_sess, const char *elem_name, const char *description) +{ + const char *str; + + /* error format */ + sr_session_set_error_format(ev_sess, "NETCONF"); + + /* error-type */ + str = "protocol"; + sr_session_push_error_data(ev_sess, strlen(str) + 1, str); + + /* error-tag */ + str = "bad-element"; + sr_session_push_error_data(ev_sess, strlen(str) + 1, str); + + /* error-message */ + sr_session_push_error_data(ev_sess, strlen(description) + 1, description); + + /* error-app-tag */ + sr_session_push_error_data(ev_sess, 1, ""); + + /* error-path */ + sr_session_push_error_data(ev_sess, 1, ""); + + /* error-info */ + str = "bad-element"; + sr_session_push_error_data(ev_sess, strlen(str) + 1, str); + + sr_session_push_error_data(ev_sess, strlen(elem_name) + 1, elem_name); +} + +void +np_err_ntf_sub_no_such_sub(sr_session_ctx_t *ev_sess, const char *message) +{ + const char *str; + + /* error format */ + sr_session_set_error_format(ev_sess, "NETCONF"); + + /* error-type */ + str = "application"; + sr_session_push_error_data(ev_sess, strlen(str) + 1, str); + + /* error-tag */ + str = "invalid-value"; + sr_session_push_error_data(ev_sess, strlen(str) + 1, str); + + /* error-message */ + sr_session_push_error_data(ev_sess, strlen(message) + 1, message); + + /* error-app-tag */ + str = "ietf-subscribed-notifications:no-such-subscription"; + sr_session_push_error_data(ev_sess, strlen(str) + 1, str); + + /* error-path */ + sr_session_push_error_data(ev_sess, 1, ""); +} diff --git a/src/err_netconf.h b/src/err_netconf.h index 8477bb685..80cb23548 100644 --- a/src/err_netconf.h +++ b/src/err_netconf.h @@ -21,4 +21,14 @@ void np_err_nacm_access_denied(sr_session_ctx_t *ev_sess, const char *module_nam void np_err_sr2nc_lock_denied(sr_session_ctx_t *ev_sess, const sr_error_info_t *err_info); +void np_err_sr2nc_in_use(sr_session_ctx_t *ev_sess, const sr_error_info_t *err_info); + +void np_err_sr2nc_same_ds(sr_session_ctx_t *ev_sess, const char *err_msg); + +void np_err_missing_element(sr_session_ctx_t *ev_sess, const char *elem_name); + +void np_err_bad_element(sr_session_ctx_t *ev_sess, const char *elem_name, const char *description); + +void np_err_ntf_sub_no_such_sub(sr_session_ctx_t *ev_sess, const char *message); + #endif /* NP2SRV_ERR_NETCONF_H_ */ diff --git a/src/log.c b/src/log.c index b7e9fcb35..4372eba12 100644 --- a/src/log.c +++ b/src/log.c @@ -12,7 +12,8 @@ * https://opensource.org/licenses/BSD-3-Clause */ -#define _DEFAULT_SOURCE +#define _GNU_SOURCE +#include #include "log.h" @@ -29,6 +30,7 @@ #include #include "common.h" +#include "compat.h" volatile uint8_t np2_verbose_level; uint8_t np2_libssh_verbose_level; @@ -78,66 +80,14 @@ np2log(int priority, const char *src, const char *fmt, ...) } } -/** - * @brief Encode message characters (% -> %%) to avoid printf arg problems. - */ -static const char * -np2log_encode(const char *msg, char **buf) -{ - const char *ptr1, *ptr2; - size_t buf_len, buf_size = 1; - void *mem; - - *buf = NULL; - if ((ptr2 = strchr(msg, '%'))) { - /* something to encode */ - ptr1 = msg; - do { - /* enlarge buffer */ - buf_len = buf_size - 1; - buf_size += (ptr2 - ptr1) + 2; - mem = realloc(*buf, buf_size * sizeof **buf); - if (!mem) { - EMEM; - return ""; - } - *buf = mem; - - /* copy preceding message */ - strncpy(*buf + buf_len, ptr1, ptr2 - ptr1); - buf_len += ptr2 - ptr1; - - /* copy % */ - strcpy(*buf + buf_len, "%%"); - - /* next iter */ - ptr1 = ptr2 + 1; - } while ((ptr2 = strchr(ptr1, '%'))); - - /* copy remaining message */ - buf_len = buf_size - 1; - buf_size += strlen(ptr1); - mem = realloc(*buf, buf_size * sizeof **buf); - if (!mem) { - EMEM; - return ""; - } - *buf = mem; - strcpy(*buf + buf_len, ptr1); - } - - return *buf ? *buf : msg; -} - /** * @brief printer callback for libnetconf2 */ void -np2log_cb_nc2(NC_VERB_LEVEL level, const char *msg) +np2log_cb_nc2(const struct nc_session *session, NC_VERB_LEVEL level, const char *msg) { int priority = LOG_ERR; - const char *log_msg; - char *buf; + char *buf = NULL; if (level > np2_verbose_level) { return; @@ -159,8 +109,12 @@ np2log_cb_nc2(NC_VERB_LEVEL level, const char *msg) break; } - log_msg = np2log_encode(msg, &buf); - np2log(priority, "LN", log_msg); + if (session && nc_session_get_id(session)) { + if (asprintf(&buf, "Session %u: %s", nc_session_get_id(session), msg) > -1) { + msg = buf; + } + } + np2log(priority, "LN", "%s", msg); free(buf); } @@ -171,8 +125,6 @@ void np2log_cb_ly(LY_LOG_LEVEL level, const char *msg, const char *path) { int priority; - const char *log_msg; - char *buf; if (level > np2_verbose_level) { return; @@ -199,9 +151,7 @@ np2log_cb_ly(LY_LOG_LEVEL level, const char *msg, const char *path) if (path) { np2log(priority, "LY", "%s (%s)", msg, path); } else { - log_msg = np2log_encode(msg, &buf); - np2log(priority, "LY", log_msg); - free(buf); + np2log(priority, "LY", "%s", msg); } } @@ -209,8 +159,6 @@ void np2log_cb_sr(sr_log_level_t level, const char *msg) { int priority = LOG_ERR; - const char *log_msg; - char *buf; if (level > np2_sr_verbose_level) { return; @@ -233,9 +181,7 @@ np2log_cb_sr(sr_log_level_t level, const char *msg) return; } - log_msg = np2log_encode(msg, &buf); - np2log(priority, "SR", log_msg); - free(buf); + np2log(priority, "SR", "%s", msg); } /** @@ -273,7 +219,6 @@ np2log_printf(NC_VERB_LEVEL level, const char *format, ...) msg_len = req_len + 1; mem = realloc(msg, msg_len); if (!mem) { - free(msg); goto cleanup; } msg = mem; diff --git a/src/log.h b/src/log.h index d4ce7d9b8..bacbd9b92 100644 --- a/src/log.h +++ b/src/log.h @@ -55,11 +55,13 @@ void np2log_printf(NC_VERB_LEVEL level, const char *format, ...); #define EMEM ERR("Memory allocation failed (%s:%d)", __FILE__, __LINE__) #define EINT ERR("Internal error (%s:%d)", __FILE__, __LINE__) +#define EUNLOCK(rc) ERR("Failed to unlock a lock (%s) (%s:%d)", strerror(rc), __FILE__, __LINE__) +#define ELOCK(rc) ERR("Failed to lock a lock (%s) (%s:%d)", strerror(rc), __FILE__, __LINE__) /** * @brief printer callback for libnetconf2 */ -void np2log_cb_nc2(NC_VERB_LEVEL level, const char *msg); +void np2log_cb_nc2(const struct nc_session *session, NC_VERB_LEVEL level, const char *msg); /** * @brief printer callback for libyang diff --git a/src/main.c b/src/main.c index 0b3b67b51..f96844251 100644 --- a/src/main.c +++ b/src/main.c @@ -89,6 +89,11 @@ signal_handler(int sig) } } +/** + * @brief Callback for deleting NC sessions. + * + * @param[in] session NC session to delete. + */ static void np2srv_del_session_cb(struct nc_session *session) { @@ -170,6 +175,12 @@ np2srv_del_session_cb(struct nc_session *session) nc_session_free(session, NULL); } +/** + * @brief Create NC rpc-error from a SR error. + * + * @param[in] err SR error. + * @return NC rpc-error opaque node tree. + */ static struct lyd_node * np2srv_err_nc(sr_error_info_err_t *err) { @@ -248,6 +259,12 @@ np2srv_err_nc(sr_error_info_err_t *err) return NULL; } +/** + * @brief Create NC error reply based on SR error info. + * + * @param[in] err_info SR error info. + * @return Server reply structure. + */ static struct nc_server_reply * np2srv_err_reply_sr(const sr_error_info_t *err_info) { @@ -290,6 +307,10 @@ np2srv_err_reply_sr(const sr_error_info_t *err_info) /** * @brief Callback for libnetconf2 handling all the RPCs. + * + * @param[in] rpc Received RPC to process. + * @param[in] ncs NC session that received @p rpc. + * @return Server reply structure. */ static struct nc_server_reply * np2srv_rpc_cb(struct lyd_node *rpc, struct nc_session *ncs) @@ -315,9 +336,11 @@ np2srv_rpc_cb(struct lyd_node *rpc, struct nc_session *ncs) free(str); /* set message */ - asprintf(&str, "Executing the operation is denied because \"%s\" NACM authorization failed.", nc_session_get_username(ncs)); - nc_err_set_msg(e, str, "en"); - free(str); + if (asprintf(&str, "Executing the operation is denied because \"%s\" NACM authorization failed.", + nc_session_get_username(ncs)) > -1) { + nc_err_set_msg(e, str, "en"); + free(str); + } return nc_server_reply_err(e); } @@ -378,6 +401,13 @@ np2srv_rpc_cb(struct lyd_node *rpc, struct nc_session *ncs) return reply; } +/** + * @brief Sysrepo callback for checking NACM of data diff. + * + * @param[in] session SR session. + * @param[in] diff Diff to check. + * @return SR error value. + */ static int np2srv_diff_check_cb(sr_session_ctx_t *session, const struct lyd_node *diff) { @@ -404,6 +434,13 @@ np2srv_diff_check_cb(sr_session_ctx_t *session, const struct lyd_node *diff) return SR_ERR_OK; } +/** + * @brief Check SR schema context for all the required schemas and features. + * + * @param[in] sr_sess SR session to use. + * @return 0 on success; + * @return -1 on error. + */ static int np2srv_check_schemas(sr_session_ctx_t *sr_sess) { @@ -482,19 +519,30 @@ np2srv_content_id_cb(void *UNUSED(user_data)) return strdup(buf); } +/** + * @brief Initialize the server, + * + * @return 0 on succes; + * @return -1 on error. + */ static int server_init(void) { const struct ly_ctx *ly_ctx; int rc; - /* connect to the sysrepo and set edit-config NACM diff check callback */ + /* connect to sysrepo */ rc = sr_connect(SR_CONN_CACHE_RUNNING, &np2srv.sr_conn); if (rc != SR_ERR_OK) { ERR("Connecting to sysrepo failed (%s).", sr_strerror(rc)); goto error; } - sr_set_diff_check_callback(np2srv.sr_conn, np2srv_diff_check_cb); + + /* set edit-config NACM diff check callback */ + rc = sr_set_diff_check_callback(np2srv.sr_conn, np2srv_diff_check_cb); + if (rc != SR_ERR_OK) { + WRN("Unable to set diff check callback (%s), NACM will not be applied when editing data.", sr_strerror(rc)); + } ly_ctx = sr_get_context(np2srv.sr_conn); @@ -576,6 +624,9 @@ server_init(void) return -1; } +/** + * @brief Destroy the server. + */ static void server_destroy(void) { @@ -633,6 +684,12 @@ np2srv_dummy_cb(sr_session_ctx_t *UNUSED(session), uint32_t UNUSED(sub_id), cons #endif +/** + * @brief Subscribe to all the handled RPCs of the server. + * + * @return 0 on success; + * @return -1 on error. + */ static int server_rpc_subscribe(void) { @@ -686,6 +743,12 @@ server_rpc_subscribe(void) return -1; } +/** + * @brief Subscribe to all the handled configuration and operational data by the server. + * + * @return 0 on success; + * @return -1 on error. + */ static int server_data_subscribe(void) { @@ -877,6 +940,12 @@ server_data_subscribe(void) return -1; } +/** + * @brief Server worker thread function. + * + * @param[in] arg Worker index. + * @return NULL. + */ static void * worker_thread(void *arg) { @@ -1209,7 +1278,7 @@ main(int argc, char *argv[]) close(pidfd); /* set printer callbacks for the used libraries and set proper log levels */ - nc_set_print_clb(np2log_cb_nc2); /* libnetconf2 */ + nc_set_print_clb_session(np2log_cb_nc2); /* libnetconf2 */ ly_set_log_clb(np2log_cb_ly, 1); /* libyang */ sr_log_set_cb(np2log_cb_sr); /* sysrepo, log level is checked by callback */ diff --git a/src/netconf.c b/src/netconf.c index c4586532d..f20a05fed 100644 --- a/src/netconf.c +++ b/src/netconf.c @@ -368,7 +368,8 @@ np2srv_rpc_editconfig_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), con /* config */ lyd_find_xpath(input, "config | url", &nodeset); if (!strcmp(nodeset->dnodes[0]->schema->name, "config")) { - config = op_parse_config((struct lyd_node_any *)nodeset->dnodes[0], LYD_PARSE_OPAQ | LYD_PARSE_ONLY, &rc, session); + config = op_parse_config((struct lyd_node_any *)nodeset->dnodes[0], LYD_PARSE_ONLY | LYD_PARSE_OPAQ | + LYD_PARSE_NO_STATE, &rc, session); if (rc) { ly_set_free(nodeset, NULL); goto cleanup; @@ -376,7 +377,8 @@ np2srv_rpc_editconfig_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), con } else { assert(!strcmp(nodeset->dnodes[0]->schema->name, "url")); #ifdef NP2SRV_URL_CAPAB - config = op_parse_url(lyd_get_value(nodeset->dnodes[0]), LYD_PARSE_OPAQ | LYD_PARSE_ONLY, &rc, session); + config = op_parse_url(lyd_get_value(nodeset->dnodes[0]), LYD_PARSE_ONLY | LYD_PARSE_OPAQ | LYD_PARSE_NO_STATE, + &rc, session); if (rc) { ly_set_free(nodeset, NULL); goto cleanup; @@ -436,9 +438,8 @@ np2srv_rpc_copyconfig_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), con { sr_datastore_t ds = SR_DS_OPERATIONAL, sds = SR_DS_OPERATIONAL; struct ly_set *nodeset = NULL; - const sr_error_info_t *err_info; struct lyd_node *config = NULL; - int rc = SR_ERR_OK, run_to_start = 0; + int rc = SR_ERR_OK, run_to_start = 0, source_is_config = 0; struct np2_user_sess *user_sess = NULL; const char *username; uint32_t *nc_sid; @@ -446,6 +447,7 @@ np2srv_rpc_copyconfig_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), con #ifdef NP2SRV_URL_CAPAB const char *trg_url = NULL; int lyp_wd_flag; + uint8_t url = 0; #endif if (NP_IGNORE_RPC(session, event)) { @@ -465,6 +467,7 @@ np2srv_rpc_copyconfig_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), con assert(!strcmp(nodeset->dnodes[0]->schema->name, "url")); #ifdef NP2SRV_URL_CAPAB trg_url = lyd_get_value(nodeset->dnodes[0]); + url++; #else ly_set_free(nodeset, NULL); rc = SR_ERR_UNSUPPORTED; @@ -492,21 +495,24 @@ np2srv_rpc_copyconfig_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), con ly_set_free(nodeset, NULL); goto cleanup; } + source_is_config = 1; } else { assert(!strcmp(nodeset->dnodes[0]->schema->name, "url")); #ifdef NP2SRV_URL_CAPAB + url++; if (trg_url && !strcmp(trg_url, lyd_get_value(nodeset->dnodes[0]))) { rc = SR_ERR_INVAL_ARG; - sr_session_set_error_message(session, "Source and target URLs are the same."); + np_err_sr2nc_same_ds(session, "Source and target URLs are the same."); goto cleanup; } - config = op_parse_url(lyd_get_value(nodeset->dnodes[0]), LYD_PARSE_STRICT | LYD_PARSE_NO_STATE | - LYD_PARSE_ONLY, &rc, session); + config = op_parse_url(lyd_get_value(nodeset->dnodes[0]), LYD_PARSE_ONLY | LYD_PARSE_OPAQ | LYD_PARSE_NO_STATE, + &rc, session); if (rc) { ly_set_free(nodeset, NULL); goto cleanup; } + source_is_config = 1; #else ly_set_free(nodeset, NULL); rc = SR_ERR_UNSUPPORTED; @@ -516,14 +522,20 @@ np2srv_rpc_copyconfig_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), con } ly_set_free(nodeset, NULL); - if (ds == sds) { + /* if both are url it is a valid call */ +#ifdef NP2SRV_URL_CAPAB + if ((ds == sds) && (url != 2)) +#else + if (ds == sds) +#endif + { rc = SR_ERR_INVAL_ARG; - sr_session_set_error_message(session, "Source and target datastores are the same."); + np_err_sr2nc_same_ds(session, "Source and target datastores are the same."); goto cleanup; } /* NACM checks */ - if (!config && !run_to_start) { + if (!source_is_config && !run_to_start) { /* get source datastore data and filter them */ sr_session_switch_ds(session, sds); rc = sr_get_data(session, "/*", 0, np2srv.sr_timeout, 0, &config); @@ -570,22 +582,21 @@ np2srv_rpc_copyconfig_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), con } else #endif { - if (config) { + if (source_is_config) { /* config is spent */ rc = sr_replace_config(user_sess->sess, NULL, config, np2srv.sr_timeout); config = NULL; } else { - assert(run_to_start); - - /* set SID to skip NACM check, only one copy-config can be executed at once */ - sr_session_get_orig_data(session, 0, NULL, (const void **)&nc_sid); - ATOMIC_STORE_RELAXED(skip_nacm_nc_sid, *nc_sid); + if (run_to_start) { + /* set SID to skip NACM check, only one copy-config can be executed at once */ + sr_session_get_orig_data(session, 0, NULL, (const void **)&nc_sid); + ATOMIC_STORE_RELAXED(skip_nacm_nc_sid, *nc_sid); + } rc = sr_copy_config(user_sess->sess, NULL, sds, np2srv.sr_timeout); ATOMIC_STORE_RELAXED(skip_nacm_nc_sid, 0); } - if (rc != SR_ERR_OK) { - sr_session_get_error(user_sess->sess, &err_info); - sr_session_set_error_message(session, err_info->err[0].message); + if (rc) { + sr_session_dup_error(user_sess->sess, session); goto cleanup; } } @@ -607,7 +618,6 @@ np2srv_rpc_deleteconfig_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), c struct ly_set *nodeset; int rc = SR_ERR_OK; struct np2_user_sess *user_sess = NULL; - const sr_error_info_t *err_info; #ifdef NP2SRV_URL_CAPAB struct lyd_node *config; @@ -648,7 +658,7 @@ np2srv_rpc_deleteconfig_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), c #ifdef NP2SRV_URL_CAPAB if (trg_url) { /* import URL to check its validity */ - config = op_parse_url(trg_url, LYD_PARSE_STRICT | LYD_PARSE_NO_STATE | LYD_PARSE_ONLY, &rc, session); + config = op_parse_url(trg_url, LYD_PARSE_ONLY | LYD_PARSE_OPAQ | LYD_PARSE_NO_STATE, &rc, session); if (rc) { goto cleanup; } @@ -662,9 +672,8 @@ np2srv_rpc_deleteconfig_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), c #endif { rc = sr_replace_config(user_sess->sess, NULL, NULL, np2srv.sr_timeout); - if (rc != SR_ERR_OK) { - sr_session_get_error(user_sess->sess, &err_info); - sr_session_set_error_message(session, err_info->err[0].message); + if (rc) { + sr_session_dup_error(user_sess->sess, session); goto cleanup; } } @@ -718,7 +727,7 @@ np2srv_rpc_un_lock_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const } else if (!strcmp(input->schema->name, "unlock")) { rc = sr_unlock(user_sess->sess, NULL); } - if ((rc == SR_ERR_LOCKED) && sr_session_get_orig_name(session) && !strcmp(sr_session_get_orig_name(session), "netopeer2")) { + if ((rc == SR_ERR_LOCKED) && NP_IS_ORIG_NP(session)) { /* NETCONF error */ sr_session_get_error(user_sess->sess, &err_info); np_err_sr2nc_lock_denied(session, err_info); @@ -808,9 +817,13 @@ np2srv_rpc_commit_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const c /* sysrepo API */ rc = sr_copy_config(user_sess->sess, NULL, SR_DS_CANDIDATE, np2srv.sr_timeout); - if (rc != SR_ERR_OK) { + if ((rc == SR_ERR_LOCKED) && NP_IS_ORIG_NP(session)) { + /* NETCONF error */ sr_session_get_error(user_sess->sess, &err_info); - sr_session_set_error_message(session, err_info->err[0].message); + np_err_sr2nc_in_use(session, err_info); + } else if (rc) { + /* Sysrepo error */ + sr_session_dup_error(user_sess->sess, session); goto cleanup; } @@ -894,8 +907,8 @@ np2srv_rpc_validate_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const } else { assert(!strcmp(nodeset->dnodes[0]->schema->name, "url")); #ifdef NP2SRV_URL_CAPAB - config = op_parse_url(lyd_get_value(nodeset->dnodes[0]), LYD_PARSE_STRICT | LYD_PARSE_NO_STATE | - LYD_PARSE_ONLY, &rc, session); + config = op_parse_url(lyd_get_value(nodeset->dnodes[0]), LYD_PARSE_ONLY | LYD_PARSE_OPAQ | LYD_PARSE_NO_STATE, + &rc, session); if (rc) { ly_set_free(nodeset, NULL); goto cleanup; @@ -945,6 +958,13 @@ np2srv_lysc_has_notif_clb(struct lysc_node *node, void *UNUSED(data), ly_bool *U return LY_SUCCESS; } +struct subscribe_ntf_data { + struct nc_session *nc_sess; + uint32_t sr_sub_count; + ATOMIC_T sr_ntf_replay_complete_count; + ATOMIC_T sr_ntf_stop_count; +}; + /** * @brief New notification callback used for notifications received on subscription made by \ RPC. */ @@ -953,23 +973,36 @@ np2srv_rpc_subscribe_ntf_cb(sr_session_ctx_t *UNUSED(session), uint32_t sub_id, const struct lyd_node *notif, struct timespec *timestamp, void *private_data) { struct nc_server_notif *nc_ntf = NULL; - struct nc_session *ncs = (struct nc_session *)private_data; + struct subscribe_ntf_data *cb_data = private_data; struct lyd_node *ly_ntf = NULL; NC_MSG_TYPE msg_type; - char *datetime; - time_t stop; + char *datetime = NULL; + struct timespec stop, cur_ts; /* create these notifications, sysrepo only emulates them */ if (notif_type == SR_EV_NOTIF_REPLAY_COMPLETE) { + ATOMIC_INC_RELAXED(cb_data->sr_ntf_replay_complete_count); + if (ATOMIC_LOAD_RELAXED(cb_data->sr_ntf_replay_complete_count) < cb_data->sr_sub_count) { + /* ignore, wait for the last SR subscription */ + goto cleanup; + } + lyd_new_path(NULL, sr_get_context(np2srv.sr_conn), "/nc-notifications:replayComplete", NULL, 0, &ly_ntf); notif = ly_ntf; } else if (notif_type == SR_EV_NOTIF_TERMINATED) { - sr_event_notif_sub_get_info(np2srv.sr_notif_sub, sub_id, NULL, NULL, NULL, &stop, NULL); - if (!stop || (stop > time(NULL))) { + sr_notif_sub_get_info(np2srv.sr_notif_sub, sub_id, NULL, NULL, NULL, &stop, NULL); + cur_ts = np_gettimespec(1); + if (!stop.tv_sec || (np_difftimespec(&stop, &cur_ts) < 0)) { /* no stop-time or it was not reached so no notification should be generated */ goto cleanup; } + ATOMIC_INC_RELAXED(cb_data->sr_ntf_stop_count); + if (ATOMIC_LOAD_RELAXED(cb_data->sr_ntf_stop_count) < cb_data->sr_sub_count) { + /* ignore, wait for the last SR subscription */ + goto cleanup; + } + lyd_new_path(NULL, sr_get_context(np2srv.sr_conn), "/nc-notifications:notificationComplete", NULL, 0, &ly_ntf); notif = ly_ntf; } else if ((notif_type == SR_EV_NOTIF_MODIFIED) || (notif_type == SR_EV_NOTIF_RESUMED) || @@ -984,30 +1017,32 @@ np2srv_rpc_subscribe_ntf_cb(sr_session_ctx_t *UNUSED(session), uint32_t sub_id, } /* check NACM */ - if (ncac_check_operation(notif, nc_session_get_username(ncs))) { + if (ncac_check_operation(notif, nc_session_get_username(cb_data->nc_sess))) { goto cleanup; } - /* create the notification object */ + /* create the notification object, all the passed arguments must exist until it is sent */ ly_time_ts2str(timestamp, &datetime); nc_ntf = nc_server_notif_new((struct lyd_node *)notif, datetime, NC_PARAMTYPE_CONST); - free(datetime); /* send the notification */ - msg_type = nc_server_notif_send(ncs, nc_ntf, NP2SRV_NOTIF_SEND_TIMEOUT); + msg_type = nc_server_notif_send(cb_data->nc_sess, nc_ntf, NP2SRV_NOTIF_SEND_TIMEOUT); if ((msg_type == NC_MSG_ERROR) || (msg_type == NC_MSG_WOULDBLOCK)) { - ERR("Sending a notification to session %d %s.", nc_session_get_id(ncs), msg_type == NC_MSG_ERROR ? "failed" : "timed out"); + ERR("Sending a notification to session %d %s.", nc_session_get_id(cb_data->nc_sess), msg_type == NC_MSG_ERROR ? + "failed" : "timed out"); goto cleanup; } - ncm_session_notification(ncs); + ncm_session_notification(cb_data->nc_sess); -cleanup: if (notif_type == SR_EV_NOTIF_TERMINATED) { /* subscription finished */ - nc_session_dec_notif_status(ncs); + nc_session_dec_notif_status(cb_data->nc_sess); + free(cb_data); } +cleanup: nc_server_notif_free(nc_ntf); + free(datetime); lyd_free_all(ly_ntf); } @@ -1024,10 +1059,12 @@ np2srv_rpc_subscribe_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), cons const char *stream; struct np2_filter filter = {0}; char *xp = NULL; - struct timespec start = {0}, stop = {0}; - int rc = SR_ERR_OK; + struct timespec start = {0}, stop = {0}, cur_ts; + int rc = SR_ERR_OK, has_nc_ntf_status = 0; const sr_error_info_t *err_info; uint32_t idx; + struct ly_set mod_set = {0}; + struct subscribe_ntf_data *cb_data = NULL; if (NP_IGNORE_RPC(session, event)) { /* ignore in this case (not supported) */ @@ -1080,7 +1117,7 @@ np2srv_rpc_subscribe_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), cons /* xpath */ if (strlen(lyd_get_meta_value(meta))) { xp = strdup(lyd_get_meta_value(meta)); - if (xp) { + if (!xp) { EMEM; rc = SR_ERR_NO_MEMORY; goto cleanup; @@ -1101,12 +1138,38 @@ np2srv_rpc_subscribe_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), cons ly_time_str2ts(lyd_get_value(node), &stop); } + /* check parameters */ + cur_ts = np_gettimespec(1); + if (start.tv_sec && (np_difftimespec(&start, &cur_ts) < 0)) { + np_err_bad_element(session, "startTime", "Specified \"startTime\" is in future."); + rc = SR_ERR_INVAL_ARG; + goto cleanup; + } else if (stop.tv_sec && !start.tv_sec) { + np_err_missing_element(session, "startTime"); + rc = SR_ERR_INVAL_ARG; + goto cleanup; + } else if (start.tv_sec && stop.tv_sec && (np_difftimespec(&stop, &start) > 0)) { + np_err_bad_element(session, "stopTime", "Specified \"stopTime\" is earlier than \"startTime\"."); + rc = SR_ERR_INVAL_ARG; + goto cleanup; + } + + /* create notif CB data */ + cb_data = calloc(1, sizeof *cb_data); + if (!cb_data) { + EMEM; + rc = SR_ERR_NO_MEMORY; + goto cleanup; + } + cb_data->nc_sess = ncs; + /* set ongoing notifications flag */ nc_session_inc_notif_status(ncs); + has_nc_ntf_status = 1; /* sysrepo API */ if (!strcmp(stream, "NETCONF")) { - /* subscribe to all modules with notifications */ + /* find all modules with notifications */ idx = 0; while ((ly_mod = ly_ctx_get_module_iter(LYD_CTX(input), &idx))) { if (!ly_mod->implemented) { @@ -1114,35 +1177,50 @@ np2srv_rpc_subscribe_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), cons } if (lysc_module_dfs_full(ly_mod, np2srv_lysc_has_notif_clb, NULL) == LY_EEXIST) { - /* a notification was found, subscribe to the module */ - rc = sr_event_notif_subscribe_tree(user_sess->sess, ly_mod->name, xp, start.tv_sec, stop.tv_sec, - np2srv_rpc_subscribe_ntf_cb, ncs, SR_SUBSCR_CTX_REUSE, &np2srv.sr_notif_sub); - if (rc != SR_ERR_OK) { - sr_session_get_error(user_sess->sess, &err_info); - sr_session_set_error_message(session, err_info->err[0].message); - break; + /* a notification was found */ + if (ly_set_add(&mod_set, (void *)ly_mod, 1, NULL)) { + rc = SR_ERR_INTERNAL; + goto cleanup; } } } + + /* subscribe to all the modules */ + cb_data->sr_sub_count = mod_set.count; + for (idx = 0; idx < mod_set.count; ++idx) { + ly_mod = mod_set.objs[idx]; + rc = sr_notif_subscribe_tree(user_sess->sess, ly_mod->name, xp, start.tv_sec ? &start : NULL, + stop.tv_sec ? &stop : NULL, np2srv_rpc_subscribe_ntf_cb, cb_data, SR_SUBSCR_CTX_REUSE, + &np2srv.sr_notif_sub); + if (rc != SR_ERR_OK) { + sr_session_get_error(user_sess->sess, &err_info); + sr_session_set_error_message(session, err_info->err[0].message); + goto cleanup; + } + } } else { /* subscribe to the specific module (stream) */ - rc = sr_event_notif_subscribe_tree(user_sess->sess, stream, xp, start.tv_sec, stop.tv_sec, np2srv_rpc_subscribe_ntf_cb, - ncs, SR_SUBSCR_CTX_REUSE, &np2srv.sr_notif_sub); + cb_data->sr_sub_count = 1; + rc = sr_notif_subscribe_tree(user_sess->sess, stream, xp, start.tv_sec ? &start : NULL, stop.tv_sec ? &stop : NULL, + np2srv_rpc_subscribe_ntf_cb, cb_data, SR_SUBSCR_CTX_REUSE, &np2srv.sr_notif_sub); if (rc != SR_ERR_OK) { sr_session_get_error(user_sess->sess, &err_info); sr_session_set_error_message(session, err_info->err[0].message); + goto cleanup; } } - if (rc) { - /* fail */ - nc_session_dec_notif_status(ncs); - goto cleanup; - } + /* owned now by the callback */ + cb_data = NULL; /* success */ cleanup: + if (rc && has_nc_ntf_status) { + nc_session_dec_notif_status(ncs); + } + ly_set_erase(&mod_set, NULL); + free(cb_data); op_filter_erase(&filter); free(xp); np_release_user_sess(user_sess); diff --git a/src/netconf_acm.c b/src/netconf_acm.c index 34879f623..9ab2a0a6c 100644 --- a/src/netconf_acm.c +++ b/src/netconf_acm.c @@ -3,7 +3,7 @@ * @author Michal Vasko * @brief NACM and ietf-netconf-acm callbacks * - * Copyright (c) 2019 CESNET, z.s.p.o. + * Copyright (c) 2019 - 2021 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. @@ -55,6 +55,7 @@ ncac_nacm_params_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const ch return rc; } + /* NACM LOCK */ pthread_mutex_lock(&nacm.lock); while ((rc = sr_get_change_tree_next(session, iter, &op, &node, NULL, NULL, NULL)) == SR_ERR_OK) { @@ -102,6 +103,7 @@ ncac_nacm_params_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const ch } } + /* NACM UNLOCK */ pthread_mutex_unlock(&nacm.lock); sr_free_change_iter(iter); @@ -123,6 +125,7 @@ ncac_oper_cb(sr_session_ctx_t *UNUSED(session), uint32_t UNUSED(sub_id), const c assert(*parent); + /* NACM LOCK */ pthread_mutex_lock(&nacm.lock); if (!strcmp(path, "/ietf-netconf-acm:nacm/denied-operations")) { @@ -137,6 +140,7 @@ ncac_oper_cb(sr_session_ctx_t *UNUSED(session), uint32_t UNUSED(sub_id), const c lyrc = lyd_new_path(*parent, NULL, "denied-notifications", num_str, 0, NULL); } + /* NACM UNLOCK */ pthread_mutex_unlock(&nacm.lock); if (lyrc) { @@ -175,6 +179,7 @@ ncac_group_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const char *UN return rc; } + /* NACM LOCK */ pthread_mutex_lock(&nacm.lock); while ((rc = sr_get_change_tree_next(session, iter, &op, &node, NULL, NULL, NULL)) == SR_ERR_OK) { @@ -188,8 +193,10 @@ ncac_group_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const char *UN /* add new group */ mem = realloc(nacm.groups, (nacm.group_count + 1) * sizeof *nacm.groups); if (!mem) { - EMEM; + /* NACM UNLOCK */ pthread_mutex_unlock(&nacm.lock); + + EMEM; return SR_ERR_NO_MEMORY; } nacm.groups = mem; @@ -229,8 +236,10 @@ ncac_group_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const char *UN group = NULL; break; default: - EINT; + /* NACM UNLOCK */ pthread_mutex_unlock(&nacm.lock); + + EINT; return SR_ERR_INTERNAL; } } else { @@ -257,8 +266,10 @@ ncac_group_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const char *UN if (op == SR_OP_CREATED) { mem = realloc(group->users, (group->user_count + 1) * sizeof *group->users); if (!mem) { - EMEM; + /* NACM UNLOCK */ pthread_mutex_unlock(&nacm.lock); + + EMEM; return SR_ERR_NO_MEMORY; } group->users = mem; @@ -289,6 +300,7 @@ ncac_group_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const char *UN } } + /* NACM UNLOCK */ pthread_mutex_unlock(&nacm.lock); sr_free_change_iter(iter); @@ -300,6 +312,11 @@ ncac_group_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const char *UN return SR_ERR_OK; } +/** + * @brief Remove all rules from a rule list. + * + * @param[in,out] list Rule list to remove from. + */ static void ncac_remove_rules(struct ncac_rule_list *list) { @@ -318,6 +335,157 @@ ncac_remove_rules(struct ncac_rule_list *list) list->rules = NULL; } +/** + * @brief Get pointer to an item on a specific index. + * + * @param[in] items Array of items. + * @param[in] item_size Size of each item. + * @param[in] idx Index of the item to get. + * @return Pointer to the item at index. + */ +#define ITEM_IDX_PTR(items, item_size, idx) (char **)(((uint64_t)(uintptr_t)items) + ((idx) * (item_size))) + +/** + * @brief Compare callback for sorting functions like qsort(3) and bsearch(3). + * + * @param[in] ptr1 Pointer to the first value. + * @param[in] ptr2 Pointer to the second value. + * @return < 0 if ptr1 < ptr2. + * @return 0 if ptr1 == ptr2. + * @return > 0 if ptr1 > ptr2. + */ +static int +ncac_sort_strcmp_cb(const void *ptr1, const void *ptr2) +{ + const char **str1, **str2; + + str1 = (const char **)ptr1; + str2 = (const char **)ptr2; + + return strcmp(*str1, *str2); +} + +/** + * @brief Find an item in a sorted array. The item structure first member must be (const char *) in the dictionary. + * + * @param[in] item Pointer to item to find. + * @param[in] item_size Size of an item. + * @param[in] items Item array. + * @param[in] item_count Number of @p items. + * @param[out] match Optional pointer to the found item. + * @return Index of the item in @p items. + * @return -1 if no matching item was found. + */ +static int32_t +ncac_strarr_sort_find(const char **item, size_t item_size, char **items, uint32_t item_count) +{ + const char **m; + int32_t idx = -1; + + if (!items) { + return idx; + } + + m = bsearch(item, items, item_count, item_size, ncac_sort_strcmp_cb); + if (m) { + idx = ((uint64_t)(uintptr_t)m - (uint64_t)(uintptr_t)items) / item_size; + } + + return idx; +} + +/** + * @brief Add an item into a sorted array. The item structure first member must be (const char *) in the dictionary. + * + * @param[in] ly_ctx libyang context. + * @param[in] item Pointer to item to add, string does not need to be in the dictionary. + * @param[in] item_size Size of an item. + * @param[in] check_dup Whether to check for duplicates before adding, returns SR_ERR_OK if duplicate found. + * @param[in,out] items Pointer to the item array. + * @param[in,out] item_count Pointer to the number of @p items. + * @return Sysrepo err value. + */ +static int +ncac_strarr_sort_add(const struct ly_ctx *ly_ctx, const char **item, size_t item_size, int check_dup, char ***items, + uint32_t *item_count) +{ + void *mem; + uint32_t i; + + if (check_dup && (ncac_strarr_sort_find(item, item_size, *items, *item_count) > -1)) { + /* already added */ + return SR_ERR_OK; + } + + /* starting index, assume normal distribution and names starting with lowercase letters */ + if ((*item)[0] < 'a') { + i = 0; + } else if ((*item)[0] > 'z') { + i = *item_count ? *item_count - 1 : 0; + } else { + i = ((*item)[0] - 'a') * ((double)*item_count / 26.0); + } + + /* find the index to add it on */ + if (*item_count && (strcmp(*ITEM_IDX_PTR(*items, item_size, i), *item) > 0)) { + while (i && (strcmp(*ITEM_IDX_PTR(*items, item_size, i - 1), *item) > 0)) { + --i; + } + } else if (*item_count && (strcmp(*ITEM_IDX_PTR(*items, item_size, i), *item) < 0)) { + while ((i < *item_count) && (strcmp(*ITEM_IDX_PTR(*items, item_size, i), *item) < 0)) { + ++i; + } + } + + /* realloc */ + mem = realloc(*items, (*item_count + 1) * item_size); + if (!mem) { + EMEM; + return SR_ERR_NO_MEMORY; + } + *items = mem; + + /* move all following items */ + if (i < *item_count) { + memmove(ITEM_IDX_PTR(*items, item_size, i + 1), ITEM_IDX_PTR(*items, item_size, i), (*item_count - i) * item_size); + } + + /* insert new item */ + lydict_insert(ly_ctx, *item, 0, (const char **)ITEM_IDX_PTR(*items, item_size, i)); + ++(*item_count); + return SR_ERR_OK; +} + +/** + * @brief Remove an item from a sorted array. The item structure first member must be (const char *) in the dictionary. + * + * @param[in] ly_ctx libyang context. + * @param[in] item Pointer to item to remove. + * @param[in] item_size Size of an item. + * @param[in,out] items Pointer to the item array. + * @param[in,out] item_count Pointer to the number of @p items. + */ +static void +ncac_strarr_sort_del(const struct ly_ctx *ly_ctx, const char **item, size_t item_size, char ***items, uint32_t *item_count) +{ + int32_t i; + + /* find the item, get its index */ + i = ncac_strarr_sort_find(item, item_size, *items, *item_count); + assert(i > -1); + + /* delete it, keep the order */ + lydict_remove(ly_ctx, *ITEM_IDX_PTR(*items, item_size, i)); + --(*item_count); + if ((uint32_t)i < *item_count) { + memmove(ITEM_IDX_PTR(*items, item_size, i), ITEM_IDX_PTR(*items, item_size, i + 1), (*item_count - i) * item_size); + } + if (!*item_count) { + free(*items); + *items = NULL; + } +} + /* /ietf-netconf-acm:nacm/rule-list */ int ncac_rule_list_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const char *UNUSED(module_name), const char *xpath, @@ -332,7 +500,6 @@ ncac_rule_list_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const char char *xpath2; int rc, len; uint32_t i; - void *mem; ly_ctx = (struct ly_ctx *)sr_get_context(np2srv.sr_conn); @@ -347,6 +514,7 @@ ncac_rule_list_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const char return rc; } + /* NACM LOCK */ pthread_mutex_lock(&nacm.lock); while ((rc = sr_get_change_tree_next(session, iter, &op, &node, NULL, &prev_list, NULL)) == SR_ERR_OK) { @@ -376,8 +544,10 @@ ncac_rule_list_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const char /* create new rule list */ rlist = calloc(1, sizeof *rlist); if (!rlist) { - EMEM; + /* NACM UNLOCK */ pthread_mutex_unlock(&nacm.lock); + + EMEM; return SR_ERR_NO_MEMORY; } lydict_insert(ly_ctx, rlist_name, 0, &rlist->name); @@ -431,8 +601,10 @@ ncac_rule_list_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const char rlist = NULL; break; default: - EINT; + /* NACM UNLOCK */ pthread_mutex_unlock(&nacm.lock); + + EINT; return SR_ERR_INTERNAL; } } else { @@ -450,40 +622,21 @@ ncac_rule_list_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const char group_name = lyd_get_value(node); if (op == SR_OP_CREATED) { - mem = realloc(rlist->groups, (rlist->group_count + 1) * sizeof *rlist->groups); - if (!mem) { - EMEM; + if ((rc = ncac_strarr_sort_add(ly_ctx, &group_name, sizeof rlist->groups, 0, &rlist->groups, + &rlist->group_count))) { + /* NACM UNLOCK */ pthread_mutex_unlock(&nacm.lock); - return SR_ERR_NO_MEMORY; + return rc; } - rlist->groups = mem; - lydict_insert(ly_ctx, group_name, 0, (const char **)&rlist->groups[rlist->group_count]); - ++rlist->group_count; } else { assert(op == SR_OP_DELETED); - for (i = 0; i < rlist->group_count; ++i) { - /* both in dictionary */ - if (rlist->groups[i] == group_name) { - break; - } - } - assert(i < rlist->group_count); - - /* delete it */ - lydict_remove(ly_ctx, rlist->groups[i]); - --rlist->group_count; - if (i < rlist->group_count) { - rlist->groups[i] = rlist->groups[rlist->group_count]; - } - if (!rlist->group_count) { - free(rlist->groups); - rlist->groups = NULL; - } + ncac_strarr_sort_del(ly_ctx, &group_name, sizeof rlist->groups, &rlist->groups, &rlist->group_count); } } } } + /* NACM UNLOCK */ pthread_mutex_unlock(&nacm.lock); sr_free_change_iter(iter); @@ -523,6 +676,7 @@ ncac_rule_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const char *UNU return rc; } + /* NACM LOCK */ pthread_mutex_lock(&nacm.lock); while ((rc = sr_get_change_tree_next(session, iter, &op, &node, NULL, &prev_list, NULL)) == SR_ERR_OK) { @@ -563,6 +717,8 @@ ncac_rule_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const char *UNU rule = calloc(1, sizeof *rule); if (!rule) { EMEM; + + /* NACM UNLOCK */ pthread_mutex_unlock(&nacm.lock); return SR_ERR_NO_MEMORY; } @@ -616,8 +772,10 @@ ncac_rule_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const char *UNU free(rule); break; default: - EINT; + /* NACM UNLOCK */ pthread_mutex_unlock(&nacm.lock); + + EINT; return SR_ERR_INTERNAL; } } else { @@ -713,6 +871,7 @@ ncac_rule_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const char *UNU } } + /* NACM UNLOCK */ pthread_mutex_unlock(&nacm.lock); sr_free_change_iter(iter); @@ -864,8 +1023,8 @@ ncac_allowed_tree(const struct lysc_node *top_node, const char *user) * * @param[in] ly_ctx libyang context for dictionary. * @param[in] user User to collect groups for. - * @param[out] groups Array of collected groups. - * @param[out] group_count Number of collected groups. + * @param[out] groups Sorted array of collected groups. + * @param[out] group_count Number of @p groups. * @return 0 on success, -1 on error. */ static int @@ -873,12 +1032,11 @@ ncac_collect_groups(const struct ly_ctx *ly_ctx, const char *user, char ***group { struct group grp, *grp_p; gid_t user_gid; - const char *user_dict = NULL, *grp_dict; + const char *user_dict = NULL; char *buf = NULL; gid_t *gids = NULL; ssize_t buflen; uint32_t i, j; - void *mem; int gid_count = 0, ret, rc = -1; lydict_insert(ly_ctx, user, 0, &user_dict); @@ -890,14 +1048,9 @@ ncac_collect_groups(const struct ly_ctx *ly_ctx, const char *user, char ***group for (i = 0; i < nacm.group_count; ++i) { for (j = 0; j < nacm.groups[i].user_count; ++j) { if (nacm.groups[i].users[j] == user_dict) { - mem = realloc(*groups, (*group_count + 1) * sizeof **groups); - if (!mem) { - EMEM; + if (ncac_strarr_sort_add(ly_ctx, &nacm.groups[i].name, sizeof **groups, 0, groups, group_count)) { goto cleanup; } - *groups = mem; - lydict_insert(ly_ctx, nacm.groups[i].name, 0, (const char **)&(*groups)[*group_count]); - ++(*group_count); } } } @@ -946,27 +1099,10 @@ ncac_collect_groups(const struct ly_ctx *ly_ctx, const char *user, char ***group ERR("Getting GID grp entry failed (Group not found)."); goto cleanup; } - lydict_insert(ly_ctx, grp.gr_name, 0, &grp_dict); - - /* check for duplicates */ - for (j = 0; j < *group_count; ++j) { - if ((*groups)[j] == grp_dict) { - break; - } - } - if (j < *group_count) { - /* duplicate */ - lydict_remove(ly_ctx, grp_dict); - } else { - mem = realloc(*groups, (*group_count + 1) * sizeof *groups); - if (!mem) { - EMEM; - goto cleanup; - } - *groups = mem; - (*groups)[*group_count] = (char *)grp_dict; - ++(*group_count); + /* add, if not already there */ + if (ncac_strarr_sort_add(ly_ctx, (const char **)&grp.gr_name, sizeof **groups, 1, groups, group_count)) { + goto cleanup; } } } @@ -1037,14 +1173,81 @@ ncac_allowed_path(const char *rule_target, const char *node_path) } } -enum ncac_access +/** + * @brief Check whether any group from a rule list matches one of the user groups. + * + * @param[in] rlist Rule list with sorted groups. + * @param[in] groups User group sorted array. + * @param[in] group_count Count of @p groups. + * @return 1 if a match is found. + * @return 0 if no matching group is found. + */ +static int +ncac_rule_group_match(struct ncac_rule_list *rlist, char **groups, uint32_t group_count) +{ + uint32_t i = 0, j = 0; + int r; + + while ((i < rlist->group_count) && (j < group_count)) { + if (!strcmp(rlist->groups[i], "*")) { + /* match for all groups */ + return 1; + } + + r = strcmp(rlist->groups[i], groups[j]); + if (r > 0) { + ++j; + } else if (r < 0) { + ++i; + } else { + /* match */ + return 1; + } + } + + /* no match */ + return 0; +} + +/** + * @brief Free all NACM groups. Supposed to be called after @ref ncac_collect_group. + * + * @param[out] groups Sorted array of collected groups to free + * @param[out] group_count Number of @p groups. + */ +static void +ncac_free_groups(char **groups, uint32_t group_count) +{ + uint32_t i; + + if (!groups) { + return; + } + + for (i = 0; i < group_count; ++i) { + lydict_remove(sr_get_context(np2srv.sr_conn), groups[i]); + } + free(groups); +} + +/** + * @brief Check NACM access for a single node. + * + * @param[in] node Node to check. Can be NULL if @p node_path and @p node_schema are set. + * @param[in] node_path Node path of the node to check. Can be NULL if @p node is set. + * @param[in] node_schema Schema of the node to check. Can be NULL if @p node is set. + * @param[in] oper Operation to check. + * @param[in] groups Array of groups name to be checked for permissions + * @param[in] group_count Length of @p groups + * @return NCAC access enum. + */ +static enum ncac_access ncac_allowed_node(const struct lyd_node *node, const char *node_path, const struct lysc_node *node_schema, - const char *user, uint8_t oper) + uint8_t oper, char **groups, uint32_t group_count) { struct ncac_rule_list *rlist; struct ncac_rule *rule; - char **groups, *path; - uint32_t i, j, group_count; + char *path; enum ncac_access access = NCAC_ACCESS_DENY; enum { @@ -1066,10 +1269,7 @@ ncac_allowed_node(const struct lyd_node *node, const char *node_path, const stru * ref https://tools.ietf.org/html/rfc8341#section-3.4.4 */ - /* 4) collect groups */ - if (ncac_collect_groups(sr_get_context(np2srv.sr_conn), user, &groups, &group_count)) { - goto cleanup; - } + /* 4) collected groups passed as argument */ /* 5) no groups */ if (!group_count) { @@ -1078,34 +1278,13 @@ ncac_allowed_node(const struct lyd_node *node, const char *node_path, const stru /* 6) find matching rule lists */ for (rlist = nacm.rule_lists; rlist; rlist = rlist->next) { - for (i = 0; i < rlist->group_count; ++i) { - if (strcmp(rlist->groups[i], "*")) { - for (j = 0; j < group_count; ++j) { - if (rlist->groups[i] == groups[j]) { - break; - } - } - if (j < group_count) { - /* match */ - break; - } - } else { - /* match for all groups */ - break; - } - } - if (i == rlist->group_count) { - /* no match */ + if (!ncac_rule_group_match(rlist, groups, group_count)) { + /* no group match */ continue; } /* 7) find matching rules */ for (rule = rlist->rules; rule; rule = rule->next) { - /* module name matching */ - if (rule->module_name && (rule->module_name != node_schema->module->name)) { - continue; - } - /* access operation matching */ if (!(rule->operations & oper)) { continue; @@ -1159,6 +1338,11 @@ ncac_allowed_node(const struct lyd_node *node, const char *node_path, const stru break; } + /* module name matching, after partial path matches */ + if (rule->module_name && (rule->module_name != node_schema->module->name)) { + continue; + } + /* 8) rule matched */ access = rule->action_deny ? NCAC_ACCESS_DENY : NCAC_ACCESS_PERMIT; goto cleanup; @@ -1224,20 +1408,18 @@ ncac_allowed_node(const struct lyd_node *node, const char *node_path, const stru /* node itself is allowed but a rule denies access to some descendants */ access = NCAC_ACCESS_PARTIAL_PERMIT; } - - for (i = 0; i < group_count; ++i) { - lydict_remove(sr_get_context(np2srv.sr_conn), groups[i]); - } - free(groups); return access; } const struct lyd_node * ncac_check_operation(const struct lyd_node *data, const char *user) { - const struct lyd_node *op; + const struct lyd_node *op = NULL; + char **groups = NULL; + uint32_t group_count = 0; int allowed = 0; + /* NACM LOCK */ pthread_mutex_lock(&nacm.lock); /* check access for the whole data tree first */ @@ -1246,6 +1428,10 @@ ncac_check_operation(const struct lyd_node *data, const char *user) goto cleanup; } + if (ncac_collect_groups(sr_get_context(np2srv.sr_conn), user, &groups, &group_count)) { + goto cleanup; + } + op = data; while (op) { if (op->schema->nodetype & (LYS_RPC | LYS_ACTION | LYS_NOTIF)) { @@ -1284,21 +1470,22 @@ ncac_check_operation(const struct lyd_node *data, const char *user) if (op->schema->nodetype & (LYS_RPC | LYS_ACTION)) { /* check X access on the RPC/action */ - if (!NCAC_ACCESS_IS_NODE_PERMIT(ncac_allowed_node(op, NULL, NULL, user, NCAC_OP_EXEC))) { + if (!NCAC_ACCESS_IS_NODE_PERMIT(ncac_allowed_node(op, NULL, NULL, NCAC_OP_EXEC, groups, group_count))) { goto cleanup; } } else { assert(op->schema->nodetype == LYS_NOTIF); /* check R access on the notification */ - if (!NCAC_ACCESS_IS_NODE_PERMIT(ncac_allowed_node(op, NULL, NULL, user, NCAC_OP_READ))) { + if (!NCAC_ACCESS_IS_NODE_PERMIT(ncac_allowed_node(op, NULL, NULL, NCAC_OP_READ, groups, group_count))) { goto cleanup; } } if (op->parent) { /* check R access on the parents, the last parent must be enough */ - if (!NCAC_ACCESS_IS_NODE_PERMIT(ncac_allowed_node(lyd_parent(op), NULL, NULL, user, NCAC_OP_READ))) { + if (!NCAC_ACCESS_IS_NODE_PERMIT(ncac_allowed_node(lyd_parent(op), NULL, NULL, NCAC_OP_READ, groups, + group_count))) { goto cleanup; } } @@ -1306,15 +1493,18 @@ ncac_check_operation(const struct lyd_node *data, const char *user) allowed = 1; cleanup: + ncac_free_groups(groups, group_count); if (allowed) { op = NULL; - } else { + } else if (op) { if (op->schema->nodetype & (LYS_RPC | LYS_ACTION)) { ++nacm.denied_operations; } else { ++nacm.denied_notifications; } } + + /* NACM UNLOCK */ pthread_mutex_unlock(&nacm.lock); return op; } @@ -1324,22 +1514,24 @@ ncac_check_operation(const struct lyd_node *data, const char *user) * * @param[in,out] first First sibling to filter. * @param[in] user User for the NACM filtering. + * @param[in] groups Array of collected groups. + * @param[in] group_count Number of @p groups. * @return Highest access among descendants (recursively), permit is the highest. */ static enum ncac_access -ncac_check_data_read_filter_r(struct lyd_node **first, const char *user) +ncac_check_data_read_filter_r(struct lyd_node **first, const char *user, char **groups, uint32_t group_count) { struct lyd_node *next, *elem; enum ncac_access node_access, ret_access = NCAC_ACCESS_DENY; LY_LIST_FOR_SAFE(*first, next, elem) { /* check access of the node */ - node_access = ncac_allowed_node(elem, NULL, NULL, user, NCAC_OP_READ); + node_access = ncac_allowed_node(elem, NULL, NULL, NCAC_OP_READ, groups, group_count); if (node_access == NCAC_ACCESS_PARTIAL_DENY) { /* only partial deny access, we must check children recursively to learn whether this node is allowed or not */ if (elem->schema->nodetype & LYD_NODE_INNER) { - node_access = ncac_check_data_read_filter_r(&((struct lyd_node_inner *)elem)->child, user); + node_access = ncac_check_data_read_filter_r(&((struct lyd_node_inner *)elem)->child, user, groups, group_count); } if (node_access != NCAC_ACCESS_PERMIT) { @@ -1349,7 +1541,7 @@ ncac_check_data_read_filter_r(struct lyd_node **first, const char *user) } else if (node_access == NCAC_ACCESS_PARTIAL_PERMIT) { /* partial permit, the node will be included in the reply but we must check children as well */ if (elem->schema->nodetype & LYD_NODE_INNER) { - ncac_check_data_read_filter_r(&((struct lyd_node_inner *)elem)->child, user); + ncac_check_data_read_filter_r(&((struct lyd_node_inner *)elem)->child, user, groups, group_count); } node_access = NCAC_ACCESS_PERMIT; } @@ -1376,15 +1568,26 @@ ncac_check_data_read_filter_r(struct lyd_node **first, const char *user) void ncac_check_data_read_filter(struct lyd_node **data, const char *user) { + char **groups = NULL; + uint32_t group_count; + assert(data); + /* NACM LOCK */ pthread_mutex_lock(&nacm.lock); + if (ncac_collect_groups(sr_get_context(np2srv.sr_conn), user, &groups, &group_count)) { + goto cleanup; + } + if (*data && !ncac_allowed_tree((*data)->schema, user)) { - ncac_check_data_read_filter_r(data, user); + ncac_check_data_read_filter_r(data, user, groups, group_count); } +cleanup: + /* NACM UNLOCK */ pthread_mutex_unlock(&nacm.lock); + ncac_free_groups(groups, group_count); } /** @@ -1393,10 +1596,12 @@ ncac_check_data_read_filter(struct lyd_node **data, const char *user) * @param[in] diff First diff sibling. * @param[in] user User for the NACM check. * @param[in] parent_op Inherited parent operation. + * @param[in] groups Array of collected groups. + * @param[in] group_count Number of @p groups. * @return NULL if access allowed, otherwise the denied access data node. */ static const struct lyd_node * -ncac_check_diff_r(const struct lyd_node *diff, const char *user, const char *parent_op) +ncac_check_diff_r(const struct lyd_node *diff, const char *user, const char *parent_op, char **groups, uint32_t group_count) { const char *op; struct lyd_meta *meta; @@ -1443,14 +1648,14 @@ ncac_check_diff_r(const struct lyd_node *diff, const char *user, const char *par } /* check access for the node, none operation is always allowed, and partial access is relevant only for read operation */ - if (oper && !NCAC_ACCESS_IS_NODE_PERMIT(ncac_allowed_node(diff, NULL, NULL, user, oper))) { + if (oper && !NCAC_ACCESS_IS_NODE_PERMIT(ncac_allowed_node(diff, NULL, NULL, oper, groups, group_count))) { node = diff; break; } /* go recursively */ if (lyd_child(diff)) { - node = ncac_check_diff_r(lyd_child(diff), user, op); + node = ncac_check_diff_r(lyd_child(diff), user, op, groups, group_count); } } @@ -1461,17 +1666,67 @@ const struct lyd_node * ncac_check_diff(const struct lyd_node *diff, const char *user) { const struct lyd_node *node = NULL; + char **groups = NULL; + uint32_t group_count; + /* NACM LOCK */ pthread_mutex_lock(&nacm.lock); + if (ncac_collect_groups(sr_get_context(np2srv.sr_conn), user, &groups, &group_count)) { + goto cleanup; + } + /* any node can be used in this case */ if (!ncac_allowed_tree(diff->schema, user)) { - node = ncac_check_diff_r(diff, user, NULL); + node = ncac_check_diff_r(diff, user, NULL, groups, group_count); if (node) { ++nacm.denied_data_writes; } } +cleanup: + /* NACM UNLOCK */ pthread_mutex_unlock(&nacm.lock); + ncac_free_groups(groups, group_count); return node; } + +void +ncac_check_yang_push_update_notif(const char *user, struct ly_set *set, int *all_removed) +{ + struct lyd_node_any *ly_value; + struct lyd_node *ly_target, *next, *iter; + uint32_t i, group_count, removed = 0; + char **groups; + + if (ncac_collect_groups(sr_get_context(np2srv.sr_conn), user, &groups, &group_count)) { + return; + } + + for (i = 0; i < set->count; ++i) { + /* check the change itself */ + lyd_find_path(set->dnodes[i], "target", 0, &ly_target); + if (!NCAC_ACCESS_IS_NODE_PERMIT(ncac_allowed_node(NULL, lyd_get_value(ly_target), ly_target->priv, + NCAC_OP_READ, groups, group_count))) { + /* not allowed, remove this change */ + lyd_free_tree(set->dnodes[i]); + ++removed; + continue; + } + + if (!lyd_find_path(set->dnodes[i], "value", 0, (struct lyd_node **)&ly_value)) { + assert(ly_value->value_type == LYD_ANYDATA_DATATREE); + + /* filter out any nested nodes */ + LY_LIST_FOR_SAFE(lyd_child(ly_value->value.tree), next, iter) { + ncac_check_data_read_filter(&iter, user); + } + } + } + ncac_free_groups(groups, group_count); + if (removed == set->count) { + *all_removed = 1; + } else { + *all_removed = 0; + } +} diff --git a/src/netconf_acm.h b/src/netconf_acm.h index b0ef26c85..60caac7fd 100644 --- a/src/netconf_acm.h +++ b/src/netconf_acm.h @@ -59,7 +59,7 @@ struct ncac { const char *name; /**< Group name. */ char **users; /**< Array of users belonging to this group. */ uint32_t user_count; /**< Number of users. */ - } *groups; /**< Array of existing groups. */ + } *groups; /**< Sorted array of existing groups. */ uint32_t group_count; /**< Number of groups. */ /** @@ -67,7 +67,7 @@ struct ncac { */ struct ncac_rule_list { const char *name; /**< Rule list name. */ - char **groups; /**< All groups associated with this rule list. */ + char **groups; /**< Sorted all groups associated with this rule list. */ uint32_t group_count; /**< Number of groups. */ /** @@ -87,7 +87,7 @@ struct ncac { struct ncac_rule_list *next; /**< Pointer to the next rule list. */ } *rule_lists; /**< List of all the rule lists. */ - pthread_mutex_t lock; + pthread_mutex_t lock; /**< Lock for accessing all the NACM members. */ }; enum ncac_access { @@ -117,19 +117,6 @@ int ncac_rule_cb(sr_session_ctx_t *session, uint32_t sub_id, const char *module_ void ncac_init(void); void ncac_destroy(void); -/** - * @brief Check NACM access for a single node. - * - * @param[in] node Node to check. Can be NULL if @p node_path and @p node_schema are set. - * @param[in] node_path Node path of the node to check. Can be NULL if @p node is set. - * @param[in] node_schema Schema of the node to check. Can be NULL if @p node is set. - * @param[in] user User, whose access to check. - * @param[in] oper Operation to check. - * @return NCAC access enum. - */ -enum ncac_access ncac_allowed_node(const struct lyd_node *node, const char *node_path, - const struct lysc_node *node_schema, const char *user, uint8_t oper); - /** * @brief Check whether an operation is allowed for a user. * @@ -170,4 +157,14 @@ void ncac_check_data_read_filter(struct lyd_node **data, const char *user); */ const struct lyd_node *ncac_check_diff(const struct lyd_node *diff, const char *user); +/** + * @brief Filter out any data in the notification the user does not have R access to + * + * @param[in] user Name of the user to check. + * @param[in] set Set of the notification data. + * @param[out] all_removed Whether or not all nodes have been removed. + * @return NULL if access allowed, otherwise the denied access data node. + */ +void ncac_check_yang_push_update_notif(const char *user, struct ly_set *set, int *all_removed); + #endif /* NP2SRV_NETCONF_ACM_H_ */ diff --git a/src/netconf_monitoring.c b/src/netconf_monitoring.c index c14d79d45..b8196ef5b 100644 --- a/src/netconf_monitoring.c +++ b/src/netconf_monitoring.c @@ -158,19 +158,20 @@ ncm_session_add(struct nc_session *session) new = realloc(stats.sessions, stats.session_count * sizeof *stats.sessions); if (!new) { EMEM; - return; + goto cleanup; } stats.sessions = new; new = realloc(stats.session_stats, stats.session_count * sizeof *stats.session_stats); if (!new) { EMEM; - return; + goto cleanup; } stats.session_stats = new; stats.sessions[stats.session_count - 1] = session; memset(&stats.session_stats[stats.session_count - 1], 0, sizeof *stats.session_stats); +cleanup: pthread_mutex_unlock(&stats.lock); } @@ -253,7 +254,7 @@ ncm_data_add_ds_lock(sr_conn_ctx_t *conn, const char *ds_str, sr_datastore_t ds, lyd_new_inner(list, NULL, "locks", 0, &cont); lyd_new_inner(cont, NULL, "global-lock", 0, &cont2); - ncs = np_get_nc_sess_by_sr_id(sid); + np_get_nc_sess_by_id(sid, 0, &ncs); sprintf(ncid_str, "%" PRIu32, ncs ? nc_session_get_id(ncs) : 0); lyd_new_term(cont2, NULL, "locked-by-session", ncid_str, 0, NULL); diff --git a/src/netconf_subscribed_notifications.c b/src/netconf_subscribed_notifications.c index bf2be7237..e8141cffd 100644 --- a/src/netconf_subscribed_notifications.c +++ b/src/netconf_subscribed_notifications.c @@ -30,6 +30,7 @@ #include "common.h" #include "compat.h" +#include "err_netconf.h" #include "log.h" #include "netconf_acm.h" #include "netconf_monitoring.h" @@ -42,6 +43,10 @@ static struct np2srv_sub_ntf_info info = { static ATOMIC_T new_nc_sub_id = 1; +#define INFO_RLOCK if ((r = pthread_rwlock_rdlock(&info.lock))) ELOCK(r) +#define INFO_WLOCK if ((r = pthread_rwlock_wrlock(&info.lock))) ELOCK(r) +#define INFO_UNLOCK if ((r = pthread_rwlock_unlock(&info.lock))) EUNLOCK(r) + /** * @brief Find an internal subscription structure. * @@ -55,15 +60,16 @@ static struct np2srv_sub_ntf * sub_ntf_find(uint32_t nc_sub_id, uint32_t nc_id, int wlock, int rlock) { uint32_t i; + int r; assert(!wlock || !rlock); if (wlock) { /* WRITE LOCK */ - pthread_rwlock_wrlock(&info.lock); + INFO_WLOCK; } else if (rlock) { /* READ LOCK */ - pthread_rwlock_rdlock(&info.lock); + INFO_RLOCK; } for (i = 0; i < info.count; ++i) { @@ -78,21 +84,24 @@ sub_ntf_find(uint32_t nc_sub_id, uint32_t nc_id, int wlock, int rlock) if (wlock || rlock) { /* UNLOCK */ - pthread_rwlock_unlock(&info.lock); + INFO_UNLOCK; } return NULL; } struct np2srv_sub_ntf * -sub_ntf_find_lock(uint32_t nc_sub_id, int write) +sub_ntf_find_lock(uint32_t nc_sub_id, uint32_t sub_id, int write) { struct np2srv_sub_ntf *sub; + int r; /* LOCK */ - if (write) { - pthread_rwlock_wrlock(&info.lock); - } else { - pthread_rwlock_rdlock(&info.lock); + if (!sub_id || (ATOMIC_LOAD_RELAXED(info.sub_id_lock) != sub_id)) { + if (write) { + INFO_WLOCK; + } else { + INFO_RLOCK; + } } sub = sub_ntf_find(nc_sub_id, 0, 0, 0); @@ -110,15 +119,21 @@ sub_ntf_find_lock(uint32_t nc_sub_id, int write) error: /* UNLOCK */ - pthread_rwlock_unlock(&info.lock); + if (!sub_id || (ATOMIC_LOAD_RELAXED(info.sub_id_lock) != sub_id)) { + INFO_UNLOCK; + } return NULL; } void -sub_ntf_unlock(void) +sub_ntf_unlock(uint32_t sub_id) { + int r; + /* UNLOCK */ - pthread_rwlock_unlock(&info.lock); + if (!sub_id || (ATOMIC_LOAD_RELAXED(info.sub_id_lock) != sub_id)) { + INFO_UNLOCK; + } } struct np2srv_sub_ntf * @@ -127,7 +142,7 @@ sub_ntf_find_next(struct np2srv_sub_ntf *last, int (*sub_ntf_match_cb)(struct np { uint32_t i, last_idx = last ? (((char *)last) - ((char *)info.subs)) / sizeof *last : 0; - for (i = last_idx ? last_idx + 1 : 0; i < info.count; ++i) { + for (i = last ? last_idx + 1 : 0; i < info.count; ++i) { if (sub_ntf_match_cb(&info.subs[i], match_data)) { return &info.subs[i]; } @@ -137,24 +152,21 @@ sub_ntf_find_next(struct np2srv_sub_ntf *last, int (*sub_ntf_match_cb)(struct np } int -sub_ntf_send_notif(struct nc_session *ncs, uint32_t nc_sub_id, struct timespec timestamp, struct lyd_node **ly_ntf, int use_ntf) +sub_ntf_send_notif(struct nc_session *ncs, uint32_t nc_sub_id, struct timespec timestamp, struct lyd_node **ly_ntf, + int use_ntf) { struct np2srv_sub_ntf *sub; struct nc_server_notif *nc_ntf = NULL; NC_MSG_TYPE msg_type; - char *datetime; - int rc; + char *datetime = NULL; + int rc = SR_ERR_OK; /* find the subscription structure */ sub = sub_ntf_find(nc_sub_id, nc_session_get_id(ncs), 0, 0); if (!sub) { - if (use_ntf) { - /* free the notification since we are not using it */ - lyd_free_tree(*ly_ntf); - *ly_ntf = NULL; - } EINT; - return SR_ERR_INTERNAL; + rc = SR_ERR_INTERNAL; + goto cleanup; } /* check NACM of the notification itself */ @@ -162,22 +174,20 @@ sub_ntf_send_notif(struct nc_session *ncs, uint32_t nc_sub_id, struct timespec t /* denied */ ATOMIC_INC_RELAXED(sub->denied_count); - if (use_ntf) { - /* free the notification since we are not using it */ - lyd_free_tree(*ly_ntf); - *ly_ntf = NULL; - } - return SR_ERR_OK; + /* success */ + goto cleanup; } /* create the notification object */ ly_time_ts2str(×tamp, &datetime); if (use_ntf) { + /* take ownership of the objects */ nc_ntf = nc_server_notif_new(*ly_ntf, datetime, NC_PARAMTYPE_FREE); *ly_ntf = NULL; + datetime = NULL; } else { + /* objects const, their lifetime must last until the notif is sent */ nc_ntf = nc_server_notif_new(*ly_ntf, datetime, NC_PARAMTYPE_CONST); - free(datetime); } /* send the notification */ @@ -186,12 +196,18 @@ sub_ntf_send_notif(struct nc_session *ncs, uint32_t nc_sub_id, struct timespec t ERR("Sending a notification to session %d %s.", nc_session_get_id(ncs), msg_type == NC_MSG_ERROR ? "failed" : "timed out"); rc = (msg_type == NC_MSG_ERROR) ? SR_ERR_OPERATION_FAILED : SR_ERR_TIME_OUT; + goto cleanup; } else { ncm_session_notification(ncs); ATOMIC_INC_RELAXED(sub->sent_count); - rc = SR_ERR_OK; } +cleanup: + if (use_ntf) { + lyd_free_tree(*ly_ntf); + *ly_ntf = NULL; + } + free(datetime); nc_server_notif_free(nc_ntf); return rc; } @@ -202,6 +218,15 @@ sub_ntf_cb_lock_pass(uint32_t sub_id) ATOMIC_STORE_RELAXED(info.sub_id_lock, sub_id); } +void +sub_ntf_cb_lock_clear(uint32_t sub_id) +{ + assert(ATOMIC_LOAD_RELAXED(info.sub_id_lock) == sub_id); + (void)sub_id; + + ATOMIC_STORE_RELAXED(info.sub_id_lock, 0); +} + void sub_ntf_inc_denied(uint32_t nc_sub_id) { @@ -259,28 +284,31 @@ sub_ntf_new(uint32_t nc_id, uint32_t nc_sub_id, const char *term_reason, struct void np2srv_sub_ntf_session_destroy(struct nc_session *ncs) { + int r; uint32_t i; /* WRITE LOCK */ - pthread_rwlock_wrlock(&info.lock); + INFO_WLOCK; for (i = 0; i < info.count; ++i) { if (info.subs[i].nc_id == nc_session_get_id(ncs)) { + /* terminate the subscription */ sub_ntf_terminate_sub(&info.subs[i], ncs); } } /* UNLOCK */ - pthread_rwlock_unlock(&info.lock); + INFO_UNLOCK; } void np2srv_sub_ntf_destroy(void) { + int r; uint32_t i; /* WRITE LOCK */ - pthread_rwlock_wrlock(&info.lock); + INFO_WLOCK; for (i = 0; i < info.count; ++i) { switch (info.subs[i].type) { @@ -307,7 +335,7 @@ np2srv_sub_ntf_destroy(void) info.count = 0; /* UNLOCK */ - pthread_rwlock_unlock(&info.lock); + INFO_UNLOCK; } int @@ -320,10 +348,9 @@ np2srv_rpc_establish_sub_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), struct np2srv_sub_ntf *sub; char id_str[11]; struct timespec stop = {0}; - int rc, ntf_status = 0; + int r, rc, ntf_status = 0; uint32_t nc_sub_id, *nc_id; enum sub_ntf_type type; - void *data = NULL; if (NP_IGNORE_RPC(session, event)) { /* ignore in this case (not supported) */ @@ -368,7 +395,7 @@ np2srv_rpc_establish_sub_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), nc_sub_id = ATOMIC_INC_RELAXED(new_nc_sub_id); /* WRITE LOCK */ - pthread_rwlock_wrlock(&info.lock); + INFO_WLOCK; /* allocate a new subscription */ sr_session_get_orig_data(session, 0, NULL, (const void **)&nc_id); @@ -391,7 +418,7 @@ np2srv_rpc_establish_sub_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), } /* UNLOCK */ - pthread_rwlock_unlock(&info.lock); + INFO_UNLOCK; /* generate output */ sprintf(id_str, "%" PRIu32, nc_sub_id); @@ -407,49 +434,32 @@ np2srv_rpc_establish_sub_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), --info.count; /* UNLOCK */ - pthread_rwlock_unlock(&info.lock); + INFO_UNLOCK; error: - if (data) { - switch (type) { - case SUB_TYPE_SUB_NTF: - sub_ntf_data_destroy(data); - break; - case SUB_TYPE_YANG_PUSH: - yang_push_data_destroy(data); - break; - } - } if (ntf_status) { nc_session_dec_notif_status(ncs); } return rc; } -/** - * @brief Create a subscription-modified notification. - * - * @param[in] sub Subscription structure. - * @param[out] ly_ntf Created notification. - * @return Sysrepo error value. - */ -static int -sub_ntf_notif_modified(struct np2srv_sub_ntf *sub, struct lyd_node **ly_ntf) +int +sub_ntf_send_notif_modified(const struct np2srv_sub_ntf *sub) { int rc = SR_ERR_OK; char buf[11], *datetime = NULL; - - *ly_ntf = NULL; + struct lyd_node *ly_ntf = NULL; + struct nc_session *ncs; if (lyd_new_path(NULL, sr_get_context(np2srv.sr_conn), "/ietf-subscribed-notifications:subscription-modified", NULL, - 0, ly_ntf)) { + 0, &ly_ntf)) { rc = SR_ERR_LY; goto cleanup; } /* id */ sprintf(buf, "%" PRIu32, sub->nc_sub_id); - if (lyd_new_term(*ly_ntf, NULL, "id", buf, 0, NULL)) { + if (lyd_new_term(ly_ntf, NULL, "id", buf, 0, NULL)) { rc = SR_ERR_LY; goto cleanup; } @@ -457,7 +467,7 @@ sub_ntf_notif_modified(struct np2srv_sub_ntf *sub, struct lyd_node **ly_ntf) /* stop-time */ if (sub->stop_time.tv_sec) { ly_time_ts2str(&sub->stop_time, &datetime); - if (lyd_new_term(*ly_ntf, NULL, "stop-time", datetime, 0, NULL)) { + if (lyd_new_term(ly_ntf, NULL, "stop-time", datetime, 0, NULL)) { rc = SR_ERR_LY; goto cleanup; } @@ -466,22 +476,30 @@ sub_ntf_notif_modified(struct np2srv_sub_ntf *sub, struct lyd_node **ly_ntf) /* type-specific data */ switch (sub->type) { case SUB_TYPE_SUB_NTF: - rc = sub_ntf_notif_modified_append_data(*ly_ntf, sub->data); + rc = sub_ntf_notif_modified_append_data(ly_ntf, sub->data); break; case SUB_TYPE_YANG_PUSH: - rc = yang_push_notif_modified_append_data(*ly_ntf, sub->data); + rc = yang_push_notif_modified_append_data(ly_ntf, sub->data); break; } if (rc != SR_ERR_OK) { goto cleanup; } + /* get NETCONF session */ + if ((rc = np_get_nc_sess_by_id(0, sub->nc_id, &ncs))) { + goto cleanup; + } + + /* send the notification */ + rc = sub_ntf_send_notif(ncs, sub->nc_sub_id, np_gettimespec(1), &ly_ntf, 1); + if (rc != SR_ERR_OK) { + goto cleanup; + } + cleanup: free(datetime); - if (rc) { - lyd_free_tree(*ly_ntf); - *ly_ntf = NULL; - } + lyd_free_tree(ly_ntf); return rc; } @@ -490,12 +508,11 @@ np2srv_rpc_modify_sub_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), con const struct lyd_node *input, sr_event_t event, uint32_t UNUSED(request_id), struct lyd_node *UNUSED(output), void *UNUSED(private_data)) { - struct lyd_node *node, *ly_ntf; + struct lyd_node *node; struct np2srv_sub_ntf *sub; - char *xp = NULL; + char *xp = NULL, *message; struct timespec stop = {0}; - struct nc_session *ncs; - int rc = SR_ERR_OK; + int r, rc = SR_ERR_OK; uint32_t nc_sub_id, *nc_id; if (NP_IGNORE_RPC(session, event)) { @@ -517,9 +534,13 @@ np2srv_rpc_modify_sub_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), con /* WRITE LOCK */ sub = sub_ntf_find(nc_sub_id, *nc_id, 1, 0); if (!sub) { + if (asprintf(&message, "Subscription with ID %" PRIu32 " for the current receiver does not exist.", nc_sub_id) == -1) { + rc = SR_ERR_NO_MEMORY; + goto cleanup; + } + np_err_ntf_sub_no_such_sub(session, message); + free(message); rc = SR_ERR_INVAL_ARG; - sr_session_set_error_message(session, "Subscription with ID %" PRIu32 " for the current receiver does not exist.", - nc_sub_id); goto cleanup; } @@ -542,25 +563,14 @@ np2srv_rpc_modify_sub_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), con } /* create the notification */ - rc = sub_ntf_notif_modified(sub, &ly_ntf); - if (rc != SR_ERR_OK) { - goto cleanup; - } - - /* get NETCONF session */ - if ((rc = np_get_user_sess(session, &ncs, NULL))) { - goto cleanup; - } - - /* send the notification */ - rc = sub_ntf_send_notif(ncs, nc_sub_id, np_gettimespec(), &ly_ntf, 1); + rc = sub_ntf_send_notif_modified(sub); if (rc != SR_ERR_OK) { - goto cleanup; + goto cleanup_unlock; } cleanup_unlock: /* UNLOCK */ - pthread_rwlock_unlock(&info.lock); + INFO_UNLOCK; cleanup: free(xp); @@ -573,18 +583,41 @@ sub_ntf_terminate_sub(struct np2srv_sub_ntf *sub, struct nc_session *ncs) int r, rc = SR_ERR_OK; struct lyd_node *ly_ntf; char buf[11]; - uint32_t i, idx; + uint32_t idx, sub_id_count, sub_id; + enum sub_ntf_type sub_type = sub->type; /* unsubscribe all sysrepo subscriptions */ - for (i = 0; i < ATOMIC_LOAD_RELAXED(sub->sub_id_count); ++i) { - r = sr_unsubscribe_sub(np2srv.sr_notif_sub, sub->sub_ids[i]); - if (r != SR_ERR_OK) { - rc = r; + switch (sub_type) { + case SUB_TYPE_SUB_NTF: + sub_id_count = ATOMIC_LOAD_RELAXED(sub->sub_id_count); + for (idx = 0; idx < sub_id_count; ++idx) { + /* pass the lock to the notification CB, which removes its sub ID, the final one the whole sub */ + sub_id = sub->sub_ids[0]; + sub_ntf_cb_lock_pass(sub_id); + r = sr_unsubscribe_sub(np2srv.sr_notif_sub, sub_id); + sub_ntf_cb_lock_clear(sub_id); + if (r != SR_ERR_OK) { + rc = r; + } + } + + if (sub_id_count) { + /* this subscription item was already freed as part of unsubscribe terminate notification */ + return rc; } + break; + case SUB_TYPE_YANG_PUSH: + for (idx = 0; idx < ATOMIC_LOAD_RELAXED(sub->sub_id_count); ++idx) { + r = sr_unsubscribe_sub(np2srv.sr_data_sub, sub->sub_ids[idx]); + if (r != SR_ERR_OK) { + rc = r; + } + } + break; } /* terminate any asynchronous tasks */ - switch (sub->type) { + switch (sub_type) { case SUB_TYPE_SUB_NTF: sub_ntf_terminate_async(sub->data); break; @@ -597,13 +630,13 @@ sub_ntf_terminate_sub(struct np2srv_sub_ntf *sub, struct nc_session *ncs) sub->terminating = 1; /* UNLOCK */ - pthread_rwlock_unlock(&info.lock); + INFO_UNLOCK; /* give the tasks a chance to wake up */ np_sleep(NP2SRV_SUB_NTF_TERMINATE_YIELD_SLEEP); /* WRITE LOCK */ - pthread_rwlock_wrlock(&info.lock); + INFO_WLOCK; if (nc_session_get_status(ncs) == NC_STATUS_RUNNING) { /* send the subscription-terminated notification */ @@ -612,7 +645,7 @@ sub_ntf_terminate_sub(struct np2srv_sub_ntf *sub, struct nc_session *ncs) buf, 0, &ly_ntf); lyd_new_path(ly_ntf, NULL, "reason", sub->term_reason, 0, NULL); - r = sub_ntf_send_notif(ncs, sub->nc_sub_id, np_gettimespec(), &ly_ntf, 1); + r = sub_ntf_send_notif(ncs, sub->nc_sub_id, np_gettimespec(1), &ly_ntf, 1); if (r != SR_ERR_OK) { rc = r; } @@ -653,7 +686,8 @@ np2srv_rpc_delete_sub_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), con struct lyd_node *node; struct np2srv_sub_ntf *sub; struct nc_session *ncs; - int rc = SR_ERR_OK; + char *message; + int r, rc = SR_ERR_OK; uint32_t nc_sub_id, *nc_id; if (NP_IGNORE_RPC(session, event)) { @@ -669,8 +703,11 @@ np2srv_rpc_delete_sub_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), con /* WRITE LOCK */ sub = sub_ntf_find(nc_sub_id, *nc_id, 1, 0); if (!sub) { - sr_session_set_error_message(session, "Subscription with ID %" PRIu32 " for the current receiver does not exist.", - nc_sub_id); + if (asprintf(&message, "Subscription with ID %" PRIu32 " for the current receiver does not exist.", nc_sub_id) == -1) { + return SR_ERR_NO_MEMORY; + } + np_err_ntf_sub_no_such_sub(session, message); + free(message); return SR_ERR_INVAL_ARG; } @@ -687,7 +724,7 @@ np2srv_rpc_delete_sub_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), con cleanup_unlock: /* UNLOCK */ - pthread_rwlock_unlock(&info.lock); + INFO_UNLOCK; return rc; } @@ -700,7 +737,8 @@ np2srv_rpc_kill_sub_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const struct lyd_node *node; struct np2srv_sub_ntf *sub; struct nc_session *ncs; - int rc = SR_ERR_OK; + char *message; + int r, rc = SR_ERR_OK; uint32_t nc_sub_id; if (NP_IGNORE_RPC(session, event)) { @@ -715,7 +753,11 @@ np2srv_rpc_kill_sub_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const /* WRITE LOCK */ sub = sub_ntf_find(nc_sub_id, 0, 1, 0); if (!sub) { - sr_session_set_error_message(session, "Subscription with ID %" PRIu32 " does not exist.", nc_sub_id); + if (asprintf(&message, "Subscription with ID %" PRIu32 " for the current receiver does not exist.", nc_sub_id) == -1) { + return SR_ERR_NO_MEMORY; + } + np_err_ntf_sub_no_such_sub(session, message); + free(message); return SR_ERR_INVAL_ARG; } @@ -732,7 +774,7 @@ np2srv_rpc_kill_sub_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), const cleanup_unlock: /* WRITE UNLOCK */ - pthread_rwlock_unlock(&info.lock); + INFO_UNLOCK; return rc; } @@ -747,17 +789,17 @@ np2srv_config_sub_ntf_filters_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_ int r, rc = SR_ERR_OK; /* WRITE LOCK */ - pthread_rwlock_wrlock(&info.lock); + INFO_WLOCK; /* subscribed-notifications */ - rc = sr_get_changes_iter(session, "/ietf-subscribed-notifications:filters/stream-filter", &iter); + rc = sr_get_changes_iter(session, "/ietf-subscribed-notifications:filters/stream-filter/*", &iter); if (rc != SR_ERR_OK) { ERR("Getting changes iter failed (%s).", sr_strerror(rc)); goto cleanup; } while ((r = sr_get_change_tree_next(session, iter, &op, &node, NULL, NULL, NULL)) == SR_ERR_OK) { - rc = sub_ntf_config_filters(session, node, op); + rc = sub_ntf_config_filters(lyd_parent(node), op); if (rc != SR_ERR_OK) { goto cleanup; } @@ -772,14 +814,14 @@ np2srv_config_sub_ntf_filters_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_ iter = NULL; /* yang-push */ - rc = sr_get_changes_iter(session, "/ietf-subscribed-notifications:filters/ietf-yang-push:selection-filter", &iter); + rc = sr_get_changes_iter(session, "/ietf-subscribed-notifications:filters/ietf-yang-push:selection-filter/*", &iter); if (rc != SR_ERR_OK) { ERR("Getting changes iter failed (%s).", sr_strerror(rc)); goto cleanup; } while ((r = sr_get_change_tree_next(session, iter, &op, &node, NULL, NULL, NULL)) == SR_ERR_OK) { - rc = yang_push_config_filters(session, node, op); + rc = yang_push_config_filters(lyd_parent(node), op); if (rc != SR_ERR_OK) { goto cleanup; } @@ -793,7 +835,7 @@ np2srv_config_sub_ntf_filters_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_ cleanup: ATOMIC_STORE_RELAXED(info.sub_id_lock, 0); /* UNLOCK */ - pthread_rwlock_unlock(&info.lock); + INFO_UNLOCK; sr_free_change_iter(iter); return rc; @@ -896,12 +938,12 @@ np2srv_oper_sub_ntf_subscriptions_cb(sr_session_ctx_t *session, uint32_t UNUSED( struct np2srv_sub_ntf *sub; char buf[26], *path = NULL, *datetime = NULL; uint32_t i, excluded_count; - int rc = SR_ERR_OK; + int r, rc = SR_ERR_OK; ly_ctx = sr_get_context(sr_session_get_connection(session)); /* READ LOCK */ - pthread_rwlock_rdlock(&info.lock); + INFO_RLOCK; if (lyd_new_path(NULL, ly_ctx, "/ietf-subscribed-notifications:subscriptions", NULL, 0, &root)) { rc = SR_ERR_LY; @@ -990,7 +1032,7 @@ np2srv_oper_sub_ntf_subscriptions_cb(sr_session_ctx_t *session, uint32_t UNUSED( cleanup: /* UNLOCK */ - pthread_rwlock_unlock(&info.lock); + INFO_UNLOCK; free(datetime); if (rc) { diff --git a/src/netconf_subscribed_notifications.h b/src/netconf_subscribed_notifications.h index 127c20738..408f8b9b3 100644 --- a/src/netconf_subscribed_notifications.h +++ b/src/netconf_subscribed_notifications.h @@ -34,7 +34,7 @@ enum sub_ntf_type { struct np2srv_sub_ntf_info { pthread_rwlock_t lock; ATOMIC_T sub_id_lock; /* subscription ID that holds the lock, if a notification callback is called with this ID, - it must not appempt locking and can access this structure directly */ + it must not attempt locking and can access this structure directly */ struct np2srv_sub_ntf { uint32_t nc_id; @@ -62,16 +62,19 @@ struct np2srv_sub_ntf_info { * @brief Lock the sub-ntf lock, if possible, and return a subscription. * * @param[in] nc_sub_id NC sub ID of the subscription. + * @param[in] sub_id SR subscription ID in a callback, 0 if not in callback. * @param[in] write Whether to write or read-lock. * @return Found subscription. * @return NULL if subscription was not found or it is terminating. */ -struct np2srv_sub_ntf *sub_ntf_find_lock(uint32_t nc_sub_id, int write); +struct np2srv_sub_ntf *sub_ntf_find_lock(uint32_t nc_sub_id, uint32_t sub_id, int write); /** * @brief Unlock the sub-ntf lock. + * + * @param[in] sub_id SR subscription ID in a callback, 0 if not in callback. */ -void sub_ntf_unlock(void); +void sub_ntf_unlock(uint32_t sub_id); /** * @brief Find the next matching sub-ntf subscription structure. @@ -101,10 +104,19 @@ int sub_ntf_send_notif(struct nc_session *ncs, uint32_t nc_sub_id, struct timesp /** * @brief If holding the sub-ntf lock, pass it to another callback that will be called by some following code. * + * Clear with sub_ntf_cb_lock_clear(). + * * @param[in] sub_id Sysrepo subscription ID obtained in the callback. */ void sub_ntf_cb_lock_pass(uint32_t sub_id); +/** + * @brief Clear the passed sub-ntf lock. + * + * @param[in] sub_id Sysrepo subscription ID that the lock was passed to. + */ +void sub_ntf_cb_lock_clear(uint32_t sub_id); + /** * @brief Increase denied notification count for a subscription. * @@ -122,6 +134,14 @@ void sub_ntf_inc_denied(uint32_t nc_sub_id); */ int sub_ntf_terminate_sub(struct np2srv_sub_ntf *sub, struct nc_session *ncs); +/** + * @brief Send a subscription-modified notification. + * + * @param[in] sub Subscription structure that was modified. + * @return Sysrepo error value. + */ +int sub_ntf_send_notif_modified(const struct np2srv_sub_ntf *sub); + /* * for main.c */ diff --git a/src/subscribed_notifications.c b/src/subscribed_notifications.c index 730548919..b7f25c860 100644 --- a/src/subscribed_notifications.c +++ b/src/subscribed_notifications.c @@ -29,29 +29,93 @@ #include "common.h" #include "compat.h" +#include "err_netconf.h" #include "log.h" #include "netconf_acm.h" #include "netconf_monitoring.h" #include "netconf_subscribed_notifications.h" +/** + * @brief Remove this SR subscription and check whether it was the last. + * + * @param[in,out] sub Subscription structure to use. + * @param[in] sub_id SR sub ID to delete. + * @return Whether it was the last SR subscription or not. + */ +static int +sub_ntf_del_sr_sub_is_last(struct np2srv_sub_ntf *sub, uint32_t sub_id) +{ + uint32_t i, count; + int last = 0; + + /* find SR subscription */ + for (i = 0; i < ATOMIC_LOAD_RELAXED(sub->sub_id_count); ++i) { + if (sub->sub_ids[i] == sub_id) { + break; + } + } + if (i == ATOMIC_LOAD_RELAXED(sub->sub_id_count)) { + EINT; + return 0; + } + + /* remove it */ + ATOMIC_DEC_RELAXED(sub->sub_id_count); + count = ATOMIC_LOAD_RELAXED(sub->sub_id_count); + if (i < count) { + memmove(sub->sub_ids + i, sub->sub_ids + i + 1, (count - i) * sizeof *sub->sub_ids); + } else if (!count) { + free(sub->sub_ids); + sub->sub_ids = NULL; + + last = 1; + } + + return last; +} + /** * @brief New notification callback used for notifications received on subscription made by \ RPC. */ static void -np2srv_rpc_establish_sub_ntf_cb(sr_session_ctx_t *UNUSED(session), uint32_t UNUSED(sub_id), - const sr_ev_notif_type_t notif_type, const struct lyd_node *notif, struct timespec *timestamp, void *private_data) +np2srv_rpc_establish_sub_ntf_cb(sr_session_ctx_t *UNUSED(session), uint32_t sub_id, const sr_ev_notif_type_t notif_type, + const struct lyd_node *notif, struct timespec *timestamp, void *private_data) { struct sub_ntf_cb_arg *arg = private_data; struct lyd_node *ly_ntf = NULL; + struct np2srv_sub_ntf *sub; char buf[26]; /* create these notifications, sysrepo only emulates them */ if (notif_type == SR_EV_NOTIF_REPLAY_COMPLETE) { + if (ATOMIC_INC_RELAXED(arg->replay_complete_count) + 1 < arg->sr_sub_count) { + /* wait until all the subscriptions finish their replay */ + goto cleanup; + } + sprintf(buf, "%" PRIu32, arg->nc_sub_id); lyd_new_path(NULL, sr_get_context(np2srv.sr_conn), "/ietf-subscribed-notifications:replay-completed/id", buf, 0, &ly_ntf); notif = ly_ntf; - } else if ((notif_type == SR_EV_NOTIF_MODIFIED) || (notif_type == SR_EV_NOTIF_TERMINATED)) { + } else if (notif_type == SR_EV_NOTIF_TERMINATED) { + /* WRITE LOCK on sub */ + sub = sub_ntf_find_lock(arg->nc_sub_id, sub_id, 1); + if (!sub) { + EINT; + goto cleanup; + } + + if (sub_ntf_del_sr_sub_is_last(sub, sub_id)) { + /* last SR subscription terminated, remove the whole NC subscription */ + sub_ntf_terminate_sub(sub, arg->ncs); + } + + /* UNLOCK */ + sub_ntf_unlock(sub_id); + + /* finish, subscription-terminated notif was already sent */ + goto cleanup; + } else if (notif_type == SR_EV_NOTIF_MODIFIED) { /* handled elsewhere */ goto cleanup; } else if ((notif_type == SR_EV_NOTIF_RESUMED) || (notif_type == SR_EV_NOTIF_SUSPENDED)) { @@ -80,28 +144,29 @@ np2srv_rpc_establish_sub_ntf_cb(sr_session_ctx_t *UNUSED(session), uint32_t UNUS * @param[in] xpath Filter to use. * @param[in] start Replay start time. * @param[in] stop Subscription stop time. - * @param[in] private_data User data to set when subscribing. + * @param[in] cb_arg Callback argument to set when subscribing. * @param[in] ev_sess Event session for reporting errors. * @param[out] sub_ids Generated sysrepo subscription IDs, the first one is used as sub-ntf subscription ID. * @param[out] sub_id_count Number of @p sub_ids. * @return Sysrepo error value. */ static int -sub_ntf_sr_subscribe(sr_session_ctx_t *user_sess, const char *stream, const char *xpath, time_t start, - time_t stop, void *private_data, sr_session_ctx_t *ev_sess, uint32_t **sub_ids, uint32_t *sub_id_count) +sub_ntf_sr_subscribe(sr_session_ctx_t *user_sess, const char *stream, const char *xpath, const struct timespec *start, + const struct timespec *stop, struct sub_ntf_cb_arg *cb_arg, sr_session_ctx_t *ev_sess, uint32_t **sub_ids, + uint32_t *sub_id_count) { const struct ly_ctx *ly_ctx = sr_get_context(sr_session_get_connection(user_sess)); const struct lys_module *ly_mod; - int rc; + int rc, suspended = 0; const sr_error_info_t *err_info; + struct ly_set mod_set = {0}; uint32_t idx; - void *mem; *sub_ids = NULL; *sub_id_count = 0; if (!strcmp(stream, "NETCONF")) { - /* subscribe to all modules with notifications */ + /* collect all modules with notifications */ idx = 0; while ((ly_mod = ly_ctx_get_module_iter(ly_ctx, &idx))) { if (!ly_mod->implemented) { @@ -109,51 +174,71 @@ sub_ntf_sr_subscribe(sr_session_ctx_t *user_sess, const char *stream, const char } if (np_ly_mod_has_notif(ly_mod)) { - /* allocate a new sub ID */ - mem = realloc(*sub_ids, (*sub_id_count + 1) * sizeof **sub_ids); - if (!mem) { - EMEM; - rc = SR_ERR_NO_MEMORY; - goto error; - } - *sub_ids = mem; - - /* a notification was found, subscribe to the module */ - rc = sr_event_notif_subscribe_tree(user_sess, ly_mod->name, xpath, start, stop, np2srv_rpc_establish_sub_ntf_cb, - private_data, SR_SUBSCR_CTX_REUSE, &np2srv.sr_notif_sub); - if (rc != SR_ERR_OK) { - sr_session_get_error(user_sess, &err_info); - sr_session_set_error_message(ev_sess, err_info->err[0].message); + if (ly_set_add(&mod_set, (void *)ly_mod, 1, NULL)) { + EINT; + rc = SR_ERR_INTERNAL; goto error; } + } + } + + /* allocate all sub IDs */ + *sub_ids = malloc(mod_set.count * sizeof **sub_ids); + if (!*sub_ids) { + EMEM; + rc = SR_ERR_NO_MEMORY; + goto error; + } - /* add new sub ID */ - (*sub_ids)[*sub_id_count] = sr_subscription_get_last_sub_id(np2srv.sr_notif_sub); - ++(*sub_id_count); + /* set SR sub count */ + cb_arg->sr_sub_count = mod_set.count; + + /* subscribe to all the modules */ + for (idx = 0; idx < mod_set.count; ++idx) { + ly_mod = mod_set.objs[idx]; + + /* subscribe to the module */ + rc = sr_notif_subscribe_tree(user_sess, ly_mod->name, xpath, start, stop, np2srv_rpc_establish_sub_ntf_cb, + cb_arg, SR_SUBSCR_CTX_REUSE | SR_SUBSCR_THREAD_SUSPEND, &np2srv.sr_notif_sub); + if (rc != SR_ERR_OK) { + sr_session_get_error(user_sess, &err_info); + sr_session_set_error_message(ev_sess, err_info->err[0].message); + goto error; } + suspended = 1; + + /* add new sub ID */ + (*sub_ids)[*sub_id_count] = sr_subscription_get_last_sub_id(np2srv.sr_notif_sub); + ++(*sub_id_count); } } else { /* allocate a new single sub ID */ *sub_ids = malloc(sizeof **sub_ids); if (!*sub_ids) { + EMEM; + rc = SR_ERR_NO_MEMORY; goto error; } + /* set SR sub count */ + cb_arg->sr_sub_count = 1; + /* subscribe to the specific module (stream) */ - rc = sr_event_notif_subscribe_tree(user_sess, stream, xpath, start, stop, np2srv_rpc_establish_sub_ntf_cb, private_data, - SR_SUBSCR_CTX_REUSE, &np2srv.sr_notif_sub); + rc = sr_notif_subscribe_tree(user_sess, stream, xpath, start, stop, np2srv_rpc_establish_sub_ntf_cb, + cb_arg, SR_SUBSCR_CTX_REUSE | SR_SUBSCR_THREAD_SUSPEND, &np2srv.sr_notif_sub); if (rc != SR_ERR_OK) { sr_session_get_error(user_sess, &err_info); sr_session_set_error_message(ev_sess, err_info->err[0].message); goto error; } + suspended = 1; /* add new sub ID */ - (*sub_ids)[0] = sr_subscription_get_last_sub_id(np2srv.sr_notif_sub); - *sub_id_count = 1; + (*sub_ids)[*sub_id_count] = sr_subscription_get_last_sub_id(np2srv.sr_notif_sub); + ++(*sub_id_count); } - return SR_ERR_OK; + goto cleanup; error: for (idx = 0; idx < *sub_id_count; ++idx) { @@ -162,6 +247,13 @@ sub_ntf_sr_subscribe(sr_session_ctx_t *user_sess, const char *stream, const char free(*sub_ids); *sub_ids = NULL; *sub_id_count = 0; + +cleanup: + if (suspended) { + /* resume subscription thread */ + sr_subscription_thread_resume(np2srv.sr_notif_sub); + } + ly_set_erase(&mod_set, NULL); return rc; } @@ -271,7 +363,7 @@ sub_ntf_rpc_filter2xpath(sr_session_ctx_t *user_sess, const struct lyd_node *rpc assert(!strcmp(node->schema->name, "stream-xpath-filter")); if (strlen(lyd_get_value(node))) { *xpath = strdup(lyd_get_value(node)); - if (*xpath) { + if (!*xpath) { EMEM; rc = SR_ERR_NO_MEMORY; goto cleanup; @@ -292,9 +384,10 @@ sub_ntf_rpc_establish_sub(sr_session_ctx_t *ev_sess, const struct lyd_node *rpc, struct nc_session *ncs; struct np2_user_sess *user_sess = NULL; struct sub_ntf_data *sn_data = NULL; + struct timespec stop, cur_ts; const char *stream, *stream_filter_name = NULL, *stream_xpath_filter = NULL; char *xp = NULL; - time_t start = 0; + struct timespec start = {0}; uint32_t sub_id_count; int rc = SR_ERR_OK; @@ -317,7 +410,25 @@ sub_ntf_rpc_establish_sub(sr_session_ctx_t *ev_sess, const struct lyd_node *rpc, /* replay start time */ lyd_find_path(rpc, "replay-start-time", 0, &node); if (node) { - ly_time_str2time(lyd_get_value(node), &start, NULL); + ly_time_str2ts(lyd_get_value(node), &start); + } + + stop = sub->stop_time; + + /* check parameters */ + cur_ts = np_gettimespec(1); + if (start.tv_sec && (np_difftimespec(&start, &cur_ts) < 0)) { + np_err_bad_element(ev_sess, "replay-start-time", "Specified \"replay-start-time\" is in future."); + rc = SR_ERR_INVAL_ARG; + goto cleanup; + } else if (!start.tv_sec && stop.tv_sec && (np_difftimespec(&stop, &cur_ts) > 0)) { + np_err_bad_element(ev_sess, "stop-time", "Specified \"stop-time\" is in the past."); + rc = SR_ERR_INVAL_ARG; + goto cleanup; + } else if (start.tv_sec && stop.tv_sec && (np_difftimespec(&stop, &start) > 0)) { + np_err_bad_element(ev_sess, "stop-time", "Specified \"stop-time\" is earlier than \"replay-start-time\"."); + rc = SR_ERR_INVAL_ARG; + goto cleanup; } /* allocate specific data */ @@ -349,9 +460,8 @@ sub_ntf_rpc_establish_sub(sr_session_ctx_t *ev_sess, const struct lyd_node *rpc, sn_data->cb_arg.nc_sub_id = sub->nc_sub_id; /* subscribe to sysrepo notifications, cb_arg is managed (freed) by the callback */ - sub_id_count = 0; - rc = sub_ntf_sr_subscribe(user_sess->sess, stream, xp, start, sub->stop_time.tv_sec, &sn_data->cb_arg, ev_sess, - &sub->sub_ids, &sub_id_count); + rc = sub_ntf_sr_subscribe(user_sess->sess, stream, xp, start.tv_sec ? &start : NULL, + sub->stop_time.tv_sec ? &sub->stop_time : NULL, &sn_data->cb_arg, ev_sess, &sub->sub_ids, &sub_id_count); ATOMIC_STORE_RELAXED(sub->sub_id_count, sub_id_count); if (rc != SR_ERR_OK) { goto cleanup; @@ -370,23 +480,29 @@ int sub_ntf_rpc_modify_sub(sr_session_ctx_t *ev_sess, const struct lyd_node *rpc, struct timespec stop, struct np2srv_sub_ntf *sub) { - struct lyd_node *node, *stream_subtree_filter = NULL; + struct lyd_node *stream_subtree_filter = NULL; struct np2_user_sess *user_sess = NULL; struct sub_ntf_data *sn_data; + struct lyd_node *node; const char *cur_xp, *stream_filter_name = NULL, *stream_xpath_filter = NULL; char *xp = NULL; - time_t cur_stop; + struct timespec cur_stop; int rc = SR_ERR_OK; - uint32_t i, nc_sub_id; + uint32_t i; /* get the user session */ if ((rc = np_get_user_sess(ev_sess, NULL, &user_sess))) { goto cleanup; } - /* id */ - lyd_find_path(rpc, "id", 0, &node); - nc_sub_id = ((struct lyd_node_term *)node)->value.uint32; + /* datastore */ + lyd_find_path(rpc, "ietf-yang-push:datastore", 0, &node); + if (node) { + sr_session_set_error_message(ev_sess, "Subscription with ID %" PRIu32 " is not yang-push but \"datastore\"" + " is set.", sub->nc_sub_id); + rc = SR_ERR_UNSUPPORTED; + goto cleanup; + } /* filter, join all into one xpath */ rc = sub_ntf_rpc_filter2xpath(user_sess->sess, rpc, ev_sess, &xp, &stream_filter_name, &stream_subtree_filter, @@ -396,28 +512,30 @@ sub_ntf_rpc_modify_sub(sr_session_ctx_t *ev_sess, const struct lyd_node *rpc, st } /* learn the current filter */ - rc = sr_event_notif_sub_get_info(np2srv.sr_notif_sub, nc_sub_id, NULL, &cur_xp, NULL, &cur_stop, NULL); + rc = sr_notif_sub_get_info(np2srv.sr_notif_sub, sub->sub_ids[0], NULL, &cur_xp, NULL, &cur_stop, NULL); if (rc != SR_ERR_OK) { goto cleanup; } - if (strcmp(cur_xp, xp)) { + if (!cur_xp || strcmp(cur_xp, xp)) { /* update the filter */ for (i = 0; i < sub->sub_id_count; ++i) { /* "pass" the lock to the callback */ sub_ntf_cb_lock_pass(sub->sub_ids[i]); rc = sr_event_notif_sub_modify_xpath(np2srv.sr_notif_sub, sub->sub_ids[i], xp); + sub_ntf_cb_lock_clear(sub->sub_ids[i]); if (rc != SR_ERR_OK) { goto cleanup; } } } - if (stop.tv_sec && (cur_stop != stop.tv_sec)) { + if (np_difftimespec(&cur_stop, &stop) != 0) { /* update stop time */ for (i = 0; i < sub->sub_id_count; ++i) { /* "pass" the lock to the callback */ sub_ntf_cb_lock_pass(sub->sub_ids[i]); - rc = sr_event_notif_sub_modify_stop_time(np2srv.sr_notif_sub, sub->sub_ids[i], stop.tv_sec); + rc = sr_notif_sub_modify_stop_time(np2srv.sr_notif_sub, sub->sub_ids[i], stop.tv_sec ? &stop : NULL); + sub_ntf_cb_lock_clear(sub->sub_ids[i]); if (rc != SR_ERR_OK) { goto cleanup; } @@ -496,7 +614,7 @@ sub_ntf_stream_filter_match_cb(struct np2srv_sub_ntf *sub, const void *match_dat } int -sub_ntf_config_filters(sr_session_ctx_t *ev_sess, const struct lyd_node *filter, sr_change_oper_t op) +sub_ntf_config_filters(const struct lyd_node *filter, sr_change_oper_t op) { int rc = SR_ERR_OK, r; struct np2srv_sub_ntf *sub; @@ -516,27 +634,30 @@ sub_ntf_config_filters(sr_session_ctx_t *ev_sess, const struct lyd_node *filter, while ((sub = sub_ntf_find_next(sub, sub_ntf_stream_filter_match_cb, lyd_get_value(lyd_child(filter))))) { /* modify the filter of the subscription(s) */ for (i = 0; i < sub->sub_id_count; ++i) { - /* "pass" the lock to the callback */ - sub_ntf_cb_lock_pass(sub->sub_ids[i]); + /* callback ignores this event */ r = sr_event_notif_sub_modify_xpath(np2srv.sr_notif_sub, sub->sub_ids[i], xp); if (r != SR_ERR_OK) { rc = r; } } - /* do not send subscription-modified since, from the perspective of YANG data, it was not modified */ + /* send subscription-modified notif */ + r = sub_ntf_send_notif_modified(sub); + if (r != SR_ERR_OK) { + rc = r; + } } free(xp); } else if (op == SR_OP_DELETED) { - /* get NETCONF session */ - if ((rc = np_get_user_sess(ev_sess, &ncs, NULL))) { - return rc; - } - /* update all the relevant subscriptions */ sub = NULL; while ((sub = sub_ntf_find_next(sub, sub_ntf_stream_filter_match_cb, lyd_get_value(lyd_child(filter))))) { + /* get NETCONF session */ + if ((rc = np_get_nc_sess_by_id(0, sub->nc_id, &ncs))) { + return rc; + } + /* terminate the subscription with the specific term reason */ sub->term_reason = "ietf-subscribed-notifications:filter-unavailable"; r = sub_ntf_terminate_sub(sub, ncs); @@ -580,8 +701,8 @@ sub_ntf_oper_subscription(struct lyd_node *subscription, void *data) } /* replay-start-time */ - if (sn_data->replay_start_time) { - ly_time_time2str(sn_data->replay_start_time, NULL, &buf); + if (sn_data->replay_start_time.tv_sec) { + ly_time_ts2str(&sn_data->replay_start_time, &buf); if (lyd_new_term(subscription, NULL, "replay-start-time", buf, 0, NULL)) { free(buf); return SR_ERR_LY; @@ -601,7 +722,7 @@ sub_ntf_oper_receiver_excluded(struct np2srv_sub_ntf *sub) /* excluded-event-records */ for (i = 0; i < ATOMIC_LOAD_RELAXED(sub->sub_id_count); ++i) { /* get filter-out count for the subscription */ - r = sr_event_notif_sub_get_info(np2srv.sr_notif_sub, sub->sub_ids[i], NULL, NULL, NULL, NULL, &filtered_out); + r = sr_notif_sub_get_info(np2srv.sr_notif_sub, sub->sub_ids[i], NULL, NULL, NULL, NULL, &filtered_out); if (r != SR_ERR_OK) { return r; } diff --git a/src/subscribed_notifications.h b/src/subscribed_notifications.h index 036fdc1cb..6c26d4d83 100644 --- a/src/subscribed_notifications.h +++ b/src/subscribed_notifications.h @@ -31,6 +31,9 @@ struct sub_ntf_cb_arg { struct nc_session *ncs; struct sub_ntf_data *sn_data; uint32_t nc_sub_id; + + uint32_t sr_sub_count; /* number of SR subscriptions made for this NC subscription */ + ATOMIC_T replay_complete_count; /* counter of special replay-complete notifications received */ }; /** @@ -42,7 +45,7 @@ struct sub_ntf_data { struct lyd_node *stream_subtree_filter; char *stream_xpath_filter; char *stream; - time_t replay_start_time; + struct timespec replay_start_time; /* internal data */ struct sub_ntf_cb_arg cb_arg; @@ -86,12 +89,11 @@ int sub_ntf_notif_modified_append_data(struct lyd_node *ntf, void *data); * @brief Called for every configuration change in type-specific filters. * sub-ntf lock held. * - * @param[in] ev_sess Event session. * @param[in] filter Changed filter node. * @param[in] op Sysrepo operation. * @return Sysrepo error value. */ -int sub_ntf_config_filters(sr_session_ctx_t *ev_sess, const struct lyd_node *filter, sr_change_oper_t op); +int sub_ntf_config_filters(const struct lyd_node *filter, sr_change_oper_t op); /** * @brief Should append type-specific operational YANG nodes to "subscription" node. diff --git a/src/yang_push.c b/src/yang_push.c index a2889a7d1..098952fbc 100644 --- a/src/yang_push.c +++ b/src/yang_push.c @@ -180,10 +180,32 @@ yang_push_notif_change_edit_append(struct lyd_node *ly_yp, enum yang_push_op yp_ const char *prev_value, const char *prev_list, struct yang_push_data *yp_data) { struct lyd_node *ly_edit, *ly_target, *value_tree; - char buf[26], *path = NULL, *point = NULL; + struct ly_set *set = NULL; + char buf[26], *path = NULL, *point = NULL, *xpath = NULL; uint32_t edit_id; int rc = SR_ERR_OK; + /* get the edit target path */ + path = lyd_path(node, LYD_PATH_STD, NULL, 0); + if (!path) { + rc = SR_ERR_LY; + goto cleanup; + } + + /* remove any previous change of this target */ + if (asprintf(&xpath, "/ietf-yang-push:push-change-update/datastore-changes/yang-patch/edit[target='%s']", path) == -1) { + rc = SR_ERR_NO_MEMORY; + goto cleanup; + } + if (lyd_find_xpath(ly_yp, xpath, &set)) { + rc = SR_ERR_LY; + goto cleanup; + } + assert((set->count == 0) || (set->count == 1)); + if (set->count) { + lyd_free_tree(set->dnodes[0]); + } + /* generate new edit ID */ edit_id = ATOMIC_INC_RELAXED(yp_data->edit_id); @@ -201,11 +223,6 @@ yang_push_notif_change_edit_append(struct lyd_node *ly_yp, enum yang_push_op yp_ } /* target */ - path = lyd_path(node, LYD_PATH_STD, NULL, 0); - if (!path) { - rc = SR_ERR_LY; - goto cleanup; - } if (lyd_new_term(ly_edit, NULL, "target", path, 0, &ly_target)) { rc = SR_ERR_LY; goto cleanup; @@ -267,6 +284,8 @@ yang_push_notif_change_edit_append(struct lyd_node *ly_yp, enum yang_push_op yp_ } cleanup: + ly_set_free(set, NULL); + free(xpath); free(path); free(point); return rc; @@ -283,10 +302,8 @@ yang_push_notif_change_edit_append(struct lyd_node *ly_yp, enum yang_push_op yp_ static int yang_push_notif_change_send(struct nc_session *ncs, struct yang_push_data *yp_data, uint32_t nc_sub_id) { - struct lyd_node_any *ly_value; - struct lyd_node *ly_target, *next, *iter; struct ly_set *set = NULL; - uint32_t i, removed; + int all_removed = 0; int rc = SR_ERR_OK; assert(yp_data->ly_change_ntf); @@ -296,40 +313,20 @@ yang_push_notif_change_send(struct nc_session *ncs, struct yang_push_data *yp_da rc = SR_ERR_LY; goto cleanup; } - removed = 0; - for (i = 0; i < set->count; ++i) { - /* check the change itself */ - lyd_find_path(set->dnodes[i], "target", 0, &ly_target); - if (!NCAC_ACCESS_IS_NODE_PERMIT(ncac_allowed_node(NULL, lyd_get_value(ly_target), ly_target->priv, - nc_session_get_username(ncs), NCAC_OP_READ))) { - /* not allowed, remove this change */ - lyd_free_tree(set->dnodes[i]); - ++removed; - continue; - } - - if (!lyd_find_path(set->dnodes[i], "value", 0, (struct lyd_node **)&ly_value)) { - assert(ly_value->value_type == LYD_ANYDATA_DATATREE); - - /* filter out any nested nodes */ - LY_LIST_FOR_SAFE(lyd_child(ly_value->value.tree), next, iter) { - ncac_check_data_read_filter(&iter, nc_session_get_username(ncs)); - } - } - } + ncac_check_yang_push_update_notif(nc_session_get_username(ncs), set, &all_removed); - if (removed == set->count) { + if (all_removed) { /* no change is actually readable, notification denied */ sub_ntf_inc_denied(nc_sub_id); goto cleanup; } /* send the notification */ - rc = sub_ntf_send_notif(ncs, nc_sub_id, np_gettimespec(), &yp_data->ly_change_ntf, 1); + rc = sub_ntf_send_notif(ncs, nc_sub_id, np_gettimespec(1), &yp_data->ly_change_ntf, 1); if (rc == SR_ERR_OK) { /* set last_notif timestamp */ - yp_data->last_notif = np_gettimespec(); + yp_data->last_notif = np_gettimespec(1); } cleanup: @@ -348,7 +345,7 @@ yang_push_damp_timer_cb(union sigval sval) struct yang_push_cb_arg *arg = sval.sival_ptr; /* READ LOCK */ - if (!sub_ntf_find_lock(arg->nc_sub_id, 0)) { + if (!sub_ntf_find_lock(arg->nc_sub_id, 0, 0)) { return; } @@ -362,7 +359,7 @@ yang_push_damp_timer_cb(union sigval sval) pthread_mutex_unlock(&arg->yp_data->notif_lock); /* UNLOCK */ - sub_ntf_unlock(); + sub_ntf_unlock(0); } /** @@ -396,7 +393,7 @@ yang_push_notif_change_ready(struct yang_push_data *yp_data, int *ready) } /* learn when the next notification is due */ - cur_time = np_gettimespec(); + cur_time = np_gettimespec(1); next_notif = yp_data->last_notif; np_addtimespec(&next_notif, yp_data->dampening_period_ms); next_notif_in = np_difftimespec(&cur_time, &next_notif); @@ -869,7 +866,7 @@ yang_push_notif_update_send(struct nc_session *ncs, struct yang_push_data *yp_da data = NULL; /* send the notification */ - rc = sub_ntf_send_notif(ncs, nc_sub_id, np_gettimespec(), &ly_ntf, 1); + rc = sub_ntf_send_notif(ncs, nc_sub_id, np_gettimespec(1), &ly_ntf, 1); if (rc != SR_ERR_OK) { goto cleanup; } @@ -890,7 +887,7 @@ yang_push_update_timer_cb(union sigval sval) struct yang_push_cb_arg *arg = sval.sival_ptr; /* READ LOCK */ - if (!sub_ntf_find_lock(arg->nc_sub_id, 0)) { + if (!sub_ntf_find_lock(arg->nc_sub_id, 0, 0)) { return; } @@ -898,7 +895,7 @@ yang_push_update_timer_cb(union sigval sval) yang_push_notif_update_send(arg->ncs, arg->yp_data, arg->nc_sub_id); /* UNLOCK */ - sub_ntf_unlock(); + sub_ntf_unlock(0); } /** @@ -911,7 +908,7 @@ yang_push_stop_timer_cb(union sigval sval) struct np2srv_sub_ntf *sub; /* WRITE LOCK */ - sub = sub_ntf_find_lock(arg->nc_sub_id, 1); + sub = sub_ntf_find_lock(arg->nc_sub_id, 0, 1); if (!sub) { return; } @@ -920,7 +917,7 @@ yang_push_stop_timer_cb(union sigval sval) sub_ntf_terminate_sub(sub, arg->ncs); /* UNLOCK */ - sub_ntf_unlock(); + sub_ntf_unlock(0); } /** @@ -928,19 +925,26 @@ yang_push_stop_timer_cb(union sigval sval) * * @param[in] cb Callback to be called. * @param[in] arg Argument for @p cb. + * @param[in] force_real Whether to force realtime clock ID or can be monotonic if available. * @param[out] timer_id Created timer ID. * @return Sysrepo error value. */ static int -yang_push_create_timer(void (*cb)(union sigval), void *arg, timer_t *timer_id) +yang_push_create_timer(void (*cb)(union sigval), void *arg, int force_real, timer_t *timer_id) { struct sigevent sevp = {0}; sevp.sigev_notify = SIGEV_THREAD; sevp.sigev_value.sival_ptr = arg; sevp.sigev_notify_function = cb; - if (timer_create(NP_CLOCK_ID, &sevp, timer_id) == -1) { - return SR_ERR_SYS; + if (force_real) { + if (timer_create(CLOCK_REALTIME, &sevp, timer_id) == -1) { + return SR_ERR_SYS; + } + } else { + if (timer_create(NP_CLOCK_ID, &sevp, timer_id) == -1) { + return SR_ERR_SYS; + } } return SR_ERR_OK; @@ -958,6 +962,7 @@ yang_push_rpc_establish_sub(sr_session_ctx_t *ev_sess, const struct lyd_node *rp sr_datastore_t datastore; char *xp = NULL; uint32_t i, period, dampening_period, sub_id_count; + int64_t anchor_msec; int rc = SR_ERR_OK, periodic, sync_on_start, excluded_change[YP_OP_OPERATION_COUNT] = {0}; struct itimerspec trspec = {0}; struct timespec anchor_time = {0}; @@ -1058,7 +1063,7 @@ yang_push_rpc_establish_sub(sr_session_ctx_t *ev_sess, const struct lyd_node *rp ATOMIC_STORE_RELAXED(yp_data->patch_id, 1); if (yp_data->dampening_period_ms) { /* create dampening timer */ - rc = yang_push_create_timer(yang_push_damp_timer_cb, &yp_data->cb_arg, &yp_data->damp_timer); + rc = yang_push_create_timer(yang_push_damp_timer_cb, &yp_data->cb_arg, 1, &yp_data->damp_timer); if (rc != SR_ERR_OK) { goto cleanup; } @@ -1073,7 +1078,7 @@ yang_push_rpc_establish_sub(sr_session_ctx_t *ev_sess, const struct lyd_node *rp if (sub->stop_time.tv_sec) { /* create stop timer */ - rc = yang_push_create_timer(yang_push_stop_timer_cb, &yp_data->cb_arg, &yp_data->stop_timer); + rc = yang_push_create_timer(yang_push_stop_timer_cb, &yp_data->cb_arg, 1, &yp_data->stop_timer); if (rc != SR_ERR_OK) { goto cleanup; } @@ -1088,19 +1093,25 @@ yang_push_rpc_establish_sub(sr_session_ctx_t *ev_sess, const struct lyd_node *rp if (periodic) { /* create update timer */ - rc = yang_push_create_timer(yang_push_update_timer_cb, &yp_data->cb_arg, &yp_data->update_timer); + rc = yang_push_create_timer(yang_push_update_timer_cb, &yp_data->cb_arg, 1, &yp_data->update_timer); if (rc != SR_ERR_OK) { goto cleanup; } /* schedule the periodic updates */ + trspec.it_value = np_gettimespec(1); if (yp_data->anchor_time.tv_sec) { - trspec.it_value = np_modtimespec(&yp_data->anchor_time, yp_data->period_ms); - } else { - trspec.it_value = np_gettimespec(); + /* first update at nearest anchor time on period */ + anchor_msec = np_difftimespec(&yp_data->anchor_time, &trspec.it_value); + if (anchor_msec < 0) { + anchor_msec *= -1; + } + anchor_msec %= yp_data->period_ms; + np_addtimespec(&trspec.it_value, anchor_msec); } trspec.it_interval.tv_sec = yp_data->period_ms / 1000; trspec.it_interval.tv_nsec = (yp_data->period_ms % 1000) * 1000000; + if (timer_settime(yp_data->update_timer, TIMER_ABSTIME, &trspec, NULL) == -1) { rc = SR_ERR_SYS; goto cleanup; @@ -1146,30 +1157,29 @@ yang_push_rpc_modify_sub(sr_session_ctx_t *ev_sess, const struct lyd_node *rpc, char *xp = NULL, *datetime = NULL; struct timespec anchor_time, next_notif; int rc = SR_ERR_OK; - uint32_t i, nc_sub_id, period, dampening_period; + uint32_t i, period, dampening_period; /* get the user session */ if ((rc = np_get_user_sess(ev_sess, NULL, &user_sess))) { goto cleanup; } - /* - * id - */ - lyd_find_path(rpc, "id", 0, &node); - nc_sub_id = ((struct lyd_node_term *)node)->value.uint32; - - /* - * datastore - */ + /* datastore */ lyd_find_path(rpc, "ietf-yang-push:datastore", 0, &node); + if (!node) { + sr_session_set_error_message(ev_sess, "Subscription with ID %" PRIu32 " is yang-push but \"datastore\"" + " is not set.", sub->nc_sub_id); + rc = SR_ERR_UNSUPPORTED; + goto cleanup; + } + rc = yang_push_ident2ds(lyd_get_value(node), &datastore); if (rc != SR_ERR_OK) { sr_session_set_error_message(ev_sess, "Unsupported datastore \"%s\".", lyd_get_value(node)); goto cleanup; } else if (datastore != yp_data->datastore) { - sr_session_set_error_message(ev_sess, "Subscription with ID %" PRIu32 " is not for \"%s\" datastore.", nc_sub_id, - lyd_get_value(node)); + sr_session_set_error_message(ev_sess, "Subscription with ID %" PRIu32 " is not for \"%s\" datastore.", + sub->nc_sub_id, lyd_get_value(node)); rc = SR_ERR_INVAL_ARG; goto cleanup; } @@ -1180,7 +1190,8 @@ yang_push_rpc_modify_sub(sr_session_ctx_t *ev_sess, const struct lyd_node *rpc, lyd_find_path(rpc, "ietf-yang-push:periodic", 0, &cont); if (cont) { if (!yp_data->periodic) { - sr_session_set_error_message(ev_sess, "Subscription with ID %" PRIu32 " is not \"periodic\".", nc_sub_id); + sr_session_set_error_message(ev_sess, "Subscription with ID %" PRIu32 " is not \"periodic\".", + sub->nc_sub_id); rc = SR_ERR_INVAL_ARG; goto cleanup; } @@ -1195,7 +1206,7 @@ yang_push_rpc_modify_sub(sr_session_ctx_t *ev_sess, const struct lyd_node *rpc, if (yp_data->anchor_time.tv_sec) { trspec.it_value = np_modtimespec(&yp_data->anchor_time, yp_data->period_ms); } else { - trspec.it_value = np_gettimespec(); + trspec.it_value = np_gettimespec(1); } trspec.it_interval.tv_sec = yp_data->period_ms / 1000; trspec.it_interval.tv_nsec = (yp_data->period_ms % 1000) * 1000000; @@ -1230,7 +1241,8 @@ yang_push_rpc_modify_sub(sr_session_ctx_t *ev_sess, const struct lyd_node *rpc, lyd_find_path(rpc, "ietf-yang-push:on-change", 0, &cont); if (cont) { if (yp_data->periodic) { - sr_session_set_error_message(ev_sess, "Subscription with ID %" PRIu32 " is not \"on-change\".", nc_sub_id); + sr_session_set_error_message(ev_sess, "Subscription with ID %" PRIu32 " is not \"on-change\".", + sub->nc_sub_id); rc = SR_ERR_INVAL_ARG; goto cleanup; } @@ -1241,7 +1253,7 @@ yang_push_rpc_modify_sub(sr_session_ctx_t *ev_sess, const struct lyd_node *rpc, if (dampening_period * 10 != yp_data->dampening_period_ms) { if (!yp_data->dampening_period_ms) { /* create dampening timer */ - rc = yang_push_create_timer(yang_push_damp_timer_cb, &yp_data->cb_arg, &yp_data->damp_timer); + rc = yang_push_create_timer(yang_push_damp_timer_cb, &yp_data->cb_arg, 1, &yp_data->damp_timer); if (rc != SR_ERR_OK) { goto cleanup; } @@ -1290,7 +1302,7 @@ yang_push_rpc_modify_sub(sr_session_ctx_t *ev_sess, const struct lyd_node *rpc, xp = NULL; for (i = 0; i < sub->sub_id_count; ++i) { - rc = sr_event_notif_sub_modify_xpath(np2srv.sr_data_sub, sub->sub_ids[i], yp_data->xpath); + rc = sr_module_change_sub_modify_xpath(np2srv.sr_data_sub, sub->sub_ids[i], yp_data->xpath); if (rc != SR_ERR_OK) { goto cleanup; } @@ -1322,7 +1334,7 @@ yang_push_rpc_modify_sub(sr_session_ctx_t *ev_sess, const struct lyd_node *rpc, if (stop.tv_sec && memcmp(&stop, &sub->stop_time, sizeof stop)) { if (!sub->stop_time.tv_sec) { /* create stop timer */ - rc = yang_push_create_timer(yang_push_stop_timer_cb, &yp_data->cb_arg, &yp_data->stop_timer); + rc = yang_push_create_timer(yang_push_stop_timer_cb, &yp_data->cb_arg, 1, &yp_data->stop_timer); if (rc != SR_ERR_OK) { goto cleanup; } @@ -1457,7 +1469,7 @@ yang_push_datastore_filter_match_cb(struct np2srv_sub_ntf *sub, const void *matc } int -yang_push_config_filters(sr_session_ctx_t *ev_sess, const struct lyd_node *filter, sr_change_oper_t op) +yang_push_config_filters(const struct lyd_node *filter, sr_change_oper_t op) { int rc = SR_ERR_OK, r; struct yang_push_data *yp_data; @@ -1492,19 +1504,23 @@ yang_push_config_filters(sr_session_ctx_t *ev_sess, const struct lyd_node *filte } } - /* do not send subscription-modified since, from the perspective of YANG data, it was not modified */ + /* send subscription-modified notif */ + r = sub_ntf_send_notif_modified(sub); + if (r != SR_ERR_OK) { + rc = r; + } } free(xp); } else if (op == SR_OP_DELETED) { - /* get NETCONF session */ - if ((rc = np_get_user_sess(ev_sess, &ncs, NULL))) { - return rc; - } - /* update all the relevant subscriptions */ sub = NULL; while ((sub = sub_ntf_find_next(sub, yang_push_datastore_filter_match_cb, lyd_get_value(lyd_child(filter))))) { + /* get NETCONF session */ + if ((rc = np_get_nc_sess_by_id(0, sub->nc_id, &ncs))) { + return rc; + } + /* terminate the subscription with the specific term reason */ sub->term_reason = "ietf-subscribed-notifications:filter-unavailable"; r = sub_ntf_terminate_sub(sub, ncs); @@ -1649,7 +1665,9 @@ yang_push_terminate_async(void *data) timer_settime(yp_data->damp_timer, TIMER_ABSTIME, &tspec, NULL); } } - timer_settime(yp_data->stop_timer, TIMER_ABSTIME, &tspec, NULL); + if (yp_data->stop_timer) { + timer_settime(yp_data->stop_timer, TIMER_ABSTIME, &tspec, NULL); + } } void @@ -1671,7 +1689,9 @@ yang_push_data_destroy(void *data) } } free(yp_data->xpath); - timer_delete(yp_data->stop_timer); + if (yp_data->stop_timer) { + timer_delete(yp_data->stop_timer); + } free(yp_data); } @@ -1698,7 +1718,7 @@ np2srv_rpc_resync_sub_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), con nc_sub_id = ((struct lyd_node_term *)node)->value.uint32; /* READ LOCK */ - sub = sub_ntf_find_lock(nc_sub_id, 0); + sub = sub_ntf_find_lock(nc_sub_id, 0, 0); if (!sub || ((struct yang_push_data *)sub->data)->periodic) { sr_session_set_error_message(session, "On-change subscription with ID %" PRIu32 " for the current receiver " "does not exist.", nc_sub_id); @@ -1711,6 +1731,7 @@ np2srv_rpc_resync_sub_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), con yp_data = sub->data; /* resync the subscription */ + ATOMIC_STORE_RELAXED(yp_data->patch_id, 1); rc = yang_push_notif_update_send(yp_data->cb_arg.ncs, yp_data, nc_sub_id); if (rc != SR_ERR_OK) { goto cleanup_unlock; @@ -1718,7 +1739,7 @@ np2srv_rpc_resync_sub_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), con cleanup_unlock: /* UNLOCK */ - sub_ntf_unlock(); + sub_ntf_unlock(0); return rc; } diff --git a/src/yang_push.h b/src/yang_push.h index bbf10a062..9eaeaf111 100644 --- a/src/yang_push.h +++ b/src/yang_push.h @@ -92,7 +92,7 @@ int yang_push_rpc_modify_sub(sr_session_ctx_t *ev_sess, const struct lyd_node *r int yang_push_notif_modified_append_data(struct lyd_node *ntf, void *data); -int yang_push_config_filters(sr_session_ctx_t *ev_sess, const struct lyd_node *filter, sr_change_oper_t op); +int yang_push_config_filters(const struct lyd_node *filter, sr_change_oper_t op); int yang_push_oper_subscription(struct lyd_node *subscription, void *data); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1f357b4f5..d41701662 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,3 +1,7 @@ +if(NOT PROJECT_NAME) + message(FATAL_ERROR "Please use the root CMakeLists file instead.") +endif() + # correct RPATH usage on OS X set(CMAKE_MACOSX_RPATH TRUE) @@ -10,6 +14,10 @@ get_filename_component(ROOT_DIR "${CMAKE_SOURCE_DIR}" REALPATH) # generate config configure_file("${CMAKE_CURRENT_SOURCE_DIR}/np_test_config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/np_test_config.h" ESCAPE_QUOTES @ONLY) +# generate config +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scripts/kill_np_server.sh.in" + "${CMAKE_CURRENT_BINARY_DIR}/scripts/kill_np_server.sh" ESCAPE_QUOTES @ONLY) + # compat header include add_test(NAME headers COMMAND ${CMAKE_SOURCE_DIR}/compat/check_includes.sh ${CMAKE_SOURCE_DIR}/src/ ${CMAKE_SOURCE_DIR}/cli/) @@ -28,12 +36,19 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(test_sources "np_test.c") # list of all the tests -set(tests test_rpc) +set(tests test_rpc test_edit test_filter test_subscribe_filter test_subscribe_param test_parallel_sessions + test_candidate test_with_defaults test_nacm test_sub_ntf test_sub_ntf_advanced test_sub_ntf_filter test_yang_push + test_yang_push_advanced) + +# append url if supported +if(NP2SRV_URL_CAPAB) + list(APPEND tests test_url) +endif() # build the executables foreach(test_name IN LISTS tests) add_executable(${test_name} ${test_sources} ${test_name}.c) - target_link_libraries(${test_name} ${CMOCKA_LIBRARIES} ${LIBNETCONF2_LIBRARIES} ${LIBYANG_LIBRARIES}) + target_link_libraries(${test_name} ${CMOCKA_LIBRARIES} ${LIBNETCONF2_LIBRARIES} ${LIBYANG_LIBRARIES} ${SYSREPO_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) set_property(TARGET ${test_name} PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) endforeach(test_name) @@ -43,22 +58,19 @@ foreach(test_name IN LISTS tests) set_property(TEST ${test_name} APPEND PROPERTY ENVIRONMENT "MALLOC_CHECK_=3" ) -endforeach(test_name) +endforeach() # valgrind tests if(ENABLE_VALGRIND_TESTS) - find_program(VALGRIND_FOUND valgrind) - if(VALGRIND_FOUND) - foreach(test_name IN LISTS tests) - add_test(NAME ${test_name}_valgrind COMMAND valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1 ${CMAKE_CURRENT_BINARY_DIR}/${test_name}) - endforeach(test_name) - else() - message(WARNING "valgrind executable not found! Disabling memory leak tests.") - endif() + foreach(test_name IN LISTS tests) + add_test(NAME ${test_name}_valgrind COMMAND valgrind --leak-check=full --show-leak-kinds=all --error-exitcode=1 ${CMAKE_CURRENT_BINARY_DIR}/${test_name}) + endforeach() endif() # phony target for clearing all sysrepo test data add_custom_target(test_clean COMMAND rm -rf ${CMAKE_CURRENT_BINARY_DIR}/repositories COMMAND rm -rf /dev/shm/_tests_np_* + COMMAND rm -rf ${CMAKE_CURRENT_BINARY_DIR}/netopeer2-server.sock + COMMAND ${CMAKE_CURRENT_BINARY_DIR}/scripts/kill_np_server.sh ) diff --git a/tests/modules/defaults1.yang b/tests/modules/defaults1.yang new file mode 100644 index 000000000..d2f2aa9b4 --- /dev/null +++ b/tests/modules/defaults1.yang @@ -0,0 +1,14 @@ +module defaults1 { + namespace "def1"; + prefix "def1"; + + container top { + leaf name { + type string; + default "Test"; + } + leaf num { + type uint32; + } + } +} diff --git a/tests/modules/edit1.xml b/tests/modules/edit1.xml new file mode 100644 index 000000000..ddf18af39 --- /dev/null +++ b/tests/modules/edit1.xml @@ -0,0 +1,3 @@ + + TestFirst + diff --git a/tests/modules/edit1.yang b/tests/modules/edit1.yang new file mode 100644 index 000000000..57beb0a79 --- /dev/null +++ b/tests/modules/edit1.yang @@ -0,0 +1,8 @@ +module edit1 { + namespace ed1; + prefix ed1; + + leaf first { + type string; + } +} diff --git a/tests/modules/edit2.yang b/tests/modules/edit2.yang new file mode 100644 index 000000000..6fbbbb6c5 --- /dev/null +++ b/tests/modules/edit2.yang @@ -0,0 +1,13 @@ +module edit2 { + namespace ed2; + prefix ed2; + + container top { + leaf name { + type string; + } + leaf num { + type int32; + } + } +} diff --git a/tests/modules/edit3.yang b/tests/modules/edit3.yang new file mode 100644 index 000000000..f42563f72 --- /dev/null +++ b/tests/modules/edit3.yang @@ -0,0 +1,13 @@ +module edit3 { + namespace ed3; + prefix ed3; + + container top { + leaf name { + type string; + } + leaf-list num { + type int32; + } + } +} diff --git a/tests/modules/edit4.yang b/tests/modules/edit4.yang new file mode 100644 index 000000000..4906eecde --- /dev/null +++ b/tests/modules/edit4.yang @@ -0,0 +1,42 @@ +module edit4 { + namespace ed4; + prefix ed4; + + container top { + choice ch1 { + case c1 { + leaf l1 { + type string; + } + leaf l2 { + type empty; + } + } + + leaf c2 { + type uint32; + } + + case c3 { + choice ch2 { + case c4 { + container cont { + leaf l3 { + type int32; + } + } + } + + case c5 { + leaf l4 { + type string; + } + leaf l5 { + type string; + } + } + } + } + } + } +} diff --git a/tests/modules/example1.yang b/tests/modules/example1.yang new file mode 100644 index 000000000..f0c517ab5 --- /dev/null +++ b/tests/modules/example1.yang @@ -0,0 +1,29 @@ +module example1 { + namespace ex1; + prefix ex1; + + import ietf-inet-types { + prefix "inet"; + } + + description "module that corresponds with one of the rfc6241#7.2 examples"; + + container top { + container interface { + leaf name { + type string; + } + leaf mtu { + type uint32; + } + container address { + leaf name { + type inet:ip-address; + } + leaf prefix-length { + type uint8; + } + } + } + } +} diff --git a/tests/modules/example2.yang b/tests/modules/example2.yang new file mode 100644 index 000000000..7c98d0d0e --- /dev/null +++ b/tests/modules/example2.yang @@ -0,0 +1,31 @@ +module example2 { + namespace ex2; + prefix ex2; + + import ietf-inet-types { + prefix "inet"; + } + + description "module that corresponds with one of the rfc6241#7.2 examples"; + + container top { + container protocols { + container ospf { + list area { + key "name"; + leaf name { + type inet:ip-address; + } + container interfaces { + list interface { + key "name"; + leaf name { + type inet:ip-address; + } + } + } + } + } + } + } +} diff --git a/tests/modules/filter1.yang b/tests/modules/filter1.yang new file mode 100644 index 000000000..9b2e8d206 --- /dev/null +++ b/tests/modules/filter1.yang @@ -0,0 +1,38 @@ +module filter1 { + namespace f1; + prefix f1; + + import ietf-inet-types { + prefix "inet"; + } + + container top { + container devices { + container desktops { + list desktop { + key "name"; + leaf name { + type string; + } + leaf address { + type inet:ip-address; + } + } + } + container servers { + list server { + key "name"; + leaf name { + type string; + } + leaf address { + type inet:ip-address; + } + leaf port { + type inet:port-number; + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/modules/issue1.yang b/tests/modules/issue1.yang new file mode 100644 index 000000000..63b3dcc17 --- /dev/null +++ b/tests/modules/issue1.yang @@ -0,0 +1,26 @@ +module issue1 { + namespace "i1"; + prefix "i1"; + description "module to reproduce sysrepo#2231"; + + container hardware { + list component { + key "name"; + leaf name { + type string; + } + leaf class { + type string; + } + leaf serial-num { + type string; + config false; + } + container feature { + leaf wireless { + type string; + } + } + } + } +} \ No newline at end of file diff --git a/tests/modules/nacm-test1.yang b/tests/modules/nacm-test1.yang new file mode 100644 index 000000000..1207c5e61 --- /dev/null +++ b/tests/modules/nacm-test1.yang @@ -0,0 +1,14 @@ +module nacm-test1 { + namespace "urn:nt1"; + prefix nt1; + + container top { + leaf name { + type string; + } + leaf num { + when "../name"; + type uint32; + } + } +} diff --git a/tests/modules/nacm-test2.yang b/tests/modules/nacm-test2.yang new file mode 100644 index 000000000..958160333 --- /dev/null +++ b/tests/modules/nacm-test2.yang @@ -0,0 +1,14 @@ +module nacm-test2 { + namespace "urn:nt2"; + prefix nt2; + + list people { + key "name"; + leaf name { + type string; + } + leaf weight { + type uint16; + } + } +} diff --git a/tests/modules/notif1.yang b/tests/modules/notif1.yang new file mode 100644 index 000000000..69cdd3cf7 --- /dev/null +++ b/tests/modules/notif1.yang @@ -0,0 +1,10 @@ +module notif1 { + namespace "n1"; + prefix "n1"; + + notification n1 { + leaf first { + type string; + } + } +} \ No newline at end of file diff --git a/tests/modules/notif2.yang b/tests/modules/notif2.yang new file mode 100644 index 000000000..43794d40c --- /dev/null +++ b/tests/modules/notif2.yang @@ -0,0 +1,19 @@ +module notif2 { + yang-version 1.1; + namespace "n2"; + prefix "n2"; + + container devices { + list device { + key "name"; + leaf name { + type string; + } + notification power-on { + leaf boot-time { + type uint32; + } + } + } + } +} \ No newline at end of file diff --git a/tests/modules/xpath.yang b/tests/modules/xpath.yang new file mode 100644 index 000000000..335497c96 --- /dev/null +++ b/tests/modules/xpath.yang @@ -0,0 +1,14 @@ +module xpath { + namespace "x1"; + prefix "x1"; + description "Module to test boolean operators for xpath"; + + container top { + list item { + key "price"; + leaf price { + type uint8; + } + } + } +} \ No newline at end of file diff --git a/tests/np_test.c b/tests/np_test.c index bcb147cbe..0674d8b66 100644 --- a/tests/np_test.c +++ b/tests/np_test.c @@ -1,6 +1,7 @@ /** * @file np_test.c * @author Michal Vasko + * @author Tadeas Vintlik * @brief base source for netopeer2 testing * * @copyright @@ -20,7 +21,7 @@ * limitations under the License. */ -#define _POSIX_C_SOURCE 200809L +#define _GNU_SOURCE #include "np_test.h" @@ -41,6 +42,21 @@ #include "np_test_config.h" +uint8_t debug = 0; /* Global variable to indicate if debugging */ + +void +parse_arg(int argc, char **argv) +{ + if (argc <= 1) { + return; + } + + if (!strcmp(argv[1], "-d") || !strcmp(*argv, "--debug")) { + puts("Starting in debug mode."); + debug = 1; + } +} + static int setup_server_socket_wait(void) { @@ -59,12 +75,13 @@ setup_server_socket_wait(void) } if (count == sleep_count) { + SETUP_FAIL_LOG; return 1; } return 0; } -static int +int setup_setenv_sysrepo(const char *test_name) { int ret = 1; @@ -73,19 +90,23 @@ setup_setenv_sysrepo(const char *test_name) /* set sysrepo environment variables */ sr_repo_path = malloc(strlen(NP_SR_REPOS_DIR) + 1 + strlen(test_name) + 1); if (!sr_repo_path) { + SETUP_FAIL_LOG; goto cleanup; } sprintf(sr_repo_path, "%s/%s", NP_SR_REPOS_DIR, test_name); if (setenv("SYSREPO_REPOSITORY_PATH", sr_repo_path, 1)) { + SETUP_FAIL_LOG; goto cleanup; } sr_shm_prefix = malloc(strlen(NP_SR_SHM_PREFIX) + strlen(test_name) + 1); if (!sr_shm_prefix) { + SETUP_FAIL_LOG; goto cleanup; } sprintf(sr_shm_prefix, "%s%s", NP_SR_SHM_PREFIX, test_name); if (setenv("SYSREPO_SHM_PREFIX", sr_shm_prefix, 1)) { + SETUP_FAIL_LOG; goto cleanup; } @@ -98,42 +119,64 @@ setup_setenv_sysrepo(const char *test_name) } int -_np_glob_setup(void **state, const char *test_name) +np_glob_setup_np2(void **state) { struct np_test *st; pid_t pid; - int fd; - - /* set sysrepo environment variables */ - if (setup_setenv_sysrepo(test_name)) { - return 1; - } + int fd, pipefd[2], buf; + /* sysrepo environment variables must be set by NP_GLOB_SETUP_ENV_FUNC prior */ /* install modules */ if (setenv("NP2_MODULE_DIR", NP_ROOT_DIR "/modules", 1)) { + SETUP_FAIL_LOG; return 1; } if (setenv("NP2_MODULE_PERMS", "600", 1)) { + SETUP_FAIL_LOG; return 1; } if (system(NP_ROOT_DIR "/scripts/setup.sh")) { + SETUP_FAIL_LOG; return 1; } if (unsetenv("NP2_MODULE_DIR")) { + SETUP_FAIL_LOG; return 1; } if (unsetenv("NP2_MODULE_PERMS")) { + SETUP_FAIL_LOG; return 1; } + if (setenv("CMOCKA_TEST_ABORT", "1", 0)) { + SETUP_FAIL_LOG; + return 1; + } + + /* create pipe for synchronisation if debugging */ + if (debug) { + if (pipe(pipefd)) { + SETUP_FAIL_LOG; + return 1; + } + } /* fork and start the server */ if (!(pid = fork())) { /* open log file */ fd = open(NP_LOG_PATH, O_WRONLY | O_CREAT | O_TRUNC, 00600); if (fd == -1) { + SETUP_FAIL_LOG; goto child_error; } + if (debug) { + printf("pid of netopeer server is: %ld\n", (long) getpid()); + puts("Press return to continue the tests..."); + buf = getc(stdin); + write(pipefd[1], &buf, sizeof buf); + close(pipefd[1]); + } + /* redirect stdout and stderr */ dup2(fd, 1); dup2(fd, 2); @@ -148,17 +191,28 @@ _np_glob_setup(void **state, const char *test_name) printf("Child execution failed\n"); exit(1); } else if (pid == -1) { + SETUP_FAIL_LOG; return 1; } + if (debug) { + if (read(pipefd[0], &buf, sizeof buf) != sizeof buf) { + SETUP_FAIL_LOG; + return 1; + } + close(pipefd[0]); + } + /* wait for the server, until it creates its socket */ if (setup_server_socket_wait()) { + SETUP_FAIL_LOG; return 1; } /* create test state structure, up to teardown now to free it */ st = calloc(1, sizeof *st); if (!st) { + SETUP_FAIL_LOG; return 1; } *state = st; @@ -167,11 +221,13 @@ _np_glob_setup(void **state, const char *test_name) /* create NETCONF sessions */ st->nc_sess = nc_connect_unix(NP_SOCKET_PATH, NULL); if (!st->nc_sess) { + SETUP_FAIL_LOG; return 1; } st->nc_sess2 = nc_connect_unix(NP_SOCKET_PATH, NULL); if (!st->nc_sess2) { + SETUP_FAIL_LOG; return 1; } @@ -216,12 +272,102 @@ np_glob_teardown(void **state) /* unset sysrepo environment variables */ if (unsetenv("SYSREPO_REPOSITORY_PATH")) { + SETUP_FAIL_LOG; ret = 1; } if (unsetenv("SYSREPO_SHM_PREFIX")) { + SETUP_FAIL_LOG; ret = 1; } + if (unsetenv("CMOCKA_TEST_ABORT")) { + SETUP_FAIL_LOG; + return 1; + } + free(st); return ret; } + +int +get_username(char **name) +{ + FILE *file; + + *name = NULL; + size_t size = 0; + + /* Get user name */ + file = popen("whoami", "r"); + if (!file) { + return 1; + } + if (getline(name, &size, file) == -1) { + return 1; + } + (*name)[strlen(*name) - 1] = '\0'; /* Remove the newline */ + pclose(file); + return 0; +} + +int +setup_nacm(void **state) +{ + struct np_test *st = *state; + char *user, *data; + const char *template = + "\n" + " false\n" + " permit\n" + " \n" + " \n" + " test-group\n" + " %s\n" + " \n" + " \n" + "\n"; + + if (get_username(&user)) { + return 1; + } + + /* Put user and message id into error template */ + if (asprintf(&data, template, user) == -1) { + return 1; + } + free(user); + + /* Parse and merge the config */ + if (lyd_parse_data_mem(st->ctx, data, LYD_XML, LYD_PARSE_STRICT | LYD_PARSE_ONLY, 0, &st->node)) { + return 1; + } + free(data); + if (!st->node) { + return 1; + } + if (sr_edit_batch(st->sr_sess, st->node, "merge")) { + return 1; + } + if (sr_apply_changes(st->sr_sess, 0)) { + return 1; + } + + FREE_TEST_VARS(st); + + return 0; +} + +int +is_nacm_rec_uid() +{ + uid_t uid; + char streuid[10]; + + /* Get UID */ + uid = geteuid(); + sprintf(streuid, "%d", (int) uid); + if (!strcmp(streuid, NACM_RECOVERY_UID)) { + return 1; + } + return 0; +} diff --git a/tests/np_test.h b/tests/np_test.h index cc5c7534d..2fbc96e01 100644 --- a/tests/np_test.h +++ b/tests/np_test.h @@ -1,6 +1,7 @@ /** * @file np_test.h * @author Michal Vasko + * @author Tadeas Vintlik * @brief base header for netopeer2 testing * * @copyright @@ -27,28 +28,212 @@ #include #include +#include -/* global setup function specific for a test */ -#define NP_GLOB_SETUP_FUNC \ -static int \ -np_glob_setup(void **state) \ -{ \ - char file[64]; \ +/* global setup for environment variables for sysrepo*/ +#define NP_GLOB_SETUP_ENV_FUNC \ + char file[128]; \ + int setenv_rv; \ \ strcpy(file, __FILE__); \ file[strlen(file) - 2] = '\0'; \ - return _np_glob_setup(state, strrchr(file, '/') + 1); \ -} + setenv_rv = setup_setenv_sysrepo(strrchr(file, '/') + 1); + +#define SETUP_FAIL_LOG \ + printf("Setup fail in %s:%d.\n", __FILE__, __LINE__) + +#define FREE_TEST_VARS(state) \ + nc_rpc_free(state->rpc); \ + state->rpc = NULL; \ + lyd_free_tree(state->envp); \ + state->envp = NULL; \ + lyd_free_tree(state->op); \ + state->op = NULL; \ + lyd_free_siblings(state->node); \ + state->node = NULL; \ + if (state->str) { \ + free(state->str); \ + } \ + state->str = NULL; + +#define ASSERT_OK_REPLY(state) \ + do { \ + state->msgtype = nc_recv_reply(state->nc_sess, state->rpc, state->msgid, 2000, &state->envp, &state->op); \ + } while (state->msgtype == NC_MSG_NOTIF); \ + assert_int_equal(state->msgtype, NC_MSG_REPLY); \ + assert_null(state->op); \ + assert_string_equal(LYD_NAME(lyd_child(state->envp)), "ok"); + +#define ASSERT_OK_REPLY_SESS2(state) \ + state->msgtype = nc_recv_reply(state->nc_sess2, state->rpc, state->msgid, 2000, &state->envp, &state->op); \ + assert_int_equal(state->msgtype, NC_MSG_REPLY); \ + assert_null(state->op); \ + assert_string_equal(LYD_NAME(lyd_child(state->envp)), "ok"); + +#define ASSERT_RPC_ERROR(state) \ + state->msgtype = nc_recv_reply(state->nc_sess, state->rpc, state->msgid, 2000, &state->envp, &state->op); \ + assert_int_equal(state->msgtype, NC_MSG_REPLY); \ + assert_null(state->op); \ + assert_string_equal(LYD_NAME(lyd_child(state->envp)), "rpc-error"); + +#define ASSERT_RPC_ERROR_SESS2(state) \ + state->msgtype = nc_recv_reply(state->nc_sess2, state->rpc, state->msgid, 2000, &state->envp, &state->op); \ + assert_int_equal(state->msgtype, NC_MSG_REPLY); \ + assert_null(state->op); \ + assert_string_equal(LYD_NAME(lyd_child(state->envp)), "rpc-error"); + +#define GET_CONFIG_DS_WD_FILTER(state, ds, wd, filter) \ + state->rpc = nc_rpc_getconfig(ds, filter, wd, NC_PARAMTYPE_CONST); \ + state->msgtype = nc_send_rpc(state->nc_sess, state->rpc, 1000, &state->msgid); \ + assert_int_equal(NC_MSG_RPC, state->msgtype); \ + state->msgtype = nc_recv_reply(state->nc_sess, state->rpc, state->msgid, 2000, &state->envp, &state->op); \ + assert_int_equal(state->msgtype, NC_MSG_REPLY); \ + assert_non_null(state->op); \ + assert_non_null(state->envp); \ + assert_string_equal(LYD_NAME(lyd_child(state->op)), "data"); \ + assert_int_equal(LY_SUCCESS, lyd_print_mem(&state->str, state->op, LYD_XML, LYD_PRINT_WD_IMPL_TAG)); + +#define GET_CONFIG_DS_FILTER(state, ds, filter) GET_CONFIG_DS_WD_FILTER(state, ds, NC_WD_ALL, filter); + +#define GET_CONFIG_WD(state, wd) GET_CONFIG_DS_WD_FILTER(state, NC_DATASTORE_RUNNING, wd, NULL); + +#define GET_CONFIG_FILTER(state, filter) \ + GET_CONFIG_DS_FILTER(state, NC_DATASTORE_RUNNING, filter); + +#define GET_CONFIG(state) GET_CONFIG_FILTER(state, NULL); + +#define GET_DS_CONFIG(state, ds) GET_CONFIG_DS_FILTER(state, ds, NULL); + +#define GET_FILTER(state, filter) \ + state->rpc = nc_rpc_get(filter, NC_WD_ALL, NC_PARAMTYPE_CONST); \ + state->msgtype = nc_send_rpc(state->nc_sess, state->rpc, 1000, &state->msgid); \ + assert_int_equal(NC_MSG_RPC, state->msgtype); \ + state->msgtype = nc_recv_reply(state->nc_sess, state->rpc, state->msgid, 2000, &state->envp, &state->op); \ + assert_int_equal(state->msgtype, NC_MSG_REPLY); \ + assert_non_null(state->op); \ + assert_non_null(state->envp); \ + assert_string_equal(LYD_NAME(lyd_child(state->op)), "data"); \ + assert_int_equal(LY_SUCCESS, lyd_print_mem(&state->str, state->op, LYD_XML, 0)); + +#define SEND_EDIT_RPC_DS(state, ds, config) \ + state->rpc = nc_rpc_edit(ds, NC_RPC_EDIT_DFLTOP_MERGE, NC_RPC_EDIT_TESTOPT_SET, NC_RPC_EDIT_ERROPT_ROLLBACK, \ + config, NC_PARAMTYPE_CONST); \ + state->msgtype = nc_send_rpc(state->nc_sess, state->rpc, 1000, &state->msgid); \ + assert_int_equal(NC_MSG_RPC, state->msgtype); + +#define SEND_EDIT_RPC(state, config) \ + SEND_EDIT_RPC_DS(state, NC_DATASTORE_RUNNING, config); + +#define EMPTY_GETCONFIG \ + "\n" \ + " \n" \ + "\n" + +#define ASSERT_EMPTY_CONFIG(state) \ + GET_CONFIG(state); \ + assert_string_equal(state->str, EMPTY_GETCONFIG); \ + FREE_TEST_VARS(state); + +#define SR_EDIT_SESSION(state, session, data) \ + assert_int_equal(LY_SUCCESS, lyd_parse_data_mem(state->ctx, data, LYD_XML, LYD_PARSE_STRICT | LYD_PARSE_ONLY, 0, \ + &state->node)); \ + assert_non_null(state->node); \ + assert_int_equal(SR_ERR_OK, sr_edit_batch(session, state->node, "merge")); \ + assert_int_equal(SR_ERR_OK, sr_apply_changes(session, 0)); + +#define SR_EDIT(state, data) SR_EDIT_SESSION(state, state->sr_sess, data); + +#define NOTIF_PARSE(state, data) \ + assert_int_equal(ly_in_new_memory(data, &state->in), LY_SUCCESS); \ + assert_int_equal(lyd_parse_op(state->ctx, NULL, state->in, LYD_XML, \ + LYD_TYPE_NOTIF_YANG, &state->node, NULL), \ + LY_SUCCESS); \ + ly_in_free(state->in, 0); + +#define RECV_NOTIF(state) \ + do { \ + state->msgtype = nc_recv_notif(state->nc_sess, 3000, &state->envp, &state->op); \ + } while (state->msgtype == NC_MSG_REPLY); \ + assert_int_equal(NC_MSG_NOTIF, state->msgtype); \ + while (state->op->parent) state->op = lyd_parent(state->op); \ + assert_int_equal(lyd_print_mem(&state->str, state->op, LYD_XML, 0), LY_SUCCESS); + +#define ASSERT_NO_NOTIF(state) \ + state->msgtype = nc_recv_notif(state->nc_sess, 10, &state->envp, &state->op); \ + assert_int_equal(NC_MSG_WOULDBLOCK, state->msgtype); \ + +#define ASSERT_OK_SUB_NTF(state) \ + do { \ + st->msgtype = nc_recv_reply(st->nc_sess, st->rpc, st->msgid, 1000, &st->envp, &st->op); \ + } while (st->msgtype == NC_MSG_NOTIF); \ + assert_int_equal(state->msgtype, NC_MSG_REPLY); \ + if (!state->op) { \ + lyd_print_file(stdout, st->envp, LYD_XML, 0); \ + fail(); \ + } \ + assert_string_equal(LYD_NAME(lyd_child(state->op)), "id"); \ + state->ntf_id = (uint32_t) strtoul(lyd_get_value(lyd_child(state->op)), NULL, 10); + +#define SEND_RPC_ESTABSUB(st, filter, stream, start_time, stop_time) \ + st->rpc = nc_rpc_establishsub(filter, stream, start_time, stop_time, NULL, NC_PARAMTYPE_CONST); \ + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); \ + assert_int_equal(NC_MSG_RPC, st->msgtype); + +#define SEND_RPC_DELSUB(st, id) \ + st->rpc = nc_rpc_deletesub(id); \ + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); \ + assert_int_equal(NC_MSG_RPC, st->msgtype); + +#define SEND_RPC_MODSUB(st, id, filter, stop_time) \ + st->rpc = nc_rpc_modifysub(id, filter, stop_time, NC_PARAMTYPE_CONST); \ + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); \ + assert_int_equal(NC_MSG_RPC, st->msgtype); + +#define SEND_RPC_KILLSUB(st, id) \ + st->rpc = nc_rpc_killsub(id); \ + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); \ + assert_int_equal(NC_MSG_RPC, st->msgtype); + +#define RECV_SUBMOD_NOTIF(st) \ + RECV_NOTIF(st); \ + assert_string_equal(LYD_NAME(st->op), "subscription-modified"); \ + lyd_free_tree(st->envp); \ + lyd_free_tree(st->op); \ + ASSERT_OK_REPLY(st); \ + FREE_TEST_VARS(st); \ /* test state structure */ struct np_test { pid_t server_pid; + sr_conn_ctx_t *conn; + sr_session_ctx_t *sr_sess; + sr_session_ctx_t *sr_sess2; + sr_subscription_ctx_t *sub; + struct ly_in *in; + const struct ly_ctx *ctx; + struct lyd_node *node; struct nc_session *nc_sess; struct nc_session *nc_sess2; + struct nc_rpc *rpc; + NC_MSG_TYPE msgtype; + uint64_t msgid; + struct lyd_node *envp, *op; + char *str; + uint32_t ntf_id; }; -int _np_glob_setup(void **state, const char *test_name); +int np_glob_setup_np2(void **state); + +int setup_setenv_sysrepo(const char *test_name); int np_glob_teardown(void **state); +void parse_arg(int argc, char **argv); + +int get_username(char **name); + +int setup_nacm(void **state); + +int is_nacm_rec_uid(); + #endif /* _NP_TEST_H_ */ diff --git a/tests/np_test_config.h.in b/tests/np_test_config.h.in index a1e9b6911..6c3e1e570 100644 --- a/tests/np_test_config.h.in +++ b/tests/np_test_config.h.in @@ -31,6 +31,8 @@ /* sysrepo test repositories directory */ #define NP_SR_REPOS_DIR "@CMAKE_CURRENT_BINARY_DIR@/repositories" +#define NP_TEST_MODULE_DIR "@CMAKE_CURRENT_SOURCE_DIR@/modules" + /* sysrepo SHM prefix */ #define NP_SR_SHM_PREFIX "_tests_np_" @@ -43,4 +45,9 @@ /* server log file */ #define NP_LOG_PATH "@CMAKE_CURRENT_BINARY_DIR@/netopeer2-server.log" +/* notifation file path */ +#define SR_NOTIFICATION_PATH "@NOTIFICATION_PATH@" + +#define NACM_RECOVERY_UID "@NACM_RECOVERY_UID@" + #endif /* _NP_TEST_CONFIG_H_ */ diff --git a/tests/scripts/kill_np_server.sh.in b/tests/scripts/kill_np_server.sh.in new file mode 100755 index 000000000..d4b5012f1 --- /dev/null +++ b/tests/scripts/kill_np_server.sh.in @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +NP2_TESTS_BINARY_DIR="@CMAKE_CURRENT_BINARY_DIR@" +[ -z "$NP2_TESTS_BINARY_DIR" ] && + echo "Expected an argument with to the test directory" && + exit 1 +pidfile="$NP2_TESTS_BINARY_DIR/netopeer2-server.pid" +[ -f "$pidfile" ] && kill "$(cat "$pidfile")" +exit 0 diff --git a/tests/test_candidate.c b/tests/test_candidate.c new file mode 100644 index 000000000..0e40af921 --- /dev/null +++ b/tests/test_candidate.c @@ -0,0 +1,556 @@ +/** + * @file test_candidate.c + * @author Tadeas Vintrlik + * @brief tests around candidate configuration and corresponding rpcs + * + * @copyright + * Copyright 2021 Deutsche Telekom AG. + * Copyright 2021 CESNET, z.s.p.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "np_test.h" +#include "np_test_config.h" + +static int +local_setup(void **state) +{ + struct np_test *st = *state; + sr_conn_ctx_t *conn; + const char *features[] = {NULL}; + const char *module1 = NP_TEST_MODULE_DIR "/edit1.yang"; + const char *module2 = NP_TEST_MODULE_DIR "/edit2.yang"; + int rv; + + /* setup environment necessary for installing module */ + NP_GLOB_SETUP_ENV_FUNC; + assert_int_equal(setenv_rv, 0); + + /* connect to server and install test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module1, NULL, features), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module2, NULL, features), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* setup netopeer2 server */ + if (!(rv = np_glob_setup_np2(state))) { + st = *state; + /* Open two connections to start a session for the tests + * One for Candidate and other for running + */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &st->conn), SR_ERR_OK); + assert_int_equal(sr_session_start(st->conn, SR_DS_RUNNING, &st->sr_sess), SR_ERR_OK); + assert_non_null(st->ctx = sr_get_context(st->conn)); + assert_int_equal(sr_session_start(st->conn, SR_DS_CANDIDATE, &st->sr_sess2), SR_ERR_OK); + rv |= setup_nacm(state); + } + return rv; +} + +static int +local_teardown(void **state) +{ + struct np_test *st = *state; + sr_conn_ctx_t *conn; + + /* Close the sessions and connection needed for tests */ + assert_int_equal(sr_session_stop(st->sr_sess), SR_ERR_OK); + assert_int_equal(sr_session_stop(st->sr_sess2), SR_ERR_OK); + assert_int_equal(sr_disconnect(st->conn), SR_ERR_OK); + + /* connect to server and remove test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "edit1"), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "edit2"), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* close netopeer2 server */ + return np_glob_teardown(state); +} + +static int +setup_candidate(void **state) +{ + struct np_test *st = *state; + const char *data; + + data = "TestFirst"; + + SR_EDIT_SESSION(st, st->sr_sess2, data); + + FREE_TEST_VARS(st); + return 0; +} + +static int +empty_candidate(void **state) +{ + struct np_test *st = *state; + const char *data; + + data = ""; + + SR_EDIT_SESSION(st, st->sr_sess2, data); + + FREE_TEST_VARS(st); + return 0; +} + +static int +empty_running(void **state) +{ + struct np_test *st = *state; + const char *data; + + data = + ""; + + SR_EDIT(st, data); + + FREE_TEST_VARS(st); + return 0; +} + +static void +test_edit_basic(void **state) +{ + struct np_test *st = *state; + char *data, *expected; + + /* Get a simple config into candidate */ + data = "TestFirst"; + + SEND_EDIT_RPC_DS(st, NC_DATASTORE_CANDIDATE, data); + + ASSERT_OK_REPLY(st); + + FREE_TEST_VARS(st); + + /* Check if it was merged */ + GET_DS_CONFIG(st, NC_DATASTORE_CANDIDATE); + + expected = + "\n" + " \n" + " TestFirst\n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); + + /* Remove it from the configuration */ + data = ""; + + SEND_EDIT_RPC_DS(st, NC_DATASTORE_CANDIDATE, data); + + ASSERT_OK_REPLY(st); + + FREE_TEST_VARS(st); +} + +static void +test_commit(void **state) +{ + struct np_test *st = *state; + char *data, *expected; + + /* Get some data into candidate */ + data = "TestFirst"; + + SR_EDIT_SESSION(st, st->sr_sess2, data); + + /* Check if running is empty */ + GET_DS_CONFIG(st, NC_DATASTORE_RUNNING); + + expected = + "\n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); + + /* Send commit rpc */ + st->rpc = nc_rpc_commit(0, 0, NULL, NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 2000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + + ASSERT_OK_REPLY(st); + + FREE_TEST_VARS(st); + + /* Check if running is now same as candidate */ + GET_DS_CONFIG(st, NC_DATASTORE_CANDIDATE); + + expected = + "\n" + " \n" + " TestFirst\n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +static void +test_discard_changes(void **state) +{ + struct np_test *st = *state; + char *expected; + + /* Check if Running is empty */ + GET_DS_CONFIG(st, NC_DATASTORE_RUNNING); + + expected = + "\n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); + + /* Check if Candidate has config */ + GET_DS_CONFIG(st, NC_DATASTORE_CANDIDATE); + + expected = + "\n" + " \n" + " TestFirst\n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); + + /* Send discard-changes rpc */ + st->rpc = nc_rpc_discard(); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 2000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + + ASSERT_OK_REPLY(st); + + FREE_TEST_VARS(st); + + /* Check if Candidate is now empty too */ + GET_DS_CONFIG(st, NC_DATASTORE_CANDIDATE); + + expected = + "\n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +static void +test_validate_valid(void **state) +{ + struct np_test *st = *state; + + /* Send validate rpc */ + st->rpc = nc_rpc_validate(NC_DATASTORE_CANDIDATE, NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 2000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + + ASSERT_OK_REPLY(st); + + FREE_TEST_VARS(st); +} + +static void +test_validate_invalid(void **state) +{ + struct np_test *st = *state; + const char *data; + + data = + "" + " TestSecond" + " ClearlyNotANumericValue" + ""; + + /* Send validate rpc */ + st->rpc = nc_rpc_validate(NC_DATASTORE_CONFIG, data, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 2000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + + ASSERT_OK_REPLY(st); + + FREE_TEST_VARS(st); +} + +static int +setup_discard_changes_advanced(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Merge config into running */ + data = + "" + " TestSecond" + ""; + + SR_EDIT(st, data); + + FREE_TEST_VARS(st); + + /* Merge config into candidate */ + data = + "" + " TestSecond" + " 12" + ""; + + SR_EDIT_SESSION(st, st->sr_sess2, data); + + FREE_TEST_VARS(st); + + return 0; +} + +static int +setup_commit_locked_running(void **state) +{ + struct np_test *st = *state; + + /* lock from another session */ + st->rpc = nc_rpc_lock(NC_DATASTORE_RUNNING); + st->msgtype = nc_send_rpc(st->nc_sess2, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + + /* receive reply, should succeed */ + ASSERT_OK_REPLY_SESS2(st); + + FREE_TEST_VARS(st); + + return 0; +} + +static int +teardown_commit_locked_running(void **state) +{ + struct np_test *st = *state; + + /* lock from another session */ + st->rpc = nc_rpc_unlock(NC_DATASTORE_RUNNING); + st->msgtype = nc_send_rpc(st->nc_sess2, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + + /* receive reply, should succeed */ + ASSERT_OK_REPLY_SESS2(st); + return 0; +} + +static void +test_commit_locked_running(void **state) +{ + struct np_test *st = *state; + + /* Send commit rpc */ + st->rpc = nc_rpc_commit(0, 0, NULL, NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 2000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + + /* Receive a reply should have error-tag in-use */ + st->msgtype = nc_recv_reply(st->nc_sess, st->rpc, st->msgid, 2000, &st->envp, &st->op); + assert_int_equal(st->msgtype, NC_MSG_REPLY); + assert_null(st->op); + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "in-use"); + + FREE_TEST_VARS(st); +} + +static int +setup_lock_modified_candidate(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Modify candidate in any way */ + data = "TestFirst"; + + SR_EDIT_SESSION(st, st->sr_sess2, data); + + FREE_TEST_VARS(st); + + return 0; +} + +static int +teardown_lock_modified_candidate(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Remove anything */ + data = ""; + + SR_EDIT_SESSION(st, st->sr_sess2, data); + + FREE_TEST_VARS(st); + + return 0; +} + +static void +test_lock_modified_candidate(void **state) +{ + struct np_test *st = *state; + + /* lock */ + st->rpc = nc_rpc_lock(NC_DATASTORE_CANDIDATE); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + + ASSERT_RPC_ERROR(st); + + FREE_TEST_VARS(st); +} + +static int +teardown_discard_changes_advanced(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Remove config from running */ + data = ""; + + SR_EDIT(st, data); + + FREE_TEST_VARS(st); + + /* Remove config from candidate */ + SR_EDIT_SESSION(st, st->sr_sess2, data); + + FREE_TEST_VARS(st); + + return 0; +} + +static void +test_discard_changes_advanced(void **state) +{ + struct np_test *st = *state; + const char *expected; + + /* Check if running has correct data */ + GET_DS_CONFIG(st, NC_DATASTORE_RUNNING); + + expected = + "\n" + " \n" + " \n" + " TestSecond\n" + " \n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); + + /* Check if candidate has correct dada */ + GET_DS_CONFIG(st, NC_DATASTORE_CANDIDATE); + + expected = + "\n" + " \n" + " \n" + " TestSecond\n" + " 12\n" + " \n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); + + /* Send discard-changes rpc */ + st->rpc = nc_rpc_discard(); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 2000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + + ASSERT_OK_REPLY(st); + + FREE_TEST_VARS(st); + + /* Check if candidate is now same as running */ + GET_DS_CONFIG(st, NC_DATASTORE_RUNNING); + + expected = + "\n" + " \n" + " \n" + " TestSecond\n" + " \n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_edit_basic), + cmocka_unit_test_setup_teardown(test_commit, setup_candidate, empty_running), + cmocka_unit_test_setup(test_discard_changes, setup_candidate), + cmocka_unit_test_setup_teardown(test_validate_valid, setup_candidate, empty_candidate), + cmocka_unit_test(test_validate_invalid), + cmocka_unit_test_setup_teardown(test_commit_locked_running, setup_commit_locked_running, + teardown_commit_locked_running), + cmocka_unit_test_setup_teardown(test_lock_modified_candidate, setup_lock_modified_candidate, + teardown_lock_modified_candidate), + cmocka_unit_test_setup_teardown(test_discard_changes_advanced, setup_discard_changes_advanced, + teardown_discard_changes_advanced), + }; + + if (is_nacm_rec_uid()) { + puts("Running as NACM_RECOVERY_UID. Tests will not run correctly as this user bypases NACM. Skipping."); + return 0; + } + + nc_verbosity(NC_VERB_WARNING); + parse_arg(argc, argv); + return cmocka_run_group_tests(tests, local_setup, local_teardown); +} diff --git a/tests/test_edit.c b/tests/test_edit.c new file mode 100644 index 000000000..9689f190a --- /dev/null +++ b/tests/test_edit.c @@ -0,0 +1,771 @@ +/** + * @file test_edit.c + * @author Tadeas Vintrlik + * @brief tests for the edit-config rpc + * + * @copyright + * Copyright 2021 Deutsche Telekom AG. + * Copyright 2021 CESNET, z.s.p.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "np_test.h" +#include "np_test_config.h" + +static int +local_setup(void **state) +{ + struct np_test *st; + sr_conn_ctx_t *conn; + const char *module1 = NP_TEST_MODULE_DIR "/edit1.yang"; + const char *module2 = NP_TEST_MODULE_DIR "/edit2.yang"; + const char *module3 = NP_TEST_MODULE_DIR "/edit3.yang"; + const char *module4 = NP_TEST_MODULE_DIR "/edit4.yang"; + const char *module5 = NP_TEST_MODULE_DIR "/example1.yang"; + const char *module6 = NP_TEST_MODULE_DIR "/example2.yang"; + int rv; + + /* Setup environment necessary for installing module */ + NP_GLOB_SETUP_ENV_FUNC; + assert_int_equal(setenv_rv, 0); + + /* Connect to server and install test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module1, NULL, NULL), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module2, NULL, NULL), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module3, NULL, NULL), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module4, NULL, NULL), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module5, NULL, NULL), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module6, NULL, NULL), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* Setup netopeer2 server */ + if (!(rv = np_glob_setup_np2(state))) { + st = *state; + /* Open the connection to start a session for the tests */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &st->conn), SR_ERR_OK); + assert_int_equal(sr_session_start(st->conn, SR_DS_RUNNING, &st->sr_sess), SR_ERR_OK); + assert_non_null(st->ctx = sr_get_context(st->conn)); + rv |= setup_nacm(state); + } + return rv; +} + +static int +local_teardown(void **state) +{ + struct np_test *st = *state; + sr_conn_ctx_t *conn; + + /* Close the session and connection needed for tests */ + assert_int_equal(sr_session_stop(st->sr_sess), SR_ERR_OK); + assert_int_equal(sr_disconnect(st->conn), SR_ERR_OK); + + /* Connect to server and remove test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "edit1"), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "edit2"), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "edit3"), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "edit4"), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "example1"), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "example2"), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* Close netopeer2 server */ + return np_glob_teardown(state); +} + +static int +teardown_common(void **state) +{ + struct np_test *st = *state; + const char *data; + + data = + "\n" + "\n" + "\n" + "\n" + "\n" + "\n"; + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_merge_edit1(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Send RPC editing module edit1 */ + data = "TestFirst\n"; + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* Check if added to config */ + GET_CONFIG(st); + assert_non_null(strstr(st->str, "TestFirst")); + FREE_TEST_VARS(st); +} + +static void +test_merge_edit2(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Send RPC editing module edit2 */ + data = + "\n" + " TestSecond\n" + " 123\n" + "\n"; + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* Check if added to config */ + GET_CONFIG(st); + assert_non_null(strstr(st->str, "TestSecond")); + FREE_TEST_VARS(st); +} + +static void +test_merge_edit2_fail(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Send invalid RPC editing module edit2 */ + data = + "\n" + " TestSecond\n" + " ClearlyNotANumericValue\n" + "\n"; + SEND_EDIT_RPC(st, data); + ASSERT_RPC_ERROR(st); + FREE_TEST_VARS(st); +} + +static int +setup_test_delete_edit1(void **state) +{ + struct np_test *st = *state; + const char *data; + + data = "TestFirst\n"; + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_delete_edit1(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Send rpc deleting config in module edit1 */ + data = "\n"; + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* Check if the config was deleted */ + ASSERT_EMPTY_CONFIG(st); +} + +static int +setup_test_delete_edit2(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Send RPC editing module edit2 */ + data = + "\n" + " TestSecond\n" + " 123\n" + "\n"; + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_delete_edit2(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Send rpc deleting config in module edit2 */ + data = "\n"; + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* Check if the config was deleted */ + ASSERT_EMPTY_CONFIG(st); +} + +static void +test_delete_empty(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Try deleting a non-existent config, should fail */ + data = "\n"; + SEND_EDIT_RPC(st, data); + ASSERT_RPC_ERROR(st); + FREE_TEST_VARS(st); +} + +static void +test_merge_patrial(void **state) +{ + struct np_test *st = *state; + const char *expected, *data = + "\n" + " TestSecond\n" + "\n"; + + /* Merge a partial config */ + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* Check if merged successfully */ + GET_CONFIG(st); + expected = + "\n" + " \n" + " \n" + " TestSecond\n" + " \n" + " \n" + "\n"; + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static int +setup_test_merge_into_existing(void **state) +{ + struct np_test *st = *state; + const char *data = + "\n" + " TestSecond\n" + "\n"; + + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_merge_into_existing(void **state) +{ + struct np_test *st = *state; + const char *expected, *data = + "\n" + " 123\n" + "\n"; + + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* Check if correctly merged */ + GET_CONFIG(st); + expected = + "\n" + " \n" + " \n" + " TestSecond\n" + " 123\n" + " \n" + " \n" + "\n"; + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static int +setup_test_merge_overwrite(void **state) +{ + struct np_test *st = *state; + const char *data = + "\n" + " TestSecond\n" + " 123\n" + "\n"; + + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_merge_overwrite(void **state) +{ + struct np_test *st = *state; + const char *expected, *data = + "\n" + " 456\n" + "\n"; + + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* Check if config was correctly overwritten */ + GET_CONFIG(st); + expected = + "\n" + " \n" + " \n" + " TestSecond\n" + " 456\n" + " \n" + " \n" + "\n"; + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static int +setup_test_replace(void **state) +{ + struct np_test *st = *state; + const char *data = + "\n" + " TestThird\n" + " 123\n" + "\n"; + + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_replace(void **state) +{ + struct np_test *st = *state; + const char *expected, *data = + "\n" + " TestThird\n" + " 456\n" + "\n"; + + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* Check if replaced correctly */ + GET_CONFIG(st); + expected = + "\n" + " \n" + " \n" + " TestThird\n" + " 456\n" + " \n" + " \n" + "\n"; + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static void +test_replace_create(void **state) +{ + struct np_test *st = *state; + const char *expected, *data = + "\n" + " TestThird\n" + " 456\n" + "\n"; + + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* Check if created correctly */ + GET_CONFIG(st); + expected = + "\n" + " \n" + " \n" + " TestThird\n" + " 456\n" + " \n" + " \n" + "\n"; + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static void +test_create(void **state) +{ + struct np_test *st = *state; + const char *expected, *data = + "" + "TestFourth" + "\n"; + + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* Check if config config now contains edit1 */ + GET_CONFIG(st); + expected = + "\n" + " \n" + " TestFourth\n" + " \n" + "\n"; + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static int +setup_test_create_fail(void **state) +{ + struct np_test *st = *state; + const char *data = + "" + "TestFourth" + "\n"; + + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_create_fail(void **state) +{ + struct np_test *st = *state; + const char *data = + "" + "TestFourth" + "\n"; + + SEND_EDIT_RPC(st, data); + ASSERT_RPC_ERROR(st); + FREE_TEST_VARS(st); +} + +static int +setup_test_remove(void **state) +{ + struct np_test *st = *state; + const char *data = "TestFirst\n"; + + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_remove(void **state) +{ + struct np_test *st = *state; + const char *data = "\n"; + + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + ASSERT_EMPTY_CONFIG(st); +} + +static void +test_remove_empty(void **state) +{ + struct np_test *st = *state; + const char *data = "\n"; + + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + ASSERT_EMPTY_CONFIG(st); +} + +static int +setup_test_ex1(void **state) +{ + struct np_test *st = *state; + const char *data = + "\n" + " \n" + " Ethernet0/0\n" + " 1500\n" + " \n" + "\n"; + + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_ex1(void **state) +{ + /* First example for edit-config from rfc 6241 section 7.2 */ + struct np_test *st = *state; + const char *expected, *data; + + data = + "\n" + " \n" + " Ethernet0/0\n" + " 1500\n" + "
\n" + " 192.0.2.4\n" + " 24\n" + "
\n" + "
\n" + "
\n"; + + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + GET_CONFIG(st); + expected = + "\n" + " \n" + " \n" + " \n" + " Ethernet0/0\n" + " 1500\n" + "
\n" + " 192.0.2.4\n" + " 24\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n"; + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static int +setup_test_ex2(void **state) +{ + struct np_test *st = *state; + const char *data = + "\n" + " \n" + " \n" + " \n" + " 0.0.0.0\n" + " \n" + " \n" + " 192.0.2.1\n" + " \n" + " \n" + " 192.0.2.4\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; + + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_ex2(void **state) +{ + /* Second example for edit-config from rfc 6241 section 7.2 */ + struct np_test *st = *state; + const char *expected, *data = + "\n" + " \n" + " \n" + " \n" + " 0.0.0.0\n" + " \n" + " \n" + " 192.0.2.4\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; + + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + GET_CONFIG(st); + expected = + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 0.0.0.0\n" + " \n" + " \n" + " 192.0.2.1\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static void +test_autodel_case(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* create case #1 */ + data = + "\n" + " value\n" + " \n" + "\n"; + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* check data */ + GET_CONFIG(st); + assert_non_null(strstr(st->str, "l1")); + assert_non_null(strstr(st->str, "l2")); + FREE_TEST_VARS(st); + + /* create case #2 */ + data = + "\n" + " 58\n" + "\n"; + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* check data */ + GET_CONFIG(st); + assert_null(strstr(st->str, "l1")); + assert_null(strstr(st->str, "l2")); + assert_non_null(strstr(st->str, "c2")); + FREE_TEST_VARS(st); + + /* create case #3 */ + data = + "\n" + " \n" + " -256\n" + " \n" + "\n"; + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* check data */ + GET_CONFIG(st); + assert_null(strstr(st->str, "c2")); + assert_non_null(strstr(st->str, "l3")); + FREE_TEST_VARS(st); + + /* create case #4 */ + data = + "\n" + " a\n" + " b\n" + "\n"; + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* check data */ + GET_CONFIG(st); + assert_null(strstr(st->str, "l3")); + assert_non_null(strstr(st->str, "l4")); + assert_non_null(strstr(st->str, "l5")); + FREE_TEST_VARS(st); +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_teardown(test_merge_edit1, teardown_common), + cmocka_unit_test_teardown(test_merge_edit2, teardown_common), + cmocka_unit_test_teardown(test_merge_edit2_fail, teardown_common), + cmocka_unit_test_setup(test_delete_edit1, setup_test_delete_edit1), + cmocka_unit_test_setup(test_delete_edit2, setup_test_delete_edit2), + cmocka_unit_test(test_delete_empty), + cmocka_unit_test_teardown(test_merge_patrial, teardown_common), + cmocka_unit_test_setup_teardown(test_merge_into_existing, setup_test_merge_into_existing, teardown_common), + cmocka_unit_test_setup_teardown(test_merge_overwrite, setup_test_merge_overwrite, teardown_common), + cmocka_unit_test_setup_teardown(test_replace, setup_test_replace, teardown_common), + cmocka_unit_test_teardown(test_replace_create, teardown_common), + cmocka_unit_test_teardown(test_create, teardown_common), + cmocka_unit_test_setup_teardown(test_create_fail, setup_test_create_fail, teardown_common), + cmocka_unit_test_setup(test_remove, setup_test_remove), + cmocka_unit_test(test_remove_empty), + cmocka_unit_test_setup_teardown(test_ex1, setup_test_ex1, teardown_common), + cmocka_unit_test_setup_teardown(test_ex2, setup_test_ex2, teardown_common), + cmocka_unit_test_teardown(test_autodel_case, teardown_common), + }; + + nc_verbosity(NC_VERB_WARNING); + parse_arg(argc, argv); + return cmocka_run_group_tests(tests, local_setup, local_teardown); +} diff --git a/tests/test_filter.c b/tests/test_filter.c new file mode 100644 index 000000000..6c77c25bf --- /dev/null +++ b/tests/test_filter.c @@ -0,0 +1,884 @@ +/** + * @file test_filter.c + * @author Tadeas Vintrlik + * @brief tests for filter in get and get-config rpc + * + * @copyright + * Copyright 2021 Deutsche Telekom AG. + * Copyright 2021 CESNET, z.s.p.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "np_test.h" +#include "np_test_config.h" + +static void +setup_data(void **state) +{ + struct np_test *st = *state; + char *data; + + data = + "\n" + " \n" + " \n" + " \n" + " 0.0.0.0\n" + " \n" + " \n" + " 192.0.2.1\n" + " \n" + " \n" + " 192.0.2.4\n" + " \n" + " \n" + " \n" + " \n" + " 192.168.0.0\n" + " \n" + " \n" + " 192.168.0.1\n" + " \n" + " \n" + " 192.168.0.12\n" + " \n" + " \n" + " 192.168.0.25\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + data = + "\n" + " \n" + " 2\n" + " \n" + " \n" + " 3\n" + " \n" + " \n" + " 4\n" + " \n" + " \n" + " 6\n" + " \n" + " \n" + " 8\n" + " \n" + " \n" + " 13\n" + " \n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + data = + "\n" + " \n" + " ComponentName\n" + " O-RAN-RADIO\n" + " \n" + " true\n" + " \n" + " \n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + data = + "\n" + " \n" + " \n" + " \n" + " First\n" + "
192.168.0.4
\n" + " 80\n" + "
\n" + " \n" + " Second\n" + "
192.168.0.12
\n" + " 80\n" + "
\n" + " \n" + " Fourth\n" + "
192.168.0.50
\n" + " 22\n" + "
\n" + " \n" + " Fifth\n" + "
192.168.0.50
\n" + " 443\n" + "
\n" + " \n" + " Sixth\n" + "
192.168.0.102
\n" + " 22\n" + "
\n" + "
\n" + " \n" + " \n" + " Seventh\n" + "
192.168.0.130
\n" + "
\n" + " \n" + " Sixth\n" + "
192.168.0.142
\n" + "
\n" + "
\n" + "
\n" + "
\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + data = "Test\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); +} + +static int +change_cb(sr_session_ctx_t *session, uint32_t sub_id, + const char *module_name, const char *xpath, + sr_event_t event, uint32_t request_id, void *private_data) +{ + (void) session; (void) sub_id; (void) module_name; (void) xpath; + (void) event; (void) request_id; (void) private_data; + return SR_ERR_OK; +} + +static int +change_serial_num(sr_session_ctx_t *session, uint32_t sub_id, + const char *module_name, const char *path, + const char *request_xpath, uint32_t request_id, + struct lyd_node **parent, void *private_data) +{ + (void) session; (void) sub_id; (void) module_name; (void) path; + (void) request_xpath; (void) request_id; (void) private_data; + if (!lyd_new_path(*parent, NULL, "serial-num", "1234", 0, NULL)) { + return SR_ERR_OK; + } else { + return SR_ERR_LY; + } +} + +static int +local_setup(void **state) +{ + struct np_test *st; + sr_conn_ctx_t *conn; + const char *features[] = {NULL}; + const char *module1 = NP_TEST_MODULE_DIR "/example2.yang"; + const char *module2 = NP_TEST_MODULE_DIR "/filter1.yang"; + const char *module3 = NP_TEST_MODULE_DIR "/xpath.yang"; + const char *module4 = NP_TEST_MODULE_DIR "/issue1.yang"; + const char *module5 = NP_TEST_MODULE_DIR "/edit1.yang"; + int rv; + + /* setup environment necessary for installing module */ + NP_GLOB_SETUP_ENV_FUNC; + assert_int_equal(setenv_rv, 0); + + /* connect to server and install test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module1, NULL, features), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module2, NULL, features), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module3, NULL, features), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module4, NULL, features), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module5, NULL, features), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* setup netopeer2 server */ + if (!(rv = np_glob_setup_np2(state))) { + /* state is allocated in np_glob_setup_np2 have to set here */ + st = *state; + /* Open connection to start a session for the tests */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &st->conn), SR_ERR_OK); + assert_int_equal(sr_session_start(st->conn, SR_DS_RUNNING, &st->sr_sess), SR_ERR_OK); + assert_non_null(st->ctx = sr_get_context(st->conn)); + setup_data(state); + + assert_int_equal(SR_ERR_OK, sr_oper_get_items_subscribe(st->sr_sess, "issue1", + "/issue1:hardware/component/serial-num", change_serial_num, NULL, SR_SUBSCR_DEFAULT, &st->sub)); + + assert_int_equal(SR_ERR_OK, sr_module_change_subscribe(st->sr_sess, "issue1", NULL, change_cb, NULL, 0, + SR_SUBSCR_CTX_REUSE, &st->sub)); + } + return rv; +} + +static int +local_teardown(void **state) +{ + struct np_test *st = *state; + sr_conn_ctx_t *conn; + + /* Unsubscribe */ + sr_unsubscribe(st->sub); + + /* Close the session and connection needed for tests */ + assert_int_equal(sr_session_stop(st->sr_sess), SR_ERR_OK); + assert_int_equal(sr_disconnect(st->conn), SR_ERR_OK); + + /* connect to server and remove test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "example2"), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "xpath"), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "filter1"), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "issue1"), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "edit1"), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* close netopeer2 server */ + return np_glob_teardown(state); +} + +static void +test_xpath_basic(void **state) +{ + struct np_test *st = *state; + const char *expected; + + expected = + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 0.0.0.0\n" + " \n" + " \n" + " 192.0.2.1\n" + " \n" + " \n" + " 192.0.2.4\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; + + /* Filter first by xpath */ + GET_CONFIG_FILTER(st, "/top/protocols/ospf/area[1]"); + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); + + /* since there are two last()-1 should be same as 1 */ + GET_CONFIG_FILTER(st, "/top/protocols/ospf/area[last()-1]"); + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); + + /* filter by area name same as the two before */ + GET_CONFIG_FILTER(st, "/top/protocols/ospf/area[name='0.0.0.0']"); + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); + + /* use arithmetic operators should also be the first */ + GET_CONFIG_FILTER(st, "/top/protocols/ospf/area[3-2]"); + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); + + GET_CONFIG_FILTER(st, "/top/protocols/ospf/area[5 mod 2]"); + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); + + GET_CONFIG_FILTER(st, "/top/protocols/ospf/area[-1 + 2]"); + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); + + GET_CONFIG_FILTER(st, "/top/protocols/ospf/area[name!='192.168.0.0']"); + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static void +test_xpath_boolean_operator(void **state) +{ + struct np_test *st = *state; + const char *expected; + + expected = + "\n" + " \n" + " \n" + " \n" + " 3\n" + " \n" + " \n" + " 4\n" + " \n" + " \n" + " 6\n" + " \n" + " \n" + " 8\n" + " \n" + " \n" + " \n" + "\n"; + + GET_CONFIG_FILTER(st, "/top/item[price > 2 and price <= 8]"); + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); + + expected = + "\n" + " \n" + " \n" + " \n" + " 2\n" + " \n" + " \n" + " 3\n" + " \n" + " \n" + " 8\n" + " \n" + " \n" + " 13\n" + " \n" + " \n" + " \n" + "\n"; + + GET_CONFIG_FILTER(st, "/top/item[price < 4 or price >= 8]"); + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static void +test_xpath_union(void **state) +{ + struct np_test *st = *state; + const char *expected; + + expected = + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 0.0.0.0\n" + " \n" + " \n" + " 192.0.2.1\n" + " \n" + " \n" + " 192.0.2.4\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " ComponentName\n" + " O-RAN-RADIO\n" + " \n" + " true\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + + GET_CONFIG_FILTER(st, "/example2:top/protocols/ospf/area[name='0.0.0.0']|" + "/issue1:hardware/component[name='ComponentName']"); + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static void +test_xpath_namespaces(void **state) +{ + struct np_test *st = *state; + const char *expected; + + expected = + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 0.0.0.0\n" + " \n" + " \n" + " 192.0.2.1\n" + " \n" + " \n" + " 192.0.2.4\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " ComponentName\n" + " O-RAN-RADIO\n" + " \n" + " true\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + + /* test namespaces */ + GET_CONFIG_FILTER(st, "/top/protocols/ospf/area[name='0.0.0.0']|" + "/hardware/component[name='ComponentName']"); + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); + + GET_CONFIG_FILTER(st, "/top"); + + expected = + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 0.0.0.0\n" + " \n" + " \n" + " 192.0.2.1\n" + " \n" + " \n" + " 192.0.2.4\n" + " \n" + " \n" + " \n" + " \n" + " 192.168.0.0\n" + " \n" + " \n" + " 192.168.0.1\n" + " \n" + " \n" + " 192.168.0.12\n" + " \n" + " \n" + " 192.168.0.25\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " Seventh\n" + "
192.168.0.130
\n" + "
\n" + " \n" + " Sixth\n" + "
192.168.0.142
\n" + "
\n" + "
\n" + " \n" + " \n" + " First\n" + "
192.168.0.4
\n" + " 80\n" + "
\n" + " \n" + " Second\n" + "
192.168.0.12
\n" + " 80\n" + "
\n" + " \n" + " Fourth\n" + "
192.168.0.50
\n" + " 22\n" + "
\n" + " \n" + " Fifth\n" + "
192.168.0.50
\n" + " 443\n" + "
\n" + " \n" + " Sixth\n" + "
192.168.0.102
\n" + " 22\n" + "
\n" + "
\n" + "
\n" + "
\n" + " \n" + " \n" + " 2\n" + " \n" + " \n" + " 3\n" + " \n" + " \n" + " 4\n" + " \n" + " \n" + " 6\n" + " \n" + " \n" + " 8\n" + " \n" + " \n" + " 13\n" + " \n" + " \n" + "
\n" + "
\n"; + + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static void +test_xpath_top_level_leaf_match(void **state) +{ + struct np_test *st = *state; + char *expected; + + GET_CONFIG_FILTER(st, "/edit1:first[.='Test']"); + + expected = + "\n" + " \n" + " Test\n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +static void +test_subtree_content_match(void **state) +{ + struct np_test *st = *state; + const char *filter, *expected; + + filter = + "\n" + " \n" + " \n" + " \n" + " 0.0.0.0\n" + " \n" + " \n" + " \n" + "\n"; + + GET_CONFIG_FILTER(st, filter); + + expected = + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 0.0.0.0\n" + " \n" + " \n" + " 192.0.2.1\n" + " \n" + " \n" + " 192.0.2.4\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +static void +test_subtree_content_match_top_level_leaf(void **state) +{ + const char *expected; + struct np_test *st = *state; + + GET_CONFIG_FILTER(st, "Test"); + + expected = + "\n" + " \n" + " Test\n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +static void +test_subtree_selection_node(void **state) +{ + struct np_test *st = *state; + char *filter, *expected; + + filter = + "\n" + " \n" + " \n" + " \n" + "\n"; + + GET_CONFIG_FILTER(st, filter); + + expected = + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " First\n" + "
192.168.0.4
\n" + " 80\n" + "
\n" + " \n" + " Second\n" + "
192.168.0.12
\n" + " 80\n" + "
\n" + " \n" + " Fourth\n" + "
192.168.0.50
\n" + " 22\n" + "
\n" + " \n" + " Fifth\n" + "
192.168.0.50
\n" + " 443\n" + "
\n" + " \n" + " Sixth\n" + "
192.168.0.102
\n" + " 22\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +static void +test_get_selection_node(void **state) +{ + struct np_test *st = *state; + char *filter, *expected; + + filter = + "\n" + " \n" + " " + " \n" + ""; + + GET_FILTER(st, filter); + + expected = + "\n" + " \n" + " \n" + " \n" + " ComponentName\n" + " 1234\n" + " \n" + " \n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +static void +test_get_containment_node(void **state) +{ + struct np_test *st = *state; + char *filter, *expected; + + filter = "\n"; + + GET_FILTER(st, filter); + + expected = + "\n" + " \n" + " \n" + " \n" + " ComponentName\n" + " O-RAN-RADIO\n" + " 1234\n" + " \n" + " true\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +static void +test_get_content_match_node(void **state) +{ + struct np_test *st = *state; + char *filter, *expected; + + filter = + "\n" + " \n" + " O-RAN-RADIO\n" + " \n" + " \n" + " \n" + "\n"; + + GET_FILTER(st, filter); + + expected = + "\n" + " \n" + " \n" + " \n" + " ComponentName\n" + " O-RAN-RADIO\n" + " 1234\n" + " \n" + " true\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +static void +test_get_operational_data(void **state) +{ + struct np_test *st = *state; + char *filter, *expected; + + filter = + "\n" + " \n" + " O-RAN-RADIO\n" + " 1234\n" + " \n" + " true\n" + " \n" + " \n" + "\n"; + + GET_FILTER(st, filter); + + expected = + "\n" + " \n" + " \n" + " \n" + " ComponentName\n" + " O-RAN-RADIO\n" + " 1234\n" + " \n" + " true\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_xpath_basic), + cmocka_unit_test(test_xpath_boolean_operator), + cmocka_unit_test(test_xpath_union), + cmocka_unit_test(test_xpath_namespaces), + cmocka_unit_test(test_xpath_top_level_leaf_match), + cmocka_unit_test(test_subtree_content_match), + cmocka_unit_test(test_subtree_content_match_top_level_leaf), + cmocka_unit_test(test_subtree_selection_node), + cmocka_unit_test(test_get_selection_node), + cmocka_unit_test(test_get_containment_node), + cmocka_unit_test(test_get_content_match_node), + cmocka_unit_test(test_get_operational_data), + }; + + nc_verbosity(NC_VERB_WARNING); + parse_arg(argc, argv); + return cmocka_run_group_tests(tests, local_setup, local_teardown); +} diff --git a/tests/test_nacm.c b/tests/test_nacm.c new file mode 100644 index 000000000..6c2301d2b --- /dev/null +++ b/tests/test_nacm.c @@ -0,0 +1,1202 @@ +/** + * @file test_nacm.c + * @author Tadeas Vintrlik + * @brief tests for NETCONF Access Control Management + * + * @copyright + * Copyright 2021 Deutsche Telekom AG. + * Copyright 2021 CESNET, z.s.p.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "np_test.h" +#include "np_test_config.h" + +static int +local_setup(void **state) +{ + struct np_test *st; + sr_conn_ctx_t *conn; + const char *features[] = {NULL}; + const char *module1 = NP_TEST_MODULE_DIR "/edit1.yang"; + const char *module2 = NP_TEST_MODULE_DIR "/example2.yang"; + const char *module3 = NP_TEST_MODULE_DIR "/nacm-test1.yang"; + const char *module4 = NP_TEST_MODULE_DIR "/nacm-test2.yang"; + int rv; + + /* setup environment necessary for installing module */ + NP_GLOB_SETUP_ENV_FUNC; + assert_int_equal(setenv_rv, 0); + + /* connect to server and install test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module1, NULL, features), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module2, NULL, features), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module3, NULL, features), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module4, NULL, features), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* setup netopeer2 server */ + if (!(rv = np_glob_setup_np2(state))) { + st = *state; + /* Open two connections to start a session for the tests + * One for Candidate and other for running + */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &st->conn), SR_ERR_OK); + assert_int_equal(sr_session_start(st->conn, SR_DS_RUNNING, &st->sr_sess), SR_ERR_OK); + assert_non_null(st->ctx = sr_get_context(st->conn)); + assert_int_equal(sr_session_start(st->conn, SR_DS_CANDIDATE, &st->sr_sess2), SR_ERR_OK); + rv |= setup_nacm(state); + } + return rv; +} + +static int +local_teardown(void **state) +{ + struct np_test *st = *state; + sr_conn_ctx_t *conn; + + /* Close the sessions and connection needed for tests */ + assert_int_equal(sr_session_stop(st->sr_sess), SR_ERR_OK); + assert_int_equal(sr_session_stop(st->sr_sess2), SR_ERR_OK); + assert_int_equal(sr_disconnect(st->conn), SR_ERR_OK); + + /* connect to server and remove test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "edit1"), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "example2"), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "nacm-test1"), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "nacm-test2"), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* close netopeer2 server */ + return np_glob_teardown(state); +} + +static int +teardown_common(void **state) +{ + struct np_test *st = *state; + const char *data = + "\n" + "\n" + "\n" + "John\n" + "Thomas\n" + "Arnold\n" + "\n" + " deny\n" + " \n" + " rule1\n" + " \n" + ""; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + data = + "\n" + "\n"; + /* Remove from candidate as well */ + SR_EDIT_SESSION(st, st->sr_sess2, data); + FREE_TEST_VARS(st); + return 0; +} + +static int +setup_test_exec_get(void **state) +{ + struct np_test *st = *state; + const char *data = + "\n" + " permit\n" + " \n" + " rule1\n" + " test-group\n" + " \n" + " disallow-get\n" + " ietf-netconf\n" + " get\n" + " exec\n" + " deny\n" + " \n" + " \n" + " disallow-get-config\n" + " ietf-netconf\n" + " get-config\n" + " exec\n" + " deny\n" + " \n" + " \n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_exec_get(void **state) +{ + struct np_test *st = *state; + + /* get and get-config bypass execution permissions */ + GET_FILTER(st, NULL); + assert_non_null(st->str); + FREE_TEST_VARS(st); + + GET_CONFIG(st); + assert_non_null(st->str); + FREE_TEST_VARS(st); +} + +static int +setup_test_read_default(void **state) +{ + struct np_test *st = *state; + const char *data = + "\n" + " deny\n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_read_default(void **state) +{ + struct np_test *st = *state; + const char *expected = + "\n" + " \n" + "\n"; + + GET_FILTER(st, NULL); + /* Since is set to deny it should return empty data */ + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static int +setup_test_read_default_allow_path(void **state) +{ + struct np_test *st = *state; + const char *data = + "TestFirst\n" + "\n" + " deny\n" + " \n" + " rule1\n" + " test-group\n" + " \n" + " allow-first\n" + " edit1\n" + " /ed1:first\n" + " read\n" + " permit\n" + " \n" + " \n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_read_default_allow_path(void **state) +{ + struct np_test *st = *state; + const char *expected = + "\n" + " \n" + " TestFirst\n" + " \n" + "\n"; + + /* Since there is an expeption for the element it should be returned */ + GET_FILTER(st, NULL); + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static int +setup_test_get_config(void **state) +{ + struct np_test *st = *state; + const char *data = + "TestFirst\n" + "\n" + " \n" + " rule1\n" + " test-group\n" + " \n" + " disallow-first\n" + " edit1\n" + " /ed1:first\n" + " read\n" + " deny\n" + " \n" + " \n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_get_config(void **state) +{ + struct np_test *st = *state; + const char *expected; + + /* Since reading of this node is denied it should return empty config */ + GET_CONFIG(st); + expected = + "\n" + " \n" + "\n"; + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static void +test_get_config_filter(void **state) +{ + struct np_test *st = *state; + const char *expected; + + /* Using a filter on a denied node should not cause access denied error */ + GET_CONFIG_FILTER(st, "/edit1:first[.='TestFirst']"); + expected = + "\n" + " \n" + "\n"; + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static int +setup_test_xpath_filter_denied(void **state) +{ + struct np_test *st = *state; + const char *data = + "\n" + " John\n" + " 75\n" + "\n" + "\n" + " Thomas\n" + " 100\n" + "\n" + "\n" + " Arnold\n" + " 110\n" + "\n" + "\n" + " \n" + " rule1\n" + " test-group\n" + " \n" + " disallow-num\n" + " nacm-test2\n" + " /nt2:people/nt2:weight\n" + " read\n" + " deny\n" + " \n" + " \n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_xpath_filter_denied(void **state) +{ + /* Issue #846 */ + struct np_test *st = *state; + const char *expected, *filter = "/people[weight>100]"; + + GET_CONFIG_FILTER(st, filter); + expected = + "\n" + " \n" + " \n" + " Arnold\n" + " \n" + " \n" + "\n"; + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static int +setup_test_filter_key_list(void **state) +{ + struct np_test *st = *state; + const char *data = + "\n" + " John\n" + " 75\n" + "\n" + "\n" + " Thomas\n" + " 100\n" + "\n" + "\n" + " Arnold\n" + " 110\n" + "\n" + "\n" + " \n" + " rule1\n" + " test-group\n" + " \n" + " disallow-num-arnold\n" + " nacm-test2\n" + " /nt2:people[nt2:name='Arnold']/nt2:weight\n" + " read\n" + " deny\n" + " \n" + " \n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_filter_key_list(void **state) +{ + /* Issue #755 */ + struct np_test *st = *state; + const char *expected; + + GET_CONFIG(st); + expected = + "\n" + " \n" + " \n" + " John\n" + " 75\n" + " \n" + " \n" + " Thomas\n" + " 100\n" + " \n" + " \n" + " Arnold\n" + " \n" + " \n" + "\n"; + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static int +setup_test_rule_wildcard_groups(void **state) +{ + struct np_test *st = *state; + const char *data = + "TestFirst\n" + "\n" + " deny\n" + " \n" + " rule1\n" + " *\n" + " \n" + " allow-first\n" + " edit1\n" + " /ed1:first\n" + " read\n" + " permit\n" + " \n" + " \n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_rule_wildcard_groups(void **state) +{ + /* Issue #619 */ + struct np_test *st = *state; + const char *expected = + "\n" + " \n" + " TestFirst\n" + " \n" + "\n"; + + /* Since there is an expeption for the element using wildcard for groups it should be returned */ + GET_FILTER(st, NULL); + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static int +setup_edit_config(void **state) +{ + struct np_test *st = *state; + const char *data = + "\n" + " deny\n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + return 0; + +} + +static int +teardown_edit_config(void **state) +{ + struct np_test *st = *state; + const char *data = + "\n" + " permit\n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + return 0; + +} + +static void +test_edit_config(void **state) +{ + struct np_test *st = *state; + const char *data = "TestFirst\n"; + + SEND_EDIT_RPC(st, data); + ASSERT_RPC_ERROR(st); + /* Check if correct error-tag */ + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "access-denied"); + FREE_TEST_VARS(st); +} + +static int +setup_test_edit_config_permit(void **state) +{ + struct np_test *st = *state; + const char *data = + "TestFirst\n" + "\n" + " deny\n" + " \n" + " rule1\n" + " test-group\n" + " \n" + " allow-first\n" + " edit1\n" + " /ed1:first\n" + " create\n" + " permit\n" + " \n" + " \n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_edit_config_permit(void **state) +{ + struct np_test *st = *state; + const char *data = "TestFirst\n"; + + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); +} + +static int +setup_test_edit_config_update(void **state) +{ + struct np_test *st = *state; + const char *data = + "\n" + " deny\n" + " \n" + " rule1\n" + " test-group\n" + " \n" + " allow-first\n" + " edit1\n" + " /ed1:first\n" + " update\n" + " permit\n" + " \n" + " \n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_edit_config_update(void **state) +{ + struct np_test *st = *state; + const char *expected, *data = "Test\n"; + + /* Creating is not permited */ + SEND_EDIT_RPC(st, data); + ASSERT_RPC_ERROR(st); + /* Check if correct error-tag */ + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "access-denied"); + FREE_TEST_VARS(st); + + /* Merge the data bypassing NACM */ + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Updating an existing node is permited */ + data = "Alt\n"; + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + GET_CONFIG(st); + expected = + "\n" + " \n" + " Alt\n" + " \n" + "\n"; + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static int +setup_test_edit_config_subtree(void **state) +{ + struct np_test *st = *state; + const char *data = + "\n" + " \n" + " \n" + " \n" + " 0.0.0.0\n" + " \n" + " \n" + " \n" + "\n" + "\n" + " \n" + " rule1\n" + " test-group\n" + " \n" + " allow-examle2-name\n" + " example2\n" + " " + "/ex2:top/ex2:protocols/ex2:ospf/ex2:area/ex2:interfaces/ex2:interface\n" + " create\n" + " permit\n" + " \n" + " \n" + " allow-examle2-remove\n" + " example2\n" + " " + "/ex2:top/ex2:protocols/ex2:ospf/ex2:area\n" + " delete\n" + " permit\n" + " \n" + " \n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_edit_config_subtree(void **state) +{ + struct np_test *st = *state; + const char *data = + "\n" + " \n" + " \n" + " \n" + " 0.0.0.0\n" + " \n" + " \n" + " 192.0.2.4\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; + + /* Adding another interface should succeed */ + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + data = + "\n" + " \n" + " \n" + " \n" + " 192.168.0.0\n" + " \n" + " \n" + " \n" + "\n"; + + /* Adding another area should fail */ + SEND_EDIT_RPC(st, data); + ASSERT_RPC_ERROR(st); + /* Check if correct error-tag */ + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "access-denied"); + FREE_TEST_VARS(st); + + data = + "\n" + " \n" + " \n" + " \n" + " 0.0.0.0\n" + " \n" + " \n" + " \n" + "\n"; + + /* removing area should succeed */ + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); +} + +static int +setup_test_edit_config_when(void **state) +{ + struct np_test *st = *state; + const char *data = + "\n" + " Test\n" + " 12\n" + "\n" + "\n" + " \n" + " rule1\n" + " test-group\n" + " \n" + " allow-nt1-name\n" + " nacm-test1\n" + " /nt1:top/nt1:name\n" + " permit\n" + " \n" + " \n" + " disallow-nt1-num\n" + " nacm-test1\n" + " /nt1:top/nt1:num\n" + " delete\n" + " deny\n" + " \n" + " \n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_edit_config_when(void **state) +{ + struct np_test *st = *state; + const char *expected, *data = + "\n" + " \n" + "\n"; + + /* Removing the num should fail */ + SEND_EDIT_RPC(st, data); + ASSERT_RPC_ERROR(st); + /* Check if correct error-tag */ + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "access-denied"); + FREE_TEST_VARS(st); + + data = + "\n" + " \n" + "\n"; + + /* Removing the name should also remove the num since it is under when-stmt */ + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + GET_CONFIG(st); + expected = "\n \n\n"; + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static void +test_copy_config_running2startup(void **state) +{ + struct np_test *st = *state; + + /* From running to startup only needs execute on copy-config */ + st->rpc = nc_rpc_copy(NC_DATASTORE_STARTUP, NULL, NC_DATASTORE_RUNNING, NULL, NC_WD_ALL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); +} + +static int +setup_test_copy_config_ds2ds_fail_read(void **state) +{ + struct np_test *st = *state; + const char *data = + "\n" + " deny\n" + " \n" + " rule1\n" + " test-group\n" + " \n" + " disallow-read-first\n" + " edit1\n" + " /ed1:first\n" + " read\n" + " deny\n" + " \n" + " \n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + data = "TestFirst\n"; + SR_EDIT_SESSION(st, st->sr_sess2, data); + FREE_TEST_VARS(st); + + return 0; +} + +static int +setup_test_copy_config_ds2ds_fail_write(void **state) +{ + struct np_test *st = *state; + const char *data = + "\n" + " deny\n" + " \n" + " rule1\n" + " test-group\n" + " \n" + " disallow-read-first\n" + " edit1\n" + " /ed1:first\n" + " read\n" + " deny\n" + " \n" + " \n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + data = "TestFirst\n"; + SR_EDIT_SESSION(st, st->sr_sess2, data); + FREE_TEST_VARS(st); + + return 0; +} + +static void +test_copy_config_ds2ds_fail_read(void **state) +{ + struct np_test *st = *state; + + st->rpc = nc_rpc_copy(NC_DATASTORE_RUNNING, NULL, NC_DATASTORE_CANDIDATE, NULL, NC_WD_ALL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + ASSERT_RPC_ERROR(st); + /* Check if correct error-tag */ + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "access-denied"); + FREE_TEST_VARS(st); +} + +static void +test_copy_config_ds2ds_fail_write(void **state) +{ + test_copy_config_ds2ds_fail_read(state); +} + +static void +test_delete_config(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Should be disabled by default */ + st->rpc = nc_rpc_delete(NC_DATASTORE_STARTUP, NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + ASSERT_RPC_ERROR(st); + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "access-denied"); + FREE_TEST_VARS(st); + + data = + "\n" + " permit\n" + " \n" + " rule1\n" + " test-group\n" + " \n" + " allow-delete\n" + " ietf-netconf\n" + " delete-config\n" + " exec\n" + " permit\n" + " \n" + " \n" + " allow-delete-all\n" + " *\n" + " delete\n" + " permit\n" + " \n" + " \n" + "\n"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Should succeed now */ + st->rpc = nc_rpc_delete(NC_DATASTORE_STARTUP, NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); +} + +static int +setup_test_commit(void **state) +{ + struct np_test *st = *state; + char *data = + "\n" + " \n" + " rule1\n" + " test-group\n" + " \n" + " disallow-name\n" + " nacm-test1\n" + " /nt1:top/nt1:name\n" + " create\n" + " deny\n" + " \n" + " \n" + " allow-num\n" + " /nt1:top/nt1:num\n" + " permit\n" + " \n" + " \n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st) + + /* Merge into candidate */ + data = + "\n" + " Test\n" + " 12\n" + ""; + SR_EDIT_SESSION(st, st->sr_sess2, data); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_commit(void **state) +{ + struct np_test *st = *state; + const char *data, *expected; + + /* Should fail since candidate has element that is denied to merge */ + st->rpc = nc_rpc_commit(0, 0, NULL, NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + ASSERT_RPC_ERROR(st); + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "access-denied"); + FREE_TEST_VARS(st); + + /* Merge the missing element into running */ + data = + "\n" + " Test\n" + ""; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Should succeed now */ + st->rpc = nc_rpc_commit(0, 0, NULL, NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + GET_CONFIG(st); + expected = + "\n" + " \n" + " \n" + " Test\n" + " 12\n" + " \n" + " \n" + "\n"; + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static int +setup_test_discard_changes(void **state) +{ + struct np_test *st = *state; + char *data = + "\n" + " \n" + " rule1\n" + " test-group\n" + " \n" + " disallow-first\n" + " edit1\n" + " /ed1:first\n" + " delete\n" + " deny\n" + " \n" + " \n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st) + + /* Merge into candidate */ + data = "Test"; + SR_EDIT_SESSION(st, st->sr_sess2, data); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_discard_changes(void **state) +{ + struct np_test *st = *state; + const char *expected; + + /* Should succeed despite not having rights over the datastore nodes */ + st->rpc = nc_rpc_discard(); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + GET_CONFIG_DS_FILTER(st, NC_DATASTORE_CANDIDATE, NULL); + expected = + "\n" + " \n" + "\n"; + assert_string_equal(st->str, expected); + FREE_TEST_VARS(st); +} + +static void +test_kill_session(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Should fail since the rpc is disallowed */ + st->rpc = nc_rpc_kill(nc_session_get_id(st->nc_sess2)); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + ASSERT_RPC_ERROR(st); + /* Check if correct error-tag */ + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "access-denied"); + FREE_TEST_VARS(st); + + data = + "\n" + " \n" + " rule1\n" + " test-group\n" + " \n" + " allow-delete\n" + " ietf-netconf\n" + " kill-session\n" + " exec\n" + " permit\n" + " \n" + " \n" + "\n"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Should succeed now */ + st->rpc = nc_rpc_kill(nc_session_get_id(st->nc_sess2)); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + ASSERT_OK_REPLY(st); +} + +int +main(int argc, char **argv) +{ + if (is_nacm_rec_uid()) { + puts("Running as NACM_RECOVERY_UID. Tests will not run correctly as this user bypases NACM. Skipping."); + return 0; + } + + nc_verbosity(NC_VERB_WARNING); + sr_log_stderr(SR_LL_WRN); + parse_arg(argc, argv); + + if (sr_get_su_uid() != getuid()) { + /* not sysrepo super user skip write tests */ + puts("Not running as sysrepo super-user. Skipping tests that depend on it."); + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(test_exec_get, + setup_test_exec_get, + teardown_common), + cmocka_unit_test_setup_teardown(test_read_default, + setup_test_read_default, + teardown_common), + cmocka_unit_test_setup_teardown(test_read_default_allow_path, + setup_test_read_default_allow_path, + teardown_common), + cmocka_unit_test_setup_teardown(test_get_config, + setup_test_get_config, + teardown_common), + cmocka_unit_test_setup_teardown(test_get_config_filter, + setup_test_get_config, + teardown_common), + cmocka_unit_test_setup_teardown(test_xpath_filter_denied, + setup_test_xpath_filter_denied, + teardown_common), + cmocka_unit_test_setup_teardown(test_filter_key_list, + setup_test_filter_key_list, + teardown_common), + cmocka_unit_test_setup_teardown(test_rule_wildcard_groups, + setup_test_rule_wildcard_groups, + teardown_common), + }; + return cmocka_run_group_tests(tests, local_setup, local_teardown); + } else { + /* sysrepo super run with write tests */ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(test_exec_get, + setup_test_exec_get, + teardown_common), + cmocka_unit_test_setup_teardown(test_read_default, + setup_test_read_default, + teardown_common), + cmocka_unit_test_setup_teardown(test_read_default_allow_path, + setup_test_read_default_allow_path, + teardown_common), + cmocka_unit_test_setup_teardown(test_get_config, + setup_test_get_config, + teardown_common), + cmocka_unit_test_setup_teardown(test_get_config_filter, + setup_test_get_config, + teardown_common), + cmocka_unit_test_setup_teardown(test_xpath_filter_denied, + setup_test_xpath_filter_denied, + teardown_common), + cmocka_unit_test_setup_teardown(test_filter_key_list, + setup_test_filter_key_list, + teardown_common), + cmocka_unit_test_setup_teardown(test_rule_wildcard_groups, + setup_test_rule_wildcard_groups, + teardown_common), + cmocka_unit_test_setup_teardown(test_edit_config, + setup_edit_config, + teardown_edit_config), + cmocka_unit_test_setup_teardown(test_edit_config_permit, + setup_test_edit_config_permit, + teardown_common), + cmocka_unit_test_setup_teardown(test_edit_config_update, + setup_test_edit_config_update, + teardown_common), + cmocka_unit_test_setup_teardown(test_edit_config_subtree, + setup_test_edit_config_subtree, + teardown_common), + cmocka_unit_test_setup_teardown(test_edit_config_when, + setup_test_edit_config_when, + teardown_common), + cmocka_unit_test(test_copy_config_running2startup), + cmocka_unit_test_setup_teardown(test_copy_config_ds2ds_fail_read, + setup_test_copy_config_ds2ds_fail_read, + teardown_common), + cmocka_unit_test_setup_teardown(test_copy_config_ds2ds_fail_write, + setup_test_copy_config_ds2ds_fail_write, + teardown_common), + cmocka_unit_test_teardown(test_delete_config, + teardown_common), + cmocka_unit_test_setup_teardown(test_commit, + setup_test_commit, + teardown_common), + cmocka_unit_test_setup_teardown(test_discard_changes, + setup_test_discard_changes, + teardown_common), + cmocka_unit_test_teardown(test_kill_session, + teardown_common), + }; + return cmocka_run_group_tests(tests, local_setup, local_teardown); + } +} diff --git a/tests/test_parallel_sessions.c b/tests/test_parallel_sessions.c new file mode 100644 index 000000000..857e17b09 --- /dev/null +++ b/tests/test_parallel_sessions.c @@ -0,0 +1,111 @@ +/** + * @file test_parallel_sessions.c + * @author * @author Tadeas Vintrlik + * @brief tests for sending parallel requests on more threads + * + * @copyright + * Copyright 2021 Deutsche Telekom AG. + * Copyright 2021 CESNET, z.s.p.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "np_test.h" +#include "np_test_config.h" + +#define THREAD_COUNT 3 + +static int +local_setup(void **state) +{ + NP_GLOB_SETUP_ENV_FUNC; + assert_int_equal(setenv_rv, 0); + return np_glob_setup_np2(state); +} + +static void * +send_get_rpc(void *barrier) +{ + struct nc_rpc *rpc; + struct nc_session *nc_sess; + struct lyd_node *envp, *op; + NC_MSG_TYPE msgtype; + uint64_t msgid; + + /* create a NETCONF session */ + nc_sess = nc_connect_unix(NP_SOCKET_PATH, NULL); + assert_non_null(nc_sess); + pthread_barrier_wait(barrier); + + /* Send get rpc */ + rpc = nc_rpc_get(NULL, NC_WD_ALL, NC_PARAMTYPE_CONST); + msgtype = nc_send_rpc(nc_sess, rpc, 1000, &msgid); + assert_int_equal(msgtype, NC_MSG_RPC); + + /* recieve reply, should succeed */ + msgtype = nc_recv_reply(nc_sess, rpc, msgid, THREAD_COUNT * 2000, &envp, &op); + assert_int_equal(msgtype, NC_MSG_REPLY); + assert_non_null(op); + assert_non_null(envp); + + nc_rpc_free(rpc); + lyd_free_tree(op); + lyd_free_tree(envp); + + /* stop the NETCONF session */ + nc_session_free(nc_sess, NULL); + + return NULL; +} + +static void +test_first(void **state) +{ + (void)state; + pthread_t t[THREAD_COUNT]; + pthread_barrier_t barrier; + + pthread_barrier_init(&barrier, NULL, THREAD_COUNT); + for (uint32_t i = 0; i < THREAD_COUNT; i++) { + pthread_create(&t[i], NULL, send_get_rpc, &barrier); + } + for (uint32_t i = 0; i < THREAD_COUNT; i++) { + pthread_join(t[i], NULL); + } +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_first), + }; + + nc_verbosity(NC_VERB_WARNING); + parse_arg(argc, argv); + return cmocka_run_group_tests(tests, local_setup, np_glob_teardown); +} diff --git a/tests/test_rpc.c b/tests/test_rpc.c index 11dfd9111..d084cfc56 100644 --- a/tests/test_rpc.c +++ b/tests/test_rpc.c @@ -1,6 +1,6 @@ /** * @file test_rpc.c - * @author Michal Vasko + * @author Tadeas Vintrlik * @brief test executing simple RPCs * * @copyright @@ -33,333 +33,422 @@ #include #include "np_test.h" -#include "np_test_config.h.in" - -#define LOCK_FAIL_TEMPLATE "\n"\ - " \n"\ - " protocol\n"\ - " lock-denied\n"\ - " error\n"\ - " Access to the requested lock is denied because the lock is currently held by another entity.\n"\ - " \n"\ - " %d\n"\ - " \n"\ - " \n"\ - "\n" - -NP_GLOB_SETUP_FUNC +#include "np_test_config.h" -static void -test_types(void **state) +static int +local_setup(void **state) { - struct nc_rpc *rpc; - - (void)state; + struct np_test *st = *state; + sr_conn_ctx_t *conn; + const char *features[] = {NULL}; + const char *module1 = NP_TEST_MODULE_DIR "/edit1.yang"; + int rv; + + /* Setup environment necessary for installing module */ + NP_GLOB_SETUP_ENV_FUNC; + assert_int_equal(setenv_rv, 0); + + /* Connect to server and install test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module1, NULL, features), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* Setup netopeer2 server */ + if (!(rv = np_glob_setup_np2(state))) { + st = *state; + /* Open the connection to start a session for the tests */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &st->conn), SR_ERR_OK); + assert_int_equal(sr_session_start(st->conn, SR_DS_RUNNING, &st->sr_sess), SR_ERR_OK); + assert_non_null(st->ctx = sr_get_context(st->conn)); + rv |= setup_nacm(state); + } + return rv; +} - /* Test that all constructors create the right type of rpc */ - rpc = nc_rpc_lock(NC_DATASTORE_RUNNING); - assert_int_equal(NC_RPC_LOCK, nc_rpc_get_type(rpc)); - nc_rpc_free(rpc); +static int +local_teardown(void **state) +{ + struct np_test *st = *state; + sr_conn_ctx_t *conn; - rpc = nc_rpc_unlock(NC_DATASTORE_RUNNING); - assert_int_equal(NC_RPC_UNLOCK, nc_rpc_get_type(rpc)); - nc_rpc_free(rpc); + /* Close the session and connection needed for tests */ + assert_int_equal(sr_session_stop(st->sr_sess), SR_ERR_OK); + assert_int_equal(sr_disconnect(st->conn), SR_ERR_OK); - rpc = nc_rpc_get("", NC_WD_ALL, NC_PARAMTYPE_CONST); - assert_int_equal(NC_RPC_GET, nc_rpc_get_type(rpc)); - nc_rpc_free(rpc); + /* Connect to server and remove test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "edit1"), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); - rpc = nc_rpc_kill(NC_DATASTORE_RUNNING); - assert_int_equal(NC_RPC_KILL, nc_rpc_get_type(rpc)); - nc_rpc_free(rpc); + /* Close netopeer2 server */ + return np_glob_teardown(state); +} - rpc = nc_rpc_commit(NC_DATASTORE_RUNNING, 0, NULL, NULL, NC_PARAMTYPE_CONST); - assert_int_equal(NC_RPC_COMMIT, nc_rpc_get_type(rpc)); - nc_rpc_free(rpc); +static int +teardown_test_lock(void **state) +{ + struct np_test *st = *state; - rpc = nc_rpc_discard(); - assert_int_equal(NC_RPC_DISCARD, nc_rpc_get_type(rpc)); - nc_rpc_free(rpc); + st->rpc = nc_rpc_unlock(NC_DATASTORE_RUNNING); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + return 0; +} - rpc = nc_rpc_cancel("", NC_PARAMTYPE_CONST); - assert_int_equal(NC_RPC_CANCEL, nc_rpc_get_type(rpc)); - nc_rpc_free(rpc); +static void +test_lock_basic(void **state) +{ + struct np_test *st = *state; - rpc = nc_rpc_validate(NC_DATASTORE_RUNNING, "", NC_PARAMTYPE_CONST); - assert_int_equal(NC_RPC_VALIDATE, nc_rpc_get_type(rpc)); - nc_rpc_free(rpc); + /* Check if lock RPC succeeds */ + st->rpc = nc_rpc_lock(NC_DATASTORE_RUNNING); + assert_non_null(st->rpc); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); +} - rpc = nc_rpc_subscribe("", "", "", "", NC_PARAMTYPE_CONST); - assert_int_equal(NC_RPC_SUBSCRIBE, nc_rpc_get_type(rpc)); - nc_rpc_free(rpc); +static void +test_lock_fail(void **state) +{ + struct np_test *st = *state; + const char *template; + char *error; + + /* Lock from first session */ + st->rpc = nc_rpc_lock(NC_DATASTORE_RUNNING); + assert_non_null(st->rpc); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* Request to lock from another session should fail when locked already */ + st->rpc = nc_rpc_lock(NC_DATASTORE_RUNNING); + st->msgtype = nc_send_rpc(st->nc_sess2, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_RPC_ERROR_SESS2(st); + + /* Check error message */ + assert_int_equal(LY_SUCCESS, lyd_print_mem(&st->str, st->envp, LYD_XML, 0)); + template = + "\n" + " \n" + " protocol\n" + " lock-denied\n" + " error\n" + " Access to the requested lock is denied" + " because the lock is currently held by another entity.\n" + " \n" + " %d\n" + " \n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&error, template, st->msgid, nc_session_get_id(st->nc_sess))); + assert_string_equal(st->str, error); + + free(error); + FREE_TEST_VARS(st); +} - rpc = nc_rpc_getdata("", "", "", NULL, 0, 0, 0, 0, NC_WD_ALL, NC_PARAMTYPE_CONST); - assert_int_equal(NC_RPC_GETDATA, nc_rpc_get_type(rpc)); - nc_rpc_free(rpc); +static int +setup_test_lock_changes(void **state) +{ + struct np_test *st = *state; - rpc = nc_rpc_editdata("", NC_RPC_EDIT_DFLTOP_MERGE, "", NC_PARAMTYPE_CONST); - assert_int_equal(NC_RPC_EDITDATA, nc_rpc_get_type(rpc)); - nc_rpc_free(rpc); + st->rpc = nc_rpc_lock(NC_DATASTORE_RUNNING); + assert_non_null(st->rpc); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + return 0; +} - /* TODO: NC_RPC_ESTABLISHSUB and the rest */ +static int +teardown_test_lock_changes(void **state) +{ + struct np_test *st = *state; + const char *data = + ""; + + SEND_EDIT_RPC(st, data); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + st->rpc = nc_rpc_unlock(NC_DATASTORE_RUNNING); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + return 0; } static void -test_lock(void **state) +test_lock_changes(void **state) +{ + struct np_test *st = *state; + + /* Send RPC editing module edit1 on the same session, should succeed */ + SEND_EDIT_RPC(st, "TestFirst"); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* Send RPC editing module edit1 on another session, should fail */ + st->rpc = nc_rpc_edit(NC_DATASTORE_RUNNING, NC_RPC_EDIT_DFLTOP_MERGE, NC_RPC_EDIT_TESTOPT_SET, + NC_RPC_EDIT_ERROPT_ROLLBACK, "TestFirst", NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess2, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + st->msgtype = nc_recv_reply(st->nc_sess2, st->rpc, st->msgid, 2000, &st->envp, &st->op); + assert_int_equal(st->msgtype, NC_MSG_REPLY); + assert_null(st->op); + assert_string_equal(LYD_NAME(lyd_child(st->envp)), "rpc-error"); + FREE_TEST_VARS(st); +} + +static int +setup_test_unlock(void **state) { struct np_test *st = *state; - struct nc_rpc *rpc; - NC_MSG_TYPE msgtype; - uint64_t msgid; - struct lyd_node *envp, *op; - char *str, *str2; - - /* lock from first session */ - rpc = nc_rpc_lock(NC_DATASTORE_RUNNING); - assert_non_null(rpc); - - /* send request */ - msgtype = nc_send_rpc(st->nc_sess, rpc, 1000, &msgid); - assert_int_equal(msgtype, NC_MSG_RPC); - - /* receive reply */ - msgtype = nc_recv_reply(st->nc_sess, rpc, msgid, 2000, &envp, &op); - assert_int_equal(msgtype, NC_MSG_REPLY); - assert_null(op); - assert_string_equal(LYD_NAME(lyd_child(envp)), "ok"); - - lyd_free_tree(envp); - - /* request to lock from another session should fail when lock already */ - msgtype = nc_send_rpc(st->nc_sess2, rpc, 1000, &msgid); - assert_int_equal(msgtype, NC_MSG_RPC); - - /* recieve reply, should yield error */ - msgtype = nc_recv_reply(st->nc_sess2, rpc, msgid, 2000, &envp, &op); - assert_int_equal(msgtype, NC_MSG_REPLY); - assert_null(op); - assert_string_equal(LYD_NAME(lyd_child(envp)), "rpc-error"); - assert_int_equal(LY_SUCCESS, lyd_print_mem(&str, envp, LYD_XML, 0)); - - assert_int_not_equal(-1, asprintf(&str2, LOCK_FAIL_TEMPLATE, msgid, nc_session_get_id(st->nc_sess))); - - /* error expected */ - assert_string_equal(str, str2); - free(str); - free(str2); - - nc_rpc_free(rpc); - lyd_free_tree(envp); - - /* unlock RPC */ - rpc = nc_rpc_unlock(NC_DATASTORE_RUNNING); - msgtype = nc_send_rpc(st->nc_sess, rpc, 1000, &msgid); - assert_int_equal(msgtype, NC_MSG_RPC); - msgtype = nc_recv_reply(st->nc_sess, rpc, msgid, 2000, &envp, &op); - assert_int_equal(msgtype, NC_MSG_REPLY); - assert_null(op); - assert_string_equal(LYD_NAME(lyd_child(envp)), "ok"); - - nc_rpc_free(rpc); - lyd_free_tree(envp); - /* TODO: check if lock prevents changes */ + + st->rpc = nc_rpc_lock(NC_DATASTORE_RUNNING); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + return 0; } static void test_unlock(void **state) { struct np_test *st = *state; - struct nc_rpc *rpc; - NC_MSG_TYPE msgtype; - uint64_t msgid; - struct lyd_node *envp, *op; - char *str, *str2; - - /* Simple locking checked in previous tests */ - - /* Lock by a different session */ - rpc = nc_rpc_lock(NC_DATASTORE_RUNNING); - msgtype = nc_send_rpc(st->nc_sess2, rpc, 1000, &msgid); - assert_int_equal(msgtype, NC_MSG_RPC); - - /* receive reply */ - msgtype = nc_recv_reply(st->nc_sess2, rpc, msgid, 2000, &envp, &op); - assert_int_equal(msgtype, NC_MSG_REPLY); - assert_null(op); - assert_string_equal(LYD_NAME(lyd_child(envp)), "ok"); - - nc_rpc_free(rpc); - lyd_free_tree(envp); - - /* Try unlocking a lock by a different session */ - rpc = nc_rpc_unlock(NC_DATASTORE_RUNNING); - msgtype = nc_send_rpc(st->nc_sess, rpc, 1000, &msgid); - assert_int_equal(msgtype, NC_MSG_RPC); - - /* recieve reply, should yield error */ - msgtype = nc_recv_reply(st->nc_sess, rpc, msgid, 2000, &envp, &op); - assert_int_equal(msgtype, NC_MSG_REPLY); - assert_null(op); - assert_string_equal(LYD_NAME(lyd_child(envp)), "rpc-error"); - assert_int_equal(LY_SUCCESS, lyd_print_mem(&str, envp, LYD_XML, 0)); - - /* error expected */ - assert_int_not_equal(-1, asprintf(&str2, LOCK_FAIL_TEMPLATE, msgid, nc_session_get_id(st->nc_sess2))); - assert_string_equal(str, str2); - free(str); - free(str2); - - nc_rpc_free(rpc); - lyd_free_tree(envp); - - /* Try unlocking the original session, should succeed */ - rpc = nc_rpc_unlock(NC_DATASTORE_RUNNING); - msgtype = nc_send_rpc(st->nc_sess2, rpc, 1000, &msgid); - assert_int_equal(NC_MSG_RPC, msgtype); - - /* recieve reply, should succeed */ - msgtype = nc_recv_reply(st->nc_sess2, rpc, msgid, 2000, &envp, &op); - assert_int_equal(msgtype, NC_MSG_REPLY); - assert_null(op); - assert_string_equal(LYD_NAME(lyd_child(envp)), "ok"); - - nc_rpc_free(rpc); - lyd_free_tree(envp); + + /* Check if unlock RPC succeeds */ + st->rpc = nc_rpc_unlock(NC_DATASTORE_RUNNING); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); +} + +static int +teardown_test_unlock_fail(void **state) +{ + struct np_test *st = *state; + + st->rpc = nc_rpc_unlock(NC_DATASTORE_RUNNING); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_unlock_fail(void **state) +{ + struct np_test *st = *state; + const char *template; + char *error; + + /* Try unlocking a lock by a different session, should fail */ + st->rpc = nc_rpc_unlock(NC_DATASTORE_RUNNING); + st->msgtype = nc_send_rpc(st->nc_sess2, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + + /* Check error message */ + ASSERT_RPC_ERROR_SESS2(st); + assert_int_equal(LY_SUCCESS, lyd_print_mem(&st->str, st->envp, LYD_XML, 0)); + template = + "\n" + " \n" + " protocol\n" + " lock-denied\n" + " error\n" + " Access to the requested lock is denied" + " because the lock is currently held by another entity.\n" + " \n" + " %d\n" + " \n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&error, template, st->msgid, nc_session_get_id(st->nc_sess))); + assert_string_equal(st->str, error); + free(error); + FREE_TEST_VARS(st); } static void test_get(void **state) { struct np_test *st = *state; - struct nc_rpc *rpc; - NC_MSG_TYPE msgtype; - uint64_t msgid; - struct lyd_node *envp, *op; - - /* Try to get all */ - rpc = nc_rpc_get(NULL, NC_WD_ALL, NC_PARAMTYPE_CONST); - msgtype = nc_send_rpc(st->nc_sess, rpc, 1000, &msgid); - assert_int_equal(NC_MSG_RPC, msgtype); - - /* recieve reply, should succeed */ - msgtype = nc_recv_reply(st->nc_sess, rpc, msgid, 2000, &envp, &op); - assert_int_equal(msgtype, NC_MSG_REPLY); - assert_non_null(op); - assert_non_null(envp); - - nc_rpc_free(rpc); - lyd_free_tree(envp); - lyd_free_tree(op); - - /* TODO: test if filter works */ + + /* Check if get RPC succeeds */ + /* TODO: get crashes the server on a locked session */ + st->rpc = nc_rpc_get(NULL, NC_WD_ALL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + st->msgtype = nc_recv_reply(st->nc_sess, st->rpc, st->msgid, 2000, &st->envp, &st->op); + assert_int_equal(st->msgtype, NC_MSG_REPLY); + assert_non_null(st->op); + assert_non_null(st->envp); + + FREE_TEST_VARS(st); } static void test_kill(void **state) { struct np_test *st = *state; - struct nc_rpc *rpc; - NC_MSG_TYPE msgtype; - uint64_t msgid; - struct lyd_node *envp, *op; - - /* Try to close a session */ - rpc = nc_rpc_kill(nc_session_get_id(st->nc_sess2)); - msgtype = nc_send_rpc(st->nc_sess, rpc, 1000, &msgid); - assert_int_equal(NC_MSG_RPC, msgtype); - - /* recieve reply, should fail since wrong permissions */ - msgtype = nc_recv_reply(st->nc_sess, rpc, msgid, 2000, &envp, &op); - assert_int_equal(msgtype, NC_MSG_REPLY); - assert_null(op); - assert_string_equal(LYD_NAME(lyd_child(envp)), "rpc-error"); - - nc_rpc_free(rpc); - lyd_free_tree(envp); - /* TODO: Check error message, would depend on current user */ - /* TODO: NACM tests */ + char *username, *error, *expected; + const char *template; + + if (is_nacm_rec_uid()) { + puts("Skipping the test."); + return; + } + + /* Try to close a session, should fail due to wrong permissions */ + st->rpc = nc_rpc_kill(nc_session_get_id(st->nc_sess)); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + ASSERT_RPC_ERROR(st); + + /* Check the error message */ + lyd_print_mem(&error, st->envp, LYD_XML, 0); + get_username(&username); + template = + "\n" + " \n" + " application\n" + " access-denied\n" + " error\n" + " /ietf-netconf:kill-session\n" + " Executing the operation is denied " + "because \"%s\" NACM authorization failed.\n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&expected, template, st->msgid, username)); + assert_string_equal(error, expected); + + free(username); + free(error); + free(expected); + FREE_TEST_VARS(st); + /* Functionality tested in test_nacm.c */ } static void test_commit(void **state) { struct np_test *st = *state; - struct nc_rpc *rpc; - NC_MSG_TYPE msgtype; - uint64_t msgid; - struct lyd_node *envp, *op; - - /* Try to close a session */ - rpc = nc_rpc_commit(0, 0, NULL, NULL, NC_PARAMTYPE_CONST); - msgtype = nc_send_rpc(st->nc_sess, rpc, 1000, &msgid); - assert_int_equal(NC_MSG_RPC, msgtype); - - /* recieve reply, should fail since wrong permissions */ - msgtype = nc_recv_reply(st->nc_sess, rpc, msgid, 2000, &envp, &op); - assert_int_equal(msgtype, NC_MSG_REPLY); - assert_null(op); - assert_string_equal(LYD_NAME(lyd_child(envp)), "ok"); - - nc_rpc_free(rpc); - lyd_free_tree(envp); - /* TODO: test funcionality */ + + /* Check if commit RPC succeeds */ + st->rpc = nc_rpc_commit(0, 0, NULL, NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + /* Functionality tested in test_candidate.c */ } static void test_discard(void **state) { struct np_test *st = *state; - struct nc_rpc *rpc; - NC_MSG_TYPE msgtype; - uint64_t msgid; - struct lyd_node *envp, *op; - - /* Try to close a session */ - rpc = nc_rpc_discard(); - msgtype = nc_send_rpc(st->nc_sess, rpc, 1000, &msgid); - assert_int_equal(NC_MSG_RPC, msgtype); - - /* recieve reply, should fail since wrong permissions */ - msgtype = nc_recv_reply(st->nc_sess, rpc, msgid, 2000, &envp, &op); - assert_int_equal(msgtype, NC_MSG_REPLY); - assert_null(op); - assert_string_equal(LYD_NAME(lyd_child(envp)), "ok"); - - nc_rpc_free(rpc); - lyd_free_tree(envp); - /* TODO: test funcionality */ + + /* Check if discard RPC succeeds */ + st->rpc = nc_rpc_discard(); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + /* Functionality tested in test_candidate.c */ +} + +static void +test_getconfig(void **state) +{ + struct np_test *st = *state; + const char *expected; + char *configuration; + + /* Try getting configuration */ + st->rpc = nc_rpc_getconfig(NC_DATASTORE_RUNNING, NULL, NC_WD_ALL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + st->msgtype = nc_recv_reply(st->nc_sess, st->rpc, st->msgid, 2000, &st->envp, &st->op); + assert_int_equal(st->msgtype, NC_MSG_REPLY); + + /* Check the reply */ + assert_non_null(st->op); + assert_non_null(st->envp); + assert_int_equal(LY_SUCCESS, lyd_print_mem(&configuration, st->op, LYD_XML, 0)); + expected = + "\n" + " \n" + "\n"; + assert_string_equal(configuration, expected); + free(configuration); + FREE_TEST_VARS(st); + /* Functionality tested in test_edit.c */ +} + +static void +test_validate(void **state) +{ + struct np_test *st = *state; + + /* Try validating configuration of the running datastore */ + st->rpc = nc_rpc_validate(NC_DATASTORE_RUNNING, NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + /* Functionality tested in test_candidate.c */ } static void test_unsuported(void **state) { struct np_test *st = *state; - struct nc_rpc *rpc; - NC_MSG_TYPE msgtype; - uint64_t msgid; - /* Testing RPCs unsupported by netopeer, all should fail */ - rpc = nc_rpc_cancel(NULL, NC_PARAMTYPE_CONST); - msgtype = nc_send_rpc(st->nc_sess, rpc, 1000, &msgid); - assert_int_equal(NC_MSG_ERROR, msgtype); + /* Testing RPCs unsupported by netopeer2, all should fail */ + st->rpc = nc_rpc_cancel(NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_ERROR, st->msgtype); - nc_rpc_free(rpc); + nc_rpc_free(st->rpc); } int -main(void) +main(int argc, char **argv) { const struct CMUnitTest tests[] = { - cmocka_unit_test(test_types), - cmocka_unit_test(test_lock), - cmocka_unit_test(test_unlock), + cmocka_unit_test_teardown(test_lock_basic, teardown_test_lock), + cmocka_unit_test_teardown(test_lock_fail, teardown_test_lock), + cmocka_unit_test_setup_teardown(test_lock_changes, setup_test_lock_changes, teardown_test_lock_changes), + cmocka_unit_test_setup(test_unlock, setup_test_unlock), + cmocka_unit_test_setup_teardown(test_unlock_fail, setup_test_unlock, teardown_test_unlock_fail), cmocka_unit_test(test_get), cmocka_unit_test(test_kill), cmocka_unit_test(test_commit), cmocka_unit_test(test_discard), + cmocka_unit_test(test_getconfig), + cmocka_unit_test(test_validate), cmocka_unit_test(test_unsuported), }; nc_verbosity(NC_VERB_WARNING); - return cmocka_run_group_tests(tests, np_glob_setup, np_glob_teardown); + parse_arg(argc, argv); + return cmocka_run_group_tests(tests, local_setup, local_teardown); } diff --git a/tests/test_sub_ntf.c b/tests/test_sub_ntf.c new file mode 100644 index 000000000..732e3a47e --- /dev/null +++ b/tests/test_sub_ntf.c @@ -0,0 +1,534 @@ +/** + * @file test_sub_ntf.c + * @author Tadeas Vintrlik + * @brief tests for subscriptions and its' parameters + * + * @copyright + * Copyright 2021 Deutsche Telekom AG. + * Copyright 2021 CESNET, z.s.p.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "np_test.h" +#include "np_test_config.h" + +static int +local_setup(void **state) +{ + struct np_test *st; + sr_conn_ctx_t *conn; + const char *features[] = {NULL}; + const char *module1 = NP_TEST_MODULE_DIR "/notif1.yang"; + const char *module2 = NP_TEST_MODULE_DIR "/notif2.yang"; + int rv; + + /* setup environment necessary for installing module */ + NP_GLOB_SETUP_ENV_FUNC; + assert_int_equal(setenv_rv, 0); + + /* connect to server and install test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module1, NULL, features), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module2, NULL, features), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* setup netopeer2 server */ + if (!(rv = np_glob_setup_np2(state))) { + /* state is allocated in np_glob_setup_np2 have to set here */ + st = *state; + /* Open connection to start a session for the tests */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &st->conn), SR_ERR_OK); + assert_int_equal(sr_session_start(st->conn, SR_DS_RUNNING, &st->sr_sess), SR_ERR_OK); + assert_int_equal(sr_session_start(st->conn, SR_DS_RUNNING, &st->sr_sess2), SR_ERR_OK); + assert_non_null(st->ctx = sr_get_context(st->conn)); + + /* Enable replay support */ + assert_int_equal(SR_ERR_OK, sr_set_module_replay_support(st->conn, "notif1", 1)); + } + return rv; +} + +void +test_path_notif_dir(char **path) +{ + if (SR_NOTIFICATION_PATH[0]) { + *path = strdup(SR_NOTIFICATION_PATH); + } else { + if (asprintf(path, "%s/data/notif", sr_get_repo_path()) == -1) { + *path = NULL; + } + } +} + +static int +teardown_common(void **state) +{ + struct np_test *st = *state; + char *path, *cmd; + int ret; + + /* Remove the notifications */ + test_path_notif_dir(&path); + if (!path) { + return 1; + } + + if (asprintf(&cmd, "rm -rf %s/notif1.notif*", path) == -1) { + return 1; + } + + free(path); + ret = system(cmd); + free(cmd); + + if (ret == -1) { + return 1; + } else if (!WIFEXITED(ret) || WEXITSTATUS(ret)) { + return 1; + } + + /* reestablish NETCONF connection */ + nc_session_free(st->nc_sess, NULL); + st->nc_sess = nc_connect_unix(NP_SOCKET_PATH, NULL); + assert_non_null(st->nc_sess); + + return 0; +} + +static int +local_teardown(void **state) +{ + struct np_test *st = *state; + sr_conn_ctx_t *conn; + + /* Disable replay support */ + assert_int_equal(SR_ERR_OK, sr_set_module_replay_support(st->conn, "notif1", 0)); + assert_int_equal(SR_ERR_OK, sr_set_module_replay_support(st->conn, "notif2", 0)); + + /* Close the sessions and connection needed for tests */ + assert_int_equal(sr_session_stop(st->sr_sess), SR_ERR_OK); + assert_int_equal(sr_session_stop(st->sr_sess2), SR_ERR_OK); + assert_int_equal(sr_disconnect(st->conn), SR_ERR_OK); + + /* connect to server and remove test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "notif1"), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "notif2"), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* Remove the notifications */ + teardown_common(state); + + /* close netopeer2 server */ + return np_glob_teardown(state); +} + +static void +test_invalid_start_time(void **state) +{ + struct np_test *st = *state; + char *start_time, *expected; + struct timespec ts; + + assert_int_not_equal(-1, clock_gettime(CLOCK_REALTIME, &ts)); + ts.tv_sec += 10; /* Put start time in the future */ + assert_int_equal(ly_time_ts2str(&ts, &start_time), LY_SUCCESS); + + SEND_RPC_ESTABSUB(st, NULL, "notif1", start_time, NULL); + free(start_time); + ASSERT_RPC_ERROR(st); + + /* Should fail since start-time has to be in the past */ + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "bad-element"); + expected = + " \n" + " replay-start-time\n" + " \n"; + + /* Check if correct error-info is given */ + lyd_print_mem(&st->str, st->envp, LYD_XML, 0); + assert_non_null(strstr(st->str, expected)); + FREE_TEST_VARS(st); +} + +static void +test_invalid_stop_time(void **state) +{ + struct np_test *st = *state; + char *stop_time, *expected; + struct timespec ts; + + assert_int_not_equal(-1, clock_gettime(CLOCK_REALTIME, &ts)); + ts.tv_sec -= 1; /* Put stop time in the past */ + assert_int_equal(ly_time_ts2str(&ts, &stop_time), LY_SUCCESS); + + /* Should fail since there is no start-time and it is in the past */ + SEND_RPC_ESTABSUB(st, NULL, "notif1", NULL, stop_time); + free(stop_time); + ASSERT_RPC_ERROR(st); + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "bad-element"); + expected = + " \n" + " stop-time\n" + " \n"; + + /* Check if correct error-info is given */ + lyd_print_mem(&st->str, st->envp, LYD_XML, 0); + assert_non_null(strstr(st->str, expected)); + FREE_TEST_VARS(st); +} + +static void +test_invalid_start_stop_time(void **state) +{ + struct np_test *st = *state; + char *start_time, *stop_time, *expected; + struct timespec ts; + + assert_int_not_equal(-1, clock_gettime(CLOCK_REALTIME, &ts)); + assert_int_equal(ly_time_ts2str(&ts, &start_time), LY_SUCCESS); + ts.tv_sec -= 2; + assert_int_equal(ly_time_ts2str(&ts, &stop_time), LY_SUCCESS); + + SEND_RPC_ESTABSUB(st, NULL, "notif1", start_time, stop_time); + free(start_time); + free(stop_time); + ASSERT_RPC_ERROR(st); + + /* Should fail since start-time exists and stop-time is not later than start-time */ + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "bad-element"); + expected = + " \n" + " stop-time\n" + " \n"; + + /* Check if correct error-info is given */ + lyd_print_mem(&st->str, st->envp, LYD_XML, 0); + assert_non_null(strstr(st->str, expected)); + FREE_TEST_VARS(st); +} + +static void +test_basic_sub(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Establish subscription */ + SEND_RPC_ESTABSUB(st, NULL, "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Send the notification */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* Check for notification content */ + RECV_NOTIF(st); + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); +} + +static void +test_replay_sub(void **state) +{ + struct np_test *st = *state; + struct timespec ts; + const char *data, *template; + char *expected; + char *timestr; + + /* Subscribe to replay */ + assert_int_not_equal(-1, clock_gettime(CLOCK_REALTIME, &ts)); + assert_int_equal(LY_SUCCESS, ly_time_ts2str(&ts, ×tr)); + + /* Parse notification into lyd_node */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + SEND_RPC_ESTABSUB(st, NULL, "notif1", timestr, NULL); + free(timestr); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Receive the notification and test the contents */ + RECV_NOTIF(st); + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); + /* Check for replay-completed notification since the replay is done */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + "\n"; + assert_int_not_equal(-1, asprintf(&expected, template, st->ntf_id)); + assert_string_equal(st->str, expected); + free(expected); + FREE_TEST_VARS(st); + + /* No other notification should arrive */ + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_replay_real_time(void **state) +{ + struct np_test *st = *state; + const char *data, *expected, *template; + char *ntf; + struct timespec ts; + char *start_time; + + /* Subscribe to replay */ + assert_int_not_equal(-1, clock_gettime(CLOCK_REALTIME, &ts)); + assert_int_equal(LY_SUCCESS, ly_time_ts2str(&ts, &start_time)); + + /* Send the notification */ + data = + "\n" + " First\n" + "\n"; + expected = data; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* Subscribe to notifications */ + SEND_RPC_ESTABSUB(st, NULL, "notif1", start_time, NULL); + free(start_time); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Send the notification */ + data = + "\n" + " Second\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* Receive the notification and test the contents */ + RECV_NOTIF(st); + assert_string_equal(expected, st->str); + FREE_TEST_VARS(st); + + /* Check for replay-completed notification since the replay is done */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* Check for real time notification */ + RECV_NOTIF(st); + expected = + "\n" + " Second\n" + "\n"; + + assert_string_equal(expected, st->str); + FREE_TEST_VARS(st); + + /* No other notification should arrive */ + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_stop_time(void **state) +{ + struct np_test *st = *state; + const char *data, *expected, *template; + char *ntf; + struct timespec ts; + char *start_time, *stop_time; + + /* To subscribe to replay of the notification */ + assert_int_not_equal(-1, clock_gettime(CLOCK_REALTIME, &ts)); + assert_int_equal(LY_SUCCESS, ly_time_ts2str(&ts, &start_time)); + + /* Send the notification */ + data = + "\n" + " Test\n" + "\n"; + expected = data; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + FREE_TEST_VARS(st); + + /* To subscribe to replay of notifications until time was called, should not include any called after */ + assert_int_not_equal(-1, clock_gettime(CLOCK_REALTIME, &ts)); + assert_int_equal(LY_SUCCESS, ly_time_ts2str(&ts, &stop_time)); + SEND_RPC_ESTABSUB(st, NULL, "notif1", start_time, stop_time); + free(start_time); + free(stop_time); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Receive the notification and test the contents */ + RECV_NOTIF(st); + assert_string_equal(expected, st->str); + FREE_TEST_VARS(st); + + /* Check for replay-completed notification since the replay is done */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* Check for subscriptin-terminated notification */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " sn:no-such-subscription\n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* Parse and send the notification */ + data = + "\n" + " Another\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + FREE_TEST_VARS(st); + + /* No other notification should arrive */ + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_history_only(void **state) +{ + struct np_test *st = *state; + const char *template; + char *ntf; + struct timespec ts; + char *start_time, *stop_time; + + /* Subscription in the past */ + assert_int_not_equal(-1, clock_gettime(CLOCK_REALTIME, &ts)); + ts.tv_sec -= 10; + assert_int_equal(LY_SUCCESS, ly_time_ts2str(&ts, &start_time)); + ts.tv_sec += 5; + assert_int_equal(LY_SUCCESS, ly_time_ts2str(&ts, &stop_time)); + SEND_RPC_ESTABSUB(st, NULL, "notif1", start_time, stop_time); + free(start_time); + free(stop_time); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Check for replay-completed notification since the replay is done */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* Check for subscriptin-terminated notification */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " sn:no-such-subscription\n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); +} + +static void +test_stream_no_pass(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Subscribe to notfications from a different stream */ + SEND_RPC_ESTABSUB(st, NULL, "notif2", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Send the notification should not be recieved */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_teardown(test_invalid_start_time, teardown_common), + cmocka_unit_test_teardown(test_invalid_stop_time, teardown_common), + cmocka_unit_test_teardown(test_invalid_start_stop_time, teardown_common), + cmocka_unit_test_teardown(test_basic_sub, teardown_common), + cmocka_unit_test_teardown(test_replay_sub, teardown_common), + cmocka_unit_test_teardown(test_replay_real_time, teardown_common), + cmocka_unit_test_teardown(test_stop_time, teardown_common), + cmocka_unit_test_teardown(test_history_only, teardown_common), + cmocka_unit_test_teardown(test_stream_no_pass, teardown_common), + }; + + nc_verbosity(NC_VERB_WARNING); + sr_log_stderr(SR_LL_WRN); + parse_arg(argc, argv); + return cmocka_run_group_tests(tests, local_setup, local_teardown); +} diff --git a/tests/test_sub_ntf_advanced.c b/tests/test_sub_ntf_advanced.c new file mode 100644 index 000000000..d8a2355e8 --- /dev/null +++ b/tests/test_sub_ntf_advanced.c @@ -0,0 +1,1036 @@ +/** + * @file test_sub_ntf_advanced.c + * @author Tadeas Vintrlik + * @brief advanced tests for subscriptions and its' parameters + * + * @copyright + * Copyright 2021 Deutsche Telekom AG. + * Copyright 2021 CESNET, z.s.p.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "np_test.h" +#include "np_test_config.h" + +static int +local_setup(void **state) +{ + struct np_test *st; + sr_conn_ctx_t *conn; + const char *features[] = {NULL}; + const char *module1 = NP_TEST_MODULE_DIR "/notif1.yang"; + const char *module2 = NP_TEST_MODULE_DIR "/notif2.yang"; + int rv; + + /* setup environment necessary for installing module */ + NP_GLOB_SETUP_ENV_FUNC; + assert_int_equal(setenv_rv, 0); + + /* connect to server and install test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module1, NULL, features), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module2, NULL, features), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* setup netopeer2 server */ + if (!(rv = np_glob_setup_np2(state))) { + /* state is allocated in np_glob_setup_np2 have to set here */ + st = *state; + /* Open connection to start a sessions for the tests */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &st->conn), SR_ERR_OK); + assert_int_equal(sr_session_start(st->conn, SR_DS_RUNNING, &st->sr_sess), SR_ERR_OK); + assert_int_equal(sr_session_start(st->conn, SR_DS_OPERATIONAL, &st->sr_sess2), SR_ERR_OK); + assert_non_null(st->ctx = sr_get_context(st->conn)); + + /* Enable replay support */ + assert_int_equal(SR_ERR_OK, sr_set_module_replay_support(st->conn, "notif1", 1)); + rv |= setup_nacm(state); + } + return rv; +} + +void +test_path_notif_dir(char **path) +{ + if (SR_NOTIFICATION_PATH[0]) { + *path = strdup(SR_NOTIFICATION_PATH); + } else { + if (asprintf(path, "%s/data/notif", sr_get_repo_path()) == -1) { + *path = NULL; + } + } +} + +static int +teardown_common(void **state) +{ + struct np_test *st = *state; + char *path, *cmd; + int ret; + + /* Remove the notifications */ + test_path_notif_dir(&path); + if (!path) { + return 1; + } + + if (asprintf(&cmd, "rm -rf %s/notif1.notif*", path) == -1) { + return 1; + } + + free(path); + ret = system(cmd); + free(cmd); + + if (ret == -1) { + return 1; + } else if (!WIFEXITED(ret) || WEXITSTATUS(ret)) { + return 1; + } + + /* reestablish NETCONF connection */ + nc_session_free(st->nc_sess, NULL); + st->nc_sess = nc_connect_unix(NP_SOCKET_PATH, NULL); + assert_non_null(st->nc_sess); + + return 0; +} + +static int +local_teardown(void **state) +{ + struct np_test *st = *state; + sr_conn_ctx_t *conn; + + /* Disable replay support */ + assert_int_equal(SR_ERR_OK, sr_set_module_replay_support(st->conn, "notif1", 0)); + + /* Close the sessions and connection needed for tests */ + assert_int_equal(sr_session_stop(st->sr_sess), SR_ERR_OK); + assert_int_equal(sr_session_stop(st->sr_sess2), SR_ERR_OK); + assert_int_equal(sr_disconnect(st->conn), SR_ERR_OK); + + /* connect to server and remove test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "notif1"), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "notif2"), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* Remove the notifications */ + teardown_common(state); + + /* close netopeer2 server */ + return np_glob_teardown(state); +} + +static void +test_filter_pass(void **state) +{ + struct np_test *st = *state; + const char *filter, *data; + + filter = ""; + SEND_RPC_ESTABSUB(st, filter, "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Parse notification into lyd_node */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + + /* Send the notification */ + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + RECV_NOTIF(st); + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); +} + +static void +test_filter_no_pass(void **state) +{ + struct np_test *st = *state; + const char *filter, *data; + + filter = "Different"; + SEND_RPC_ESTABSUB(st, filter, "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Parse notification into lyd_node */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + + /* Notification should not pass */ + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_modifysub_filter(void **state) +{ + struct np_test *st = *state; + const char *filter, *data, *template; + char *ntf; + + filter = ""; + SEND_RPC_ESTABSUB(st, filter, "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Send the notification */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + RECV_NOTIF(st); + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); + + /* Modify the filter so that notifications do no pass */ + filter = "Different"; + SEND_RPC_MODSUB(st, st->ntf_id, filter, NULL); + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + " \n" + " Different\n" + " \n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + st->rpc = nc_rpc_modifysub(st->ntf_id, NULL, NULL, NC_PARAMTYPE_CONST); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* Send the notification */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_modifysub_stop_time(void **state) +{ + struct np_test *st = *state; + const char *data, *template; + char *ntf; + char *stop_time; + + /* Establish a subscription with no stop-time */ + SEND_RPC_ESTABSUB(st, NULL, "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Nothing should happen */ + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); + + /* Modify the stop_time to now, session should end */ + assert_int_equal(LY_SUCCESS, ly_time_time2str(time(NULL) + 1, NULL, &stop_time)); + SEND_RPC_MODSUB(st, st->ntf_id, "", stop_time); + free(stop_time); + RECV_NOTIF(st); + /* Checking the content of the notification would depend on having precise timestamp */ + FREE_TEST_VARS(st); + st->rpc = nc_rpc_modifysub(st->ntf_id, NULL, NULL, NC_PARAMTYPE_CONST); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* Check for subscription-terminated notification */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " sn:no-such-subscription\n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* No notification should arrive now */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_modifysub_fail_no_such_sub(void **state) +{ + struct np_test *st = *state; + + /* Try modifying a non-existent subscription */ + SEND_RPC_MODSUB(st, 1, "", NULL); + ASSERT_RPC_ERROR(st); + /* Check if correct error-tag */ + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "invalid-value"); + /* Check if correct error-app-tag */ + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next->next->next->next), + "ietf-subscribed-notifications:no-such-subscription"); + FREE_TEST_VARS(st); +} + +static void +test_deletesub(void **state) +{ + struct np_test *st = *state; + const char *template, *data; + char *ntf; + + /* Establish a subscription to delete */ + SEND_RPC_ESTABSUB(st, NULL, "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + SEND_RPC_DELSUB(st, st->ntf_id); + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " sn:no-such-subscription\n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + st->rpc = nc_rpc_deletesub(st->ntf_id); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_deletesub_fail(void **state) +{ + struct np_test *st = *state; + + /* Try deleting a non-existent subscription */ + SEND_RPC_DELSUB(st, 1); + ASSERT_RPC_ERROR(st); + /* Check if correct error-tag */ + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "invalid-value"); + /* Check if correct error-app-tag */ + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next->next->next->next), + "ietf-subscribed-notifications:no-such-subscription"); + FREE_TEST_VARS(st); +} + +static void +test_deletesub_fail_diff_sess(void **state) +{ + struct np_test *st = *state; + const char *data; + struct nc_session *tmp; + + /* Establish a sub */ + SEND_RPC_ESTABSUB(st, NULL, "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Send notification, should arrive */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + FREE_TEST_VARS(st); + RECV_NOTIF(st); + assert_string_equal(st->str, data); + FREE_TEST_VARS(st); + + /* Create a new session */ + tmp = nc_connect_unix(NP_SOCKET_PATH, NULL); + assert_non_null(tmp); + + /* Try to delete it */ + st->rpc = nc_rpc_deletesub(st->ntf_id); + st->msgtype = nc_send_rpc(tmp, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + /* Receive rpc-error reply */ + st->msgtype = nc_recv_reply(tmp, st->rpc, st->msgid, 2000, &st->envp, &st->op); + assert_int_equal(st->msgtype, NC_MSG_REPLY); + assert_null(st->op); + assert_string_equal(LYD_NAME(lyd_child(st->envp)), "rpc-error"); + /* Check if correct error-tag */ + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "invalid-value"); + /* Check if correct error-app-tag */ + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next->next->next->next), + "ietf-subscribed-notifications:no-such-subscription"); + FREE_TEST_VARS(st); + + /* Close the new session */ + nc_session_free(tmp, NULL); +} + +static void +test_ds_subscriptions(void **state) +{ + struct np_test *st = *state; + char *expected; + const char *template = + "\n" + " \n" + " \n" + " \n" + " %d\n" + " \n" + " \n" + " NETCONF session %d\n" + " 0\n" + " 0\n" + " active\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; + + /* Establish a subscription */ + SEND_RPC_ESTABSUB(st, NULL, "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + GET_FILTER(st, "/subscriptions"); + assert_int_not_equal(-1, asprintf(&expected, template, st->ntf_id, nc_session_get_id(st->nc_sess))); + assert_string_equal(st->str, expected); + free(expected); + FREE_TEST_VARS(st); +} + +static void +test_ds_subscriptions_sent_event(void **state) +{ + struct np_test *st = *state; + char *expected; + const char *data, *template = + "\n" + " \n" + " \n" + " \n" + " %d\n" + " \n" + " \n" + " NETCONF session %d\n" + " 3\n" + " 0\n" + " active\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; + + /* Establish a subscription */ + SEND_RPC_ESTABSUB(st, NULL, "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Send 3 notifications */ + data = + "\n" + " Test\n" + "\n"; + for (uint8_t i = 0; i < 3; i++) { + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + RECV_NOTIF(st); + FREE_TEST_VARS(st); + } + + GET_FILTER(st, "/subscriptions"); + assert_int_not_equal(-1, asprintf(&expected, template, st->ntf_id, nc_session_get_id(st->nc_sess))); + assert_string_equal(st->str, expected); + free(expected); + FREE_TEST_VARS(st); +} + +static void +test_ds_subscriptions_excluded_event(void **state) +{ + struct np_test *st = *state; + char *expected; + const char *data, *filter, *template = + "\n" + " \n" + " \n" + " \n" + " %d\n" + " \n" + " \n" + " NETCONF session %d\n" + " 1\n" + " 1\n" + " active\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; + + /* Establish a subscription */ + filter = "Different"; + SEND_RPC_ESTABSUB(st, filter, "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Send 2 notifications, one should pass the filter, the other should not */ + data = + "\n" + " Different\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + RECV_NOTIF(st); + FREE_TEST_VARS(st); + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); + + GET_FILTER(st, "/subscriptions"); + assert_int_not_equal(-1, asprintf(&expected, template, st->ntf_id, nc_session_get_id(st->nc_sess))); + assert_string_equal(st->str, expected); + free(expected); + FREE_TEST_VARS(st); +} + +static void +test_multiple_subscriptions(void **state) +{ + struct np_test *st = *state; + char *expected; + uint32_t nc_sess_id, tmp_id; + const char *template = + "\n" + " \n" + " \n" + " \n" + " %d\n" + " \n" + " \n" + " NETCONF session %d\n" + " 0\n" + " 0\n" + " active\n" + " \n" + " \n" + " \n" + " \n" + " %d\n" + " \n" + " \n" + " NETCONF session %d\n" + " 0\n" + " 0\n" + " active\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; + + /* Establish a subscription */ + SEND_RPC_ESTABSUB(st, NULL, "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + tmp_id = st->ntf_id; + + /* Establish another subscription */ + SEND_RPC_ESTABSUB(st, NULL, "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + GET_FILTER(st, "/subscriptions"); + nc_sess_id = nc_session_get_id(st->nc_sess); + assert_int_not_equal(-1, asprintf(&expected, template, tmp_id, nc_sess_id, st->ntf_id, nc_sess_id)); + assert_string_equal(st->str, expected); + free(expected); + FREE_TEST_VARS(st); +} + +static void +test_multiple_subscriptions_notif(void **state) +{ + struct np_test *st = *state; + char *expected; + uint32_t nc_sess_id, tmp_ids[3]; + const char *data, *template = + "\n" + " \n" + " \n" + " \n" + " %d\n" + " \n" + " \n" + " NETCONF session %d\n" + " 1\n" + " 0\n" + " active\n" + " \n" + " \n" + " \n" + " \n" + " %d\n" + " \n" + " \n" + " NETCONF session %d\n" + " 1\n" + " 0\n" + " active\n" + " \n" + " \n" + " \n" + " \n" + " %d\n" + " \n" + " \n" + " NETCONF session %d\n" + " 1\n" + " 0\n" + " active\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; + + /* Establish three subscriptions */ + for (uint8_t i = 0; i < 3; i++) { + SEND_RPC_ESTABSUB(st, NULL, "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + tmp_ids[i] = st->ntf_id; + FREE_TEST_VARS(st); + } + + /* Send one notification, should arrive for all subscriptions */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + FREE_TEST_VARS(st); + + /* Receive three notifications */ + for (uint8_t i = 0; i < 3; i++) { + RECV_NOTIF(st); + FREE_TEST_VARS(st); + } + + GET_FILTER(st, "/subscriptions"); + nc_sess_id = nc_session_get_id(st->nc_sess); + assert_int_not_equal(-1, asprintf(&expected, template, tmp_ids[0], nc_sess_id, + tmp_ids[1], nc_sess_id, tmp_ids[2], nc_sess_id)); + assert_string_equal(st->str, expected); + free(expected); + FREE_TEST_VARS(st); +} + +static int +setup_notif2_data(void **state) +{ + struct np_test *st = *state; + const char *data; + + data = + "\n" + " \n" + " Main\n" + " \n" + "\n"; + + SR_EDIT_SESSION(st, st->sr_sess2, data); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_multiple_subscriptions_notif_interlaced(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Establish first sub */ + SEND_RPC_ESTABSUB(st, NULL, "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Send one notification to the first session and check it */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + FREE_TEST_VARS(st); + RECV_NOTIF(st); + assert_string_equal(st->str, data); + FREE_TEST_VARS(st); + + /* Send another notification to the first session */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + FREE_TEST_VARS(st); + + /* Establish second sub for a different stream */ + SEND_RPC_ESTABSUB(st, NULL, "notif2", NULL, NULL); + + /* Receive the notification sent before establishing another subscription and check it */ + RECV_NOTIF(st); + assert_string_equal(st->str, data); + FREE_TEST_VARS(st); + + /* Check for establishing the sub */ + st->rpc = nc_rpc_establishsub(NULL, "notif2", NULL, NULL, NULL, NC_PARAMTYPE_CONST); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Send last notification to the first session */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + FREE_TEST_VARS(st); + + /* Send notification to the second session */ + data = + "\n" + " \n" + " Main\n" + " \n" + " 12\n" + " \n" + " \n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + FREE_TEST_VARS(st); + + /* Receive the notification from first sub */ + RECV_NOTIF(st); + data = + "\n" + " Test\n" + "\n"; + assert_string_equal(st->str, data); + FREE_TEST_VARS(st); + + /* Receive the notification from second sub */ + RECV_NOTIF(st); + data = + "\n" + " \n" + " Main\n" + " \n" + " 12\n" + " \n" + " \n" + "\n"; + assert_string_equal(st->str, data); + FREE_TEST_VARS(st); +} + +static int +teardown_nacm(void **state) +{ + struct np_test *st = *state; + const char *data; + + teardown_common(state); + + /* Remove NACM rules */ + data = + "\n" + " deny\n" + " \n" + " rule1\n" + " \n" + ""; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + return 0; +} + +static void +test_killsub_fail_nacm(void **state) +{ + struct np_test *st = *state; + + /* Check for NACM_RECOVERY_UID */ + if (is_nacm_rec_uid()) { + puts("Running as NACM_RECOVERY_UID. Tests will not run correctly as this user bypases NACM. Skipping."); + return; + } + + /* Should fail on NACM */ + SEND_RPC_KILLSUB(st, 1); + ASSERT_RPC_ERROR(st); + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "access-denied"); + FREE_TEST_VARS(st); +} + +static int +setup_test_killsub(void **state) +{ + struct np_test *st = *state; + const char *data = + "\n" + " \n" + " rule1\n" + " test-group\n" + " \n" + " allow-killsub\n" + " ietf-subscribed-notifications\n" + " kill-subscription\n" + " exec\n" + " permit\n" + " \n" + " \n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_killsub_fail_no_such_sub(void **state) +{ + struct np_test *st = *state; + + SEND_RPC_KILLSUB(st, 1); + /* Should fail on no such sub */ + ASSERT_RPC_ERROR(st); + /* Check if correct error-tag */ + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "invalid-value"); + /* Check if correct error-app-tag */ + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next->next->next->next), + "ietf-subscribed-notifications:no-such-subscription"); + FREE_TEST_VARS(st); +} + +static void +test_killsub_same_sess(void **state) +{ + struct np_test *st = *state; + const char *data, *template; + char *ntf; + + /* Establish a sub */ + SEND_RPC_ESTABSUB(st, NULL, "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Send notification, should arrive */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + FREE_TEST_VARS(st); + RECV_NOTIF(st); + assert_string_equal(st->str, data); + FREE_TEST_VARS(st); + + /* Kill it */ + SEND_RPC_KILLSUB(st, st->ntf_id); + /* Check for subscription-terminated notification */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " sn:no-such-subscription\n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + st->rpc = nc_rpc_killsub(st->ntf_id); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* Send notification, should NOT arrive */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + FREE_TEST_VARS(st); + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_killsub_diff_sess(void **state) +{ + struct np_test *st = *state; + const char *data, *template; + struct nc_session *tmp; + char *ntf; + + /* Establish a sub */ + SEND_RPC_ESTABSUB(st, NULL, "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Send notification, should arrive */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + FREE_TEST_VARS(st); + RECV_NOTIF(st); + assert_string_equal(st->str, data); + FREE_TEST_VARS(st); + + /* Create a new session */ + tmp = nc_connect_unix(NP_SOCKET_PATH, NULL); + assert_non_null(tmp); + + /* Kill it */ + st->rpc = nc_rpc_killsub(st->ntf_id); + st->msgtype = nc_send_rpc(tmp, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + /* Check for subscription-terminated notification */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " sn:no-such-subscription\n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + st->rpc = nc_rpc_killsub(st->ntf_id); + /* Receive OK reply */ + st->msgtype = nc_recv_reply(tmp, st->rpc, st->msgid, 2000, &st->envp, &st->op); + assert_int_equal(st->msgtype, NC_MSG_REPLY); + assert_null(st->op); + assert_string_equal(LYD_NAME(lyd_child(st->envp)), "ok"); + FREE_TEST_VARS(st); + + /* Send notification, should NOT arrive */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + FREE_TEST_VARS(st); + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); + + /* Close the new session */ + nc_session_free(tmp, NULL); +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_teardown(test_filter_pass, teardown_common), + cmocka_unit_test_teardown(test_filter_no_pass, teardown_common), + cmocka_unit_test_teardown(test_modifysub_filter, teardown_common), + cmocka_unit_test_teardown(test_modifysub_stop_time, teardown_common), + cmocka_unit_test(test_modifysub_fail_no_such_sub), + cmocka_unit_test_teardown(test_deletesub, teardown_common), + cmocka_unit_test(test_deletesub_fail), + cmocka_unit_test_teardown(test_deletesub_fail_diff_sess, teardown_common), + cmocka_unit_test_teardown(test_ds_subscriptions, teardown_common), + cmocka_unit_test_teardown(test_ds_subscriptions_sent_event, teardown_common), + cmocka_unit_test_teardown(test_ds_subscriptions_excluded_event, teardown_common), + cmocka_unit_test_teardown(test_multiple_subscriptions, teardown_common), + cmocka_unit_test_teardown(test_multiple_subscriptions_notif, teardown_common), + cmocka_unit_test_setup_teardown(test_multiple_subscriptions_notif_interlaced, setup_notif2_data, teardown_common), + cmocka_unit_test_teardown(test_killsub_fail_nacm, teardown_nacm), + cmocka_unit_test_setup_teardown(test_killsub_fail_no_such_sub, setup_test_killsub, teardown_nacm), + cmocka_unit_test_setup_teardown(test_killsub_same_sess, setup_test_killsub, teardown_nacm), + cmocka_unit_test_setup_teardown(test_killsub_diff_sess, setup_test_killsub, teardown_nacm), + }; + + nc_verbosity(NC_VERB_WARNING); + sr_log_stderr(SR_LL_WRN); + parse_arg(argc, argv); + return cmocka_run_group_tests(tests, local_setup, local_teardown); +} diff --git a/tests/test_sub_ntf_filter.c b/tests/test_sub_ntf_filter.c new file mode 100644 index 000000000..23cca4712 --- /dev/null +++ b/tests/test_sub_ntf_filter.c @@ -0,0 +1,525 @@ +/** + * @file test_sub_filter.c + * @author Tadeas Vintrlik + * @brief tests for subscription filters including filters by ref + * + * @copyright + * Copyright 2021 Deutsche Telekom AG. + * Copyright 2021 CESNET, z.s.p.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "np_test.h" +#include "np_test_config.h" + +static int +local_setup(void **state) +{ + struct np_test *st; + sr_conn_ctx_t *conn; + const char *features[] = {NULL}; + const char *module1 = NP_TEST_MODULE_DIR "/notif1.yang"; + const char *module2 = NP_TEST_MODULE_DIR "/notif2.yang"; + int rv; + + /* setup environment necessary for installing module */ + NP_GLOB_SETUP_ENV_FUNC; + assert_int_equal(setenv_rv, 0); + + /* connect to server and install test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module1, NULL, features), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module2, NULL, features), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* setup netopeer2 server */ + if (!(rv = np_glob_setup_np2(state))) { + /* state is allocated in np_glob_setup_np2 have to set here */ + st = *state; + /* Open connection to start a session for the tests */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &st->conn), SR_ERR_OK); + assert_int_equal(sr_session_start(st->conn, SR_DS_RUNNING, &st->sr_sess), SR_ERR_OK); + assert_int_equal(sr_session_start(st->conn, SR_DS_RUNNING, &st->sr_sess2), SR_ERR_OK); + assert_non_null(st->ctx = sr_get_context(st->conn)); + + /* Enable replay support */ + assert_int_equal(SR_ERR_OK, sr_set_module_replay_support(st->conn, "notif1", 1)); + } + return rv; +} + +void +test_path_notif_dir(char **path) +{ + if (SR_NOTIFICATION_PATH[0]) { + *path = strdup(SR_NOTIFICATION_PATH); + } else { + if (asprintf(path, "%s/data/notif", sr_get_repo_path()) == -1) { + *path = NULL; + } + } +} + +static int +teardown_common(void **state) +{ + struct np_test *st = *state; + char *path, *cmd; + int ret; + + /* Remove the notifications */ + test_path_notif_dir(&path); + if (!path) { + return 1; + } + + if (asprintf(&cmd, "rm -rf %s/notif1.notif*", path) == -1) { + return 1; + } + + free(path); + ret = system(cmd); + free(cmd); + + if (ret == -1) { + return 1; + } else if (!WIFEXITED(ret) || WEXITSTATUS(ret)) { + return 1; + } + + /* reestablish NETCONF connection */ + nc_session_free(st->nc_sess, NULL); + st->nc_sess = nc_connect_unix(NP_SOCKET_PATH, NULL); + assert_non_null(st->nc_sess); + + return 0; +} + +static void +remove_filter(void **state) +{ + struct np_test *st = *state; + const char *data; + + data = + ""; + SR_EDIT(st, data); + FREE_TEST_VARS(st); +} + +static int +teardown_filter(void **state) +{ + teardown_common(state); + remove_filter(state); + return 0; +} + +static int +local_teardown(void **state) +{ + struct np_test *st = *state; + sr_conn_ctx_t *conn; + + /* Disable replay support */ + assert_int_equal(SR_ERR_OK, sr_set_module_replay_support(st->conn, "notif1", 0)); + assert_int_equal(SR_ERR_OK, sr_set_module_replay_support(st->conn, "notif2", 0)); + + /* Close the sessions and connection needed for tests */ + assert_int_equal(sr_session_stop(st->sr_sess), SR_ERR_OK); + assert_int_equal(sr_session_stop(st->sr_sess2), SR_ERR_OK); + assert_int_equal(sr_disconnect(st->conn), SR_ERR_OK); + + /* connect to server and remove test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "notif1"), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "notif2"), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* Remove the notifications */ + teardown_common(state); + + /* close netopeer2 server */ + return np_glob_teardown(state); +} + +static void +test_basic_xpath_pass(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Establish subscription */ + SEND_RPC_ESTABSUB(st, "/notif1:n1/first", "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Send the notification */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* Check for notification content */ + RECV_NOTIF(st); + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); +} + +static void +test_basic_xpath_no_pass(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Establish subscription */ + SEND_RPC_ESTABSUB(st, "/notif1:n1/first[.=Alt]", "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Send the notification */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* No notification should pass the filter */ + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_basic_subtree_pass(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Establish subscription */ + data = ""; + SEND_RPC_ESTABSUB(st, data, "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Send the notification */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* Check for notification content */ + RECV_NOTIF(st); + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); +} + +static void +test_basic_subtree_no_pass(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Establish subscription */ + data = + "\n" + " Alt\n" + "\n"; + SEND_RPC_ESTABSUB(st, data, "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Send the notification */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* No notification should pass the filter */ + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static int +setup_filter(void **state) +{ + struct np_test *st = *state; + const char *data; + + data = + "\n" + " \n" + " xpath-pass\n" + " /n1/first\n" + " \n" + " \n" + " xpath-no-pass\n" + " /n1/first[.=Alt]\n" + " \n" + " \n" + " subtree-pass\n" + " \n" + " \n" + " \n" + " subtree-no-pass\n" + " Alt\n" + " \n" + "\n"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + return 0; +} + +static void +test_ref_xpath_pass(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Establish subscription */ + SEND_RPC_ESTABSUB(st, "xpath-pass", "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Send the notification */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* Check for notification content */ + RECV_NOTIF(st); + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); +} + +static void +test_ref_xpath_no_pass(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Establish subscription */ + SEND_RPC_ESTABSUB(st, "xpath-no-pass", "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Send the notification */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* No notification should pass the filter */ + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_ref_subtree_pass(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Establish subscription */ + SEND_RPC_ESTABSUB(st, "subtree-pass", "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Send the notification */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* Check for notification content */ + RECV_NOTIF(st); + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); +} + +static void +test_ref_subtree_no_pass(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Establish subscription */ + SEND_RPC_ESTABSUB(st, "subtree-no-pass", "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Send the notification */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* No notification should pass the filter */ + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_filter_change(void **state) +{ + struct np_test *st = *state; + const char *data, *template; + char *ntf; + + /* Establish subscription */ + SEND_RPC_ESTABSUB(st, "xpath-pass", "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Send the notification */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* Check for notification content */ + RECV_NOTIF(st); + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); + + /* Modify the filter */ + data = + "\n" + " \n" + " xpath-pass\n" + " /n1/first[.=Alt]\n" + " \n" + "\n"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Check for subscription-modified notification */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " xpath-pass\n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); +} + +static void +test_filter_remove(void **state) +{ + struct np_test *st = *state; + const char *data, *template; + char *ntf; + + /* Establish subscription */ + SEND_RPC_ESTABSUB(st, "xpath-pass", "notif1", NULL, NULL); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Send the notification */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* Check for notification content */ + RECV_NOTIF(st); + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); + + /* Remove the filter */ + remove_filter(state); + + /* Check for subscription-terminated notification */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " sn:filter-unavailable\n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); +} + +static void +test_filter_non_existent(void **state) +{ + struct np_test *st = *state; + + /* Establish subscription */ + SEND_RPC_ESTABSUB(st, "non-existant", "notif1", NULL, NULL); + ASSERT_RPC_ERROR(st); + FREE_TEST_VARS(st); +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_teardown(test_basic_xpath_pass, teardown_common), + cmocka_unit_test_teardown(test_basic_xpath_no_pass, teardown_common), + cmocka_unit_test_teardown(test_basic_subtree_pass, teardown_common), + cmocka_unit_test_teardown(test_basic_subtree_no_pass, teardown_common), + cmocka_unit_test_setup_teardown(test_ref_xpath_pass, setup_filter, teardown_filter), + cmocka_unit_test_setup_teardown(test_ref_xpath_no_pass, setup_filter, teardown_filter), + cmocka_unit_test_setup_teardown(test_ref_subtree_pass, setup_filter, teardown_filter), + cmocka_unit_test_setup_teardown(test_ref_subtree_no_pass, setup_filter, teardown_filter), + cmocka_unit_test_setup_teardown(test_filter_change, setup_filter, teardown_filter), + cmocka_unit_test_setup_teardown(test_filter_remove, setup_filter, teardown_filter), + cmocka_unit_test_setup_teardown(test_filter_non_existent, setup_filter, teardown_filter), + }; + + nc_verbosity(NC_VERB_WARNING); + sr_log_stderr(SR_LL_WRN); + parse_arg(argc, argv); + return cmocka_run_group_tests(tests, local_setup, local_teardown); +} diff --git a/tests/test_subscribe_filter.c b/tests/test_subscribe_filter.c new file mode 100644 index 000000000..b14c6d7da --- /dev/null +++ b/tests/test_subscribe_filter.c @@ -0,0 +1,523 @@ +/** + * @file test_subscribe_filter.c + * @author Tadeas Vintrlik + * @brief tests for filtering notifications + * + * @copyright + * Copyright 2021 Deutsche Telekom AG. + * Copyright 2021 CESNET, z.s.p.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "np_test.h" +#include "np_test_config.h" + +static void +setup_data(void **state) +{ + struct np_test *st = *state; + const char *data; + + data = + "\n" + " \n" + " Main\n" + " \n" + " \n" + " Secondary\n" + " \n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); +} + +static void +reestablish_sub(void **state, const char *filter) +{ + struct np_test *st = *state; + + /* Reestablish NETCONF connection */ + nc_session_free(st->nc_sess, NULL); + st->nc_sess = nc_connect_unix(NP_SOCKET_PATH, NULL); + assert_non_null(st->nc_sess); + + /* Get a subscription to receive notifications */ + st->rpc = nc_rpc_subscribe(NULL, filter, NULL, NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + + /* Check reply */ + st->msgtype = nc_recv_reply(st->nc_sess, st->rpc, st->msgid, 1000, &st->envp, &st->op); + assert_int_equal(NC_MSG_REPLY, st->msgtype); + assert_null(st->op); + assert_string_equal(LYD_NAME(lyd_child(st->envp)), "ok"); + + FREE_TEST_VARS(st); +} + +static int +local_setup(void **state) +{ + struct np_test *st; + sr_conn_ctx_t *conn; + const char *features[] = {NULL}; + const char *module1 = NP_TEST_MODULE_DIR "/notif1.yang"; + const char *module2 = NP_TEST_MODULE_DIR "/notif2.yang"; + int rv; + + /* Setup environment necessary for installing module */ + NP_GLOB_SETUP_ENV_FUNC; + assert_int_equal(setenv_rv, 0); + + /* Connect to server and install test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module1, NULL, features), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module2, NULL, features), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* Setup netopeer2 server */ + if (!(rv = np_glob_setup_np2(state))) { + /* State is allocated in np_glob_setup_np2 have to set here */ + st = *state; + /* Open connection to start a session for the tests */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &st->conn), SR_ERR_OK); + assert_int_equal(sr_session_start(st->conn, SR_DS_OPERATIONAL, &st->sr_sess), SR_ERR_OK); + assert_non_null(st->ctx = sr_get_context(st->conn)); + setup_data(state); + } + return rv; +} + +static int +local_teardown(void **state) +{ + struct np_test *st = *state; + sr_conn_ctx_t *conn; + + /* Close the session and connection needed for tests */ + assert_int_equal(sr_session_stop(st->sr_sess), SR_ERR_OK); + assert_int_equal(sr_disconnect(st->conn), SR_ERR_OK); + + /* Connect to server and remove test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "notif1"), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "notif2"), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* Close netopeer2 server */ + return np_glob_teardown(state); +} + +static void +test_basic_notif(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Send the notification */ + reestablish_sub(state, NULL); + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* Receive the notification and test the contents */ + RECV_NOTIF(st); + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); +} + +static void +test_list_notif(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Send the notification */ + reestablish_sub(state, NULL); + data = + "\n" + " \n" + " Main\n" + " \n" + " 12\n" + " \n" + " \n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* Receive the notification and test the contents */ + RECV_NOTIF(st); + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); +} + +static void +test_subtree_filter_no_matching_node(void **state) +{ + struct np_test *st = *state; + const char *filter = + "\n" + " \n" + " Main\n" + " \n" + "\n"; + + /* Reestablish NETCONF connection */ + nc_session_free(st->nc_sess, NULL); + st->nc_sess = nc_connect_unix(NP_SOCKET_PATH, NULL); + assert_non_null(st->nc_sess); + + /* Get a subscription to receive notifications */ + st->rpc = nc_rpc_subscribe(NULL, filter, NULL, NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + + /* Check reply */ + st->msgtype = nc_recv_reply(st->nc_sess, st->rpc, st->msgid, 1000, &st->envp, &st->op); + assert_int_equal(NC_MSG_REPLY, st->msgtype); + assert_null(st->op); + + /* Should be an rpc-error since no notification can match the filter */ + assert_string_equal(LYD_NAME(lyd_child(st->envp)), "rpc-error"); + FREE_TEST_VARS(st); +} + +static void +test_subtree_filter_notif_selection_node_no_pass(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Send the notification */ + reestablish_sub(state, ""); + data = + "\n" + " \n" + " Main\n" + " \n" + " 12\n" + " \n" + " \n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* No notification should pass due to the filter */ + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_subtree_filter_notif_selection_node_pass(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Send the notification */ + reestablish_sub(state, ""); + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* Notification should pass the filter */ + RECV_NOTIF(st); + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); +} + +static void +test_subtree_filter_notif_content_match_node_no_pass(void **state) +{ + struct np_test *st = *state; + const char *data, *filter = + "\n" + " \n" + " Main\n" + " \n" + " \n" + "\n"; + + /* Send the notification */ + reestablish_sub(state, filter); + data = + "\n" + " \n" + " Secondary\n" + " \n" + " 45\n" + " \n" + " \n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* No notification should pass due to the filter */ + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_subtree_filter_notif_content_match_node_pass(void **state) +{ + struct np_test *st = *state; + const char *data, *filter = + "\n" + " \n" + " Main\n" + " \n" + " \n" + "\n"; + + /* Send the notification */ + reestablish_sub(state, filter); + data = + "\n" + " \n" + " Main\n" + " \n" + " 12\n" + " \n" + " \n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* Notification should pass the filter */ + RECV_NOTIF(st); + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); +} + +static void +test_xpath_filter_no_matching_node(void **state) +{ + struct np_test *st = *state; + + /* Reestablish NETCONF connection */ + nc_session_free(st->nc_sess, NULL); + st->nc_sess = nc_connect_unix(NP_SOCKET_PATH, NULL); + assert_non_null(st->nc_sess); + + /* Get a subscription to receive notifications */ + st->rpc = nc_rpc_subscribe(NULL, "/notif2:devices/device[name='Main']", NULL, NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + + /* Check reply */ + st->msgtype = nc_recv_reply(st->nc_sess, st->rpc, st->msgid, 1000, &st->envp, &st->op); + assert_int_equal(NC_MSG_REPLY, st->msgtype); + assert_null(st->op); + + /* Should be an rpc-error since no notification can match the filter */ + assert_string_equal(LYD_NAME(lyd_child(st->envp)), "rpc-error"); + FREE_TEST_VARS(st); +} + +static void +test_xpath_filter_notif_selection_node_no_pass(void **state) +{ + struct np_test *st = *state; + const char *data; + + reestablish_sub(state, "/notif1:n1/first"); + + /* Send the notification */ + data = + "\n" + " \n" + " Main\n" + " \n" + " 12\n" + " \n" + " \n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* No notification should pass due to the filter */ + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_xpath_filter_notif_selection_node_pass(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Send the notification */ + reestablish_sub(state, "/notif1:n1/first"); + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* Notification should pass the filter */ + RECV_NOTIF(st); + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); +} + +static void +test_xpath_filter_notif_content_match_node_no_pass(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Send the notification */ + reestablish_sub(state, "/notif2:devices/device[name='Main']/power-on"); + data = + "\n" + " \n" + " Secondary\n" + " \n" + " 45\n" + " \n" + " \n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* No notification should pass due to the filter */ + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_xpath_filter_notif_content_match_node_pass(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Send the notification */ + reestablish_sub(state, "/notif2:devices/device[name='Main']/power-on"); + data = + "\n" + " \n" + " Main\n" + " \n" + " 12\n" + " \n" + " \n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* Notification should pass the filter */ + RECV_NOTIF(st); + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); +} + +static void +test_xpath_boolean_no_pass(void **state) +{ + struct np_test *st = *state; + char *data, *filter = + "/notif2:devices/device[name='Secondary']/power-on and /notif2:devices/device/power-on[boot-time=12]"; + + /* Send the notification */ + reestablish_sub(state, filter); + data = + "\n" + " \n" + " Main\n" + " \n" + " 12\n" + " \n" + " \n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* No notification should pass due to the filter */ + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_xpath_boolean_pass(void **state) +{ + struct np_test *st = *state; + char *data, *filter = + "/notif2:devices/device[name='Main']/power-on and /notif2:devices/device/power-on[boot-time=12]"; + + /* Send the notification */ + reestablish_sub(state, filter); + data = + "\n" + " \n" + " Main\n" + " \n" + " 12\n" + " \n" + " \n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* Notification should pass the filter */ + RECV_NOTIF(st); + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_basic_notif), + cmocka_unit_test(test_list_notif), + cmocka_unit_test(test_subtree_filter_no_matching_node), + cmocka_unit_test(test_subtree_filter_notif_selection_node_no_pass), + cmocka_unit_test(test_subtree_filter_notif_selection_node_pass), + cmocka_unit_test(test_subtree_filter_notif_content_match_node_no_pass), + cmocka_unit_test(test_subtree_filter_notif_content_match_node_pass), + cmocka_unit_test(test_xpath_filter_notif_selection_node_no_pass), + cmocka_unit_test(test_xpath_filter_notif_selection_node_pass), + cmocka_unit_test(test_xpath_filter_notif_content_match_node_no_pass), + cmocka_unit_test(test_xpath_filter_notif_content_match_node_pass), + cmocka_unit_test(test_xpath_filter_no_matching_node), + cmocka_unit_test(test_xpath_boolean_no_pass), + cmocka_unit_test(test_xpath_boolean_pass), + }; + + nc_verbosity(NC_VERB_WARNING); + sr_log_stderr(SR_LL_WRN); + parse_arg(argc, argv); + return cmocka_run_group_tests(tests, local_setup, local_teardown); +} diff --git a/tests/test_subscribe_param.c b/tests/test_subscribe_param.c new file mode 100644 index 000000000..26e6e08c5 --- /dev/null +++ b/tests/test_subscribe_param.c @@ -0,0 +1,678 @@ +/** + * @file test_subscribe_param.c + * @author Tadeas Vintrlik + * @brief tests for subscriptions and its' parameters + * + * @copyright + * Copyright 2021 Deutsche Telekom AG. + * Copyright 2021 CESNET, z.s.p.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "np_test.h" +#include "np_test_config.h" + +static void +reestablish_sub(void **state, const char *stream, const char *start_time, const char *stop_time) +{ + struct np_test *st = *state; + + /* Reestablish NETCONF connection */ + nc_session_free(st->nc_sess, NULL); + st->nc_sess = nc_connect_unix(NP_SOCKET_PATH, NULL); + assert_non_null(st->nc_sess); + + /* Get a subscription to receive notifications */ + st->rpc = nc_rpc_subscribe(stream, NULL, start_time, stop_time, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + + /* Check reply */ + st->msgtype = NC_MSG_NOTIF; + while (st->msgtype == NC_MSG_NOTIF) { + st->msgtype = nc_recv_reply(st->nc_sess, st->rpc, st->msgid, 1000, &st->envp, &st->op); + } + assert_int_equal(NC_MSG_REPLY, st->msgtype); + assert_null(st->op); + if (strcmp(LYD_NAME(lyd_child(st->envp)), "ok")) { + lyd_print_file(stdout, st->envp, LYD_XML, 0); + fprintf(stdout, "start time: %s\n", start_time); + fprintf(stdout, "stop time: %s\n", stop_time); + fail(); + } + + FREE_TEST_VARS(st); +} + +static int +local_setup(void **state) +{ + struct np_test *st; + sr_conn_ctx_t *conn; + const char *features[] = {NULL}; + const char *module1 = NP_TEST_MODULE_DIR "/notif1.yang"; + const char *module2 = NP_TEST_MODULE_DIR "/notif2.yang"; + int rv; + + /* Setup environment necessary for installing module */ + NP_GLOB_SETUP_ENV_FUNC; + assert_int_equal(setenv_rv, 0); + + /* connect to server and install test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module1, NULL, features), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module2, NULL, features), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* Setup netopeer2 server */ + if (!(rv = np_glob_setup_np2(state))) { + /* State is allocated in np_glob_setup_np2 have to set here */ + st = *state; + /* Open connection to start a session for the tests */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &st->conn), SR_ERR_OK); + assert_int_equal(sr_session_start(st->conn, SR_DS_RUNNING, &st->sr_sess), SR_ERR_OK); + assert_int_equal(sr_session_start(st->conn, SR_DS_RUNNING, &st->sr_sess2), SR_ERR_OK); + assert_non_null(st->ctx = sr_get_context(st->conn)); + + /* Enable replay support */ + assert_int_equal(SR_ERR_OK, sr_set_module_replay_support(st->conn, "notif1", 1)); + } + return rv; +} + +void +test_path_notif_dir(char **path) +{ + if (SR_NOTIFICATION_PATH[0]) { + *path = strdup(SR_NOTIFICATION_PATH); + } else { + if (asprintf(path, "%s/data/notif", sr_get_repo_path()) == -1) { + *path = NULL; + } + } +} + +static int +clear_notif(void **state) +{ + char *path, *cmd; + int ret; + + (void)state; + + test_path_notif_dir(&path); + if (!path) { + return 1; + } + + if (asprintf(&cmd, "rm -rf %s/notif1.notif*", path) == -1) { + return 1; + } + + free(path); + ret = system(cmd); + free(cmd); + + if (ret == -1) { + return 1; + } else if (!WIFEXITED(ret) || WEXITSTATUS(ret)) { + return 1; + } + + return 0; +} + +static int +local_teardown(void **state) +{ + struct np_test *st = *state; + sr_conn_ctx_t *conn; + + /* Disable replay support */ + assert_int_equal(SR_ERR_OK, sr_set_module_replay_support(st->conn, "notif1", 0)); + + /* Close the sessions and connection needed for tests */ + assert_int_equal(sr_session_stop(st->sr_sess), SR_ERR_OK); + assert_int_equal(sr_session_stop(st->sr_sess2), SR_ERR_OK); + assert_int_equal(sr_disconnect(st->conn), SR_ERR_OK); + + /* Connect to server and remove test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "notif1"), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "notif2"), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* Remove the notfications */ + clear_notif(state); + + /* close netopeer2 server */ + return np_glob_teardown(state); +} + +static void +test_stop_time_invalid(void **state) +{ + struct np_test *st = *state; + const char *expected; + struct timespec ts; + char *start_time, *stop_time; + + /* startTime is current time */ + assert_int_not_equal(-1, clock_gettime(CLOCK_REALTIME, &ts)); + assert_int_equal(LY_SUCCESS, ly_time_ts2str(&ts, &start_time)); + + /* stopTime is in the past */ + ts.tv_sec--; + assert_int_equal(LY_SUCCESS, ly_time_ts2str(&ts, &stop_time)); + + /* Reestablish NETCONF connection */ + nc_session_free(st->nc_sess, NULL); + st->nc_sess = nc_connect_unix(NP_SOCKET_PATH, NULL); + assert_non_null(st->nc_sess); + + /* Get a subscription to receive notifications */ + st->rpc = nc_rpc_subscribe(NULL, NULL, start_time, stop_time, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + + /* Check reply */ + st->msgtype = NC_MSG_NOTIF; + while (st->msgtype == NC_MSG_NOTIF) { + st->msgtype = nc_recv_reply(st->nc_sess, st->rpc, st->msgid, 1000, &st->envp, &st->op); + } + assert_int_equal(NC_MSG_REPLY, st->msgtype); + assert_null(st->op); + assert_string_equal(LYD_NAME(lyd_child(st->envp)), "rpc-error"); + + /* Check if correct error-tag and error-info */ + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "bad-element"); + expected = + " \n" + " stopTime\n" + " \n"; + lyd_print_mem(&st->str, st->envp, LYD_XML, 0); + assert_non_null(strstr(st->str, expected)); + + FREE_TEST_VARS(st); + free(start_time); + free(stop_time); +} + +static void +test_start_time_invalid(void **state) +{ + struct np_test *st = *state; + const char *expected; + struct timespec ts; + char *start_time; + + /* startTime is in the future */ + assert_int_not_equal(-1, clock_gettime(CLOCK_REALTIME, &ts)); + ts.tv_sec += 10; + assert_int_equal(LY_SUCCESS, ly_time_ts2str(&ts, &start_time)); + + /* Reestablish NETCONF connection */ + nc_session_free(st->nc_sess, NULL); + st->nc_sess = nc_connect_unix(NP_SOCKET_PATH, NULL); + assert_non_null(st->nc_sess); + + /* Get a subscription to receive notifications */ + st->rpc = nc_rpc_subscribe(NULL, NULL, start_time, NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + + /* Check reply */ + st->msgtype = NC_MSG_NOTIF; + while (st->msgtype == NC_MSG_NOTIF) { + st->msgtype = nc_recv_reply(st->nc_sess, st->rpc, st->msgid, 1000, &st->envp, &st->op); + } + assert_int_equal(NC_MSG_REPLY, st->msgtype); + assert_null(st->op); + assert_string_equal(LYD_NAME(lyd_child(st->envp)), "rpc-error"); + + /* Check if correct error-tag and error-info */ + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "bad-element"); + expected = + " \n" + " startTime\n" + " \n"; + lyd_print_mem(&st->str, st->envp, LYD_XML, 0); + assert_non_null(strstr(st->str, expected)); + + FREE_TEST_VARS(st); + free(start_time); +} + +static void +test_stop_time_no_start_time(void **state) +{ + struct np_test *st = *state; + const char *expected; + struct timespec ts; + char *stop_time; + + /* stopTime is the current time */ + assert_int_not_equal(-1, clock_gettime(CLOCK_REALTIME, &ts)); + assert_int_equal(LY_SUCCESS, ly_time_ts2str(&ts, &stop_time)); + + /* Reestablish NETCONF connection */ + nc_session_free(st->nc_sess, NULL); + st->nc_sess = nc_connect_unix(NP_SOCKET_PATH, NULL); + assert_non_null(st->nc_sess); + + /* Get a subscription to receive notifications */ + st->rpc = nc_rpc_subscribe(NULL, NULL, NULL, stop_time, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + + /* Check reply */ + st->msgtype = NC_MSG_NOTIF; + while (st->msgtype == NC_MSG_NOTIF) { + st->msgtype = nc_recv_reply(st->nc_sess, st->rpc, st->msgid, 1000, &st->envp, &st->op); + } + assert_int_equal(NC_MSG_REPLY, st->msgtype); + assert_null(st->op); + assert_string_equal(LYD_NAME(lyd_child(st->envp)), "rpc-error"); + + /* Check if correct error-tag and error-info */ + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "missing-element"); + expected = + " \n" + " startTime\n" + " \n"; + lyd_print_mem(&st->str, st->envp, LYD_XML, 0); + assert_non_null(strstr(st->str, expected)); + + FREE_TEST_VARS(st); + free(stop_time); +} + +static void +test_basic_replay(void **state) +{ + struct np_test *st = *state; + const char *data; + struct timespec ts; + char *start_time; + + /* Subsrcibe to replay */ + assert_int_not_equal(-1, clock_gettime(CLOCK_REALTIME, &ts)); + assert_int_equal(LY_SUCCESS, ly_time_ts2str(&ts, &start_time)); + + /* Send the notification */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* Subscribe to notfications */ + reestablish_sub(state, NULL, start_time, NULL); + free(start_time); + + /* Receive the notification and test the contents */ + RECV_NOTIF(st); + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); + + /* Check for replayComplete notification since the replay is done */ + RECV_NOTIF(st); + data = "\n"; + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); + + /* No other notification should arrive */ + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_replay_real_time(void **state) +{ + struct np_test *st = *state; + const char *data, *expected; + struct timespec ts; + char *timestr; + + /* Subsrcibe to replay */ + assert_int_not_equal(-1, clock_gettime(CLOCK_REALTIME, &ts)); + assert_int_equal(LY_SUCCESS, ly_time_ts2str(&ts, ×tr)); + + /* Send the notification */ + data = + "\n" + " First\n" + "\n"; + expected = data; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* Subscribe to notfications */ + reestablish_sub(state, NULL, timestr, NULL); + free(timestr); + + /* Send the notification */ + data = + "\n" + " Second\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* Receive the notification and test the contents */ + RECV_NOTIF(st); + assert_string_equal(expected, st->str); + FREE_TEST_VARS(st); + + /* Check for replayComplete notification since the replay is done */ + RECV_NOTIF(st); + expected = "\n"; + assert_string_equal(expected, st->str); + FREE_TEST_VARS(st); + + /* Check for real time notification */ + RECV_NOTIF(st); + expected = + "\n" + " Second\n" + "\n"; + assert_string_equal(expected, st->str); + FREE_TEST_VARS(st); + + /* No other notification should arrive */ + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_stop_time(void **state) +{ + struct np_test *st = *state; + const char *data, *expected; + struct timespec start, stop; + char *start_time, *stop_time; + + /* To subscribe to replay of the notification */ + clock_gettime(CLOCK_REALTIME, &start); + assert_int_equal(LY_SUCCESS, ly_time_ts2str(&start, &start_time)); + + /* Parse notification into lyd_node */ + data = + "\n" + " Test\n" + "\n"; + expected = data; + NOTIF_PARSE(st, data); + + /* Send the notification */ + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + FREE_TEST_VARS(st); + + /* To subscribe to replay of notifications until time was called, should not include any called after */ + clock_gettime(CLOCK_REALTIME, &stop); + assert_int_equal(LY_SUCCESS, ly_time_ts2str(&stop, &stop_time)); + + /* Subscribe to notfications */ + reestablish_sub(state, NULL, start_time, stop_time); + free(start_time); + free(stop_time); + + /* Receive the notification and test the contents */ + RECV_NOTIF(st); + assert_string_equal(expected, st->str); + FREE_TEST_VARS(st); + + /* Check for replayComplete notification since there was nothing to replay */ + RECV_NOTIF(st); + data = "\n"; + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); + + /* Check for notficationComplete notification since the subscription should be done */ + RECV_NOTIF(st); + data = "\n"; + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); + + /* Send the notification */ + data = + "\n" + " Another\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + FREE_TEST_VARS(st); + + /* No other notification should arrive */ + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_stop_time_sub_end(void **state) +{ + struct np_test *st = *state; + const char *data; + struct timespec ts; + char *start_time, *stop_time; + + /* Needed for stopTime */ + assert_int_not_equal(-1, clock_gettime(CLOCK_REALTIME, &ts)); + assert_int_equal(LY_SUCCESS, ly_time_ts2str(&ts, &start_time)); + + /* stopTime is now, should end right away */ + assert_int_not_equal(-1, clock_gettime(CLOCK_REALTIME, &ts)); + assert_int_equal(LY_SUCCESS, ly_time_ts2str(&ts, &stop_time)); + + /* Subscribe to notfications */ + reestablish_sub(state, NULL, start_time, stop_time); + free(start_time); + free(stop_time); + + /* Receive the notification and test the contents */ + RECV_NOTIF(st); + data = "\n"; + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); + + /* Receive the notification and test the contents */ + RECV_NOTIF(st); + data = "\n"; + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); + + /* Subsription should have ended now due to stop time, try to create a new one */ + st->rpc = nc_rpc_subscribe(NULL, NULL, NULL, NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + + /* Check reply */ + st->msgtype = NC_MSG_NOTIF; + while (st->msgtype == NC_MSG_NOTIF) { + st->msgtype = nc_recv_reply(st->nc_sess, st->rpc, st->msgid, 1000, &st->envp, &st->op); + } + assert_int_equal(NC_MSG_REPLY, st->msgtype); + assert_null(st->op); + assert_string_equal(LYD_NAME(lyd_child(st->envp)), "ok"); + FREE_TEST_VARS(st); + + /* Try sending a notfication real time on new session */ + /* Send the notification */ + data = + "\n" + " Second\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + FREE_TEST_VARS(st); + + /* Receive the notification and test the contents */ + RECV_NOTIF(st); + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); +} + +static void +test_history_only(void **state) +{ + struct np_test *st = *state; + const char *data; + struct timespec ts; + char *start_time, *stop_time; + + /* startTime is 10 seconds in the past */ + assert_int_not_equal(-1, clock_gettime(CLOCK_REALTIME, &ts)); + ts.tv_sec -= 10; + assert_int_equal(LY_SUCCESS, ly_time_ts2str(&ts, &start_time)); + + /* stopTime is 5 seconds in the past */ + ts.tv_sec += 5; + assert_int_equal(LY_SUCCESS, ly_time_ts2str(&ts, &stop_time)); + + /* Subscribe to notfications */ + reestablish_sub(state, NULL, start_time, stop_time); + free(start_time); + free(stop_time); + + /* Receive the notification and test the contents */ + RECV_NOTIF(st); + data = "\n"; + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); + + /* Receive the notification and test the contents */ + RECV_NOTIF(st); + data = "\n"; + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); +} + +static void +test_stream_no_pass(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Subscribe to notfications from a different stream */ + reestablish_sub(state, "notif2", NULL, NULL); + + /* Send the notification */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* It should no be recieved since it is in a a different stream than subscribed to */ + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_stream_pass(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Subscribe to notfications from the same stream */ + reestablish_sub(state, "notif1", NULL, NULL); + + /* Send the notification */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + + /* Receive the notification and test the contents */ + RECV_NOTIF(st); + assert_string_equal(st->str, data); + FREE_TEST_VARS(st); +} + +static void +test_stream_no_pass_start_time(void **state) +{ + struct np_test *st = *state; + const char *data; + struct timespec ts; + char *start_time; + + /* Subscribe to replay */ + assert_int_not_equal(-1, clock_gettime(CLOCK_REALTIME, &ts)); + assert_int_equal(LY_SUCCESS, ly_time_ts2str(&ts, &start_time)); + + /* Send the notification */ + data = + "\n" + " Test\n" + "\n"; + NOTIF_PARSE(st, data); + assert_int_equal(sr_event_notif_send_tree(st->sr_sess, st->node, 1000, 1), SR_ERR_OK); + FREE_TEST_VARS(st); + + /* Subscribe to notfications from a different stream */ + reestablish_sub(state, "notif2", start_time, NULL); + free(start_time); + + /* Check for replayComplete notification since the replay is done */ + RECV_NOTIF(st); + data = "\n"; + assert_string_equal(data, st->str); + FREE_TEST_VARS(st); + + /* No other notification should arrive */ + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_stop_time_invalid), + cmocka_unit_test(test_start_time_invalid), + cmocka_unit_test(test_stop_time_no_start_time), + cmocka_unit_test_teardown(test_basic_replay, clear_notif), + cmocka_unit_test_teardown(test_replay_real_time, clear_notif), + cmocka_unit_test_teardown(test_stop_time, clear_notif), + cmocka_unit_test_teardown(test_stop_time_sub_end, clear_notif), + cmocka_unit_test_teardown(test_history_only, clear_notif), + cmocka_unit_test(test_stream_no_pass), + cmocka_unit_test(test_stream_pass), + cmocka_unit_test(test_stream_no_pass_start_time), + }; + + nc_verbosity(NC_VERB_WARNING); + sr_log_stderr(SR_LL_WRN); + parse_arg(argc, argv); + return cmocka_run_group_tests(tests, local_setup, local_teardown); +} diff --git a/tests/test_url.c b/tests/test_url.c new file mode 100644 index 000000000..2c64f579d --- /dev/null +++ b/tests/test_url.c @@ -0,0 +1,468 @@ +/** + * @file test_url.c + * @author Tadeas Vintrlik + * @brief tests for RPCs that take url as an argument + * + * @copyright + * Copyright 2021 Deutsche Telekom AG. + * Copyright 2021 CESNET, z.s.p.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "np_test.h" +#include "np_test_config.h" + +static int +setup_nacm_rules(void **state) +{ + struct np_test *st = *state; + const char *data = + "\n" + " \n" + " rule1\n" + " test-group\n" + " \n" + " allow-keystore\n" + " ietf-keystore\n" + " /ks:keystore\n" + " permit\n" + " \n" + " \n" + " allow-nacm\n" + " ietf-netconf-acm\n" + " /nacm:nacm\n" + " permit\n" + " \n" + " \n" + " allow-truststore\n" + " ietf-truststore\n" + " /ts:truststore\n" + " permit\n" + " \n" + " \n" + "\n"; + + SR_EDIT(st, data); + FREE_TEST_VARS(st); + return 0; +} + +static int +local_setup(void **state) +{ + struct np_test *st = *state; + sr_conn_ctx_t *conn; + const char *features[] = {NULL}; + const char *module1 = NP_TEST_MODULE_DIR "/edit1.yang"; + int rv; + + /* setup environment necessary for installing module */ + NP_GLOB_SETUP_ENV_FUNC; + assert_int_equal(setenv_rv, 0); + + /* connect to server and install test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module1, NULL, features), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* setup netopeer2 server */ + if (!(rv = np_glob_setup_np2(state))) { + /* state is allocated in np_glob_setup_np2 have to set here */ + st = *state; + /* Open connection to start a session for the tests */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &st->conn), SR_ERR_OK); + assert_int_equal(sr_session_start(st->conn, SR_DS_RUNNING, &st->sr_sess), SR_ERR_OK); + assert_non_null(st->ctx = sr_get_context(st->conn)); + rv |= setup_nacm(state); + rv |= setup_nacm_rules(state); + } + return rv; +} + +static int +local_teardown(void **state) +{ + struct np_test *st = *state; + sr_conn_ctx_t *conn; + + /* Close the session and connection needed for tests */ + assert_int_equal(sr_session_stop(st->sr_sess), SR_ERR_OK); + assert_int_equal(sr_disconnect(st->conn), SR_ERR_OK); + + /* connect to server and remove test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "edit1"), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* close netopeer2 server */ + return np_glob_teardown(state); +} + +static void +test_validate(void **state) +{ + struct np_test *st = *state; + const char *url; + + url = "file://" NP_TEST_MODULE_DIR "/edit1.xml"; + + /* Send validate rpc */ + st->rpc = nc_rpc_validate(NC_DATASTORE_URL, url, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 2000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + + ASSERT_OK_REPLY(st); + + FREE_TEST_VARS(st); +} + +static int +teardown_data(void **state) +{ + struct np_test *st = *state; + const char *data; + + if (setup_nacm(state) || setup_nacm_rules(state)) { + return 1; + } + + data = ""; + + SR_EDIT(st, data); + + FREE_TEST_VARS(st); + return 0; +} + +static void +test_copy_config(void **state) +{ + struct np_test *st = *state; + const char *url, *expected; + + url = "file://" NP_TEST_MODULE_DIR "/edit1.xml"; + + /* Send copy config */ + st->rpc = nc_rpc_copy(NC_DATASTORE_RUNNING, NULL, NC_DATASTORE_URL, url, NC_WD_ALL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + + /* Recieve reply */ + ASSERT_OK_REPLY(st); + + FREE_TEST_VARS(st); + + GET_CONFIG(st); + + expected = + "\n" + " \n" + " TestFirst\n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +static void +test_copy_config_same(void **state) +{ + struct np_test *st = *state; + const char *url; + + url = "file://" NP_TEST_MODULE_DIR "/edit1.xml"; + + /* Send copy config */ + st->rpc = nc_rpc_copy(NC_DATASTORE_URL, url, NC_DATASTORE_URL, url, NC_WD_ALL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + + /* Recieve reply */ + ASSERT_RPC_ERROR(st); + + /* Check if correct error-tag */ + assert_string_equal(lyd_get_value(lyd_child(lyd_child(st->envp))->next), "invalid-value"); + + FREE_TEST_VARS(st); +} + +static int +setup_data(void **state) +{ + struct np_test *st = *state; + const char *data; + + data = "TestFirst"; + + SR_EDIT(st, data); + + FREE_TEST_VARS(st); + return 0; +} + +static void +test_copy_config_into_file(void **state) +{ + struct np_test *st = *state; + const char *path, *url, *template; + char *expected, *config, *user; + long size; + FILE *file; + + /* Remove the file if it exists */ + path = "/tmp/np2-test.xml"; + url = "file:///tmp/np2-test.xml"; + + /* Send copy config */ + st->rpc = nc_rpc_copy(NC_DATASTORE_URL, url, NC_DATASTORE_RUNNING, NULL, NC_WD_ALL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + + /* Recieve reply */ + ASSERT_OK_REPLY(st); + + FREE_TEST_VARS(st); + + file = fopen(path, "r"); + assert_non_null(file); + + /* Get file size */ + fseek(file, 0, SEEK_END); + size = ftell(file); + rewind(file); + + /* Allcate buffer */ + config = calloc((size + 1), sizeof *config); + assert_non_null(config); + + /* Read the file */ + assert_int_equal(fread(config, sizeof *config, size, file), size); + + template = + "\n" + " TestFirst\n" + " \n" + " true\n" + " permit\n" + " permit\n" + " permit\n" + " false\n" + " \n" + " \n" + " test-group\n" + " %s\n" + " \n" + " \n" + " \n" + " rule1\n" + " test-group\n" + " \n" + " allow-keystore\n" + " ietf-keystore\n" + " /ks:keystore\n" + " *\n" + " permit\n" + " \n" + " \n" + " allow-nacm\n" + " ietf-netconf-acm\n" + " /nacm:nacm\n" + " *\n" + " permit\n" + " \n" + " \n" + " allow-truststore\n" + " ietf-truststore\n" + " /ts:truststore\n" + " *\n" + " permit\n" + " \n" + " \n" + " \n" + "\n"; + + assert_int_equal(0, get_username(&user)); + assert_int_not_equal(-1, asprintf(&expected, template, user) == -1); + assert_string_equal(config, expected); + free(expected); + free(user); + + free(config); + fclose(file); + assert_int_equal(0, remove(path)); + FREE_TEST_VARS(st); +} + +static void +test_copy_config_url2url(void **state) +{ + struct np_test *st = *state; + const char *url_source, *url_target, *path, *expected; + char *config; + FILE *file; + long size; + + url_source = "file://" NP_TEST_MODULE_DIR "/edit1.xml"; + url_target = "file:///tmp/np2-test.xml"; + path = "/tmp/np2-test.xml"; + + /* Send copy config */ + st->rpc = nc_rpc_copy(NC_DATASTORE_URL, url_target, NC_DATASTORE_URL, url_source, NC_WD_ALL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + + /* Recieve reply */ + ASSERT_OK_REPLY(st); + + FREE_TEST_VARS(st); + + file = fopen(path, "r"); + assert_non_null(file); + + /* Get file size */ + fseek(file, 0, SEEK_END); + size = ftell(file); + rewind(file); + + /* Allcate buffer */ + config = calloc((size + 1), sizeof *config); + assert_non_null(config); + + /* Read the file */ + assert_int_equal(fread(config, sizeof *config, size, file), size); + + expected = + "\n" + " TestFirst\n" + "\n"; + + assert_string_equal(config, expected); + + free(config); + fclose(file); + assert_int_equal(0, remove(path)); + FREE_TEST_VARS(st); +} + +static void +test_edit_config(void **state) +{ + struct np_test *st = *state; + const char *url, *template; + char *expected, *user; + + url = "file://" NP_TEST_MODULE_DIR "/edit1.xml"; + + /* Send rpc edit */ + st->rpc = nc_rpc_edit(NC_DATASTORE_RUNNING, NC_RPC_EDIT_DFLTOP_MERGE, NC_RPC_EDIT_TESTOPT_SET, + NC_RPC_EDIT_ERROPT_ROLLBACK, url, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); \ + assert_int_equal(NC_MSG_RPC, st->msgtype); + + ASSERT_OK_REPLY(st); + + FREE_TEST_VARS(st); + + /* Check if merged */ + GET_CONFIG(st); + + template = + "\n" + " \n" + " TestFirst\n" + " \n" + " true\n" + " permit\n" + " permit\n" + " permit\n" + " false\n" + " \n" + " \n" + " test-group\n" + " %s\n" + " \n" + " \n" + " \n" + " rule1\n" + " test-group\n" + " \n" + " allow-keystore\n" + " ietf-keystore\n" + " /ks:keystore\n" + " *\n" + " permit\n" + " \n" + " \n" + " allow-nacm\n" + " ietf-netconf-acm\n" + " /nacm:nacm\n" + " *\n" + " permit\n" + " \n" + " \n" + " allow-truststore\n" + " ietf-truststore\n" + " /ts:truststore\n" + " *\n" + " permit\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + + assert_int_equal(0, get_username(&user)); + assert_int_not_equal(-1, asprintf(&expected, template, user) == -1); + assert_string_equal(st->str, expected); + free(expected); + free(user); + + FREE_TEST_VARS(st); +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_validate), + cmocka_unit_test_teardown(test_copy_config, teardown_data), + cmocka_unit_test_teardown(test_copy_config_same, teardown_data), + cmocka_unit_test_setup_teardown(test_copy_config_into_file, setup_data, teardown_data), + cmocka_unit_test(test_copy_config_url2url), + cmocka_unit_test_teardown(test_edit_config, teardown_data), + }; + + if (is_nacm_rec_uid()) { + puts("Running as NACM_RECOVERY_UID. Tests will not run correctly as this user bypases NACM. Skipping."); + return 0; + } + + nc_verbosity(NC_VERB_WARNING); + parse_arg(argc, argv); + return cmocka_run_group_tests(tests, local_setup, local_teardown); +} diff --git a/tests/test_with_defaults.c b/tests/test_with_defaults.c new file mode 100644 index 000000000..6f0cec154 --- /dev/null +++ b/tests/test_with_defaults.c @@ -0,0 +1,413 @@ +/** + * @file test_with_defaults.c + * @author Tadeas Vintrlik + * @brief tests for with-defaults arguement + * + * @copyright + * Copyright 2021 Deutsche Telekom AG. + * Copyright 2021 CESNET, z.s.p.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "np_test.h" +#include "np_test_config.h" + +static int +local_setup(void **state) +{ + struct np_test *st = *state; + sr_conn_ctx_t *conn; + const char *features[] = {NULL}; + const char *module1 = NP_TEST_MODULE_DIR "/defaults1.yang"; + int rv; + + /* setup environment necessary for installing module */ + NP_GLOB_SETUP_ENV_FUNC; + assert_int_equal(setenv_rv, 0); + + /* connect to server and install test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module1, NULL, features), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* setup netopeer2 server */ + if (!(rv = np_glob_setup_np2(state))) { + /* state is allocated in np_glob_setup_np2 have to set here */ + st = *state; + /* Open connection to start a session for the tests */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &st->conn), SR_ERR_OK); + assert_int_equal(sr_session_start(st->conn, SR_DS_RUNNING, &st->sr_sess), SR_ERR_OK); + assert_non_null(st->ctx = sr_get_context(st->conn)); + } + return rv; +} + +static int +local_teardown(void **state) +{ + struct np_test *st = *state; + sr_conn_ctx_t *conn; + + /* Close the session and connection needed for tests */ + assert_int_equal(sr_session_stop(st->sr_sess), SR_ERR_OK); + assert_int_equal(sr_disconnect(st->conn), SR_ERR_OK); + + /* connect to server and remove test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "defaults1"), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* close netopeer2 server */ + return np_glob_teardown(state); +} + +static void +test_all_nothing_set(void **state) +{ + struct np_test *st = *state; + const char *expected; + + /* Send RPC trying to get all including default values */ + st->rpc = nc_rpc_getconfig(NC_DATASTORE_RUNNING, NULL, NC_WD_ALL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(NC_MSG_RPC, st->msgtype); + st->msgtype = nc_recv_reply(st->nc_sess, st->rpc, st->msgid, 2000, &st->envp, &st->op); + + /* Get reply, should succeed */ + assert_int_equal(st->msgtype, NC_MSG_REPLY); + assert_non_null(st->op); + assert_non_null(st->envp); + assert_string_equal(LYD_NAME(lyd_child(st->op)), "data"); + assert_int_equal(LY_SUCCESS, lyd_print_mem(&st->str, st->op, LYD_XML, 0)); + + expected = + "\n" + " \n" + " \n" + " Test\n" + " \n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +static int +setup_data_num(void **state) +{ + struct np_test *st = *state; + const char *data; + + data = "1"; + + SR_EDIT(st, data); + + FREE_TEST_VARS(st); + + return 0; +} + +static int +setup_data_all(void **state) +{ + struct np_test *st = *state; + const char *data; + + data = "Alt1"; + + SR_EDIT(st, data); + + FREE_TEST_VARS(st); + + return 0; +} + +static int +setup_data_all_default(void **state) +{ + struct np_test *st = *state; + const char *data; + + data = "Test1"; + + SR_EDIT(st, data); + + FREE_TEST_VARS(st); + + return 0; +} + +static int +teardown_data(void **state) +{ + struct np_test *st = *state; + const char *data; + + data = ""; + + SR_EDIT(st, data); + + FREE_TEST_VARS(st); + + return 0; +} + +static void +test_all_non_default_set(void **state) +{ + struct np_test *st = *state; + const char *expected; + + GET_CONFIG_WD(st, NC_WD_ALL); + + expected = + "\n" + " \n" + " \n" + " Test\n" + " 1\n" + " \n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +static void +test_all_tag_non_default_set(void **state) +{ + struct np_test *st = *state; + const char *expected; + + GET_CONFIG_WD(st, NC_WD_ALL_TAG); + + expected = + "\n" + " \n" + " \n" + " Test\n" + " 1\n" + " \n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +static void +test_trim_non_default_set(void **state) +{ + struct np_test *st = *state; + const char *expected; + + GET_CONFIG_WD(st, NC_WD_TRIM); + + expected = + "\n" + " \n" + " \n" + " 1\n" + " \n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +static void +test_explicit_non_default_set(void **state) +{ + struct np_test *st = *state; + const char *expected; + + GET_CONFIG_WD(st, NC_WD_TRIM); + + expected = + "\n" + " \n" + " \n" + " 1\n" + " \n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +static void +test_all_set_all(void **state) +{ + struct np_test *st = *state; + const char *expected; + + GET_CONFIG_WD(st, NC_WD_ALL); + + expected = + "\n" + " \n" + " \n" + " Alt\n" + " 1\n" + " \n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +static void +test_all_tag_set_all(void **state) +{ + struct np_test *st = *state; + const char *expected; + + GET_CONFIG_WD(st, NC_WD_ALL_TAG); + + expected = + "\n" + " \n" + " \n" + " Alt\n" + " 1\n" + " \n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +static void +test_trim_set_all(void **state) +{ + struct np_test *st = *state; + const char *expected; + + GET_CONFIG_WD(st, NC_WD_TRIM); + + expected = + "\n" + " \n" + " \n" + " Alt\n" + " 1\n" + " \n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +static void +test_explicit_all_set(void **state) +{ + struct np_test *st = *state; + const char *expected; + + GET_CONFIG_WD(st, NC_WD_EXPLICIT); + + expected = + "\n" + " \n" + " \n" + " Alt\n" + " 1\n" + " \n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +static void +test_explicit_all_set_default(void **state) +{ + struct np_test *st = *state; + const char *expected; + + GET_CONFIG_WD(st, NC_WD_EXPLICIT); + + expected = + "\n" + " \n" + " \n" + " Test\n" + " 1\n" + " \n" + " \n" + "\n"; + + assert_string_equal(st->str, expected); + + FREE_TEST_VARS(st); +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_all_nothing_set), + cmocka_unit_test_setup_teardown(test_all_non_default_set, setup_data_num, teardown_data), + cmocka_unit_test_setup_teardown(test_all_tag_non_default_set, setup_data_num, teardown_data), + cmocka_unit_test_setup_teardown(test_trim_non_default_set, setup_data_num, teardown_data), + cmocka_unit_test_setup_teardown(test_explicit_non_default_set, setup_data_num, teardown_data), + cmocka_unit_test_setup_teardown(test_all_set_all, setup_data_all, teardown_data), + cmocka_unit_test_setup_teardown(test_all_tag_set_all, setup_data_all, teardown_data), + cmocka_unit_test_setup_teardown(test_trim_set_all, setup_data_all, teardown_data), + cmocka_unit_test_setup_teardown(test_explicit_all_set, setup_data_all, teardown_data), + cmocka_unit_test_setup_teardown(test_all_non_default_set, setup_data_all_default, teardown_data), + cmocka_unit_test_setup_teardown(test_all_tag_non_default_set, setup_data_all_default, teardown_data), + cmocka_unit_test_setup_teardown(test_trim_non_default_set, setup_data_all_default, teardown_data), + cmocka_unit_test_setup_teardown(test_explicit_all_set_default, setup_data_all_default, teardown_data), + }; + + if (is_nacm_rec_uid()) { + puts("Running as NACM_RECOVERY_UID. Tests will not run correctly as this user bypases NACM. Skipping."); + return 0; + } + + nc_verbosity(NC_VERB_WARNING); + parse_arg(argc, argv); + return cmocka_run_group_tests(tests, local_setup, local_teardown); +} diff --git a/tests/test_yang_push.c b/tests/test_yang_push.c new file mode 100644 index 000000000..c651f29b4 --- /dev/null +++ b/tests/test_yang_push.c @@ -0,0 +1,942 @@ +/** + * @file test_yang_push.c + * @author Tadeas Vintrlik + * @brief tests yang push notifications + * + * @copyright + * Copyright 2021 Deutsche Telekom AG. + * Copyright 2021 CESNET, z.s.p.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "np_test.h" +#include "np_test_config.h" + +static int +local_setup(void **state) +{ + struct np_test *st; + sr_conn_ctx_t *conn; + const char *features[] = {NULL}; + const char *module1 = NP_TEST_MODULE_DIR "/edit1.yang"; + const char *module2 = NP_TEST_MODULE_DIR "/edit2.yang"; + int rv; + + /* setup environment necessary for installing module */ + NP_GLOB_SETUP_ENV_FUNC; + assert_int_equal(setenv_rv, 0); + + /* connect to server and install test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module1, NULL, features), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module2, NULL, features), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* setup netopeer2 server */ + if (!(rv = np_glob_setup_np2(state))) { + /* state is allocated in np_glob_setup_np2 have to set here */ + st = *state; + /* Open connection to start a session for the tests */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &st->conn), SR_ERR_OK); + assert_int_equal(sr_session_start(st->conn, SR_DS_RUNNING, &st->sr_sess), SR_ERR_OK); + assert_non_null(st->ctx = sr_get_context(st->conn)); + + /* Enable replay support */ + assert_int_equal(SR_ERR_OK, sr_set_module_replay_support(st->conn, "edit1", 1)); + } + return rv; +} + +void +test_path_notif_dir(char **path) +{ + if (SR_NOTIFICATION_PATH[0]) { + *path = strdup(SR_NOTIFICATION_PATH); + } else { + if (asprintf(path, "%s/data/notif", sr_get_repo_path()) == -1) { + *path = NULL; + } + } +} + +static int +teardown_common(void **state) +{ + struct np_test *st = *state; + const char *data; + char *path, *cmd; + int ret; + + /* stop NETCONF session */ + nc_session_free(st->nc_sess, NULL); + + /* Remove the data */ + data = ""; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + data = ""; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Remove the notifications */ + test_path_notif_dir(&path); + if (!path) { + return 1; + } + + if (asprintf(&cmd, "rm -rf %s/notif1.notif*", path) == -1) { + return 1; + } + + free(path); + ret = system(cmd); + free(cmd); + + if (ret == -1) { + return 1; + } else if (!WIFEXITED(ret) || WEXITSTATUS(ret)) { + return 1; + } + + /* create new default NETCONF session */ + st->nc_sess = nc_connect_unix(NP_SOCKET_PATH, NULL); + assert_non_null(st->nc_sess); + + return 0; +} + +static int +local_teardown(void **state) +{ + struct np_test *st = *state; + sr_conn_ctx_t *conn; + + /* Disable replay support */ + assert_int_equal(SR_ERR_OK, sr_set_module_replay_support(st->conn, "edit1", 0)); + + /* Close the sessions and connection needed for tests */ + assert_int_equal(sr_session_stop(st->sr_sess), SR_ERR_OK); + assert_int_equal(sr_session_stop(st->sr_sess2), SR_ERR_OK); + assert_int_equal(sr_disconnect(st->conn), SR_ERR_OK); + + /* connect to server and remove test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "edit1"), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "edit2"), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* close netopeer2 server */ + return np_glob_teardown(state); +} + +static void +test_periodic_basic(void **state) +{ + struct np_test *st = *state; + const char *template, *data; + char *ntf; + + /* Establish periodic push */ + st->rpc = nc_rpc_establishpush_periodic("ietf-datastores:running", NULL, NULL, NULL, 10, NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Receive a notification */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* Put some data into the datastore */ + data = "TestFirst"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Receive a notification with the new data */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + " TestFirst\n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + FREE_TEST_VARS(st); + + /* Test yet again if arives with the same data */ + RECV_NOTIF(st); + assert_string_equal(st->str, ntf); + FREE_TEST_VARS(st); + free(ntf); +} + +static void +test_on_change_basic(void **state) +{ + struct np_test *st = *state; + const char *template, *data; + char *ntf; + + /* Establish on-change push */ + st->rpc = nc_rpc_establishpush_onchange("ietf-datastores:running", NULL, NULL, NULL, 0, 0, NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* No notification should arrive until a change occurs */ + ASSERT_NO_NOTIF(st); + + /* Put some data into the datastore */ + data = "TestFirst"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Receive a notification with the new data */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + " \n" + " patch-1\n" + " \n" + " edit-1\n" + " create\n" + " /edit1:first\n" + " \n" + " TestFirst\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* No other notification should be sent */ + ASSERT_NO_NOTIF(st); +} + +static void +test_on_change_multiple(void **state) +{ + struct np_test *st = *state; + const char *template, *data; + char *ntf; + + /* Establish on-change push */ + st->rpc = nc_rpc_establishpush_onchange("ietf-datastores:running", NULL, NULL, NULL, 0, 0, NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* No notification should arrive until a change occurs */ + ASSERT_NO_NOTIF(st); + + /* Put some data into the datastore */ + data = "TestFirst"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Receive a notification with the new data */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + " \n" + " patch-1\n" + " \n" + " edit-1\n" + " create\n" + " /edit1:first\n" + " \n" + " TestFirst\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* Change the data */ + data = "TestSecond"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Receive a notification with the change */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + " \n" + " patch-2\n" + " \n" + " edit-1\n" + " replace\n" + " /edit1:first\n" + " \n" + " TestSecond\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* Remove the data */ + data = "TestFirst"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Receive a notification with the deletion */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + " \n" + " patch-3\n" + " \n" + " edit-1\n" + " delete\n" + " /edit1:first\n" + " \n" + " \n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* No other notification should be sent */ + ASSERT_NO_NOTIF(st); +} + +static void +test_periodic_anchor_time(void **state) +{ + struct np_test *st = *state; + const char *template; + char *ntf; + + /* Establish periodic push with anchor-time */ + st->rpc = nc_rpc_establishpush_periodic("ietf-datastores:running", NULL, NULL, NULL, 10, + "1970-01-01T01:00:00+01:00", NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Receive a notification */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* Testing the exact time proved to be too unreliable */ +} + +static void +test_on_change_dampening_time(void **state) +{ + struct np_test *st = *state; + const char *template, *data; + char *ntf; + + /* Establish on-change push with 0.1s dampening time */ + st->rpc = nc_rpc_establishpush_onchange("ietf-datastores:running", NULL, NULL, NULL, 10, 0, + NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Put some data into the datastore */ + data = "TestFirst"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Receive a notification with the new data */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + " \n" + " patch-1\n" + " \n" + " edit-1\n" + " create\n" + " /edit1:first\n" + " \n" + " TestFirst\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* Put some data into the datastore */ + data = + "\n" + " Test\n" + "\n"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Put some more data into the datastore */ + data = + "\n" + " 123\n" + "\n"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Receive a notification with the new data dampened */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + " \n" + " patch-2\n" + " \n" + " edit-1\n" + " create\n" + " /edit2:top/name\n" + " \n" + " Test\n" + " \n" + " \n" + " \n" + " edit-2\n" + " create\n" + " /edit2:top/num\n" + " \n" + " 123\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* No other notification should be sent */ + ASSERT_NO_NOTIF(st); +} + +static void +test_on_change_dampening_time_same_node(void **state) +{ + struct np_test *st = *state; + const char *template, *data; + char *ntf; + + /* Establish on-change push with 0.1s dampening time */ + st->rpc = nc_rpc_establishpush_onchange("ietf-datastores:running", NULL, NULL, NULL, 10, 0, + NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Put some data into the datastore */ + data = "TestFirst"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Receive a notification with the new data */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + " \n" + " patch-1\n" + " \n" + " edit-1\n" + " create\n" + " /edit1:first\n" + " \n" + " TestFirst\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* Put some data into the datastore */ + data = "TestSecond"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Put some more data into the datastore */ + data = "TestThird"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Receive a notification with the new data dampened */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + " \n" + " patch-2\n" + " \n" + " edit-2\n" + " replace\n" + " /edit1:first\n" + " \n" + " TestThird\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* No other notification should be sent */ + ASSERT_NO_NOTIF(st); +} + +static void +test_on_change_dampening_time_create_delete(void **state) +{ + struct np_test *st = *state; + const char *template, *data; + char *ntf; + + /* Establish on-change push with 0.1s dampening time */ + st->rpc = nc_rpc_establishpush_onchange("ietf-datastores:running", NULL, NULL, NULL, 10, 0, + NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Put some data into the datastore */ + data = "TestFirst"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Receive a notification with the new data */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + " \n" + " patch-1\n" + " \n" + " edit-1\n" + " create\n" + " /edit1:first\n" + " \n" + " TestFirst\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* Put some data into the datastore */ + data = + "\n" + " Test\n" + "\n"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Remove the data from the datastore */ + data = + "\n" + " Test\n" + "\n"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Receive a notification with deletion */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + " \n" + " patch-2\n" + " \n" + " edit-2\n" + " delete\n" + " /edit2:top/name\n" + " \n" + " \n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* No other notification should be sent */ + ASSERT_NO_NOTIF(st); +} + +static void +test_on_change_excluded(void **state) +{ + struct np_test *st = *state; + const char *template, *data; + const char *excluded[] = {"create", NULL}; + char *ntf; + + /* Establish on-change push */ + st->rpc = nc_rpc_establishpush_onchange("ietf-datastores:running", NULL, NULL, NULL, 0, 0, + excluded, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* No notification should arrive until a change occurs */ + ASSERT_NO_NOTIF(st); + + /* Put some data into the datastore */ + data = "TestFirst"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* No notification should arrive on creation */ + ASSERT_NO_NOTIF(st); + + /* Modify the data */ + data = "TestSecond"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Receive a notification with the new data */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + " \n" + " patch-1\n" + " \n" + " edit-1\n" + " replace\n" + " /edit1:first\n" + " \n" + " TestSecond\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); +} + +static void +test_sync_on_start(void **state) +{ + struct np_test *st = *state; + const char *template; + char *ntf; + + /* Establish on-change push with sync on start */ + st->rpc = nc_rpc_establishpush_onchange("ietf-datastores:running", NULL, NULL, NULL, 0, 1, + NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Sync notification should arrive */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); +} + +static int +setup_test_sync_on_start_non_empty(void **state) +{ + struct np_test *st = *state; + const char *data; + + /* Put some data into the datastore */ + data = "TestFirst"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + return 0; +} + +static void +test_sync_on_start_non_empty(void **state) +{ + struct np_test *st = *state; + const char *template; + char *ntf; + + /* Establish on-change push with sync on start */ + st->rpc = nc_rpc_establishpush_onchange("ietf-datastores:running", NULL, NULL, NULL, 0, 1, + NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Sync notification should arrive */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + " TestFirst\n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); +} + +static void +test_resync(void **state) +{ + struct np_test *st = *state; + const char *template; + char *ntf; + + /* Establish on-change push */ + st->rpc = nc_rpc_establishpush_onchange("ietf-datastores:running", NULL, NULL, NULL, 0, 0, + NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* No notification should arrive */ + ASSERT_NO_NOTIF(st); + + /* Resync the subscription */ + st->rpc = nc_rpc_resyncsub(st->ntf_id); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + + /* Sync notification should arrive */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + lyd_free_tree(st->envp); + lyd_free_tree(st->op); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); +} + +static void +test_resync_id_reset(void **state) +{ + struct np_test *st = *state; + const char *data, *template; + char *ntf; + + /* Establish on-change push */ + st->rpc = nc_rpc_establishpush_onchange("ietf-datastores:running", NULL, NULL, NULL, 0, 0, + NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Put some data into the datastore */ + data = "TestFirst"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Receive a notification with patch id 1 */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + " \n" + " patch-1\n" + " \n" + " edit-1\n" + " create\n" + " /edit1:first\n" + " \n" + " TestFirst\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* Resync the subscription */ + st->rpc = nc_rpc_resyncsub(st->ntf_id); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + + /* Sync notification should arrive */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + " TestFirst\n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + lyd_free_tree(st->op); + lyd_free_tree(st->envp); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* Replace the data */ + data = "TestSecond"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Receive a notification with patch id 1 */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + " \n" + " patch-1\n" + " \n" + " edit-1\n" + " replace\n" + " /edit1:first\n" + " \n" + " TestSecond\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_teardown(test_periodic_basic, teardown_common), + cmocka_unit_test_teardown(test_on_change_basic, teardown_common), + cmocka_unit_test_teardown(test_on_change_multiple, teardown_common), + cmocka_unit_test_teardown(test_periodic_anchor_time, teardown_common), + cmocka_unit_test_teardown(test_on_change_dampening_time, teardown_common), + cmocka_unit_test_teardown(test_on_change_dampening_time_same_node, teardown_common), + cmocka_unit_test_teardown(test_on_change_dampening_time_create_delete, teardown_common), + cmocka_unit_test_teardown(test_on_change_excluded, teardown_common), + cmocka_unit_test_teardown(test_sync_on_start, teardown_common), + cmocka_unit_test_setup_teardown(test_sync_on_start_non_empty, + setup_test_sync_on_start_non_empty, teardown_common), + cmocka_unit_test_teardown(test_resync, teardown_common), + cmocka_unit_test_teardown(test_resync_id_reset, teardown_common), + }; + + nc_verbosity(NC_VERB_WARNING); + sr_log_stderr(SR_LL_WRN); + parse_arg(argc, argv); + return cmocka_run_group_tests(tests, local_setup, local_teardown); +} diff --git a/tests/test_yang_push_advanced.c b/tests/test_yang_push_advanced.c new file mode 100644 index 000000000..dffea5dd3 --- /dev/null +++ b/tests/test_yang_push_advanced.c @@ -0,0 +1,562 @@ +/** + * @file test_yang_push.c + * @author Tadeas Vintrlik + * @brief tests yang push notifications + * + * @copyright + * Copyright 2021 Deutsche Telekom AG. + * Copyright 2021 CESNET, z.s.p.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "np_test.h" +#include "np_test_config.h" + +static int +local_setup(void **state) +{ + struct np_test *st; + sr_conn_ctx_t *conn; + const char *features[] = {NULL}; + const char *module1 = NP_TEST_MODULE_DIR "/edit1.yang"; + const char *module2 = NP_TEST_MODULE_DIR "/edit2.yang"; + int rv; + + /* setup environment necessary for installing module */ + NP_GLOB_SETUP_ENV_FUNC; + assert_int_equal(setenv_rv, 0); + + /* connect to server and install test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module1, NULL, features), SR_ERR_OK); + assert_int_equal(sr_install_module(conn, module2, NULL, features), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* setup netopeer2 server */ + if (!(rv = np_glob_setup_np2(state))) { + /* state is allocated in np_glob_setup_np2 have to set here */ + st = *state; + /* Open connection to start a session for the tests */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &st->conn), SR_ERR_OK); + assert_int_equal(sr_session_start(st->conn, SR_DS_RUNNING, &st->sr_sess), SR_ERR_OK); + assert_non_null(st->ctx = sr_get_context(st->conn)); + + /* Enable replay support */ + assert_int_equal(SR_ERR_OK, sr_set_module_replay_support(st->conn, "edit1", 1)); + } + return rv; +} + +void +test_path_notif_dir(char **path) +{ + if (SR_NOTIFICATION_PATH[0]) { + *path = strdup(SR_NOTIFICATION_PATH); + } else { + if (asprintf(path, "%s/data/notif", sr_get_repo_path()) == -1) { + *path = NULL; + } + } +} + +static int +teardown_common(void **state) +{ + struct np_test *st = *state; + const char *data; + char *path, *cmd; + int ret; + + /* Remove the notifications */ + test_path_notif_dir(&path); + if (!path) { + return 1; + } + + if (asprintf(&cmd, "rm -rf %s/notif1.notif*", path) == -1) { + return 1; + } + + free(path); + ret = system(cmd); + free(cmd); + + if (ret == -1) { + return 1; + } else if (!WIFEXITED(ret) || WEXITSTATUS(ret)) { + return 1; + } + + /* reestablish NETCONF connection */ + nc_session_free(st->nc_sess, NULL); + st->nc_sess = nc_connect_unix(NP_SOCKET_PATH, NULL); + assert_non_null(st->nc_sess); + + /* Remove the data */ + data = ""; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + data = ""; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + return 0; +} + +static int +local_teardown(void **state) +{ + struct np_test *st = *state; + sr_conn_ctx_t *conn; + + /* Disable replay support */ + assert_int_equal(SR_ERR_OK, sr_set_module_replay_support(st->conn, "edit1", 0)); + + /* Close the sessions and connection needed for tests */ + assert_int_equal(sr_session_stop(st->sr_sess), SR_ERR_OK); + assert_int_equal(sr_session_stop(st->sr_sess2), SR_ERR_OK); + assert_int_equal(sr_disconnect(st->conn), SR_ERR_OK); + + /* connect to server and remove test modules */ + assert_int_equal(sr_connect(SR_CONN_DEFAULT, &conn), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "edit1"), SR_ERR_OK); + assert_int_equal(sr_remove_module(conn, "edit2"), SR_ERR_OK); + assert_int_equal(sr_disconnect(conn), SR_ERR_OK); + + /* close netopeer2 server */ + return np_glob_teardown(state); +} + +static void +test_on_change_stop_time(void **state) +{ + struct np_test *st = *state; + const char *template, *data; + char *ntf, *stop_time; + struct timespec ts; + + /* Get stop_time */ + assert_int_not_equal(-1, clock_gettime(CLOCK_REALTIME, &ts)); + assert_int_equal(ly_time_ts2str(&ts, &stop_time), LY_SUCCESS); + + /* Establish onchange push with stop_time */ + st->rpc = nc_rpc_establishpush_onchange("ietf-datastores:running", NULL, stop_time, NULL, 0, 0, + NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + free(stop_time); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Check for subscription-terminated notification */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " sn:no-such-subscription\n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* Insert some data */ + data = "TestFirst"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* No notification should arrive since the subscription has been terminated */ + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_on_change_modify_fail(void **state) +{ + struct np_test *st = *state; + char *stop_time; + struct timespec ts; + + /* Establish on-change push */ + st->rpc = nc_rpc_establishpush_onchange("ietf-datastores:running", NULL, NULL, NULL, 0, 0, + NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Get stop_time */ + assert_int_not_equal(-1, clock_gettime(CLOCK_REALTIME, &ts)); + assert_int_equal(ly_time_ts2str(&ts, &stop_time), LY_SUCCESS); + + /* Modify the stop_time, should fail since wrong rpc is used */ + SEND_RPC_MODSUB(st, st->ntf_id, "", stop_time); + ASSERT_RPC_ERROR(st); + FREE_TEST_VARS(st); + free(stop_time); +} + +static void +test_on_change_modify_stoptime(void **state) +{ + struct np_test *st = *state; + const char *template, *data; + char *ntf, *stop_time; + struct timespec ts; + + /* Establish on-change push */ + st->rpc = nc_rpc_establishpush_onchange("ietf-datastores:running", NULL, NULL, NULL, 0, 0, + NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Get stop_time */ + assert_int_not_equal(-1, clock_gettime(CLOCK_REALTIME, &ts)); + assert_int_equal(ly_time_ts2str(&ts, &stop_time), LY_SUCCESS); + + /* Modify the stop_time */ + st->rpc = nc_rpc_modifypush_onchange(st->ntf_id, "ietf-datastores:running", NULL, stop_time, 0, + NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + free(stop_time); + RECV_SUBMOD_NOTIF(st); + + /* Check for subscription-terminated notification */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " sn:no-such-subscription\n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* Insert some data */ + data = "TestFirst"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* No notification should arrive since the subscription has been terminated */ + ASSERT_NO_NOTIF(st); + FREE_TEST_VARS(st); +} + +static void +test_on_change_modify_filter(void **state) +{ + struct np_test *st = *state; + const char *template, *data; + char *ntf; + + /* Establish on-change push */ + st->rpc = nc_rpc_establishpush_onchange("ietf-datastores:running", NULL, NULL, NULL, 0, 0, + NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Modify the filter */ + st->rpc = nc_rpc_modifypush_onchange(st->ntf_id, "ietf-datastores:running", "", NULL, 0, + NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + RECV_SUBMOD_NOTIF(st); + + /* Insert some data */ + data = "TestFirst"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* Receive a notification with the new data */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + " \n" + " patch-1\n" + " \n" + " edit-1\n" + " create\n" + " /edit1:first\n" + " \n" + " TestFirst\n" + " \n" + " \n" + " \n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* Insert some data */ + data = + "\n" + " Test\n" + "\n"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* This notification should not pass the new filter */ + ASSERT_NO_NOTIF(st); +} + +static int +setup_test_periodic_modify_filter(void **state) +{ + struct np_test *st = *state; + const char *data; + + data = + "\n" + " Test\n" + "\n"; + SR_EDIT(st, data); + return 0; +} + +static void +test_periodic_modify_filter(void **state) +{ + struct np_test *st = *state; + const char *template; + char *ntf; + + /* Establish periodic push */ + st->rpc = nc_rpc_establishpush_periodic("ietf-datastores:running", NULL, NULL, NULL, 25, NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Receive a notification */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + " \n" + " Test\n" + " \n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* Modify the filter */ + st->rpc = nc_rpc_modifypush_periodic(st->ntf_id, "ietf-datastores:running", "", NULL, 25, + NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + RECV_SUBMOD_NOTIF(st); + + /* Receive a notification */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); +} + +static void +test_periodic_modify_period(void **state) +{ + struct np_test *st = *state; + const char *template; + char *ntf; + + /* Establish periodic push */ + st->rpc = nc_rpc_establishpush_periodic("ietf-datastores:running", NULL, NULL, NULL, 50, NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Receive a notification */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* Modify the period */ + st->rpc = nc_rpc_modifypush_periodic(st->ntf_id, "ietf-datastores:running", NULL, NULL, 1000, NULL, + NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + RECV_SUBMOD_NOTIF(st); + + /* Receive a notification */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + /* No notification should arrive in timeout since the period is too long */ + ASSERT_NO_NOTIF(st); +} + +static void +test_periodic_deletesub(void **state) +{ + struct np_test *st = *state; + const char *template; + char *ntf; + + /* Establish periodic push */ + st->rpc = nc_rpc_establishpush_periodic("ietf-datastores:running", NULL, NULL, NULL, 50, NULL, NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + /* Receive a notification */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " \n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + FREE_TEST_VARS(st); + + st->rpc = nc_rpc_deletesub(st->ntf_id); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + + /* Check for subscription-terminated notification */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " sn:no-such-subscription\n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + lyd_free_tree(st->op); + lyd_free_tree(st->envp); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* No other notification should arrive */ + ASSERT_NO_NOTIF(st); +} + +static void +test_onchange_deletesub(void **state) +{ + struct np_test *st = *state; + const char *data, *template; + char *ntf; + + /* Establish onchange push with stop_time */ + st->rpc = nc_rpc_establishpush_onchange("ietf-datastores:running", NULL, NULL, NULL, 0, 0, NULL, + NC_PARAMTYPE_CONST); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + ASSERT_OK_SUB_NTF(st); + FREE_TEST_VARS(st); + + st->rpc = nc_rpc_deletesub(st->ntf_id); + st->msgtype = nc_send_rpc(st->nc_sess, st->rpc, 1000, &st->msgid); + assert_int_equal(st->msgtype, NC_MSG_RPC); + + /* Check for subscription-terminated notification */ + RECV_NOTIF(st); + template = + "\n" + " %d\n" + " sn:no-such-subscription\n" + "\n"; + assert_int_not_equal(-1, asprintf(&ntf, template, st->ntf_id)); + assert_string_equal(st->str, ntf); + free(ntf); + lyd_free_tree(st->op); + lyd_free_tree(st->envp); + ASSERT_OK_REPLY(st); + FREE_TEST_VARS(st); + + /* Insert some data */ + data = "TestFirst"; + SR_EDIT(st, data); + FREE_TEST_VARS(st); + + /* No notification should arrive since the subscription has been terminated */ + ASSERT_NO_NOTIF(st); +} + +int +main(int argc, char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_teardown(test_on_change_stop_time, teardown_common), + cmocka_unit_test_teardown(test_on_change_modify_fail, teardown_common), + cmocka_unit_test_teardown(test_on_change_modify_stoptime, teardown_common), + cmocka_unit_test_teardown(test_on_change_modify_filter, teardown_common), + cmocka_unit_test_setup_teardown(test_periodic_modify_filter, setup_test_periodic_modify_filter, + teardown_common), + cmocka_unit_test_teardown(test_periodic_modify_period, teardown_common), + cmocka_unit_test_teardown(test_periodic_deletesub, teardown_common), + cmocka_unit_test_teardown(test_onchange_deletesub, teardown_common), + }; + + nc_verbosity(NC_VERB_WARNING); + sr_log_stderr(SR_LL_WRN); + parse_arg(argc, argv); + return cmocka_run_group_tests(tests, local_setup, local_teardown); +}