diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..cf99a98 --- /dev/null +++ b/.clang-format @@ -0,0 +1,8 @@ +BasedOnStyle: LLVM +ColumnLimit: 88 +PointerAlignment: Right +UseTab: Always +IndentWidth: 4 +TabWidth: 4 +BreakBeforeBraces: Linux +AlignTrailingComments: false diff --git a/.github/workflows/CMake.yml b/.github/workflows/CMake.yml new file mode 100644 index 0000000..54f8cdc --- /dev/null +++ b/.github/workflows/CMake.yml @@ -0,0 +1,40 @@ +name: CMake + +on: + push: + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + container: + image: ghcr.io/fraunhofer-iis/libjapi_ci + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v1 + with: + submodules: true + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake3 -B ${{ env.GITHUB_WORKSPACE }}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + # Build your program with the given configuration + run: cmake3 --build ${{ env.GITHUB_WORKSPACE }}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{ env.GITHUB_WORKSPACE }}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: make run_test diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml new file mode 100644 index 0000000..79b5a5a --- /dev/null +++ b/.github/workflows/package.yml @@ -0,0 +1,28 @@ +name: package + +on: + push: + branches: ["master", "dev"] + +jobs: + package: + runs-on: ubuntu-latest + container: + image: ghcr.io/fraunhofer-iis/libjapi_ci + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v1 + with: + submodules: true + + - name: Build RPM package + run: ls -la; package/create_rpm.sh + + - name: Upload package + uses: actions/upload-artifact@v3 + with: + name: Binary RPM + path: package/rpmbuild/RPMS/x86_64/libjapi*.rpm \ No newline at end of file diff --git a/.github/workflows/pages_static.yml b/.github/workflows/pages_static.yml new file mode 100644 index 0000000..2b22661 --- /dev/null +++ b/.github/workflows/pages_static.yml @@ -0,0 +1,74 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static content to Pages + +on: + push: + branches: ["master"] + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Debug + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + container: + image: ghcr.io/fraunhofer-iis/libjapi_ci + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v1 + with: + submodules: true + + - name: Install debug dependencies + run: yum -y install lcov + + - name: Configure CMake + run: cmake3 -B ${{ env.GITHUB_WORKSPACE }}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Create Docs + working-directory: ${{ env.GITHUB_WORKSPACE }}/build + run: make doc + + - name: Create Code Coverage Report + working-directory: ${{ env.GITHUB_WORKSPACE }}/build + run: | + make coverage && + cp -r coverage doc/html + + - name: Upload artifacts + uses: actions/upload-pages-artifact@v2 + with: + path: '${{ env.GITHUB_WORKSPACE }}/build/doc/html/' + + + deploy: + # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages + permissions: + contents: read + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + needs: build + runs-on: ubuntu-latest + + steps: + - name: Setup Pages + uses: actions/configure-pages@v3 + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 475c586..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,94 +0,0 @@ -image: git01.iis.fhg.de:5005/ks-ip-lib/software/libjapi:latest - -variables: - GIT_SUBMODULE_STRATEGY: recursive - -stages: - - build - - build_test - - run_test - - doc - - build_rpm - - publish - -job 1: - stage: build - script: - - "mkdir build && cd build" - - "cmake3 ../ 2>&1 | tee cmake.log" - - "make 2>&1 | tee build.log" - artifacts: - when: always - expire_in: 1 week - paths: - - "build/libjapi.so" - - "build/libjapi-static.a" - - "build/cmake.log" - - "build/build.log" -job 2: - stage: build_test - script: - - "cd build" - - "cmake3 ../" - - "make testsuite 2>&1 | tee build_test.log" - artifacts: - when: always - expire_in: 1 week - paths: - - "build/build_test.log" - - "test/*" - - "build/testsuite" -job 3: - stage: run_test - script: - - "cd build" - - "cmake3 ../" - - "make run_test 2>&1 | tee run_test.log" - artifacts: - when: always - expire_in: 1 week - paths: - - "build/run_test.log" -job 4: - stage: doc - script: - - "cd build" - - "cmake3 ../" - - "make doc 2>&1 | tee doc.log" - artifacts: - when: always - expire_in: 1 week - paths: - - "build/doc.log" - - "build/doc/*" -job 5: - stage: build_rpm - script: - - "package/create_rpm.sh" - artifacts: - when: always - expire_in: 1 week - paths: - - "package/rpmbuild/RPMS/x86_64/libjapi*.rpm" -pages: - stage: publish - dependencies: - - job 4 - - job 1 - - job 5 - script: - - mkdir -p public - - mkdir -p public/repo/x86_64 - - mv build/doc public - - mv package/libjapi.repo public/repo/ - - touch public/repo/index.html - - $(echo "

$(basename package/rpmbuild/RPMS/x86_64/libjapi-[0-9]*.rpm)

" >> public/repo/index.html) - - $(echo "

$(basename package/rpmbuild/RPMS/x86_64/libjapi-doc*.rpm)

" >> public/repo/index.html) - - "cp package/rpmbuild/RPMS/x86_64/libjapi*.rpm public/repo/x86_64/" - - createrepo public/repo/ - - yum-config-manager --add-repo public/repo/libjapi.repo - artifacts: - paths: - - public - only: - - master diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..c8820ad --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,6 @@ +repos: + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v16.0.6 + hooks: + - id: clang-format + args: [--style, file] diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..bf7a302 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "matepek.vscode-catch2-test-adapter", + "ms-vscode.cmake-tools", + "ms-vscode.cpptools" + ] +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index f9c209d..8809b35 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,3 +88,20 @@ target_include_directories(testsuite ) add_custom_target(run_test COMMAND testsuite DEPENDS testsuite) + +################################ +# Test code coverage # + +if(CMAKE_BUILD_TYPE MATCHES "Debug") + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + include(${CMAKE_SOURCE_DIR}/cmake/CodeCoverage.cmake) + set(COVERAGE_EXCLUDES + "doxydir" + "/usr/include/*" + "googletest/*" + "test/*" + ) + append_coverage_compiler_flags() + + setup_target_for_coverage_lcov(NAME coverage EXECUTABLE testsuite) +endif() diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..349a761 --- /dev/null +++ b/COPYING @@ -0,0 +1,20 @@ + +Copyright (c) 2023 Fraunhofer IIS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/ChangeLog b/ChangeLog index 344802c..1ccb84a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,10 +1,23 @@ +next +===== +* + +0.4.0 +===== +* Add MIT license and publish to GitHub +* Add make target coverage +* Add githook for formatting with clang +* Move GitLab-CI to GitHub actions +* Add japi handler that lists registered commands +* Some fixes and doc update + 0.3.2 ===== +* Seperated doc in overview and details +* Using README as doxygen frontpage, removed duplicated content * Fixed invalid memory access after a client disconnects * Added build support for macOS * Increased CMake requirement to version 3.6 -* Using README as doxygen frontpage, removed duplicated content -* Improved doxygen documentation 0.3.1 ===== diff --git a/README.md b/README.md index 5fe2e05..58d3c50 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,10 @@ create push services, which asynchronously push JSON messages to the clients subscribed to them. ## Documentation -The documentation can be found [here](http://ks-ip-lib.git01.iis.fhg.de/software/libjapi/doc/html/index.html). +The documentation can be found [here](https://fraunhofer-iis.github.io/libjapi/). ## Packages -Prebuild packages can be downloaded [here](http://ks-ip-lib.git01.iis.fhg.de/software/libjapi/repo/index.html). +Prebuild packages for CentOS 7 can be downloaded from the [latest package Action](https://github.com/Fraunhofer-IIS/libjapi/actions/workflows/package.yml). ## Features * Synchronous communication (request, response) @@ -24,6 +24,10 @@ Prebuild packages can be downloaded [here](http://ks-ip-lib.git01.iis.fhg.de/sof * [cmake version 3.6](https://cmake.org/) ### Installation +Clone the git repository and it's submodules: + + $ git clone --recurse-submodules git@github.com:Fraunhofer-IIS/libjapi.git + Create a build directory and call *cmake* in that directory. $ mkdir build @@ -41,144 +45,35 @@ You can clone the [demo project](https://git01.iis.fhg.de/ks-ip-lib/software/lib $ git clone --recurse-submodules git@git01.iis.fhg.de:ks-ip-lib/software/libjapi-demo.git -## Usage & Examples -* Create a JAPI context -* Write application specific functions -* Register these application specific functions to libjapi -* Start a JAPI server -* Enjoy the flexibility - -### Server example - - #include - #include - #include - #include /* sleep */ - - #include - #include - #include - - /* User defined push service routine */ - int push_counter(japi_pushsrv_context *psc) - { - json_object *jmsg; - int i; - - assert(psc != NULL); - - i = 0; - jmsg = json_object_new_object(); - - while (psc->enabled) { - /* Create JSON response string */ - json_object_object_add(jmsg,"counter",json_object_new_int(i)); - - /* Push message */ - japi_pushsrv_sendmsg(psc,jmsg); - - i++; - sleep(1); - } - json_object_put(jmsg); - - return 0; - } - - static void rnf_handler(japi_context *ctx, json_object *request, json_object *response) - { - json_object_object_add(response, "japi_response_msg", json_object_new_string("ERROR: No request handler found!")); - } - - static void get_temperature(japi_context *ctx, json_object *request, json_object *response) - { - double temperature; - const char *unit; - - /* - * TODO: Read the temperature from a sensor... - */ - temperature = 27.0; - - /* Provide the temperature in KELVIN (if requested) - * or CELSIUS (default) */ - unit = japi_get_value_as_str(request, "unit"); - if (unit != NULL && strcmp(unit, "kelvin") == 0) { - temperature += 273; - } else { - unit = "celsius"; - } - - /* Prepare and provide response */ - json_object_object_add(response, "temperature", json_object_new_double(temperature)); - json_object_object_add(response, "unit", json_object_new_string(unit)); - } - - int main(int argc, char *argv[]) - { - int ret; - japi_context *ctx; - japi_pushsrv_context *psc_counter; - - /* Read port */ - if (argc != 2) { - fprintf(stderr, "ERROR: Missing argument or wrong amount of arguments.\n" \ - "Usage:\n\t%s \n", argv[0]); - return -1; - } - - /* Create JSON API context */ - ctx = japi_init(NULL); - if (ctx == NULL) { - fprintf(stderr, "ERROR: Failed to create japi context\n"); - return -1; - } - - /* Register JSON API request */ - japi_register_request(ctx, "get_temperature", &get_temperature); - - /* Register push service */ - psc_counter = japi_pushsrv_register(ctx, "push_counter"); - - /* Start push thread */ - japi_pushsrv_start(psc_counter,&push_counter); - - /* Provide JSON API interface via TCP */ - ret = japi_start_server(ctx, argv[1]); - - /* Wait for the thread to finish */ - japi_pushsrv_stop(psc_counter); - - /* Destroy JAPI context */ - japi_destroy(ctx); - - return ret; - } - -### Client JSON request examples - - { - "japi_request": "get_temperature", - "args": { - "unit": "kelvin" - } - } - - { - "japi_request": "japi_pushsrv_list", - } - - { - "japi_request": "japi_pushsrv_subscribe", - "args": { - "service": "push_counter" - } - } - - { - "japi_request": "japi_pushsrv_unsubscribe", - "args": { - "service": "push_counter" - } - } +## References +* https://github.com/json-c/json-c +* http://json-c.github.io/json-c/ +* https://en.wikipedia.org/wiki/JSON +* https://alan-mushi.github.io/2014/10/28/json-c-tutorial-part-1.html + +## Contributing + +### Pre-commit hooks +When contributing to this project, automatic formatting of changes is strongly encouraged, so that formatting is getting more consistent over time. + +To do so, ensure you have [`pre-commit`](https://pre-commit.com/) installed and do the following + +```console +$ pre-commit install +pre-commit installed at .git/hooks/pre-commit +``` + +This will run `clang-format` on all *changes* you commit, gradually reformatting the code base to a more consistent state. + +### Code Coverage +To test which part of the code is called by tests, use the coverage tool. + +To do so, make sure you have lcov installed and do the following + + $ mkdir build + $ cd build/ + $ cmake -DCMAKE_BUILD_TYPE=Debug ../ + $ make coverage +The result ist displayed in the console. Additionally a report is created at "build/coverage/index.html". +You can also find it [here](https://fraunhofer-iis.github.io/libjapi/coverage/index.html). diff --git a/cmake/CodeCoverage.cmake b/cmake/CodeCoverage.cmake new file mode 100644 index 0000000..deb0f6e --- /dev/null +++ b/cmake/CodeCoverage.cmake @@ -0,0 +1,742 @@ +# Copyright (c) 2012 - 2017, Lars Bilke +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# CHANGES: +# +# 2012-01-31, Lars Bilke +# - Enable Code Coverage +# +# 2013-09-17, Joakim Söderberg +# - Added support for Clang. +# - Some additional usage instructions. +# +# 2016-02-03, Lars Bilke +# - Refactored functions to use named parameters +# +# 2017-06-02, Lars Bilke +# - Merged with modified version from github.com/ufz/ogs +# +# 2019-05-06, Anatolii Kurotych +# - Remove unnecessary --coverage flag +# +# 2019-12-13, FeRD (Frank Dana) +# - Deprecate COVERAGE_LCOVR_EXCLUDES and COVERAGE_GCOVR_EXCLUDES lists in favor +# of tool-agnostic COVERAGE_EXCLUDES variable, or EXCLUDE setup arguments. +# - CMake 3.4+: All excludes can be specified relative to BASE_DIRECTORY +# - All setup functions: accept BASE_DIRECTORY, EXCLUDE list +# - Set lcov basedir with -b argument +# - Add automatic --demangle-cpp in lcovr, if 'c++filt' is available (can be +# overridden with NO_DEMANGLE option in setup_target_for_coverage_lcovr().) +# - Delete output dir, .info file on 'make clean' +# - Remove Python detection, since version mismatches will break gcovr +# - Minor cleanup (lowercase function names, update examples...) +# +# 2019-12-19, FeRD (Frank Dana) +# - Rename Lcov outputs, make filtered file canonical, fix cleanup for targets +# +# 2020-01-19, Bob Apthorpe +# - Added gfortran support +# +# 2020-02-17, FeRD (Frank Dana) +# - Make all add_custom_target()s VERBATIM to auto-escape wildcard characters +# in EXCLUDEs, and remove manual escaping from gcovr targets +# +# 2021-01-19, Robin Mueller +# - Add CODE_COVERAGE_VERBOSE option which will allow to print out commands which are run +# - Added the option for users to set the GCOVR_ADDITIONAL_ARGS variable to supply additional +# flags to the gcovr command +# +# 2020-05-04, Mihchael Davis +# - Add -fprofile-abs-path to make gcno files contain absolute paths +# - Fix BASE_DIRECTORY not working when defined +# - Change BYPRODUCT from folder to index.html to stop ninja from complaining about double defines +# +# 2021-05-10, Martin Stump +# - Check if the generator is multi-config before warning about non-Debug builds +# +# 2022-02-22, Marko Wehle +# - Change gcovr output from -o for --xml and --html output respectively. +# This will allow for Multiple Output Formats at the same time by making use of GCOVR_ADDITIONAL_ARGS, e.g. GCOVR_ADDITIONAL_ARGS "--txt". +# +# 2022-09-28, Sebastian Mueller +# - fix append_coverage_compiler_flags_to_target to correctly add flags +# - replace "-fprofile-arcs -ftest-coverage" with "--coverage" (equivalent) +# +# USAGE: +# +# 1. Copy this file into your cmake modules path. +# +# 2. Add the following line to your CMakeLists.txt (best inside an if-condition +# using a CMake option() to enable it just optionally): +# include(CodeCoverage) +# +# 3. Append necessary compiler flags for all supported source files: +# append_coverage_compiler_flags() +# Or for specific target: +# append_coverage_compiler_flags_to_target(YOUR_TARGET_NAME) +# +# 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og +# +# 4. If you need to exclude additional directories from the report, specify them +# using full paths in the COVERAGE_EXCLUDES variable before calling +# setup_target_for_coverage_*(). +# Example: +# set(COVERAGE_EXCLUDES +# '${PROJECT_SOURCE_DIR}/src/dir1/*' +# '/path/to/my/src/dir2/*') +# Or, use the EXCLUDE argument to setup_target_for_coverage_*(). +# Example: +# setup_target_for_coverage_lcov( +# NAME coverage +# EXECUTABLE testrunner +# EXCLUDE "${PROJECT_SOURCE_DIR}/src/dir1/*" "/path/to/my/src/dir2/*") +# +# 4.a NOTE: With CMake 3.4+, COVERAGE_EXCLUDES or EXCLUDE can also be set +# relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR) +# Example: +# set(COVERAGE_EXCLUDES "dir1/*") +# setup_target_for_coverage_gcovr_html( +# NAME coverage +# EXECUTABLE testrunner +# BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src" +# EXCLUDE "dir2/*") +# +# 5. Use the functions described below to create a custom make target which +# runs your test executable and produces a code coverage report. +# +# 6. Build a Debug build: +# cmake -DCMAKE_BUILD_TYPE=Debug .. +# make +# make my_coverage_target +# + +include(CMakeParseArguments) + +option(CODE_COVERAGE_VERBOSE "Verbose information" FALSE) + +# Check prereqs +find_program( GCOV_PATH gcov ) +find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl) +find_program( FASTCOV_PATH NAMES fastcov fastcov.py ) +find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat ) +find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) +find_program( CPPFILT_PATH NAMES c++filt ) + +if(NOT GCOV_PATH) + message(FATAL_ERROR "gcov not found! Aborting...") +endif() # NOT GCOV_PATH + +# Check supported compiler (Clang, GNU and Flang) +get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) +foreach(LANG ${LANGUAGES}) + if("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + if("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3) + message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") + endif() + elseif(NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "GNU" + AND NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(LLVM)?[Ff]lang") + message(FATAL_ERROR "Compiler is not GNU or Flang! Aborting...") + endif() +endforeach() + +set(COVERAGE_COMPILER_FLAGS "-g --coverage" + CACHE INTERNAL "") +if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag(-fprofile-abs-path HAVE_fprofile_abs_path) + if(HAVE_fprofile_abs_path) + set(COVERAGE_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + endif() +endif() + +set(CMAKE_Fortran_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the Fortran compiler during coverage builds." + FORCE ) +set(CMAKE_CXX_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C++ compiler during coverage builds." + FORCE ) +set(CMAKE_C_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C compiler during coverage builds." + FORCE ) +set(CMAKE_EXE_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used for linking binaries during coverage builds." + FORCE ) +set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used by the shared libraries linker during coverage builds." + FORCE ) +mark_as_advanced( + CMAKE_Fortran_FLAGS_COVERAGE + CMAKE_CXX_FLAGS_COVERAGE + CMAKE_C_FLAGS_COVERAGE + CMAKE_EXE_LINKER_FLAGS_COVERAGE + CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) + +get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)) + message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") +endif() # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG) + +if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + link_libraries(gcov) +endif() + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_lcov( +# NAME testrunner_coverage # New target name +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES testrunner # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# NO_DEMANGLE # Don't demangle C++ symbols +# # even if c++filt is found +# ) +function(setup_target_for_coverage_lcov) + + set(options NO_DEMANGLE SONARQUBE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() # NOT LCOV_PATH + + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() # NOT GENHTML_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(LCOV_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_LCOV_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND LCOV_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES LCOV_EXCLUDES) + + # Conditional arguments + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) + set(GENHTML_EXTRA_ARGS "--demangle-cpp") + endif() + + # Setting up commands which will be run to generate coverage data. + # Cleanup lcov + set(LCOV_CLEAN_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -directory . + -b ${BASEDIR} --zerocounters + ) + # Create baseline to make sure untouched files show up in the report + set(LCOV_BASELINE_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d . -b + ${BASEDIR} -o ${Coverage_NAME}.base + ) + # Run tests + set(LCOV_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Capturing lcov counters and generating report + set(LCOV_CAPTURE_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . -b + ${BASEDIR} --capture --output-file ${Coverage_NAME}.capture + ) + # add baseline counters + set(LCOV_BASELINE_COUNT_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base + -a ${Coverage_NAME}.capture --output-file ${Coverage_NAME}.total + ) + # filter collected data to final coverage report + set(LCOV_FILTER_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove + ${Coverage_NAME}.total ${LCOV_EXCLUDES} --output-file ${Coverage_NAME}.info + ) + # Generate HTML output + set(LCOV_GEN_HTML_CMD + ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o + ${Coverage_NAME} ${Coverage_NAME}.info + ) + if(${Coverage_SONARQUBE}) + # Generate SonarQube output + set(GCOVR_XML_CMD + ${GCOVR_PATH} --sonarqube ${Coverage_NAME}_sonarqube.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + set(GCOVR_XML_CMD_COMMAND + COMMAND ${GCOVR_XML_CMD} + ) + set(GCOVR_XML_CMD_BYPRODUCTS ${Coverage_NAME}_sonarqube.xml) + set(GCOVR_XML_CMD_COMMENT COMMENT "SonarQube code coverage info report saved in ${Coverage_NAME}_sonarqube.xml.") + endif() + + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + message(STATUS "Command to clean up lcov: ") + string(REPLACE ";" " " LCOV_CLEAN_CMD_SPACED "${LCOV_CLEAN_CMD}") + message(STATUS "${LCOV_CLEAN_CMD_SPACED}") + + message(STATUS "Command to create baseline: ") + string(REPLACE ";" " " LCOV_BASELINE_CMD_SPACED "${LCOV_BASELINE_CMD}") + message(STATUS "${LCOV_BASELINE_CMD_SPACED}") + + message(STATUS "Command to run the tests: ") + string(REPLACE ";" " " LCOV_EXEC_TESTS_CMD_SPACED "${LCOV_EXEC_TESTS_CMD}") + message(STATUS "${LCOV_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to capture counters and generate report: ") + string(REPLACE ";" " " LCOV_CAPTURE_CMD_SPACED "${LCOV_CAPTURE_CMD}") + message(STATUS "${LCOV_CAPTURE_CMD_SPACED}") + + message(STATUS "Command to add baseline counters: ") + string(REPLACE ";" " " LCOV_BASELINE_COUNT_CMD_SPACED "${LCOV_BASELINE_COUNT_CMD}") + message(STATUS "${LCOV_BASELINE_COUNT_CMD_SPACED}") + + message(STATUS "Command to filter collected data: ") + string(REPLACE ";" " " LCOV_FILTER_CMD_SPACED "${LCOV_FILTER_CMD}") + message(STATUS "${LCOV_FILTER_CMD_SPACED}") + + message(STATUS "Command to generate lcov HTML output: ") + string(REPLACE ";" " " LCOV_GEN_HTML_CMD_SPACED "${LCOV_GEN_HTML_CMD}") + message(STATUS "${LCOV_GEN_HTML_CMD_SPACED}") + + if(${Coverage_SONARQUBE}) + message(STATUS "Command to generate SonarQube XML output: ") + string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") + message(STATUS "${GCOVR_XML_CMD_SPACED}") + endif() + endif() + + # Setup target + add_custom_target(${Coverage_NAME} + COMMAND ${LCOV_CLEAN_CMD} + COMMAND ${LCOV_BASELINE_CMD} + COMMAND ${LCOV_EXEC_TESTS_CMD} + COMMAND ${LCOV_CAPTURE_CMD} + COMMAND ${LCOV_BASELINE_COUNT_CMD} + COMMAND ${LCOV_FILTER_CMD} + COMMAND ${LCOV_GEN_HTML_CMD} + ${GCOVR_XML_CMD_COMMAND} + + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS + ${Coverage_NAME}.base + ${Coverage_NAME}.capture + ${Coverage_NAME}.total + ${Coverage_NAME}.info + ${GCOVR_XML_CMD_BYPRODUCTS} + ${Coverage_NAME}/index.html + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." + ) + + # Show where to find the lcov info report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info." + ${GCOVR_XML_CMD_COMMENT} + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_lcov + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_xml( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the +# GCVOR command. +function(setup_target_for_coverage_gcovr_xml) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + # Set up commands which will be run to generate coverage data + # Run tests + set(GCOVR_XML_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Running gcovr + set(GCOVR_XML_CMD + ${GCOVR_PATH} --xml ${Coverage_NAME}.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + + message(STATUS "Command to run tests: ") + string(REPLACE ";" " " GCOVR_XML_EXEC_TESTS_CMD_SPACED "${GCOVR_XML_EXEC_TESTS_CMD}") + message(STATUS "${GCOVR_XML_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to generate gcovr XML coverage data: ") + string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") + message(STATUS "${GCOVR_XML_CMD_SPACED}") + endif() + + add_custom_target(${Coverage_NAME} + COMMAND ${GCOVR_XML_EXEC_TESTS_CMD} + COMMAND ${GCOVR_XML_CMD} + + BYPRODUCTS ${Coverage_NAME}.xml + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce Cobertura code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml." + ) +endfunction() # setup_target_for_coverage_gcovr_xml + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_html( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the +# GCVOR command. +function(setup_target_for_coverage_gcovr_html) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + # Set up commands which will be run to generate coverage data + # Run tests + set(GCOVR_HTML_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Create folder + set(GCOVR_HTML_FOLDER_CMD + ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME} + ) + # Running gcovr + set(GCOVR_HTML_CMD + ${GCOVR_PATH} --html ${Coverage_NAME}/index.html --html-details -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + + message(STATUS "Command to run tests: ") + string(REPLACE ";" " " GCOVR_HTML_EXEC_TESTS_CMD_SPACED "${GCOVR_HTML_EXEC_TESTS_CMD}") + message(STATUS "${GCOVR_HTML_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to create a folder: ") + string(REPLACE ";" " " GCOVR_HTML_FOLDER_CMD_SPACED "${GCOVR_HTML_FOLDER_CMD}") + message(STATUS "${GCOVR_HTML_FOLDER_CMD_SPACED}") + + message(STATUS "Command to generate gcovr HTML coverage data: ") + string(REPLACE ";" " " GCOVR_HTML_CMD_SPACED "${GCOVR_HTML_CMD}") + message(STATUS "${GCOVR_HTML_CMD_SPACED}") + endif() + + add_custom_target(${Coverage_NAME} + COMMAND ${GCOVR_HTML_EXEC_TESTS_CMD} + COMMAND ${GCOVR_HTML_FOLDER_CMD} + COMMAND ${GCOVR_HTML_CMD} + + BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html # report directory + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce HTML code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_gcovr_html + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_fastcov( +# NAME testrunner_coverage # New target name +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES testrunner # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/" "src/dir2/" # Patterns to exclude. +# NO_DEMANGLE # Don't demangle C++ symbols +# # even if c++filt is found +# SKIP_HTML # Don't create html report +# POST_CMD perl -i -pe s!${PROJECT_SOURCE_DIR}/!!g ctest_coverage.json # E.g. for stripping source dir from file paths +# ) +function(setup_target_for_coverage_fastcov) + + set(options NO_DEMANGLE SKIP_HTML) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES FASTCOV_ARGS GENHTML_ARGS POST_CMD) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT FASTCOV_PATH) + message(FATAL_ERROR "fastcov not found! Aborting...") + endif() + + if(NOT Coverage_SKIP_HTML AND NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (Patterns, not paths, for fastcov) + set(FASTCOV_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_FASTCOV_EXCLUDES}) + list(APPEND FASTCOV_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES FASTCOV_EXCLUDES) + + # Conditional arguments + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) + set(GENHTML_EXTRA_ARGS "--demangle-cpp") + endif() + + # Set up commands which will be run to generate coverage data + set(FASTCOV_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}) + + set(FASTCOV_CAPTURE_CMD ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} + --search-directory ${BASEDIR} + --process-gcno + --output ${Coverage_NAME}.json + --exclude ${FASTCOV_EXCLUDES} + ) + + set(FASTCOV_CONVERT_CMD ${FASTCOV_PATH} + -C ${Coverage_NAME}.json --lcov --output ${Coverage_NAME}.info + ) + + if(Coverage_SKIP_HTML) + set(FASTCOV_HTML_CMD ";") + else() + set(FASTCOV_HTML_CMD ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} + -o ${Coverage_NAME} ${Coverage_NAME}.info + ) + endif() + + set(FASTCOV_POST_CMD ";") + if(Coverage_POST_CMD) + set(FASTCOV_POST_CMD ${Coverage_POST_CMD}) + endif() + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Code coverage commands for target ${Coverage_NAME} (fastcov):") + + message(" Running tests:") + string(REPLACE ";" " " FASTCOV_EXEC_TESTS_CMD_SPACED "${FASTCOV_EXEC_TESTS_CMD}") + message(" ${FASTCOV_EXEC_TESTS_CMD_SPACED}") + + message(" Capturing fastcov counters and generating report:") + string(REPLACE ";" " " FASTCOV_CAPTURE_CMD_SPACED "${FASTCOV_CAPTURE_CMD}") + message(" ${FASTCOV_CAPTURE_CMD_SPACED}") + + message(" Converting fastcov .json to lcov .info:") + string(REPLACE ";" " " FASTCOV_CONVERT_CMD_SPACED "${FASTCOV_CONVERT_CMD}") + message(" ${FASTCOV_CONVERT_CMD_SPACED}") + + if(NOT Coverage_SKIP_HTML) + message(" Generating HTML report: ") + string(REPLACE ";" " " FASTCOV_HTML_CMD_SPACED "${FASTCOV_HTML_CMD}") + message(" ${FASTCOV_HTML_CMD_SPACED}") + endif() + if(Coverage_POST_CMD) + message(" Running post command: ") + string(REPLACE ";" " " FASTCOV_POST_CMD_SPACED "${FASTCOV_POST_CMD}") + message(" ${FASTCOV_POST_CMD_SPACED}") + endif() + endif() + + # Setup target + add_custom_target(${Coverage_NAME} + + # Cleanup fastcov + COMMAND ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} + --search-directory ${BASEDIR} + --zerocounters + + COMMAND ${FASTCOV_EXEC_TESTS_CMD} + COMMAND ${FASTCOV_CAPTURE_CMD} + COMMAND ${FASTCOV_CONVERT_CMD} + COMMAND ${FASTCOV_HTML_CMD} + COMMAND ${FASTCOV_POST_CMD} + + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS + ${Coverage_NAME}.info + ${Coverage_NAME}.json + ${Coverage_NAME}/index.html # report directory + + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero. Processing code coverage counters and generating report." + ) + + set(INFO_MSG "fastcov code coverage info report saved in ${Coverage_NAME}.info and ${Coverage_NAME}.json.") + if(NOT Coverage_SKIP_HTML) + string(APPEND INFO_MSG " Open ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html in your browser to view the coverage report.") + endif() + # Show where to find the fastcov info report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E echo ${INFO_MSG} + ) + +endfunction() # setup_target_for_coverage_fastcov + +function(append_coverage_compiler_flags) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}") +endfunction() # append_coverage_compiler_flags + +# Setup coverage for specific library +function(append_coverage_compiler_flags_to_target name) + separate_arguments(_flag_list NATIVE_COMMAND "${COVERAGE_COMPILER_FLAGS}") + target_compile_options(${name} PRIVATE ${_flag_list}) + if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + target_link_libraries(${name} PRIVATE gcov) + endif() +endfunction() \ No newline at end of file diff --git a/doxydir/1_setup.md b/doxydir/1_setup.md deleted file mode 100644 index 1dbacd9..0000000 --- a/doxydir/1_setup.md +++ /dev/null @@ -1,32 +0,0 @@ - -# Setup - -## Prerequisites -- [json-c](https://github.com/json-c/json-c) -- [cmake version 3.6](https://cmake.org/) - -## Installation - -### Build -Clone the git repository and it's submodules: -\code -git clone git clone --recurse-submodules git@git01.iis.fhg.de:ks-ip-lib/software/libjapi.git -\endcode - -Create a build directory in the libjapi repository and run `cmake`. -\code -cd libjapi -mkdir build/ -cd build/ -cmake ../ -\endcode - -A Makefile is generated. Run `make` to build the libjapi libraries. -\code -make -\endcode -A shared and a static library is built. - -### Packages -Prebuild packages can be found here. - diff --git a/doxydir/3_usage.md b/doxydir/1_usage.md similarity index 77% rename from doxydir/3_usage.md rename to doxydir/1_usage.md index 0277474..7528992 100644 --- a/doxydir/3_usage.md +++ b/doxydir/1_usage.md @@ -19,6 +19,8 @@ ctx = japi_init(NULL); japi_register_request(ctx,"function_name",&function_handler); \endcode +Note, that "function_name" may not start with "japi_" which is used to mark internal commands. + The function will be saved in the passed JAPI context. The server which manages the requests, is started with: \code japi_start_server(ctx,8080); @@ -35,3 +37,8 @@ ctx = japi_init(object_pointer); ctx->userptr ... #access to passed argument \endcode +## Default handler +There is a default `japi_request_not_found_handler` which responds with an error +message if an unknown request is received. If you want to change that behavior, +you can register a `request_not_found_handler` which will then be used instead +to behave as you desire. diff --git a/doxydir/2_demo.md b/doxydir/2_demo.md deleted file mode 100644 index 3cac733..0000000 --- a/doxydir/2_demo.md +++ /dev/null @@ -1,9 +0,0 @@ - -# Demo -You can clone the demo project, with examples for all features from the repository listed below: - -\code -git clone --recurse-submodules git@git01.iis.fhg.de:ks-ip-lib/software/libjapi-demo.git -\endcode - -Follow the instructions given in libjapi-demo's README.md file. diff --git a/doxydir/8_examples.md b/doxydir/2_examples.md similarity index 66% rename from doxydir/8_examples.md rename to doxydir/2_examples.md index 19e20b8..58f27b4 100644 --- a/doxydir/8_examples.md +++ b/doxydir/2_examples.md @@ -1,6 +1,6 @@ # Examples -To view the full example take a look here. +To view the full example take a look [here](https://github.com/Fraunhofer-IIS/libjapi/blob/master/doxydir/demo.cpp). \anchor serverExample ## Server example diff --git a/doxydir/4_communication_concepts.md b/doxydir/3_communication_concepts.md similarity index 100% rename from doxydir/4_communication_concepts.md rename to doxydir/3_communication_concepts.md diff --git a/doxydir/9_references.md b/doxydir/9_references.md deleted file mode 100644 index b4325b3..0000000 --- a/doxydir/9_references.md +++ /dev/null @@ -1,5 +0,0 @@ -## References -* https://github.com/json-c/json-c -* http://json-c.github.io/json-c/ -* https://en.wikipedia.org/wiki/JSON -* https://alan-mushi.github.io/2014/10/28/json-c-tutorial-part-1.html diff --git a/doxydir/demo.cpp b/doxydir/demo.cpp index 369d3c0..bae61ba 100644 --- a/doxydir/demo.cpp +++ b/doxydir/demo.cpp @@ -9,9 +9,26 @@ * \details * This application demonstrates the usage of the JSON API library (libjapi). * - * \copyright - * Copyright (c) 2018 Fraunhofer IIS. - * All rights reserved. + *\copyright + * Copyright (c) 2023 Fraunhofer IIS + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ #include @@ -20,27 +37,36 @@ #include #include /* sleep */ -#include -#include -#include +#include "japi.h" +#include "japi_pushsrv.h" +#include "japi_utils.h" + +/* Create a resource structure to pass to libjapi*/ +typedef struct resources +{ + double temperature; +}resources; /* * User defined push temperature service routine. * Simulates a circular sinus push value. */ -int push_temperature(japi_pushsrv_context *psc) +void push_temperature(japi_pushsrv_context *psc) { json_object *jmsg; double i; assert(psc != NULL); + /* Get back pointer on resources (i.e for example sensors values) */ + resources* sensor_values = (resources*) psc->userptr; + jmsg = json_object_new_object(); while (psc->enabled) { for (i = 0.0; i <= 3.14; i += 0.1) { /* Create JSON response string */ - json_object_object_add(jmsg,"temperature",json_object_new_double(30+10*sin(i))); + json_object_object_add(jmsg,"temperature",json_object_new_double(sensor_values->temperature+10*sin(i))); /* Push message */ japi_pushsrv_sendmsg(psc,jmsg); @@ -49,12 +75,10 @@ int push_temperature(japi_pushsrv_context *psc) } json_object_put(jmsg); - - return 0; } /* User defined push service routine */ -int push_counter(japi_pushsrv_context *psc) +void push_counter(japi_pushsrv_context *psc) { json_object *jmsg; int i; @@ -75,8 +99,6 @@ int push_counter(japi_pushsrv_context *psc) sleep(1); } json_object_put(jmsg); - - return 0; } static void rnf_handler(japi_context *ctx, json_object *request, json_object *response) @@ -89,14 +111,15 @@ static void get_temperature(japi_context *ctx, json_object *request, json_object double temperature; const char *unit; - /* - * TODO: Read the temperature from a sensor... - */ - temperature = 27.0; + /* Get back pointer on resources (i.e for example sensors values) */ + resources* sensor_values = (resources*) ctx->userptr; + temperature = sensor_values->temperature; /* Provide the temperature in KELVIN (if requested) * or CELSIUS (default) */ - unit = japi_get_value_as_str(request, "unit"); + if (japi_get_value_as_str(request, "unit", &unit) != 0 ) { + fprintf(stderr, "Failed to get string value from key 'unit'\n"); + } if (unit != NULL && strcmp(unit, "kelvin") == 0) { temperature += 273; } else { @@ -105,7 +128,6 @@ static void get_temperature(japi_context *ctx, json_object *request, json_object /* Prepare and provide response */ json_object_object_add(response, "temperature", json_object_new_double(temperature)); - json_object_object_add(response, "unit", json_object_new_string(unit)); } int main(int argc, char *argv[]) @@ -114,6 +136,9 @@ int main(int argc, char *argv[]) japi_context *ctx; japi_pushsrv_context *psc_counter, *psc_temperature; + /* Declare & initialise resources*/ + resources temperature_sensor = {17.0}; + /* Read port */ if (argc != 2) { fprintf(stderr, "ERROR: Missing argument or wrong amount of arguments.\n" \ @@ -122,12 +147,15 @@ int main(int argc, char *argv[]) } /* Create JSON API context */ - ctx = japi_init(NULL); + ctx = japi_init(&temperature_sensor); if (ctx == NULL) { fprintf(stderr, "ERROR: Failed to create japi context\n"); return -1; } + /* Include request args in response */ + japi_include_args_in_response(ctx, true); + /* Register JSON API requests */ japi_register_request(ctx, "request_not_found_handler", &rnf_handler); japi_register_request(ctx, "get_temperature", &get_temperature); @@ -137,16 +165,15 @@ int main(int argc, char *argv[]) psc_temperature = japi_pushsrv_register(ctx, "push_temperature"); /* Start push threads */ - japi_pushsrv_start(psc_counter,&push_counter); - japi_pushsrv_start(psc_temperature,&push_temperature); + japi_pushsrv_start(psc_counter, &push_counter); + japi_pushsrv_start(psc_temperature, &push_temperature); + + /* Set maximal number of allowed clients. 0 for unlimited */ + japi_set_max_allowed_clients(ctx, 3); /* Provide JSON API interface via TCP */ ret = japi_start_server(ctx, argv[1]); - /* Wait for the threads to finish */ - japi_pushsrv_stop(psc_counter); - japi_pushsrv_stop(psc_temperature); - /* Destroy JAPI context */ japi_destroy(ctx); diff --git a/doxydir/test.py b/doxydir/test.py index 51e942f..86c058f 100755 --- a/doxydir/test.py +++ b/doxydir/test.py @@ -1,5 +1,27 @@ #!/usr/bin/env python3 +# Copyright (c) 2023 Fraunhofer IIS +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the “Software”), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + + +import errno import json import socket import select @@ -10,7 +32,9 @@ def process_request(cmd_request,sock): resp = sock.recv(4096) - print(json.dumps(json.loads(resp.decode("utf-8")), indent=4)) + # Just dump if not empty + if resp: + print(json.dumps(json.loads(resp.decode("utf-8")), indent=4)) def main(): IP = 'localhost' @@ -19,38 +43,32 @@ def main(): try: sock = socket.create_connection((IP, TCP_PORT)) - except (ConnectionError): - log.error("A connection to %s:%s could not be established.", *addr) - return {} - + except socket.error as e: + if e.errno != errno.ECONNREFUSED: + print("Exception was thrown. Message is %s" % (e)) + return 1 + print("A connection to '%s:%d' could not be established." %(IP, TCP_PORT)) + return 1 + cmd_request = { "japi_request": "get_temperature", - "unit": "kelvin", + "args": {"unit": "kelvin"}, } push_service_request = { "japi_request": "japi_pushsrv_list", } push_temperature_request_subscr = { "japi_request": "japi_pushsrv_subscribe", - "service": "push_temperature", + "args": {"service": "push_temperature"}, } push_temperature_request_unsubscr = { "japi_request": "japi_pushsrv_unsubscribe", - "service": "push_temperature", - } - push_counter_request_subscr = { - "japi_request": "japi_pushsrv_subscribe", - "service": "push_counter", - } - push_counter_request_unsubscr = { - "japi_request": "japi_pushsrv_unsubscribe", - "service": "push_counter", + "args": {"service": "push_temperature"}, } process_request(cmd_request,sock) process_request(push_service_request,sock) process_request(push_temperature_request_subscr,sock) - process_request(push_counter_request_subscr,sock) # get push service messages sock.settimeout(10) @@ -58,15 +76,16 @@ def main(): resp = sock.recv(4096) # Iterate line by line for n, line in enumerate(sock.makefile(), start=1): - jdata = json.loads(line) - print(json.dumps(json.loads(line), indent=4)) + # Just dump if received message is not empty + if line: + jdata = json.loads(line) + print(json.dumps(json.loads(line), indent=4)) except: pass finally: pass process_request(push_temperature_request_unsubscr,sock) - process_request(push_counter_request_unsubscr,sock) sock.close() diff --git a/gitlab-ci/Dockerfile b/gitlab-ci/Dockerfile index 44c724b..293f3a0 100644 --- a/gitlab-ci/Dockerfile +++ b/gitlab-ci/Dockerfile @@ -1,6 +1,4 @@ FROM centos:7 -MAINTAINER Deniz Armagan # Install dependencies -RUN yum install -y epel-release json-c-devel gcc gcc-c++ make git rpm-build doxygen graphviz createrepo -RUN yum install -y cmake3 +RUN yum install -y epel-release json-c-devel gcc gcc-c++ make rpm-build doxygen graphviz createrepo cmake3 diff --git a/gitlab-ci/docker-build.sh b/gitlab-ci/docker-build.sh deleted file mode 100755 index 6358501..0000000 --- a/gitlab-ci/docker-build.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh - -set -e - -# create a temporary directory for the build -BUILDDIR=$(mktemp -d) - -cp Dockerfile $BUILDDIR/ - -echo "Setting up docker build context in $BUILDDIR ..." - -cd $BUILDDIR - -docker build -t libjapi:latest . - -# clean up -cd / - -echo "Build completed successfully. Removing $BUILDDIR ..." -rm -rf "$BUILDDIR" - diff --git a/gitlab-ci/docker-upload.sh b/gitlab-ci/docker-upload.sh deleted file mode 100755 index a4b26c5..0000000 --- a/gitlab-ci/docker-upload.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh - -# see https://git01.iis.fhg.de/ks-ip-lib/software/libjapi/container_registry - -set -e # terminate on any error - -LOCAL_IMAGE="libjapi:latest" -REGISTRY="git01.iis.fhg.de:5005" -GITLAB_IMAGE="ks-ip-lib/software/libjapi" - -docker tag $LOCAL_IMAGE $REGISTRY/$GITLAB_IMAGE -docker login $REGISTRY -docker push $REGISTRY/$GITLAB_IMAGE -docker logout $REGISTRY - diff --git a/include/creadline.h b/include/creadline.h index 98fe7a7..a380d0e 100644 --- a/include/creadline.h +++ b/include/creadline.h @@ -10,9 +10,26 @@ * This readline implementation reads a single line from a file descriptor * (e.g. a socket). Two versions are provided. * - * \copyright - * Copyright (c) 2018 Fraunhofer IIS. - * All rights reserved. + *\copyright + * Copyright (c) 2023 Fraunhofer IIS + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ #ifndef __CREADLINE_H__ diff --git a/include/japi.h b/include/japi.h index 3f1ca9c..f30bf8f 100644 --- a/include/japi.h +++ b/include/japi.h @@ -9,19 +9,37 @@ * \details * libjapi is a universal JSON API library. * - * \copyright - * Copyright (c) 2018 Fraunhofer IIS. - * All rights reserved. + *\copyright + * Copyright (c) 2023 Fraunhofer IIS + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ #ifndef __JAPI_H__ #define __JAPI_H__ #include -#include #include #include +#include "creadline.h" + #ifdef __cplusplus extern "C" { #endif @@ -37,10 +55,12 @@ typedef struct __japi_context { uint16_t max_clients; /*!< Number of maximal allowed clients */ pthread_mutex_t lock; /*!< Mutual access lock */ struct __japi_request *requests; /*!< Pointer to the JAPI request list */ - struct __japi_pushsrv_context *push_services; /*!< Pointer to the JAPI push service list */ + struct __japi_pushsrv_context + *push_services; /*!< Pointer to the JAPI push service list */ struct __japi_client *clients; /*!< Pointer to the JAPI client context */ bool include_args_in_response; /*!< Flag to include request args in response */ bool shutdown; /*!< Flag to shutdown the JAPI server */ + bool init; /*!< Flag to mark finished initialization */ } japi_context; /*! @@ -51,23 +71,25 @@ typedef struct __japi_context { typedef struct __japi_client { int socket; /*!< Socket to connect */ creadline_buf_t crl_buffer; /*!< Buffer used by creadline_r() */ - struct __japi_client* next; /*!< Pointer to the next client struct or NULL */ + struct __japi_client *next; /*!< Pointer to the next client struct or NULL */ } japi_client; /*! * \brief JAPI request handler type. */ -typedef void (*japi_req_handler)(japi_context *ctx, json_object *request, json_object *response); +typedef void (*japi_req_handler)(japi_context *ctx, json_object *request, + json_object *response); /*! * \brief JAPI request struct. * - * A JAPI request struct is a mapping between a unique request name and a JAPI request handler. + * A JAPI request struct is a mapping between a unique request name and a JAPI request + * handler. */ typedef struct __japi_request { const char *name; /*!< Printable name of the request */ japi_req_handler func; /*!< Function to call */ - struct __japi_request* next; /*!< Pointer to the next request struct or NULL */ + struct __japi_request *next; /*!< Pointer to the next request struct or NULL */ } japi_request; /*! @@ -80,7 +102,7 @@ typedef struct __japi_request { * * \returns On success, a japi_context object is returned. On error, NULL is returned. */ -japi_context* japi_init(void *userptr); +japi_context *japi_init(void *userptr); /*! * \brief Destroy a JAPI context. @@ -103,13 +125,16 @@ int japi_destroy(japi_context *ctx); * \param req_name Request name * \param req_handler Function pointer * - * \returns On success, zero is returned. On error, -1 for empty JAPI context, + * \returns On success, zero is returned. On error, + * -1 for empty JAPI context, * -2 for empty request name, * -3 for empty request handler, * -4 for duplicate naming, - * -5 for failed memory allocation, is returned. + * -5 for failed memory allocation, + * -6 for bad request name (starting with "japi_") is returned. */ -int japi_register_request(japi_context *ctx, const char *req_name, japi_req_handler req_handler); +int japi_register_request(japi_context *ctx, const char *req_name, + japi_req_handler req_handler); /*! * \brief Start a JAPI server @@ -131,7 +156,8 @@ int japi_start_server(japi_context *ctx, const char *port); * \param ctx JAPI context * \param num Number of clients to be allowed. 0 stands for unlimited. * - * \returns On success, zero is returned. On error, -1 for empty JAPI context, is returned. + * \returns On success, zero is returned. On error, -1 for empty JAPI context, is + * returned. */ int japi_set_max_allowed_clients(japi_context *ctx, uint16_t num); @@ -143,7 +169,8 @@ int japi_set_max_allowed_clients(japi_context *ctx, uint16_t num); * \param ctx JAPI context * \param include_args Include request arguments in response. * - * \returns On success, zero is returned. On error, -1 for empty JAPI context, is returned. + * \returns On success, zero is returned. On error, -1 for empty JAPI context, is + * returned. */ int japi_include_args_in_response(japi_context *ctx, bool include_args); @@ -154,7 +181,8 @@ int japi_include_args_in_response(japi_context *ctx, bool include_args); * * \param ctx JAPI context * - * \returns On success, zero is returned. On error, -1 for empty JAPI context, is returned. + * \returns On success, zero is returned. On error, -1 for empty JAPI context, is + * returned. */ int japi_shutdown(japi_context *ctx); diff --git a/include/japi_pushsrv.h b/include/japi_pushsrv.h index 0997d7b..a385fda 100644 --- a/include/japi_pushsrv.h +++ b/include/japi_pushsrv.h @@ -10,8 +10,25 @@ * japi_pushsrv is a universal JSON API library. * * \copyright - * Copyright (c) 2019 Fraunhofer IIS. - * All rights reserved. + * Copyright (c) 2023 Fraunhofer IIS + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ #ifndef __JAPI_PUSHSRV_H__ @@ -64,15 +81,19 @@ typedef struct __japi_pushsrv_context { japi_pushsrv_context* japi_pushsrv_register(japi_context *ctx, const char *pushsrv_name); /*! - * \brief Unsubscribes and frees memory space for all push service clients - * - * Iterates through push service clients and unsubscribes and frees memory. - * - * \param psc JAPI push service context + * \brief Remove push service context from japi context, unsubscribe for all clients and free memory + * + * Clean up the push service if no more needed: + * * remove entry from linked list in `ctx->push_services` + * * unsubscribe all clients and free their memory + * * stop the push service and free the used memory * + * \param ctx JAPI context + * \param psc JAPI push service context which is to be removed + * * \returns On success, 0 is returned. On error, -1 is returned. */ -int japi_pushsrv_destroy(japi_pushsrv_context *psc); +int japi_pushsrv_destroy(japi_context *ctx, japi_pushsrv_context *psc); /*! * \brief Send messages to all subscribed clients diff --git a/include/japi_utils.h b/include/japi_utils.h index 111e501..38817ff 100644 --- a/include/japi_utils.h +++ b/include/japi_utils.h @@ -9,9 +9,26 @@ * \details * libjapi is a universal JSON API library. * - * \copyright - * Copyright (c) 2018 Fraunhofer IIS. - * All rights reserved. + *\copyright + * Copyright (c) 2023 Fraunhofer IIS + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ #ifndef __JAPI_UTILS_H__ diff --git a/include/networking.h b/include/networking.h index 877b168..4eaca38 100644 --- a/include/networking.h +++ b/include/networking.h @@ -9,9 +9,26 @@ * \details * This module collects networking helper functions like tcp_start_server(). * - * \copyright - * Copyright (c) 2018 Fraunhofer IIS. - * All rights reserved. + *\copyright + * Copyright (c) 2023 Fraunhofer IIS + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ #ifndef __NETWORKING_H__ diff --git a/include/rw_n.h b/include/rw_n.h index 2ce4549..04979e1 100644 --- a/include/rw_n.h +++ b/include/rw_n.h @@ -10,9 +10,26 @@ * Use read_n() to read or write_n() to write a fixed number of bytes from or * to a file descriptor. * - * \copyright - * Copyright (c) 2018 Fraunhofer IIS. - * All rights reserved. + *\copyright + * Copyright (c) 2023 Fraunhofer IIS + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ #ifndef __RW_N_H__ diff --git a/package/create_rpm.sh b/package/create_rpm.sh index 22f07a4..abd93ce 100755 --- a/package/create_rpm.sh +++ b/package/create_rpm.sh @@ -1,4 +1,5 @@ #!/bin/bash +# Copyright (c) 2023 Fraunhofer IIS ###--------------------------------------------------- # 0. change to target directory and cleanup environment diff --git a/src/creadline.c b/src/creadline.c index 430501a..63b6826 100644 --- a/src/creadline.c +++ b/src/creadline.c @@ -10,9 +10,26 @@ * This readline implementation reads a single line from a file descriptor * (e.g. a socket). Two versions are provided. * - * \copyright - * Copyright (c) 2018 Fraunhofer IIS. - * All rights reserved. + *\copyright + * Copyright (c) 2023 Fraunhofer IIS + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ #include diff --git a/src/japi.c b/src/japi.c index bc814d9..57af2e4 100644 --- a/src/japi.c +++ b/src/japi.c @@ -10,17 +10,34 @@ * libjapi is a universal JSON API library. * * \copyright - * Copyright (c) 2018 Fraunhofer IIS. - * All rights reserved. + * Copyright (c) 2023 Fraunhofer IIS + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ #include #include #include -#include /* strcasecmp */ #include /* strcmp */ -#include +#include /* strcasecmp */ #include +#include #include #include @@ -28,13 +45,12 @@ #include "creadline.h" #include "japi_intern.h" -#include "japi_pushsrv_intern.h" #include "japi_pushsrv.h" +#include "japi_pushsrv_intern.h" #include "japi_utils.h" -#include "rw_n.h" #include "networking.h" #include "prntdbg.h" - +#include "rw_n.h" /* Look for a request handler matching the name 'name'. * @@ -64,9 +80,10 @@ static japi_req_handler japi_get_request_handler(japi_context *ctx, const char * * - Prepare the JSON response * - Free memory */ -int japi_process_message(japi_context *ctx, const char *request, char **response, int socket) +int japi_process_message(japi_context *ctx, const char *request, char **response, + int socket) { - const char* req_name; + const char *req_name; json_object *jreq; json_object *jreq_no; json_object *jresp; @@ -77,9 +94,8 @@ int japi_process_message(japi_context *ctx, const char *request, char **response bool args; assert(ctx != NULL); - assert(request != NULL); assert(response != NULL); - assert(socket != -1); + assert(socket >= 0); ret = -1; *response = NULL; @@ -87,20 +103,21 @@ int japi_process_message(japi_context *ctx, const char *request, char **response /* Create JSON object from received message */ jreq = json_tokener_parse(request); if (jreq == NULL) { - fprintf(stderr, "ERROR: json_tokener_parse() failed. Received message: %s\n", request); + fprintf(stderr, "ERROR: json_tokener_parse() failed. Received message: %s\n", + request); return -1; } - + /* Only create new JSON objects after a valid JSON request was parsed. */ jresp = json_object_new_object(); /* Response object */ jresp_data = json_object_new_object(); - if ((japi_get_value_as_str(jreq, "japi_request", &req_name)) == 0) { /* Prepare response */ - json_object_object_add(jresp, "japi_response", json_object_new_string(req_name)); - + json_object_object_add(jresp, "japi_response", + json_object_new_string(req_name)); + /* Include japi_request_no in response, if included with request */ if (json_object_object_get_ex(jreq, "japi_request_no", &jreq_no)) { json_object_get(jreq_no); @@ -109,11 +126,11 @@ int japi_process_message(japi_context *ctx, const char *request, char **response /* Get arguments as an JSON object */ args = json_object_object_get_ex(jreq, "args", &jargs); - + /* Add an empty args JSON object if no args were given Otherwise, include args with response, if configured. */ if (!args) { - json_object_object_add(jreq,"args",NULL); + json_object_object_add(jreq, "args", NULL); json_object_object_get_ex(jreq, "args", &jargs); } else { if (ctx->include_args_in_response) { @@ -122,32 +139,37 @@ int japi_process_message(japi_context *ctx, const char *request, char **response } } - /* Look for subscribe/unsubscribe service and add/remove client socket if found */ - if (strcasecmp(req_name,"japi_pushsrv_subscribe") == 0) { - japi_pushsrv_subscribe(ctx,socket,jargs,jresp_data); - } else if (strcasecmp(req_name,"japi_pushsrv_unsubscribe") == 0) { - japi_pushsrv_unsubscribe(ctx,socket,jargs,jresp_data); - } else { + /* Subscribe/unsubscribe service needs client socket */ + if (strcasecmp(req_name, "japi_pushsrv_subscribe") == 0 || + strcasecmp(req_name, "japi_pushsrv_unsubscribe") == 0) { + json_object_object_add(jargs, "socket", json_object_new_int(socket)); + } - /* Try to find a suitable handler for the given request */ - req_handler = japi_get_request_handler(ctx, req_name); - if (req_handler == NULL) { + /* Try to find a suitable handler for the given request */ + req_handler = japi_get_request_handler(ctx, req_name); + if (req_handler == NULL) { - /* No request handler found? Check if a fallback handler was registered. */ - req_handler = japi_get_request_handler(ctx, "request_not_found_handler"); + /* No request handler found? Check if a fallback handler was registered. */ + req_handler = japi_get_request_handler(ctx, "request_not_found_handler"); - if (req_handler == NULL) { - fprintf(stderr, "ERROR: No suitable request handler found. Request was: %s\n", req_name); - goto out_free; - } else { - fprintf(stderr, "WARNING: No suitable request handler found. Falling back to registered fallback handler. Request was: %s\n", req_name); - } + if (req_handler == NULL) { + fprintf(stderr, + "ERROR: No suitable request handler found. Falling back to " + "default fallback handler. Request was: %s\n", + req_name); + req_handler = + japi_get_request_handler(ctx, "japi_request_not_found_handler"); + } else { + fprintf(stderr, + "WARNING: No suitable request handler found. Falling back to " + "user registered fallback handler. Request was: %s\n", + req_name); } - - /* Call request handler */ - req_handler(ctx, jargs, jresp_data); } + /* Call request handler */ + req_handler(ctx, jargs, jresp_data); + } else { /* Get request name */ if (req_name == NULL) { @@ -181,7 +203,7 @@ int japi_shutdown(japi_context *ctx) ctx->shutdown = true; - return 0; + return 0; } int japi_destroy(japi_context *ctx) @@ -204,7 +226,7 @@ int japi_destroy(japi_context *ctx) psc = ctx->push_services; while (psc != NULL) { psc_next = psc->next; - japi_pushsrv_destroy(psc); + japi_pushsrv_destroy(ctx, psc); psc = psc_next; } @@ -214,9 +236,11 @@ int japi_destroy(japi_context *ctx) return 0; } -int japi_register_request(japi_context* ctx, const char *req_name, japi_req_handler req_handler) +int japi_register_request(japi_context *ctx, const char *req_name, + japi_req_handler req_handler) { japi_request *req; + char *bad_req_name = "japi_"; /* Error handling */ if (ctx == NULL) { @@ -224,7 +248,7 @@ int japi_register_request(japi_context* ctx, const char *req_name, japi_req_hand return -1; } - if ((req_name == NULL) || (strcmp(req_name,"") == 0)) { + if ((req_name == NULL) || (strcmp(req_name, "") == 0)) { fprintf(stderr, "ERROR: Request name is NULL or empty.\n"); return -2; } @@ -234,11 +258,18 @@ int japi_register_request(japi_context* ctx, const char *req_name, japi_req_hand return -3; } - if (japi_get_request_handler(ctx,req_name) != NULL) { - fprintf(stderr,"ERROR: A request handler called '%s' was already registered.\n",req_name); + if (japi_get_request_handler(ctx, req_name) != NULL) { + fprintf(stderr, + "ERROR: A request handler called '%s' was already registered.\n", + req_name); return -4; } + if (ctx->init && strncmp(req_name, bad_req_name, strlen(bad_req_name)) == 0) { + fprintf(stderr, "ERROR: Request name is not allowed.\n"); + return -6; + } + req = (japi_request *)malloc(sizeof(japi_request)); if (req == NULL) { perror("ERROR: malloc() failed"); @@ -254,7 +285,7 @@ int japi_register_request(japi_context* ctx, const char *req_name, japi_req_hand return 0; } -japi_context* japi_init(void *userptr) +japi_context *japi_init(void *userptr) { japi_context *ctx; @@ -264,6 +295,7 @@ japi_context* japi_init(void *userptr) return NULL; } + ctx->init = false; ctx->userptr = userptr; ctx->requests = NULL; ctx->push_services = NULL; @@ -274,16 +306,25 @@ japi_context* japi_init(void *userptr) ctx->shutdown = false; /* Initialize mutex */ - if (pthread_mutex_init(&(ctx->lock),NULL) != 0) { - fprintf(stderr,"ERROR: mutex initialization has failed\n"); + if (pthread_mutex_init(&(ctx->lock), NULL) != 0) { + fprintf(stderr, "ERROR: mutex initialization has failed\n"); return NULL; } - /* Ignore SIGPIPE Signal */ + /* Ignore SIGPIPE Signal */ signal(SIGPIPE, SIG_IGN); + /* Register the default fallback handler */ + japi_register_request(ctx, "japi_request_not_found_handler", + &japi_request_not_found_handler); + /* Register subscribe/unsubscribe service function */ + japi_register_request(ctx, "japi_pushsrv_subscribe", &japi_pushsrv_subscribe); + japi_register_request(ctx, "japi_pushsrv_unsubscribe", &japi_pushsrv_unsubscribe); /* Register list_push_service function */ japi_register_request(ctx, "japi_pushsrv_list", &japi_pushsrv_list); + japi_register_request(ctx, "japi_cmd_list", &japi_cmd_list); + + ctx->init = true; return ctx; } @@ -296,10 +337,10 @@ int japi_set_max_allowed_clients(japi_context *ctx, uint16_t num) { /* Error handling */ if (ctx == NULL) { - fprintf(stderr, "ERROR: JAPI context is NULL.\n"); - return -1; - } - + fprintf(stderr, "ERROR: JAPI context is NULL.\n"); + return -1; + } + ctx->max_clients = num; return 0; @@ -315,7 +356,7 @@ int japi_include_args_in_response(japi_context *ctx, bool include_args) fprintf(stderr, "ERROR: JAPI context is NULL.\n"); return -1; } - + ctx->include_args_in_response = include_args; return 0; @@ -330,7 +371,7 @@ int japi_add_client(japi_context *ctx, int socket) /* Error handling */ assert(ctx != NULL); - assert(socket > 0); + assert(socket >= 0); /* Create new client list element */ client = (japi_client *)malloc(sizeof(japi_client)); @@ -343,7 +384,7 @@ int japi_add_client(japi_context *ctx, int socket) client->crl_buffer.nbytes = 0; pthread_mutex_lock(&(ctx->lock)); - prntdbg("adding client %d to japi context\n",socket); + prntdbg("adding client %d to japi context\n", socket); /* Add socket */ client->socket = socket; @@ -367,13 +408,13 @@ int japi_remove_client(japi_context *ctx, int socket) /* Error Handling */ assert(ctx != NULL); - assert(socket > 0); + assert(socket >= 0); client = ctx->clients; prev = NULL; ret = -1; - japi_pushsrv_remove_client_from_all_pushsrv(ctx,socket); + japi_pushsrv_remove_client_from_all_pushsrv(ctx, socket); pthread_mutex_lock(&(ctx->lock)); /* Remove client from list */ @@ -381,7 +422,8 @@ int japi_remove_client(japi_context *ctx, int socket) /* If first element */ if ((client->socket == socket) && (prev == NULL)) { ctx->clients = client->next; - prntdbg("removing client %d from japi context and close socket\n",client->socket); + prntdbg("removing client %d from japi context and close socket\n", + client->socket); close(client->socket); free(client); ctx->num_clients--; @@ -391,7 +433,8 @@ int japi_remove_client(japi_context *ctx, int socket) /* If last element */ if ((client->socket == socket) && (client->next == NULL)) { prev->next = NULL; - prntdbg("removing client %d from japi context and close socket\n",client->socket); + prntdbg("removing client %d from japi context and close socket\n", + client->socket); close(client->socket); free(client); ctx->num_clients--; @@ -400,7 +443,8 @@ int japi_remove_client(japi_context *ctx, int socket) } if (client->socket == socket) { prev->next = client->next; - prntdbg("removing client %d from japi context and close socket\n",client->socket); + prntdbg("removing client %d from japi context and close socket\n", + client->socket); close(client->socket); free(client); ctx->num_clients--; @@ -416,17 +460,17 @@ int japi_remove_client(japi_context *ctx, int socket) return ret; } -int japi_remove_all_clients(japi_context *ctx) +int japi_remove_all_clients(japi_context *ctx) { japi_client *client, *following_client; /* Error Handling */ assert(ctx != NULL); - + client = ctx->clients; while (client != NULL) { following_client = client->next; - if (japi_remove_client(ctx,client->socket) != 0) { + if (japi_remove_client(ctx, client->socket) != 0) { return -1; } client = following_client; @@ -500,12 +544,13 @@ int japi_start_server(japi_context *ctx, const char *port) if (FD_ISSET(client->socket, &fdrd)) { int ret; - char* request; - char* response; + char *request; + char *response; do { - ret = creadline_r(client->socket, (void**)&request, &(client->crl_buffer)); + ret = creadline_r(client->socket, (void **)&request, + &(client->crl_buffer)); if (ret > 0) { response = NULL; @@ -515,8 +560,8 @@ int japi_start_server(japi_context *ctx, const char *port) /* After the request buffer is processed, the memory *is not needed anymore and can be freed at this point. */ - free(request); - + free(request); + /* Send response (if provided) */ if (response != NULL) { ret = write_n(client->socket, response, strlen(response)); @@ -524,16 +569,19 @@ int japi_start_server(japi_context *ctx, const char *port) if (ret <= 0) { /* Write failed */ - fprintf(stderr, "ERROR: Failed to send response to client %i (write returned %i)\n",client->socket, ret); - japi_remove_client(ctx,client->socket); + fprintf(stderr, + "ERROR: Failed to send response to client %i " + "(write returned %i)\n", + client->socket, ret); + japi_remove_client(ctx, client->socket); break; } } } else if (ret == 0) { - if(request == NULL) { + if (request == NULL) { /* Received EOF (client disconnected) */ - prntdbg("client %d disconnected\n",client->socket); - japi_remove_client(ctx,client->socket); + prntdbg("client %d disconnected\n", client->socket); + japi_remove_client(ctx, client->socket); break; } else { /* Received an empty line */ @@ -541,7 +589,7 @@ int japi_start_server(japi_context *ctx, const char *port) } } else { fprintf(stderr, "ERROR: creadline() failed (ret = %i)\n", ret); - japi_remove_client(ctx,client->socket); + japi_remove_client(ctx, client->socket); break; } @@ -560,8 +608,8 @@ int japi_start_server(japi_context *ctx, const char *port) return -1; } if (ctx->max_clients == 0 || ctx->num_clients < ctx->max_clients) { - japi_add_client(ctx,client_socket); - prntdbg("client %d added\n",client_socket); + japi_add_client(ctx, client_socket); + prntdbg("client %d added\n", client_socket); } else { close(client_socket); } @@ -569,9 +617,45 @@ int japi_start_server(japi_context *ctx, const char *port) } /* Clean up */ - japi_remove_all_clients(ctx); + japi_remove_all_clients(ctx); close(server_socket); return 0; } + +/* + * Provide the names of all registered commands as a JAPI response. + */ +void japi_cmd_list(japi_context *ctx, json_object *request, json_object *response) +{ + japi_request *req; + json_object *jstring; + json_object *jarray; + + assert(ctx != NULL); + assert(response != NULL); + + jarray = json_object_new_array(); + req = ctx->requests; + + /* Iterate through push service list and return JSON object */ + while (req != NULL) { + jstring = json_object_new_string(req->name); /* Create JSON-string */ + json_object_array_add(jarray, jstring); /* Add string to JSON array */ + req = req->next; + } + + /* Add array to JSON-object */ + json_object_object_add(response, "commands", jarray); +} + +/* + * Default handler for reacting to unknown requests. + */ +void japi_request_not_found_handler(japi_context *ctx, json_object *request, + json_object *response) +{ + json_object_object_add(response, "error", + json_object_new_string("no request handler found")); +} \ No newline at end of file diff --git a/src/japi_intern.h b/src/japi_intern.h index b35fba1..ba6e0d9 100644 --- a/src/japi_intern.h +++ b/src/japi_intern.h @@ -10,8 +10,25 @@ * libjapi is a universal JSON API library. * * \copyright - * Copyright (c) 2019 Fraunhofer IIS. - * All rights reserved. + * Copyright (c) 2023 Fraunhofer IIS + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ #ifndef __JAPI_INTERN_H__ @@ -98,4 +115,35 @@ int japi_add_client(japi_context *ctx, int socket); */ void japi_pushsrv_remove_client_from_all_pushsrv(japi_context *ctx, int socket); +/*! + * \brief Provide the names of all registered commands as a JAPI response. + * + * Provides the names of all registered commands as a JAPI response. + * + * \param ctx JAPI context + * \param request Pointer to JAPI JSON request + * \param response Pointer to JAPI JSON response + * \note Parameter 'request' declared, although not used in function. + * Function declaration needs to be identical to respective handler. + */ +void japi_cmd_list(japi_context *ctx, json_object *request, json_object *response); + +/*! + * \brief Default handler for reacting to unknown requests. + * + * Can be overwritten by the user by registering a command called + * `request_not_found_handler`. + * Is called if no suitable registered request is found for the received command. + * Adds a field named "error" to the response which contains a string describing + * the issue. + * + * \param ctx JAPI context + * \param request Pointer to JAPI JSON request + * \param response Pointer to JAPI JSON response + * \note Parameter 'ctx' and request' declared, although not used in function. + * Function declaration needs to be identical to respective handler. +*/ +void japi_request_not_found_handler( + japi_context *ctx, json_object *request, json_object *response); + #endif /* __JAPI_INTERN_H__ */ diff --git a/src/japi_pushsrv.c b/src/japi_pushsrv.c index f225e50..bf47e8b 100644 --- a/src/japi_pushsrv.c +++ b/src/japi_pushsrv.c @@ -10,16 +10,33 @@ * japi_pushsrv is a universal JSON API library. * * \copyright - * Copyright (c) 2019 Fraunhofer IIS. - * All rights reserved. + * Copyright (c) 2023 Fraunhofer IIS + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ #include #include -#include #include -#include +#include #include +#include #include #include "japi_intern.h" @@ -29,27 +46,26 @@ #include "rw_n.h" - /*! -* \brief Add client to push service -* -* Add client socket to given push service. -* -* \param socket Socket to add -* \param pushsrv_name The name of the push service -* \param psc JAPI push service context -* -* \returns On success, 0 is returned. On error, -1 if memory allocation failed. -*/ + * \brief Add client to push service + * + * Add client socket to given push service. + * + * \param socket Socket to add + * \param pushsrv_name The name of the push service + * \param psc JAPI push service context + * + * \returns On success, 0 is returned. On error, -1 if memory allocation failed. + */ static int japi_pushsrv_add_client(japi_pushsrv_context *psc, int socket) { japi_client *client; /* Error handling */ assert(psc != NULL); - assert(socket > 0); + assert(socket >= 0); - client = (japi_client*)malloc(sizeof(japi_client)); + client = (japi_client *)malloc(sizeof(japi_client)); if (client == NULL) { perror("ERROR: malloc() failed\n"); return -1; @@ -84,7 +100,8 @@ int japi_pushsrv_remove_client(japi_pushsrv_context *psc, int socket) /* If first element */ if ((client->socket == socket) && (prev == NULL)) { psc->clients = client->next; - prntdbg("removing client %d from pushsrv %s\n",client->socket,psc->pushsrv_name); + prntdbg("removing client %d from pushsrv %s\n", client->socket, + psc->pushsrv_name); free(client); ret = 0; break; @@ -92,14 +109,16 @@ int japi_pushsrv_remove_client(japi_pushsrv_context *psc, int socket) /* If last element */ if ((client->socket == socket) && (client->next == NULL)) { prev->next = NULL; - prntdbg("removing client %d from pushsrv %s\n",client->socket,psc->pushsrv_name); + prntdbg("removing client %d from pushsrv %s\n", client->socket, + psc->pushsrv_name); free(client); ret = 0; break; } if (client->socket == socket) { prev->next = client->next; - prntdbg("removing client %d from pushsrv %s\n",client->socket,psc->pushsrv_name); + prntdbg("removing client %d from pushsrv %s\n", client->socket, + psc->pushsrv_name); free(client); ret = 0; break; @@ -123,12 +142,12 @@ void japi_pushsrv_remove_client_from_all_pushsrv(japi_context *ctx, int socket) assert(ctx != NULL); assert(socket >= 0); - prntdbg("removing client %i from all pushsrv\n",socket); + prntdbg("removing client %i from all pushsrv\n", socket); psc = ctx->push_services; while (psc != NULL) { pthread_mutex_lock(&(psc->lock)); - japi_pushsrv_remove_client(psc,socket); + japi_pushsrv_remove_client(psc, socket); pthread_mutex_unlock(&(psc->lock)); psc = psc->next; } @@ -137,61 +156,70 @@ void japi_pushsrv_remove_client_from_all_pushsrv(japi_context *ctx, int socket) /* * Saves client socket, if passed push service is registered */ -void japi_pushsrv_subscribe(japi_context *ctx, int socket, json_object *jreq, json_object *jresp) +void japi_pushsrv_subscribe(japi_context *ctx, json_object *jreq, json_object *jresp) { japi_pushsrv_context *psc; json_object *jval; - const char* pushsrv_name; + const char *pushsrv_name; + int socket, ret; /* Error handling */ assert(ctx != NULL); - assert(socket != -1); - assert(jreq != NULL); assert(jresp != NULL); psc = ctx->push_services; /* Get the push service name */ - if (!json_object_object_get_ex(jreq,"service",&jval) || jval == NULL) { - json_object_object_add(jresp,"success",json_object_new_boolean(false)); - json_object_object_add(jresp,"message",json_object_new_string("Push service not found.")); + if (!json_object_object_get_ex(jreq, "service", &jval) || jval == NULL) { + json_object_object_add(jresp, "success", json_object_new_boolean(false)); + json_object_object_add(jresp, "message", + json_object_new_string("Push service not found.")); return; } pushsrv_name = json_object_get_string(jval); + ret = json_object_object_get_ex(jreq, "socket", &jval); + socket = json_object_get_int(jval); + if (!ret || socket < 0) { + json_object_object_add(jresp, "success", json_object_new_boolean(false)); + json_object_object_add( + jresp, "message", + json_object_new_string("Subscribing push service to non-existing socket")); + return; + } /* Search for push service in list and save socket, if found */ while (psc != NULL) { - if (strcasecmp(pushsrv_name,psc->pushsrv_name) == 0) { - japi_pushsrv_add_client(psc,socket); + if (strcasecmp(pushsrv_name, psc->pushsrv_name) == 0) { + ret = japi_pushsrv_add_client(psc, socket); break; } psc = psc->next; } - json_object_object_add(jresp,"service",json_object_new_string(pushsrv_name)); + json_object_object_add(jresp, "service", json_object_new_string(pushsrv_name)); /* Create JSON response object */ - if (psc != NULL) { - json_object_object_add(jresp,"success",json_object_new_boolean(true)); + if (psc == NULL || ret < 0) { + json_object_object_add(jresp, "success", json_object_new_boolean(false)); + json_object_object_add(jresp, "message", + json_object_new_string("Push service not found.")); } else { - json_object_object_add(jresp,"success",json_object_new_boolean(false)); - json_object_object_add(jresp,"message",json_object_new_string("Push service not found.")); + json_object_object_add(jresp, "success", json_object_new_boolean(true)); } } /* * Removes client socket, if passed push service is registered */ -void japi_pushsrv_unsubscribe(japi_context *ctx, int socket, json_object *jreq, json_object *jresp) +void japi_pushsrv_unsubscribe(japi_context *ctx, json_object *jreq, json_object *jresp) { japi_pushsrv_context *psc; - json_object* jval; - const char* pushsrv_name; + json_object *jval; + const char *pushsrv_name; + int ret, socket; /* Error handling */ assert(ctx != NULL); - assert(socket != -1); - assert(jreq != NULL); assert(jresp != NULL); psc = ctx->push_services; @@ -199,18 +227,31 @@ void japi_pushsrv_unsubscribe(japi_context *ctx, int socket, json_object *jreq, bool unsubscribed = false; /* Service unsubscribed? */ /* Get the push service name */ - if (!json_object_object_get_ex(jreq,"service",&jval) || jval == NULL) { - json_object_object_add(jresp,"success",json_object_new_boolean(false)); - json_object_object_add(jresp,"message",json_object_new_string("Push service not found.")); + if (!json_object_object_get_ex(jreq, "service", &jval) || jval == NULL) { + json_object_object_add(jresp, "success", json_object_new_boolean(false)); + json_object_object_add(jresp, "message", + json_object_new_string("Push service not found.")); return; } pushsrv_name = json_object_get_string(jval); - /* Search for push service in list and remove socket, if found & socket is registered */ + ret = json_object_object_get_ex(jreq, "socket", &jval); + socket = json_object_get_int(jval); + if (!ret || socket < 0) { + json_object_object_add(jresp, "success", json_object_new_boolean(false)); + json_object_object_add( + jresp, "message", + json_object_new_string( + "Unsubscribing push service from non-existing socket")); + return; + } + + /* Search for push service in list and remove socket, if found & socket is + * registered */ while (psc != NULL) { - if (strcasecmp(pushsrv_name,psc->pushsrv_name) == 0) { + if (strcasecmp(pushsrv_name, psc->pushsrv_name) == 0) { registered = true; - if (japi_pushsrv_remove_client(psc,socket) >= 0) { + if (japi_pushsrv_remove_client(psc, socket) >= 0) { unsubscribed = true; break; } @@ -218,17 +259,21 @@ void japi_pushsrv_unsubscribe(japi_context *ctx, int socket, json_object *jreq, psc = psc->next; } - json_object_object_add(jresp,"service",json_object_new_string(pushsrv_name)); + json_object_object_add(jresp, "service", json_object_new_string(pushsrv_name)); /* Create JSON response object */ if (registered && unsubscribed) { /* Subscribed */ - json_object_object_add(jresp,"success",json_object_new_boolean(true)); + json_object_object_add(jresp, "success", json_object_new_boolean(true)); } else if (registered && !unsubscribed) { /* Registered, but not subscribed */ - json_object_object_add(jresp,"success",json_object_new_boolean(false)); - json_object_object_add(jresp,"message",json_object_new_string("Can't unsubscribe a service that wasn't subscribed before.")); + json_object_object_add(jresp, "success", json_object_new_boolean(false)); + json_object_object_add( + jresp, "message", + json_object_new_string( + "Can't unsubscribe a service that wasn't subscribed before.")); } else { /* Not registered */ - json_object_object_add(jresp,"success",json_object_new_boolean(false)); - json_object_object_add(jresp,"message",json_object_new_string("Push service not found.")); + json_object_object_add(jresp, "success", json_object_new_boolean(false)); + json_object_object_add(jresp, "message", + json_object_new_string("Push service not found.")); } } @@ -261,22 +306,23 @@ static void free_pushsrv(japi_pushsrv_context *psc) /* * Registers push-service and returns pointer to that service object. */ -japi_pushsrv_context* japi_pushsrv_register(japi_context* ctx, const char* pushsrv_name) +japi_pushsrv_context *japi_pushsrv_register(japi_context *ctx, const char *pushsrv_name) { japi_pushsrv_context *psc; if (ctx == NULL) { - fprintf(stderr,"ERROR: JAPI context is NULL.\n"); + fprintf(stderr, "ERROR: JAPI context is NULL.\n"); return NULL; } - if ((pushsrv_name == NULL) || (strcmp(pushsrv_name,"") == 0)) { - fprintf(stderr,"ERROR: Push service name is NULL or empty.\n"); + if ((pushsrv_name == NULL) || (strcmp(pushsrv_name, "") == 0)) { + fprintf(stderr, "ERROR: Push service name is NULL or empty.\n"); return NULL; } - if (pushsrv_isredundant(ctx,pushsrv_name)) { - fprintf(stderr,"ERROR: A push service called '%s' was already registered.\n",pushsrv_name); + if (pushsrv_isredundant(ctx, pushsrv_name)) { + fprintf(stderr, "ERROR: A push service called '%s' was already registered.\n", + pushsrv_name); return NULL; } @@ -302,8 +348,8 @@ japi_pushsrv_context* japi_pushsrv_register(japi_context* ctx, const char* pushs psc->enabled = false; psc->userptr = ctx->userptr; - if (pthread_mutex_init(&(psc->lock),NULL) != 0) { - fprintf(stderr,"ERROR: mutex initialization has failed\n"); + if (pthread_mutex_init(&(psc->lock), NULL) != 0) { + fprintf(stderr, "ERROR: mutex initialization has failed\n"); return NULL; } @@ -315,24 +361,47 @@ japi_pushsrv_context* japi_pushsrv_register(japi_context* ctx, const char* pushs } /* - * Iterate through push service and unsubscribe & free memory for all clients + * Remove push service context from japi context, unsubscribe for all clients and free + * memory */ -int japi_pushsrv_destroy(japi_pushsrv_context *psc) +int japi_pushsrv_destroy(japi_context *ctx, japi_pushsrv_context *psc) { + japi_pushsrv_context *psc_iter, *psc_prev, *psc_next; japi_client *client, *client_next; + assert(ctx != NULL); + if (psc == NULL) { - fprintf(stderr,"ERROR: push service context is NULL"); + fprintf(stderr, "ERROR: push service context is NULL\n"); return -1; } - client = psc->clients; + /* clean up linked list in ctx->push_service */ + psc_prev = NULL; + psc_iter = ctx->push_services; + + while (psc_iter != NULL) { + psc_next = psc_iter->next; + if (psc_iter == psc) { + /* If first element */ + if (psc_prev == NULL) { + ctx->push_services = psc_next; + } else { + psc_prev->next = psc_next; + } + break; + } + psc_prev = psc_iter; + psc_iter = psc_next; + } - /* Iterates through push service client list and frees memory for every element and for the push service themself */ + /* Iterates through push service client list and frees memory for every element and + * for the push service themself */ + client = psc->clients; pthread_mutex_lock(&(psc->lock)); while (client != NULL) { client_next = client->next; - japi_pushsrv_remove_client(psc,client->socket); + japi_pushsrv_remove_client(psc, client->socket); client = client_next; } pthread_mutex_unlock(&(psc->lock)); @@ -363,7 +432,7 @@ void japi_pushsrv_list(japi_context *ctx, json_object *request, json_object *res /* Iterate through push service list and return JSON object */ while (psc != NULL) { jstring = json_object_new_string(psc->pushsrv_name); /* Create JSON-string */ - json_object_array_add(jarray,jstring); /* Add string to JSON array */ + json_object_array_add(jarray, jstring); /* Add string to JSON array */ psc = psc->next; } @@ -385,7 +454,7 @@ int japi_pushsrv_sendmsg(japi_pushsrv_context *psc, json_object *jmsg_data) /* Return -1 if there is no message to send */ if (jmsg_data == NULL) { - fprintf(stderr,"ERROR: Nothing to send.\n"); + fprintf(stderr, "ERROR: Nothing to send.\n"); return -1; } @@ -399,9 +468,12 @@ int japi_pushsrv_sendmsg(japi_pushsrv_context *psc, json_object *jmsg_data) jmsg = json_object_new_object(); jdata = NULL; - json_object_object_add(jmsg,"japi_pushsrv",json_object_new_string(psc->pushsrv_name)); - jdata = json_object_get(jmsg_data); // increment refcount before calling json_object_object_add as jmesg_data may still be in use by the caller - json_object_object_add(jmsg,"data",jdata); + json_object_object_add(jmsg, "japi_pushsrv", + json_object_new_string(psc->pushsrv_name)); + jdata = json_object_get( + jmsg_data); // increment refcount before calling json_object_object_add as + // jmesg_data may still be in use by the caller + json_object_object_add(jmsg, "data", jdata); msg = japi_get_jobj_as_ndstr(jmsg); json_object_put(jmsg); @@ -410,16 +482,20 @@ int japi_pushsrv_sendmsg(japi_pushsrv_context *psc, json_object *jmsg_data) client = psc->clients; while (client != NULL) { - prntdbg("pushsrv '%s': Sending message to client %d\n. Message: '%s'",psc->pushsrv_name,client->socket,msg); + prntdbg("pushsrv '%s': Sending message to client %d\n. Message: '%s'", + psc->pushsrv_name, client->socket, msg); following_client = client->next; // Save pointer to next element ret = write_n(client->socket, msg, strlen(msg)); if (ret <= 0) { /* If write failed print error and unsubscribe client */ - fprintf(stderr, "ERROR: Failed to send push service message to client %i (write returned %i)\n", client->socket, ret); + fprintf(stderr, + "ERROR: Failed to send push service message to client %i (write " + "returned %i)\n", + client->socket, ret); /* Remove client from respective push service and free */ - japi_pushsrv_remove_client(psc,client->socket); + japi_pushsrv_remove_client(psc, client->socket); } else { success++; } @@ -433,14 +509,15 @@ int japi_pushsrv_sendmsg(japi_pushsrv_context *psc, json_object *jmsg_data) } /* - * Wrapper function that is executed by pthread_create and starts the desired push service routine + * Wrapper function that is executed by pthread_create and starts the desired push + * service routine */ static void *generic_pushsrv_runner(void *arg) { japi_pushsrv_context *psc; japi_pushsrv_routine routine; - psc = (japi_pushsrv_context*)arg; + psc = (japi_pushsrv_context *)arg; assert(psc != NULL); @@ -457,19 +534,21 @@ static void *generic_pushsrv_runner(void *arg) int japi_pushsrv_start(japi_pushsrv_context *psc, japi_pushsrv_routine routine) { if (psc == NULL) { - fprintf(stderr,"ERROR: No push service context passed. Not starting thread.\n"); + fprintf(stderr, + "ERROR: No push service context passed. Not starting thread.\n"); return -1; } if (routine == NULL) { - fprintf(stderr,"ERROR: No routine passed. Not starting thread.\n"); + fprintf(stderr, "ERROR: No routine passed. Not starting thread.\n"); return -2; } psc->enabled = true; psc->routine = routine; - if (pthread_create(&(psc->thread_id),NULL,generic_pushsrv_runner,(void*)psc) != 0) { + if (pthread_create(&(psc->thread_id), NULL, generic_pushsrv_runner, (void *)psc) != + 0) { fprintf(stderr, "ERROR: Error creating push service thread.\n"); psc->enabled = false; return -3; @@ -484,12 +563,12 @@ int japi_pushsrv_start(japi_pushsrv_context *psc, japi_pushsrv_routine routine) int japi_pushsrv_stop(japi_pushsrv_context *psc) { if (psc == NULL) { - fprintf(stderr,"ERROR: No push service context passed. Can't stop thread.\n"); + fprintf(stderr, "ERROR: No push service context passed. Can't stop thread.\n"); return -1; } if (psc->enabled == false) { - fprintf(stderr,"ERROR: Thread not running.\n"); + fprintf(stderr, "ERROR: Thread not running.\n"); return -2; } @@ -497,8 +576,9 @@ int japi_pushsrv_stop(japi_pushsrv_context *psc) psc->enabled = false; /* Wait for thread to end and close it */ - if (pthread_join(psc->thread_id,NULL) != 0) { - fprintf(stderr, "ERROR: Error joining push service routine '%s'\n",psc->pushsrv_name); + if (pthread_join(psc->thread_id, NULL) != 0) { + fprintf(stderr, "ERROR: Error joining push service routine '%s'\n", + psc->pushsrv_name); return -3; } diff --git a/src/japi_pushsrv_intern.h b/src/japi_pushsrv_intern.h index 5d772d6..fcdcdcf 100644 --- a/src/japi_pushsrv_intern.h +++ b/src/japi_pushsrv_intern.h @@ -10,8 +10,25 @@ * libjapi is a universal JSON API library. * * \copyright - * Copyright (c) 2019 Fraunhofer IIS. - * All rights reserved. + * Copyright (c) 2023 Fraunhofer IIS + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ #ifndef __JAPI_PUSHSRV_INTERN_H__ @@ -25,11 +42,10 @@ * Subscribe a registered JAPI push service specified by pushsrv_name. * * \param ctx JAPI context - * \param socket Client socket * \param jreq Request JSON object * \param jresp Response JSON object */ -void japi_pushsrv_subscribe(japi_context *ctx, int socket, json_object *jreq, json_object *jresp); +void japi_pushsrv_subscribe(japi_context *ctx, json_object *jreq, json_object *jresp); /*! * \brief Unsubscribe a registered JAPI push service @@ -37,11 +53,10 @@ void japi_pushsrv_subscribe(japi_context *ctx, int socket, json_object *jreq, js * Unsubscribe a registered JAPI push service specified by pushsrv_name. * * \param ctx JAPI context - * \param socket Client socket * \param jreq Request JSON object * \param jresp Response JSON object */ -void japi_pushsrv_unsubscribe(japi_context *ctx, int socket, json_object *jreq, json_object *jresp); +void japi_pushsrv_unsubscribe(japi_context *ctx, json_object *jreq, json_object *jresp); /*! * \brief List registered JAPI push services as JAPI response diff --git a/src/japi_utils.c b/src/japi_utils.c index 6a31380..9283c7d 100644 --- a/src/japi_utils.c +++ b/src/japi_utils.c @@ -10,8 +10,25 @@ * japi_utils is a universal JSON API helper library. * * \copyright - * Copyright (c) 2019 Fraunhofer IIS. - * All rights reserved. + * Copyright (c) 2023 Fraunhofer IIS + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ #include diff --git a/src/networking.c b/src/networking.c index 43be171..3aa17ce 100644 --- a/src/networking.c +++ b/src/networking.c @@ -9,9 +9,26 @@ * \details * This module collects networking helper functions like tcp_start_server(). * - * \copyright - * Copyright (c) 2018 Fraunhofer IIS. - * All rights reserved. + *\copyright + * Copyright (c) 2023 Fraunhofer IIS + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ #include diff --git a/src/prntdbg.h b/src/prntdbg.h index e0808dc..ae07ec3 100644 --- a/src/prntdbg.h +++ b/src/prntdbg.h @@ -10,8 +10,25 @@ * This module collects macros for debugging. * * \copyright - * Copyright (c) 2019 Fraunhofer IIS. - * All rights reserved. + * Copyright (c) 2023 Fraunhofer IIS + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ /* diff --git a/src/rw_n.c b/src/rw_n.c index 4d4c227..576cfd3 100644 --- a/src/rw_n.c +++ b/src/rw_n.c @@ -10,9 +10,26 @@ * Use read_n() to read or write_n() to write a fixed number of bytes from or * to a file descriptor. * - * \copyright - * Copyright (c) 2018 Fraunhofer IIS. - * All rights reserved. + *\copyright + * Copyright (c) 2023 Fraunhofer IIS + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ #include diff --git a/test/japi_test.cc b/test/japi_test.cc index 704f23d..513d5b4 100644 --- a/test/japi_test.cc +++ b/test/japi_test.cc @@ -1,59 +1,82 @@ +/*! +Copyright (c) 2023 Fraunhofer IIS + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the “Software”), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + #include #include -extern "C"{ -#include -#include -#include -#include -#include -#include +extern "C" { +#include "japi.h" +#include "japi_intern.h" +#include "japi_pushsrv.h" +#include "japi_pushsrv_intern.h" +#include "japi_utils.h" +#include "rw_n.h" } /* The handler for japi_register_request test */ -static void dummy_request_handler(japi_context *ctx, json_object *request, json_object *response) +static void dummy_request_handler(japi_context *ctx, json_object *request, + json_object *response) { /* Not existent dummy request */ - json_object_object_add(response,"value",json_object_new_string("hello world")); + json_object_object_add(response, "value", json_object_new_string("hello world")); } -TEST(JAPI,Init) +TEST(JAPI, Init) { /* On success, a japi_context object is returned. On error, NULL is returned */ EXPECT_TRUE(japi_init(NULL) != NULL); } -TEST(JAPI,GetValueAsX) +TEST(JAPI, GetValueAsX) { bool bval; - const char* sval; + const char *sval; int ival; long long int lval; double dval; json_object *jresp; jresp = json_object_new_object(); - json_object_object_add(jresp,"string",json_object_new_string("value")); - json_object_object_add(jresp,"bool",json_object_new_boolean(true)); - json_object_object_add(jresp,"int",json_object_new_int(10)); - json_object_object_add(jresp,"int64",json_object_new_int64(9000000000000000000)); - json_object_object_add(jresp,"double",json_object_new_double(10.12345)); + json_object_object_add(jresp, "string", json_object_new_string("value")); + json_object_object_add(jresp, "bool", json_object_new_boolean(true)); + json_object_object_add(jresp, "int", json_object_new_int(10)); + json_object_object_add(jresp, "int64", json_object_new_int64(9000000000000000000)); + json_object_object_add(jresp, "double", json_object_new_double(10.12345)); /* On success, string is returned. On error, <0 is returned */ EXPECT_EQ(japi_get_value_as_str(jresp, "string", &sval), 0); - EXPECT_STREQ(sval,"value"); + EXPECT_STREQ(sval, "value"); /* On success, bool is returned. On error, <0 is returned */ - EXPECT_EQ(japi_get_value_as_bool(jresp, "bool",&bval),0); + EXPECT_EQ(japi_get_value_as_bool(jresp, "bool", &bval), 0); EXPECT_TRUE(bval); /* On success, int is returned. On error, <0 is returned */ - EXPECT_EQ(japi_get_value_as_int(jresp, "int",&ival),0); - EXPECT_EQ(ival,10); + EXPECT_EQ(japi_get_value_as_int(jresp, "int", &ival), 0); + EXPECT_EQ(ival, 10); /* On success, int64 is returned. On error, <0 is returned */ - EXPECT_EQ(japi_get_value_as_int64(jresp, "int64",&lval),0); - EXPECT_EQ(lval,9000000000000000000); + EXPECT_EQ(japi_get_value_as_int64(jresp, "int64", &lval), 0); + EXPECT_EQ(lval, 9000000000000000000); /* On success, double is returned. On error, <0 is returned */ - EXPECT_EQ(japi_get_value_as_double(jresp, "double",&dval),0); - EXPECT_EQ(dval,10.12345); + EXPECT_EQ(japi_get_value_as_double(jresp, "double", &dval), 0); + EXPECT_EQ(dval, 10.12345); /* Test error return values */ /* Given json-object is NULL, expecting -1 */ @@ -85,43 +108,52 @@ TEST(JAPI,GetValueAsX) json_object_put(jresp); } -TEST(JAPI,ProcessMessage) +TEST(JAPI, ProcessMessage) { japi_context *ctx; - char* response; - const char* request; - const char* sval; + char *response; + const char *request; + const char *sval; json_object *jobj; json_object *jdata; int socket; jobj = json_object_new_object(); - jdata= json_object_new_object(); + jdata = json_object_new_object(); request = "{'japi_request':'dummy_request_handler'}"; response = NULL; ctx = japi_init(NULL); socket = 4; - japi_register_request(ctx,"dummy_request_handler",&dummy_request_handler); + /* If no request is registered, do not die */ + EXPECT_EQ(japi_process_message(ctx, request, &response, socket), 0); + jobj = json_tokener_parse(response); + json_object_object_get_ex(jobj, "data", &jdata); + EXPECT_EQ(japi_get_value_as_str(jdata, "error", &sval), 0); + EXPECT_STREQ("no request handler found", sval); + + japi_register_request(ctx, "dummy_request_handler", &dummy_request_handler); /* On success, 0 returned. On error, -1 is returned */ - EXPECT_EQ(japi_process_message(ctx, request, &response, socket),0); + EXPECT_EQ(japi_process_message(ctx, request, &response, socket), 0); jobj = json_tokener_parse(response); - json_object_object_get_ex(jobj,"data",&jdata); - EXPECT_EQ(japi_get_value_as_str(jdata,"value",&sval),0); - EXPECT_STREQ("hello world",sval); + json_object_object_get_ex(jobj, "data", &jdata); + EXPECT_EQ(japi_get_value_as_str(jdata, "value", &sval), 0); + EXPECT_STREQ("hello world", sval); /* Clean up */ japi_destroy(ctx); } -TEST(JAPI,IncludeArgsWithResponse) +TEST(JAPI, IncludeArgsWithResponse) { /* Setup */ japi_context *ctx = japi_init(NULL); - char* response = NULL; - const char* sval; - const char* request = "{'japi_request': 'dummy_request_handler', 'args': {'foo': 'bar'}}"; - const char* request_int_args = "{'japi_request': 'dummy_request_handler', 'args': 42}"; + char *response = NULL; + const char *sval; + const char *request = + "{'japi_request': 'dummy_request_handler', 'args': {'foo': 'bar'}}"; + const char *request_int_args = + "{'japi_request': 'dummy_request_handler', 'args': 42}"; json_object *jobj; json_object *jdata; int socket = 4; @@ -132,66 +164,109 @@ TEST(JAPI,IncludeArgsWithResponse) EXPECT_EQ(japi_include_args_in_response(ctx, true), 0); /* Register dummy request handler */ - japi_register_request(ctx,"dummy_request_handler",&dummy_request_handler); + japi_register_request(ctx, "dummy_request_handler", &dummy_request_handler); /* Response should include request arguments object */ - EXPECT_EQ(japi_process_message(ctx, request, &response, socket),0); + EXPECT_EQ(japi_process_message(ctx, request, &response, socket), 0); jobj = json_tokener_parse(response); EXPECT_TRUE(json_object_object_get_ex(jobj, "args", &jdata)); - EXPECT_EQ(japi_get_value_as_str(jdata,"foo",&sval),0); + EXPECT_EQ(japi_get_value_as_str(jdata, "foo", &sval), 0); EXPECT_STREQ("bar", sval); /* Response should include request argument integer */ - EXPECT_EQ(japi_process_message(ctx, request_int_args, &response, socket),0); + EXPECT_EQ(japi_process_message(ctx, request_int_args, &response, socket), 0); jobj = json_tokener_parse(response); EXPECT_TRUE(json_object_object_get_ex(jobj, "args", &jdata)); EXPECT_EQ(42, json_object_get_int(jdata)); - + /* Teardown */ japi_destroy(ctx); } -TEST(JAPI,Register) +TEST(JAPI, Register) { japi_context *ctx; ctx = japi_init(NULL); /* On success, zero is returned. On error, -1..-4 is returned */ - EXPECT_EQ(japi_register_request(ctx,"req_name",&dummy_request_handler),0); - EXPECT_EQ(japi_register_request(NULL,"req_name",&dummy_request_handler),-1); - EXPECT_EQ(japi_register_request(ctx,NULL,&dummy_request_handler),-2); - EXPECT_EQ(japi_register_request(ctx,"req_name",NULL),-3); + EXPECT_EQ(japi_register_request(ctx, "req_name", &dummy_request_handler), 0); + EXPECT_EQ(japi_register_request(NULL, "req_name", &dummy_request_handler), -1); + EXPECT_EQ(japi_register_request(ctx, NULL, &dummy_request_handler), -2); + EXPECT_EQ(japi_register_request(ctx, "req_name", NULL), -3); + + /* Registering the same request name again or an empty request name, should not be + * possible */ + EXPECT_EQ(japi_register_request(ctx, "req_name", &dummy_request_handler), -4); + EXPECT_EQ(japi_register_request(ctx, "dummy_request_02", &dummy_request_handler), + 0); // same handler for another name + EXPECT_EQ(japi_register_request(ctx, "", &dummy_request_handler), -2); - /* Registering the same request name again or an empty request name, should not be possible */ - EXPECT_EQ(japi_register_request(ctx,"req_name",&dummy_request_handler),-4); - EXPECT_EQ(japi_register_request(ctx,"dummy_request_02",&dummy_request_handler),0); // same handler for another name - EXPECT_EQ(japi_register_request(ctx,"",&dummy_request_handler),-2); + /* Request names starting with japi_ are not allowed */ + EXPECT_EQ(japi_register_request(ctx, "japi_test", &dummy_request_handler), -6); japi_destroy(ctx); } -TEST(JAPI_Push_Service,Register) +TEST(JAPI, ListCommands) { japi_context *ctx; + japi_request *cmd; + json_object *jobj; + ctx = japi_init(NULL); + jobj = json_object_new_object(); - /* On success, a pointer to the japi_push_service context is returned. On error, NULL is returned */ - EXPECT_TRUE(japi_pushsrv_register(ctx,"test_pushsrv") != NULL); - EXPECT_TRUE(japi_pushsrv_register(NULL,"test_pushsrv") == NULL); - EXPECT_TRUE(japi_pushsrv_register(ctx,NULL) == NULL); + /* Register some test requests */ + japi_register_request(ctx, "test01", &dummy_request_handler); + japi_register_request(ctx, "test02", &dummy_request_handler); + japi_register_request(ctx, "test03", &dummy_request_handler); - /* Registering the same push service name again or an empty push service name, should not be possible */ - EXPECT_TRUE(japi_pushsrv_register(ctx,"test_pushsrv") == NULL); - EXPECT_TRUE(japi_pushsrv_register(ctx,"") == NULL); + /* The function to be tested */ + japi_cmd_list(ctx, NULL, jobj); + + cmd = ctx->requests; + + /* Iterate commands array & context and compare strings */ + json_object_object_foreach(jobj, key, val) + { + json_object_object_get_ex(jobj, key, &val); + int arraylen = json_object_array_length(val); + int i; + json_object *jvalue; + while (cmd != NULL) { + for (i = 0; i < arraylen; i++) { + jvalue = json_object_array_get_idx(val, i); + EXPECT_STREQ(json_object_get_string(jvalue), cmd->name); + cmd = cmd->next; + } + } + } +} + +TEST(JAPI_Push_Service, Register) +{ + japi_context *ctx; + ctx = japi_init(NULL); + + /* On success, a pointer to the japi_push_service context is returned. On error, + * NULL is returned */ + EXPECT_TRUE(japi_pushsrv_register(ctx, "test_pushsrv") != NULL); + EXPECT_TRUE(japi_pushsrv_register(NULL, "test_pushsrv") == NULL); + EXPECT_TRUE(japi_pushsrv_register(ctx, NULL) == NULL); + + /* Registering the same push service name again or an empty push service name, + * should not be possible */ + EXPECT_TRUE(japi_pushsrv_register(ctx, "test_pushsrv") == NULL); + EXPECT_TRUE(japi_pushsrv_register(ctx, "") == NULL); /* Clean up */ japi_destroy(ctx); } -TEST(JAPI_Push_Service,SubscribeAndUnsubscribe) +TEST(JAPI_Push_Service, SubscribeAndUnsubscribe) { int socket; - char* pushsrv_name; + char *pushsrv_name; bool bval; japi_context *ctx; json_object *jreq; @@ -200,7 +275,7 @@ TEST(JAPI_Push_Service,SubscribeAndUnsubscribe) json_object *bad_req; socket = 4; - pushsrv_name = (char*)"test_pushsrv"; + pushsrv_name = (char *)"test_pushsrv"; jreq = json_object_new_object(); jresp = json_object_new_object(); illegal_req = json_object_new_object(); @@ -208,60 +283,62 @@ TEST(JAPI_Push_Service,SubscribeAndUnsubscribe) ctx = japi_init(NULL); /* Build JSON request */ - json_object_object_add(jreq,"service",json_object_new_string(pushsrv_name)); + json_object_object_add(jreq, "service", json_object_new_string(pushsrv_name)); + json_object_object_add(jreq, "socket", json_object_new_int(socket)); /* Sub-/unsubscribe before registering, expecting false */ - japi_pushsrv_subscribe(ctx,socket,jreq,jresp); - EXPECT_EQ(japi_get_value_as_bool(jresp, "success",&bval),0); + japi_pushsrv_subscribe(ctx, jreq, jresp); + EXPECT_EQ(japi_get_value_as_bool(jresp, "success", &bval), 0); EXPECT_FALSE(bval); - japi_pushsrv_unsubscribe(ctx,socket,jreq,jresp); - EXPECT_EQ(japi_get_value_as_bool(jresp, "success",&bval),0); + japi_pushsrv_unsubscribe(ctx, jreq, jresp); + EXPECT_EQ(japi_get_value_as_bool(jresp, "success", &bval), 0); EXPECT_FALSE(bval); /* Pass illegal JSON request, expecting false */ - json_object_object_add(illegal_req,"service",NULL); + json_object_object_add(illegal_req, "service", NULL); + json_object_object_add(illegal_req, "socket", json_object_new_int(socket)); - japi_pushsrv_subscribe(ctx,socket,illegal_req,jresp); - EXPECT_EQ(japi_get_value_as_bool(jresp, "success",&bval),0); + japi_pushsrv_subscribe(ctx, illegal_req, jresp); + EXPECT_EQ(japi_get_value_as_bool(jresp, "success", &bval), 0); EXPECT_FALSE(bval); - japi_pushsrv_unsubscribe(ctx,socket,illegal_req,jresp); - EXPECT_EQ(japi_get_value_as_bool(jresp, "success",&bval),0); + japi_pushsrv_unsubscribe(ctx, illegal_req, jresp); + EXPECT_EQ(japi_get_value_as_bool(jresp, "success", &bval), 0); EXPECT_FALSE(bval); /* Pass illegal key */ - json_object_object_add(bad_req,"bad_key",json_object_new_string(pushsrv_name)); + json_object_object_add(bad_req, "bad_key", json_object_new_string(pushsrv_name)); + json_object_object_add(bad_req, "socket", json_object_new_int(socket)); - japi_pushsrv_subscribe(ctx,socket,bad_req,jresp); - EXPECT_EQ(japi_get_value_as_bool(jresp, "success",&bval),0); + japi_pushsrv_subscribe(ctx, bad_req, jresp); + EXPECT_EQ(japi_get_value_as_bool(jresp, "success", &bval), 0); EXPECT_FALSE(bval); - japi_pushsrv_unsubscribe(ctx,socket,bad_req,jresp); - EXPECT_EQ(japi_get_value_as_bool(jresp, "success",&bval),0); + japi_pushsrv_unsubscribe(ctx, bad_req, jresp); + EXPECT_EQ(japi_get_value_as_bool(jresp, "success", &bval), 0); EXPECT_FALSE(bval); /* Try to unsubscribe without subscribed before, should fail */ - japi_pushsrv_register(ctx,"test_pushsrv"); - japi_pushsrv_unsubscribe(ctx,socket,jreq,jresp); - EXPECT_EQ(japi_get_value_as_bool(jresp, "success",&bval),0); + japi_pushsrv_register(ctx, "test_pushsrv"); + japi_pushsrv_unsubscribe(ctx, jreq, jresp); + EXPECT_EQ(japi_get_value_as_bool(jresp, "success", &bval), 0); EXPECT_FALSE(bval); /* Expect true */ - japi_pushsrv_subscribe(ctx,socket,jreq,jresp); - EXPECT_EQ(japi_get_value_as_bool(jresp, "success",&bval),0); + japi_pushsrv_subscribe(ctx, jreq, jresp); + EXPECT_EQ(japi_get_value_as_bool(jresp, "success", &bval), 0); EXPECT_TRUE(bval); - japi_pushsrv_unsubscribe(ctx,socket,jreq,jresp); - EXPECT_EQ(japi_get_value_as_bool(jresp, "success",&bval),0); + japi_pushsrv_unsubscribe(ctx, jreq, jresp); + EXPECT_EQ(japi_get_value_as_bool(jresp, "success", &bval), 0); EXPECT_TRUE(bval); /* Clean up */ json_object_put(jresp); - } -TEST(JAPI_Push_Service,List) +TEST(JAPI_Push_Service, List) { japi_context *ctx; japi_pushsrv_context *psc; @@ -271,9 +348,9 @@ TEST(JAPI_Push_Service,List) jobj = json_object_new_object(); /* Register some test services */ - japi_pushsrv_register(ctx,"test01"); - japi_pushsrv_register(ctx,"test02"); - japi_pushsrv_register(ctx,"test03"); + japi_pushsrv_register(ctx, "test01"); + japi_pushsrv_register(ctx, "test02"); + japi_pushsrv_register(ctx, "test03"); /* The function to be tested */ japi_pushsrv_list(ctx, NULL, jobj); @@ -281,15 +358,16 @@ TEST(JAPI_Push_Service,List) psc = ctx->push_services; /* Iterate push service array & context and compare strings */ - json_object_object_foreach(jobj, key, val) { + json_object_object_foreach(jobj, key, val) + { json_object_object_get_ex(jobj, key, &val); int arraylen = json_object_array_length(val); int i; - json_object * jvalue; + json_object *jvalue; while (psc != NULL) { - for (i=0; i< arraylen; i++) { + for (i = 0; i < arraylen; i++) { jvalue = json_object_array_get_idx(val, i); - EXPECT_STREQ(json_object_get_string(jvalue),psc->pushsrv_name); + EXPECT_STREQ(json_object_get_string(jvalue), psc->pushsrv_name); psc = psc->next; } } @@ -300,7 +378,7 @@ TEST(JAPI_Push_Service,List) json_object_put(jobj); } -TEST(JAPI,AddRemoveClient) +TEST(JAPI, AddRemoveClient) { japi_context *ctx; japi_client *client; @@ -309,14 +387,14 @@ TEST(JAPI,AddRemoveClient) ctx = japi_init(NULL); /* Add some clients */ - EXPECT_EQ(japi_add_client(ctx,4),0); - EXPECT_EQ(japi_add_client(ctx,5),0); - EXPECT_EQ(japi_add_client(ctx,6),0); - EXPECT_EQ(japi_add_client(ctx,7),0); + EXPECT_EQ(japi_add_client(ctx, 4), 0); + EXPECT_EQ(japi_add_client(ctx, 5), 0); + EXPECT_EQ(japi_add_client(ctx, 6), 0); + EXPECT_EQ(japi_add_client(ctx, 7), 0); /* Add the same client again */ - EXPECT_EQ(japi_add_client(ctx,5),0); - EXPECT_EQ(japi_add_client(ctx,5),0); + EXPECT_EQ(japi_add_client(ctx, 5), 0); + EXPECT_EQ(japi_add_client(ctx, 5), 0); counter = 0; client = ctx->clients; @@ -325,11 +403,11 @@ TEST(JAPI,AddRemoveClient) client = client->next; } /* Counter should count 6 added clients */ - EXPECT_EQ(counter,6); + EXPECT_EQ(counter, 6); /* Remove some clients */ - EXPECT_EQ(japi_remove_client(ctx,4),0); - EXPECT_EQ(japi_remove_client(ctx,5),0); + EXPECT_EQ(japi_remove_client(ctx, 4), 0); + EXPECT_EQ(japi_remove_client(ctx, 5), 0); counter = 0; client = ctx->clients; @@ -338,14 +416,14 @@ TEST(JAPI,AddRemoveClient) client = client->next; } /* Counter should count 2 less clients */ - EXPECT_EQ(counter,4); + EXPECT_EQ(counter, 4); /* Remove not existent client */ - EXPECT_EQ(japi_remove_client(ctx,12),-1); - EXPECT_EQ(japi_remove_client(ctx,13),-1); + EXPECT_EQ(japi_remove_client(ctx, 12), -1); + EXPECT_EQ(japi_remove_client(ctx, 13), -1); } -TEST(JAPI_Push_Service,AddRemoveClient) +TEST(JAPI_Push_Service, AddRemoveClient) { japi_context *ctx; japi_pushsrv_context *psc; @@ -362,22 +440,30 @@ TEST(JAPI_Push_Service,AddRemoveClient) ctx = japi_init(NULL); /* Build JSON request */ - json_object_object_add(push_status_jreq,"service",json_object_new_string("pushsrv_status")); - json_object_object_add(push_temperature_jreq,"service",json_object_new_string("pushsrv_temperature")); + json_object_object_add(push_status_jreq, "service", + json_object_new_string("pushsrv_status")); + json_object_object_add(push_temperature_jreq, "service", + json_object_new_string("pushsrv_temperature")); /* Register some push services */ - japi_pushsrv_register(ctx,"pushsrv_status"); - japi_pushsrv_register(ctx,"pushsrv_temperature"); + japi_pushsrv_register(ctx, "pushsrv_status"); + japi_pushsrv_register(ctx, "pushsrv_temperature"); /* Add some clients */ - japi_pushsrv_subscribe(ctx,4,push_temperature_jreq,jobj); - japi_pushsrv_subscribe(ctx,5,push_temperature_jreq,jobj); - japi_pushsrv_subscribe(ctx,6,push_temperature_jreq,jobj); + json_object_object_add(push_temperature_jreq, "socket", json_object_new_int(4)); + japi_pushsrv_subscribe(ctx, push_temperature_jreq, jobj); + json_object_object_add(push_temperature_jreq, "socket", json_object_new_int(5)); + japi_pushsrv_subscribe(ctx, push_temperature_jreq, jobj); + json_object_object_add(push_temperature_jreq, "socket", json_object_new_int(6)); + japi_pushsrv_subscribe(ctx, push_temperature_jreq, jobj); /* Add a clients a second time */ - japi_pushsrv_subscribe(ctx,7,push_temperature_jreq,jobj); - japi_pushsrv_subscribe(ctx,7,push_temperature_jreq,jobj); + json_object_object_add(push_temperature_jreq, "socket", json_object_new_int(7)); + japi_pushsrv_subscribe(ctx, push_temperature_jreq, jobj); + json_object_object_add(push_temperature_jreq, "socket", json_object_new_int(7)); + japi_pushsrv_subscribe(ctx, push_temperature_jreq, jobj); - japi_pushsrv_subscribe(ctx,5,push_status_jreq,jobj); + json_object_object_add(push_temperature_jreq, "socket", json_object_new_int(15)); + japi_pushsrv_subscribe(ctx, push_status_jreq, jobj); counter = 0; psc = ctx->push_services; @@ -387,11 +473,13 @@ TEST(JAPI_Push_Service,AddRemoveClient) client = client->next; } /* Counter should count 5 clients for get_temperature */ - EXPECT_EQ(counter,5); + EXPECT_EQ(counter, 5); /* Unsubscribe some clients */ - japi_pushsrv_unsubscribe(ctx,5,push_temperature_jreq,jobj); - japi_pushsrv_unsubscribe(ctx,6,push_temperature_jreq,jobj); + json_object_object_add(push_temperature_jreq, "socket", json_object_new_int(5)); + japi_pushsrv_unsubscribe(ctx, push_temperature_jreq, jobj); + json_object_object_add(push_temperature_jreq, "socket", json_object_new_int(6)); + japi_pushsrv_unsubscribe(ctx, push_temperature_jreq, jobj); counter = 0; psc = ctx->push_services; @@ -401,15 +489,16 @@ TEST(JAPI_Push_Service,AddRemoveClient) client = client->next; } /* Two less clients should be counted for get_temperature */ - EXPECT_EQ(counter,3); + EXPECT_EQ(counter, 3); /* Unsubscribe client that is not subscribed */ - japi_pushsrv_unsubscribe(ctx,15,push_temperature_jreq,jobj); - EXPECT_EQ(japi_get_value_as_bool(jobj, "success",&bval),0); + json_object_object_add(push_temperature_jreq, "socket", json_object_new_int(15)); + japi_pushsrv_unsubscribe(ctx, push_temperature_jreq, jobj); + EXPECT_EQ(japi_get_value_as_bool(jobj, "success", &bval), 0); EXPECT_FALSE(bval); } -TEST(JAPI_Push_Service,PushServiceDestroy) +TEST(JAPI_Push_Service, PushServiceDestroy) { japi_context *ctx; japi_pushsrv_context *psc_status, *psc_temperature; @@ -417,13 +506,48 @@ TEST(JAPI_Push_Service,PushServiceDestroy) ctx = japi_init(NULL); /* Register some push services */ - psc_temperature = japi_pushsrv_register(ctx,"pushsrv_status"); - psc_status = japi_pushsrv_register(ctx,"pushsrv_temperature"); + psc_temperature = japi_pushsrv_register(ctx, "pushsrv_status"); + psc_status = japi_pushsrv_register(ctx, "pushsrv_temperature"); /* Destroy push services */ - EXPECT_EQ(japi_pushsrv_destroy(psc_status),0); - EXPECT_EQ(japi_pushsrv_destroy(psc_temperature),0); + EXPECT_EQ(japi_pushsrv_destroy(ctx, psc_status), 0); + EXPECT_EQ(japi_pushsrv_destroy(ctx, psc_temperature), 0); /* Pass bad push service context */ - EXPECT_EQ(japi_pushsrv_destroy(NULL),-1); + EXPECT_EQ(japi_pushsrv_destroy(ctx, NULL), -1); +} + +TEST(JAPI_Push_Service, PushServiceRemoveEntryFromLInkedList) +{ + japi_context *ctx; + japi_pushsrv_context *psc01, *psc02, *psc05; + json_object *jobj; + + ctx = japi_init(NULL); + jobj = json_object_new_object(); + + /* Register some test services, creates + { "services": [ "test05", "test04", "test03", "test02", "test01" ] } + */ + psc01 = japi_pushsrv_register(ctx, "test01"); + psc02 = japi_pushsrv_register(ctx, "test02"); + japi_pushsrv_register(ctx, "test03"); + japi_pushsrv_register(ctx, "test04"); + psc05 = japi_pushsrv_register(ctx, "test05"); + + japi_pushsrv_destroy(ctx, psc02); + japi_pushsrv_list(ctx, NULL, jobj); + EXPECT_STREQ( + json_object_to_json_string(jobj), + "{ \"services\": [ \"test05\", \"test04\", \"test03\", \"test01\" ] }"); + + japi_pushsrv_destroy(ctx, psc05); + japi_pushsrv_list(ctx, NULL, jobj); + EXPECT_STREQ(json_object_to_json_string(jobj), + "{ \"services\": [ \"test04\", \"test03\", \"test01\" ] }"); + + japi_pushsrv_destroy(ctx, psc01); + japi_pushsrv_list(ctx, NULL, jobj); + EXPECT_STREQ(json_object_to_json_string(jobj), + "{ \"services\": [ \"test04\", \"test03\" ] }"); }