From a3e2a8d41a49ee1da305d93b96d845a451febdca Mon Sep 17 00:00:00 2001 From: "Rule Timothy (CC/EMT2)" Date: Thu, 26 Oct 2023 10:51:36 +0200 Subject: [PATCH] Network Codec support. Signed-off-by: Rule Timothy (CC/EMT2) --- Makefile | 1 + doc/content/docs/devel/modelc_debug.md | 6 +- doc/content/docs/devel/modelc_ncodec.md | 103 +++++++++ dse/modelc/CMakeLists.txt | 28 +++ dse/modelc/model.h | 4 + dse/modelc/model/CMakeLists.txt | 4 + dse/modelc/model/model.c | 6 +- dse/modelc/model/ncodec.c | 177 +++++++++++++++ dse/modelc/model/signal.c | 60 ++++- extra/external/CMakeLists.txt | 16 ++ extra/external/oss_repos.cmake | 1 + tests/cmocka/CMakeLists.txt | 34 ++- tests/cmocka/Makefile | 2 + tests/cmocka/model/__test__.c | 42 +--- tests/cmocka/model/ncodec.yaml | 54 +++++ tests/cmocka/model/test_gateway.c | 17 +- tests/cmocka/model/test_ncodec.c | 280 ++++++++++++++++++++++++ tests/cmocka/model/test_signal.c | 19 +- 18 files changed, 807 insertions(+), 47 deletions(-) create mode 100644 doc/content/docs/devel/modelc_ncodec.md create mode 100644 dse/modelc/model/ncodec.c create mode 100644 tests/cmocka/model/ncodec.yaml create mode 100644 tests/cmocka/model/test_ncodec.c diff --git a/Makefile b/Makefile index 97a7542..bab92f1 100644 --- a/Makefile +++ b/Makefile @@ -203,6 +203,7 @@ super-linter: --env RUN_LOCAL=true \ --env DEFAULT_BRANCH=main \ --env IGNORE_GITIGNORED_FILES=true \ + --env FILTER_REGEX_EXCLUDE="(dse/modelc/examples/apis/.*|doc/content/apis/modelc/examples/.*)" \ --env VALIDATE_CPP=true \ --env VALIDATE_YAML=true \ github/super-linter:slim-v5 diff --git a/doc/content/docs/devel/modelc_debug.md b/doc/content/docs/devel/modelc_debug.md index 9fc34e9..d293e0a 100644 --- a/doc/content/docs/devel/modelc_debug.md +++ b/doc/content/docs/devel/modelc_debug.md @@ -1,14 +1,14 @@ --- title: "Model C Debug Techniques" -linkTitle: "Debug" +linkTitle: "ModelC Debug" draft: false tags: - Developer - ModelC - GDB github_repo: "https://github.com/boschglobal/dse.modelc" -github_subdir: "doc/content/developer" -path_base_for_github_subdir: "content/docs/developer/" +github_subdir: "doc" +path_base_for_github_subdir: "content/docs/devel/" --- ## GDB diff --git a/doc/content/docs/devel/modelc_ncodec.md b/doc/content/docs/devel/modelc_ncodec.md new file mode 100644 index 0000000..627ba87 --- /dev/null +++ b/doc/content/docs/devel/modelc_ncodec.md @@ -0,0 +1,103 @@ +--- +title: "Model C with Network Codec" +linkTitle: "ModelC NCodec" +draft: false +tags: +- Developer +- ModelC +github_repo: "https://github.com/boschglobal/dse.modelc" +github_subdir: "doc" +path_base_for_github_subdir: "content/docs/devel/" +--- + +## Network Codec + +The Model C Library integrates the [DSE Network Codec](https://github.com/boschglobal/dse.standards/tree/main/dse/ncodec) implementation of the [Automotive Bus schemas](https://github.com/boschglobal/automotive-bus-schema). + + +### Build Integration + +The Network Codec integration repackages the necessary include files with the Model C Library packages. No additional build integration is required. + + +### Configuration of Binary Signals + +Binary signals are configured with a MIME Type. The Signal Vector integration with the Network Codec will automatically open `codec` objects for each supported MIME Type that is supported (by the codec). Additional configuration of the `codec` objects can be done with the `ncodec_config()` API function. + +```yaml +kind: SignalGroup +metadata: + name: network + labels: + channel: network_vector + annotations: + vector_type: binary +spec: + signals: + - signal: can_bus + annotations: + mime_type: application/x-automotive-bus; interface=stream; type=frame; bus=can; schema=fbs; bus_id=1; node_id=2; interface_id=3 +``` + +Additional configuration information is available [here](https://github.com/boschglobal/dse.standards/blob/main/dse/ncodec/libs/automotive-bus/README.md). Especially the behaviour of `bus_id`,`node_id`and `interface_id` configuration items are described. + + +Configuration items can also be set at runtime with the `ncodec_config()` API as the following example shows: + +```c +#include +#include + + +static void _setup_node_id(SignalVector* sv, uint32_t idx) +{ + const char* v = sv->annotation(sv, idx, "node_id"); + if (v) { + NCODEC* nc = sv->codec(sv, idx); + ncodec_config(nc, (struct NCodecConfigItem){ + .name = "node_id", + .value = v, + }); + } +} +``` + +### Usage in Model Code + +The Network Codec integration is fairly easy to use. The general approach is as follows: + +```c +#include +#include + + +void do_bus_rx(SignalVector* sv, uint32_t idx) +{ + NCODEC* nc = sv->codec(sv, idx); + + while (1) { + NCodecMessage msg = {}; + len = ncodec_read(nc, &msg); + if (len < 0) break; + put_rx_frame_to_queue(msg.frame_id, msg.buffer, msg.len); + } +} + +void do_bus_tx(SignalVector* sv, uint32_t idx) +{ + uint32_t id; + uint8_t* msg; + size_t len; + NCODEC* nc = sv->codec(sv, idx); + + while (get_tx_frame_from_queue(&id, &msg, &len)) { + ncodec_write(nc, &(struct NCodecMessage){ + .frame_id = id, + .buffer = msg, + .len = len, + }); + } + ncodec_flush(nc); +} + +``` \ No newline at end of file diff --git a/dse/modelc/CMakeLists.txt b/dse/modelc/CMakeLists.txt index dcda700..7523320 100644 --- a/dse/modelc/CMakeLists.txt +++ b/dse/modelc/CMakeLists.txt @@ -55,6 +55,26 @@ set(DSE_CLIB_SOURCE_FILES set(DSE_CLIB_INCLUDE_DIR "${DSE_CLIB_SOURCE_DIR}/..") +# External Project - DSE Network Codec +# ------------------------------------ +set(DSE_NCODEC_SOURCE_DIR "$ENV{EXTERNAL_BUILD_DIR}/dse_ncodec") +set(DSE_NCODEC_BINARY_DIR "$ENV{EXTERNAL_BUILD_DIR}/dse_ncodec") +find_library(DSE_NCODEC_LIB + NAMES + libautomotive-bus-codec.a + PATHS + ${DSE_NCODEC_BINARY_DIR} + REQUIRED + NO_DEFAULT_PATH +) +add_library(dse_ncodec STATIC IMPORTED GLOBAL) +set_target_properties(dse_ncodec + PROPERTIES + IMPORTED_LOCATION "${DSE_NCODEC_LIB}" + INTERFACE_INCLUDE_DIRECTORIES "${DSE_NCODEC_SOURCE_DIR}" +) +set(DSE_NCODEC_INCLUDE_DIR "${DSE_NCODEC_SOURCE_DIR}") + # External Projects (targets common to sub projects) # ================= @@ -274,6 +294,14 @@ install( PATTERN "*fmi*" EXCLUDE PATTERN "*process*" EXCLUDE ) +install( + FILES + ${DSE_NCODEC_SOURCE_DIR}/dse/ncodec/codec.h + DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/dse/ncodec + COMPONENT + modelc +) install( FILES ${DSE_MODELC_PUBLIC_HEADERS} diff --git a/dse/modelc/model.h b/dse/modelc/model.h index 938d13f..0670936 100644 --- a/dse/modelc/model.h +++ b/dse/modelc/model.h @@ -160,6 +160,7 @@ typedef int (*BinarySignalAppendFunc)( SignalVector* sv, uint32_t index, void* data, uint32_t len); typedef int (*BinarySignalResetFunc)(SignalVector* sv, uint32_t index); typedef int (*BinarySignalReleaseFunc)(SignalVector* sv, uint32_t index); +typedef void* (*BinarySignalCodecFunc)(SignalVector* sv, uint32_t index); typedef const char* (*SignalAnnotationGetFunc)( SignalVector* sv, uint32_t index, const char* name); @@ -168,6 +169,7 @@ typedef struct SignalVectorVTable { BinarySignalResetFunc reset; BinarySignalReleaseFunc release; SignalAnnotationGetFunc annotation; + BinarySignalCodecFunc codec; } SignalVectorVTable; typedef struct SignalVector { @@ -187,6 +189,7 @@ typedef struct SignalVector { uint32_t* length; /* Length of binary object. */ uint32_t* buffer_size; /* Size of allocated buffer. */ const char** mime_type; + void** ncodec; /* Network Codec objects. */ }; }; /* Helper functions. */ @@ -194,6 +197,7 @@ typedef struct SignalVector { BinarySignalResetFunc reset; BinarySignalReleaseFunc release; SignalAnnotationGetFunc annotation; + BinarySignalCodecFunc codec; /* Reference data. */ ModelInstanceSpec* mi; } SignalVector; diff --git a/dse/modelc/model/CMakeLists.txt b/dse/modelc/model/CMakeLists.txt index 5a46ad0..de6c0d0 100644 --- a/dse/modelc/model/CMakeLists.txt +++ b/dse/modelc/model/CMakeLists.txt @@ -16,17 +16,21 @@ project(ModelC_Model_API) add_library(model_api OBJECT gateway.c model.c + ncodec.c schema.c signal.c + ${DSE_NCODEC_SOURCE_DIR}/dse/ncodec/codec.c ) target_include_directories(model_api PRIVATE ${DSE_CLIB_INCLUDE_DIR} + ${DSE_NCODEC_INCLUDE_DIR} ${YAML_SOURCE_DIR}/include ../../.. ) target_link_libraries(model_api PRIVATE + dse_ncodec yaml m $<$:ws2_32> diff --git a/dse/modelc/model/model.c b/dse/modelc/model/model.c index eb55758..dc1af5b 100644 --- a/dse/modelc/model/model.c +++ b/dse/modelc/model/model.c @@ -39,8 +39,10 @@ void model_function_destroy(ModelFunction* model_function) if (_mfc && _mfc->signal_value_double) free(_mfc->signal_value_double); if (_mfc && _mfc->signal_value_binary) { - for (uint32_t _ = 0; _ < _mfc->signal_count; _++) - free((void*)_mfc->signal_value_binary[_]); + for (uint32_t _ = 0; _ < _mfc->signal_count; _++) { + if ((void*)_mfc->signal_value_binary[_]) + free((void*)_mfc->signal_value_binary[_]); + } free(_mfc->signal_value_binary); } if (_mfc && _mfc->signal_value_binary_size) diff --git a/dse/modelc/model/ncodec.c b/dse/modelc/model/ncodec.c new file mode 100644 index 0000000..dacca4f --- /dev/null +++ b/dse/modelc/model/ncodec.c @@ -0,0 +1,177 @@ +// Copyright 2023 Robert Bosch GmbH +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include + + +#define UNUSED(x) ((void)x) + + +/* Stream Interface for binary signals (supports NCodec). */ +typedef struct __BinarySignalStream { + NCodecStreamVTable s; + /**/ + SignalVector* sv; + uint32_t idx; + uint32_t pos; +} __BinarySignalStream; + + +static size_t stream_read(NCODEC* nc, uint8_t** data, size_t* len, int pos_op) +{ + NCodecInstance* _nc = (NCodecInstance*)nc; + if (_nc == NULL || _nc->stream == NULL) return -ENOSTR; + if (data == NULL || len == NULL) return -EINVAL; + + __BinarySignalStream* _s = (__BinarySignalStream*)_nc->stream; + uint32_t s_len = _s->sv->length[_s->idx]; + uint8_t* s_buffer = _s->sv->binary[_s->idx]; + + /* Check if any buffer. */ + if (s_buffer == NULL) { + *data = NULL; + *len = 0; + return 0; + } + /* Check EOF. */ + if (_s->pos >= s_len) { + *data = NULL; + *len = 0; + return 0; + } + /* Return buffer, from current pos. */ + *data = &s_buffer[_s->pos]; + *len = s_len - _s->pos; + /* Advance the position indicator. */ + if (pos_op == NCODEC_POS_UPDATE) _s->pos = s_len; + + return *len; +} + +static size_t stream_write(NCODEC* nc, uint8_t* data, size_t len) +{ + NCodecInstance* _nc = (NCodecInstance*)nc; + if (_nc == NULL || _nc->stream == NULL) return -ENOSTR; + + __BinarySignalStream* _s = (__BinarySignalStream*)_nc->stream; + uint32_t s_len = _s->sv->length[_s->idx]; + + /* Write from current pos (i.e. truncate). */ + if (_s->pos > s_len) _s->pos = s_len; + _s->sv->length[_s->idx] = _s->pos; + _s->sv->append(_s->sv, _s->idx, data, len); + _s->pos += len; + + return len; +} + +static int stream_seek(NCODEC* nc, size_t pos, int op) +{ + NCodecInstance* _nc = (NCodecInstance*)nc; + if (_nc && _nc->stream) { + __BinarySignalStream* _s = (__BinarySignalStream*)_nc->stream; + uint32_t s_len = _s->sv->length[_s->idx]; + + if (op == NCODEC_SEEK_SET) { + if (pos > s_len) { + _s->pos = s_len; + } else { + _s->pos = pos; + } + } else if (op == NCODEC_SEEK_CUR) { + pos = _s->pos + pos; + if (pos > s_len) { + _s->pos = s_len; + } else { + _s->pos = pos; + } + } else if (op == NCODEC_SEEK_END) { + _s->pos = s_len; + } else if (op == NCODEC_SEEK_RESET) { + _s->pos = 0; + } else { + return -EINVAL; + } + return _s->pos; + } + return -ENOSTR; +} + +static size_t stream_tell(NCODEC* nc) +{ + NCodecInstance* _nc = (NCodecInstance*)nc; + if (_nc && _nc->stream) { + __BinarySignalStream* _s = (__BinarySignalStream*)_nc->stream; + return _s->pos; + } + return -ENOSTR; +} + +static int stream_eof(NCODEC* nc) +{ + NCodecInstance* _nc = (NCodecInstance*)nc; + if (_nc && _nc->stream) { + __BinarySignalStream* _s = (__BinarySignalStream*)_nc->stream; + uint32_t s_len = _s->sv->length[_s->idx]; + + if (_s->pos < s_len) return 0; + } + return 1; +} + +static int stream_close(NCODEC* nc) +{ + UNUSED(nc); + + return 0; +} + + +/* Private stream interface. */ + +void* _create_stream(SignalVector* sv, uint32_t idx) +{ + if (sv->is_binary != true) { + errno = EINVAL; + return NULL; + } + __BinarySignalStream* stream = calloc(1, sizeof(__BinarySignalStream)); + stream->s = (struct NCodecStreamVTable){ + .read = stream_read, + .write = stream_write, + .seek = stream_seek, + .tell = stream_tell, + .eof = stream_eof, + .close = stream_close, + }; + stream->sv = sv; + stream->idx = idx; + stream->pos = 0; + + return stream; +} + +void _free_stream(void* stream) +{ + if (stream) free(stream); +} + + +/* NCodec Interface. */ + +NCODEC* ncodec_open(const char* mime_type, NCodecStreamVTable* stream) +{ + NCODEC* nc = ncodec_create(mime_type); + if (nc == NULL || stream == NULL) { + errno = EINVAL; + return NULL; + } + NCodecInstance* _nc = (NCodecInstance*)nc; + _nc->stream = stream; + return nc; +} diff --git a/dse/modelc/model/signal.c b/dse/modelc/model/signal.c index 7ea96a1..54f8634 100644 --- a/dse/modelc/model/signal.c +++ b/dse/modelc/model/signal.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -16,6 +17,11 @@ #define DEFAULT_BINARY_MIME_TYPE "application/octet-stream" +/* Private interface from ncodec.c */ +extern void* _create_stream(SignalVector* sv, uint32_t idx); +extern void _free_stream(void* stream); + + /* Signal Annotation Functions. */ static SchemaSignalObject* __signal_match; @@ -103,6 +109,14 @@ static int __binary_release_nop(SignalVector* sv, uint32_t index) return 0; } +static void* __binary_codec_nop(SignalVector* sv, uint32_t index) +{ + UNUSED(sv); + UNUSED(index); + + return 0; +} + static int __binary_append( SignalVector* sv, uint32_t index, void* data, uint32_t len) { @@ -116,14 +130,20 @@ static int __binary_reset(SignalVector* sv, uint32_t index) { sv->length[index] = 0; + NCodecInstance* nc = sv->ncodec[index]; + if (nc && nc->stream && nc->stream->seek) { + nc->stream->seek((NCODEC*)nc, 0, NCODEC_SEEK_RESET); + } + return 0; } static int __binary_release(SignalVector* sv, uint32_t index) { + __binary_reset(sv, index); + free(sv->binary[index]); sv->binary[index] = NULL; - sv->length[index] = 0; sv->buffer_size[index] = 0; return 0; @@ -138,6 +158,15 @@ static const char* __annotation_get( return _signal_annotation(sv->mi, sv, sv->signal[index], name); } +static void* __binary_codec(SignalVector* sv, uint32_t index) +{ + assert(sv); + assert(sv->mi); + assert(index < sv->count); + + return sv->ncodec[index]; +} + /* Enumerator Functions, for model_sv_create(). */ @@ -176,6 +205,7 @@ static int _add_sv(void* _mfc, void* _sv_data) current_sv->append = __binary_append; current_sv->reset = __binary_reset; current_sv->release = __binary_release; + current_sv->codec = __binary_codec; /* Mime Type. */ current_sv->mime_type = calloc(current_sv->count, sizeof(char*)); for (uint32_t i = 0; i < current_sv->count; i++) { @@ -184,12 +214,24 @@ static int _add_sv(void* _mfc, void* _sv_data) mt = current_sv->annotation(current_sv, i, "mime_type"); if (mt) current_sv->mime_type[i] = mt; } + /* NCodec. */ + current_sv->ncodec = calloc(current_sv->count, sizeof(NCODEC*)); + for (uint32_t i = 0; i < current_sv->count; i++) { + void* stream = _create_stream(current_sv, i); + NCODEC* nc = ncodec_open(current_sv->mime_type[i], stream); + if (nc) { + current_sv->ncodec[i] = nc; + } else { + _free_stream(stream); + } + } } else { current_sv->is_binary = false; current_sv->scalar = mfc->signal_value_double; current_sv->append = __binary_append_nop; current_sv->reset = __binary_reset_nop; current_sv->release = __binary_release_nop; + current_sv->codec = __binary_codec_nop; } /* Progress the data object to the next item (for next call). */ @@ -270,7 +312,8 @@ SignalVector* model_sv_create(ModelInstanceSpec* mi) while (sv_p && sv_p->name) { const char* selector[] = { "name" }; const char* value[] = { sv_p->name }; - YamlNode* ch_node = dse_yaml_find_node_in_seq(mi->spec, "channels", selector, value, 1); + YamlNode* ch_node = + dse_yaml_find_node_in_seq(mi->spec, "channels", selector, value, 1); if (ch_node) { sv_p->alias = dse_yaml_get_scalar(ch_node, "alias"); } @@ -303,6 +346,19 @@ void model_sv_destroy(SignalVector* sv) while (sv && sv->name) { if (sv->mime_type) free(sv->mime_type); + if (sv->is_binary) { + /* NCodec. */ + for (uint32_t i = 0; i < sv->count; i++) { + NCodecInstance* nc = sv->ncodec[i]; + if (nc) { + _free_stream(nc->stream); + ncodec_close((NCODEC*)nc); + sv->ncodec[i] = NULL; + } + } + free(sv->ncodec); + } + /* Next signal vector. */ sv++; } diff --git a/extra/external/CMakeLists.txt b/extra/external/CMakeLists.txt index 78cdae7..14df816 100644 --- a/extra/external/CMakeLists.txt +++ b/extra/external/CMakeLists.txt @@ -29,6 +29,21 @@ set_property(DIRECTORY PROPERTY EP_STEP_TARGETS download) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") +# DSE Network Codec +# ----------------- +ExternalProject_Add(dse_ncodec + URL ${ExternalProject__DSE_NCODEC__URL} + HTTP_USERNAME ${ExternalProject__DSE_NCODEC__USERNAME} + HTTP_PASSWORD ${ExternalProject__DSE_NCODEC__PASSWORD} + SOURCE_DIR "$ENV{EXTERNAL_BUILD_DIR}/dse_ncodec" + SOURCE_SUBDIR "dse/ncodec/libs/automotive-bus" + BINARY_DIR "$ENV{EXTERNAL_BUILD_DIR}/dse_ncodec" + CMAKE_ARGS + -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} + INSTALL_COMMAND "" +) + + # Lib Event # --------- ExternalProject_Add(event @@ -125,6 +140,7 @@ ExternalProject_Add(dlfcnwin32 # ========== add_custom_target(oss) add_dependencies(oss + dse_ncodec-download event-download yaml-download msgpackc-download diff --git a/extra/external/oss_repos.cmake b/extra/external/oss_repos.cmake index 78dcd9d..64b2c35 100644 --- a/extra/external/oss_repos.cmake +++ b/extra/external/oss_repos.cmake @@ -7,6 +7,7 @@ # OSS Projects can be listed here, even if they are not used by the # External Projects, to maintain an accurate inventory of OSS Projects. +set(ExternalProject__DSE_NCODEC__URL https://github.com/boschglobal/dse.standards/archive/refs/tags/v1.0.2.tar.gz) set(ExternalProject__EVENT__URL https://github.com/libevent/libevent/archive/refs/tags/release-2.1.12-stable.tar.gz) set(ExternalProject__YAML__URL https://github.com/yaml/libyaml/archive/0.2.5.tar.gz) set(ExternalProject__MSGPACK__URL https://github.com/msgpack/msgpack-c/archive/refs/tags/cpp-3.3.0.tar.gz) diff --git a/tests/cmocka/CMakeLists.txt b/tests/cmocka/CMakeLists.txt index 8f0c0e6..06fc4ac 100644 --- a/tests/cmocka/CMakeLists.txt +++ b/tests/cmocka/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.21) -set(CMAKE_VERBOSE_MAKEFILE ON) +#set(CMAKE_VERBOSE_MAKEFILE ON) project(test_modelc) @@ -65,6 +65,30 @@ set(DSE_CLIB_SOURCE_FILES set(DSE_CLIB_INCLUDE_DIR "${DSE_CLIB_SOURCE_DIR}/../..") +# External Project - DSE Network Codec +# ------------------------------------ +set(DSE_NCODEC_SOURCE_DIR "$ENV{EXTERNAL_BUILD_DIR}/dse_ncodec") +set(DSE_NCODEC_BINARY_DIR "$ENV{EXTERNAL_BUILD_DIR}/dse_ncodec") +find_library(DSE_NCODEC_LIB + NAMES + libautomotive-bus-codec.a + PATHS + ${DSE_NCODEC_BINARY_DIR} + REQUIRED + NO_DEFAULT_PATH +) +add_library(dse_ncodec STATIC IMPORTED GLOBAL) +set_target_properties(dse_ncodec + PROPERTIES + IMPORTED_LOCATION "${DSE_NCODEC_LIB}" + INTERFACE_INCLUDE_DIRECTORIES "${DSE_NCODEC_SOURCE_DIR}" +) +set(DSE_NCODEC_SOURCE_FILES + ${DSE_NCODEC_SOURCE_DIR}/dse/ncodec/codec.c +) +set(DSE_NCODEC_INCLUDE_DIR "${DSE_NCODEC_SOURCE_DIR}") + + # Set the project paths # ===================== @@ -72,6 +96,7 @@ set(DSE_MODELC_SOURCE_DIR ../../dse/modelc) set(DSE_MODELC_SOURCE_FILES ${DSE_MODELC_SOURCE_DIR}/model/gateway.c ${DSE_MODELC_SOURCE_DIR}/model/model.c + ${DSE_MODELC_SOURCE_DIR}/model/ncodec.c ${DSE_MODELC_SOURCE_DIR}/model/schema.c ${DSE_MODELC_SOURCE_DIR}/model/signal.c @@ -95,28 +120,32 @@ add_executable(test_model model/__test__.c model/test_gateway.c model/test_model.c + model/test_ncodec.c model/test_schema.c model/test_signal.c model/stub_controller.c ${DSE_MODELC_SOURCE_FILES} + ${DSE_NCODEC_SOURCE_FILES} ${DSE_CLIB_SOURCE_FILES} ) target_include_directories(test_model PRIVATE ${DSE_MODELC_INCLUDE_DIR} + ${DSE_NCODEC_INCLUDE_DIR} ${DSE_CLIB_INCLUDE_DIR} ${YAML_SOURCE_DIR}/include ./ ) target_compile_definitions(test_model PUBLIC - UNIT_TESTING +# UNIT_TESTING # Using valgrind instead. PRIVATE PLATFORM_OS="${CDEF_PLATFORM_OS}" PLATFORM_ARCH="${CDEF_PLATFORM_ARCH}" ) target_link_libraries(test_model PRIVATE + dse_ncodec cmocka yaml dl @@ -127,6 +156,7 @@ install(TARGETS test_model) install( FILES model/gateway.yaml + model/ncodec.yaml model/signal.yaml DESTINATION resources/model diff --git a/tests/cmocka/Makefile b/tests/cmocka/Makefile index 7ad5e55..2d2815c 100644 --- a/tests/cmocka/Makefile +++ b/tests/cmocka/Makefile @@ -2,6 +2,8 @@ # # SPDX-License-Identifier: Apache-2.0 +GDB_CMD ?= valgrind -q --leak-check=yes +# GDB_CMD ?= gdb -q -ex='set confirm on' -ex=run -ex=quit default: build diff --git a/tests/cmocka/model/__test__.c b/tests/cmocka/model/__test__.c index d1ea66f..6616bc3 100644 --- a/tests/cmocka/model/__test__.c +++ b/tests/cmocka/model/__test__.c @@ -15,44 +15,18 @@ extern uint8_t __log_level__; /* LOG_ERROR LOG_INFO LOG_DEBUG LOG_TRACE */ -int test_gateway_setup(void** state); -int test_gateway_teardown(void** state); -void test_gateway__scalar_sv(void** state); - -void test_model__(void** state); - -void test_schema__(void** state); - -int test_signal_setup(void** state); -int test_signal_teardown(void** state); -void test_signal__scalar(void** state); -void test_signal__binary(void** state); -void test_signal__annotations(void** state); +extern int run_gateway_tests(void); +extern int run_signal_tests(void); +extern int run_ncodec_tests(void); int main() { __log_level__ = LOG_QUIET; - const struct CMUnitTest tests[] = { - /* test_gateway.c */ - cmocka_unit_test_setup_teardown( - test_gateway__scalar_sv, test_gateway_setup, test_gateway_teardown), - - /* test_model.c */ - // cmocka_unit_test(test_model__), - - /* test_schema.c */ - // cmocka_unit_test(test_schema__), - - /* test_signal.c */ - cmocka_unit_test_setup_teardown( - test_signal__scalar, test_signal_setup, test_signal_teardown), - cmocka_unit_test_setup_teardown( - test_signal__binary, test_signal_setup, test_signal_teardown), - cmocka_unit_test_setup_teardown( - test_signal__annotations, test_signal_setup, test_signal_teardown), - }; - - return cmocka_run_group_tests(tests, NULL, NULL); + int rc = 0; + rc |= run_gateway_tests(); + rc |= run_signal_tests(); + rc |= run_ncodec_tests(); + return rc; } diff --git a/tests/cmocka/model/ncodec.yaml b/tests/cmocka/model/ncodec.yaml new file mode 100644 index 0000000..8e6e19c --- /dev/null +++ b/tests/cmocka/model/ncodec.yaml @@ -0,0 +1,54 @@ +--- +kind: Stack +metadata: + name: stack +spec: + connection: + transport: + redispubsub: + uri: redis://redis:6379 + timeout: 60 + models: + - name: signal + uid: 42 + model: + name: Signal + channels: + - name: binary + alias: binary_vector +--- +kind: Model +metadata: + name: Signal +spec: + runtime: + dynlib: + - os: linux + arch: amd64 + path: lib/model.so + channels: + - alias: binary_vector + selectors: + channel: binary +--- +kind: SignalGroup +metadata: + name: test_binary_signals + labels: + channel: binary + annotations: + vector_type: binary + vector_name: network_vector +spec: + signals: + - signal: no_mime + annotations: + name: no_mime + - signal: no_stream + annotations: + name: no_stream + mime_type: application/custom-stream + - signal: stream + annotations: + name: stream + mime_type: application/x-automotive-bus; interface=stream; type=frame; bus=can; schema=fbs; bus_id=1; node_id=2; interface_id=3 diff --git a/tests/cmocka/model/test_gateway.c b/tests/cmocka/model/test_gateway.c index 7cacd59..bad9b2d 100644 --- a/tests/cmocka/model/test_gateway.c +++ b/tests/cmocka/model/test_gateway.c @@ -29,7 +29,7 @@ void stub_setup_objects(Controller* c, Endpoint* e); void stub_release_objects(Controller* c, Endpoint* e); -int test_gateway_setup(void** state) +static int test_setup(void** state) { ModelCMock* mock = calloc(1, sizeof(ModelCMock)); assert_non_null(mock); @@ -45,7 +45,7 @@ int test_gateway_setup(void** state) } -int test_gateway_teardown(void** state) +static int test_teardown(void** state) { ModelCMock* mock = *state; @@ -119,3 +119,16 @@ void test_gateway__scalar_sv(void** state) /* Exit the simulation. */ model_gw_exit(&gw); } + + +int run_gateway_tests(void) +{ + void* s = test_setup; + void* t = test_teardown; + + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(test_gateway__scalar_sv, s, t), + }; + + return cmocka_run_group_tests_name("GATEWAY", tests, NULL, NULL); +} diff --git a/tests/cmocka/model/test_ncodec.c b/tests/cmocka/model/test_ncodec.c new file mode 100644 index 0000000..f772fde --- /dev/null +++ b/tests/cmocka/model/test_ncodec.c @@ -0,0 +1,280 @@ +// Copyright 2023 Robert Bosch GmbH +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define UNUSED(x) ((void)x) +#define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0])) + + +extern uint8_t __log_level__; + +typedef struct ModelCMock { + SimulationSpec sim; + ModelInstanceSpec* mi; +} ModelCMock; + + +static uint _sv_count(SignalVector* sv) +{ + uint count = 0; + while (sv && sv->name) { + count++; + /* Next signal vector. */ + sv++; + } + return count; +} + + +static int _sv_nop(double* model_time, double stop_time) +{ + UNUSED(model_time); + UNUSED(stop_time); + return 0; +} + + +static int test_setup(void** state) +{ + ModelCMock* mock = calloc(1, sizeof(ModelCMock)); + assert_non_null(mock); + + int rc; + ModelCArguments args; + char* argv[] = { + (char*)"test_signal", + (char*)"--name=signal", + (char*)"resources/model/ncodec.yaml", + }; + + modelc_set_default_args(&args, "test", 0.005, 0.005); + args.log_level = __log_level__; + modelc_parse_arguments(&args, ARRAY_SIZE(argv), argv, "Signal"); + rc = modelc_configure(&args, &mock->sim); + assert_int_equal(rc, 0); + mock->mi = modelc_get_model_instance(&mock->sim, args.name); + assert_non_null(mock->mi); + rc = model_function_register(mock->mi, "NOP", 0.005, _sv_nop); + assert_int_equal(rc, 0); + + /* Binary channel. */ + static ModelChannelDesc binary_channel_desc = { + .name = "binary_vector", + .function_name = "NOP", + }; + rc = model_configure_channel(mock->mi, &binary_channel_desc); + assert_int_equal(rc, 0); + + /* Return the mock. */ + *state = mock; + return 0; +} + + +static int test_teardown(void** state) +{ + ModelCMock* mock = *state; + + if (mock && mock->mi) { + dse_yaml_destroy_doc_list(mock->mi->yaml_doc_list); + } + if (mock) { + modelc_exit(&mock->sim); + free(mock); + } + + return 0; +} + + +void test_ncodec__ncodec_open(void** state) +{ + ModelCMock* mock = *state; + + SignalVector* sv_save = model_sv_create(mock->mi); + assert_int_equal(_sv_count(sv_save), 1); + + /* Use the "binary" signal vector. */ + SignalVector* sv = sv_save; + while (sv && sv->name) { + if (strcmp(sv->name, "binary") == 0) break; + /* Next signal vector. */ + sv++; + } + + /* Check the ncodec objects. */ + assert_null(sv->codec(sv, 0)); + assert_null(sv->codec(sv, 1)); + assert_non_null(sv->codec(sv, 2)); + assert_null(sv->ncodec[0]); + assert_null(sv->ncodec[1]); + assert_non_null(sv->ncodec[2]); + assert_ptr_equal(sv->codec(sv, 2), sv->ncodec[2]); + + model_sv_destroy(sv_save); +} + + +void test_ncodec__read_empty(void** state) +{ + ModelCMock* mock = *state; + size_t len; + NCodecMessage msg; + + /* Use the "binary" signal vector. */ + SignalVector* sv_save = model_sv_create(mock->mi); + assert_int_equal(_sv_count(sv_save), 1); + SignalVector* sv = sv_save; + while (sv && sv->name) { + if (strcmp(sv->name, "binary") == 0) break; + /* Next signal vector. */ + sv++; + } + assert_non_null(sv->codec(sv, 2)); + NCODEC* nc = sv->codec(sv, 2); + + /* First read. */ + memset(&msg, 0, sizeof(NCodecMessage)); + len = ncodec_read(nc, &msg); + assert_int_equal(len, -ENOMSG); + assert_int_equal(msg.len, 0); + assert_null(msg.buffer); + + /* Read after reset. */ + memset(&msg, 0, sizeof(NCodecMessage)); + sv->reset(sv, 2); + len = ncodec_read(nc, &msg); + assert_int_equal(len, -ENOMSG); + assert_int_equal(msg.len, 0); + assert_null(msg.buffer); + + /* Read after release. */ + memset(&msg, 0, sizeof(NCodecMessage)); + sv->release(sv, 2); + len = ncodec_read(nc, &msg); + assert_int_equal(len, -ENOMSG); + assert_int_equal(msg.len, 0); + assert_null(msg.buffer); + + model_sv_destroy(sv_save); +} + + +void test_ncodec__network_stream(void** state) +{ + ModelCMock* mock = *state; + int rc; + + /* Use the "binary" signal vector. */ + SignalVector* sv_save = model_sv_create(mock->mi); + assert_int_equal(_sv_count(sv_save), 1); + SignalVector* sv = sv_save; + while (sv && sv->name) { + if (strcmp(sv->name, "binary") == 0) break; + /* Next signal vector. */ + sv++; + } + + /* Check the ncodec objects. */ + assert_non_null(sv->codec(sv, 2)); + assert_non_null(sv->ncodec[2]); + assert_ptr_equal(sv->codec(sv, 2), sv->ncodec[2]); + + /* Use the codec object with the ncodec library. */ + const char* greeting = "Hello World"; + NCODEC* nc = sv->codec(sv, 2); + sv->reset(sv, 2); + rc = ncodec_write(nc, &(struct NCodecMessage){ .frame_id = 42, + .buffer = (uint8_t*)greeting, + .len = strlen(greeting) }); + assert_int_equal(rc, strlen(greeting)); + size_t len = ncodec_flush(nc); + assert_int_equal(len, 0x66); + assert_int_equal(len, sv->length[2]); + + /* Copy message, keeping the content, modify the node_id. */ + uint8_t buffer[1024]; + memcpy(buffer, sv->binary[2], len); + buffer[54] = 8; + // for (uint32_t i = 0; i< buffer_len;i+=8) printf("%02x %02x %02x %02x %02x + // %02x %02x %02x\n", + // buffer[i+0], buffer[i+1], buffer[i+2], buffer[i+3], + // buffer[i+4], buffer[i+5], buffer[i+6], buffer[i+7]); + + /* Read the message back. */ + sv->release(sv, 2); + sv->append(sv, 2, buffer, len); + NCodecMessage msg = {}; + len = ncodec_read(nc, &msg); + assert_int_equal(len, strlen(greeting)); + assert_int_equal(msg.len, strlen(greeting)); + assert_non_null(msg.buffer); + assert_memory_equal(msg.buffer, greeting, strlen(greeting)); + + model_sv_destroy(sv_save); +} + + +void test_ncodec__config(void** state) +{ + ModelCMock* mock = *state; + + /* Use the "binary" signal vector. */ + SignalVector* sv_save = model_sv_create(mock->mi); + assert_int_equal(_sv_count(sv_save), 1); + SignalVector* sv = sv_save; + while (sv && sv->name) { + if (strcmp(sv->name, "binary") == 0) break; + /* Next signal vector. */ + sv++; + } + assert_non_null(sv->codec(sv, 2)); + NCODEC* nc = sv->codec(sv, 2); + + /* Adjust the node_id. */ + ncodec_config(nc, (struct NCodecConfigItem){ + .name = "node_id", + .value = "8", + }); + + /* Write a message, and check the emitted node_id. */ + const char* greeting = "Hello World"; + ncodec_write(nc, &(struct NCodecMessage){ .frame_id = 42, + .buffer = (uint8_t*)greeting, + .len = strlen(greeting) }); + size_t len = ncodec_flush(nc); + assert_int_equal(len, 0x66); + assert_int_equal(len, sv->length[2]); + assert_int_equal(((uint8_t*)(sv->binary[2]))[54], 8); + + model_sv_destroy(sv_save); +} + + +int run_ncodec_tests(void) +{ + void* s = test_setup; + void* t = test_teardown; + + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(test_ncodec__ncodec_open, s, t), + cmocka_unit_test_setup_teardown(test_ncodec__read_empty, s, t), + cmocka_unit_test_setup_teardown(test_ncodec__network_stream, s, t), + cmocka_unit_test_setup_teardown(test_ncodec__config, s, t), + }; + + return cmocka_run_group_tests_name("NCODEC", tests, NULL, NULL); +} diff --git a/tests/cmocka/model/test_signal.c b/tests/cmocka/model/test_signal.c index 28771de..78e7b7a 100644 --- a/tests/cmocka/model/test_signal.c +++ b/tests/cmocka/model/test_signal.c @@ -46,7 +46,7 @@ static int _sv_nop(double* model_time, double stop_time) } -int test_signal_setup(void** state) +static int test_setup(void** state) { ModelCMock* mock = calloc(1, sizeof(ModelCMock)); assert_non_null(mock); @@ -91,7 +91,7 @@ int test_signal_setup(void** state) } -int test_signal_teardown(void** state) +static int test_teardown(void** state) { ModelCMock* mock = *state; @@ -265,3 +265,18 @@ void test_signal__annotations(void** state) model_sv_destroy(sv_save); } + + +int run_signal_tests(void) +{ + void* s = test_setup; + void* t = test_teardown; + + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(test_signal__scalar, s, t), + cmocka_unit_test_setup_teardown(test_signal__binary, s, t), + cmocka_unit_test_setup_teardown(test_signal__annotations, s, t), + }; + + return cmocka_run_group_tests_name("NCODEC", tests, NULL, NULL); +}