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\" ] }");
}