From fd9cc7a5b3355146388ebdf4d558cb284c66c5f1 Mon Sep 17 00:00:00 2001 From: Ashwin Sridharan Date: Wed, 3 Apr 2019 16:47:02 -0400 Subject: [PATCH] Initial commit of RMR Library Change-Id: Ic4c998b056e8759f4a47a9a8c50c77e88df0f325 Signed-off-by: Ashwin Sridharan --- BUILD | 103 +++++ CMakeLists.txt | 248 +++++++++++ LICENSE.txt | 29 ++ README | 93 ++++ doc/CMakeLists.txt | 117 +++++ doc/README | 17 + doc/src/generic_ps.im | 75 ++++ doc/src/man/.gitignore | 6 + doc/src/man/README | 16 + doc/src/man/rmr.7.xfm | 135 ++++++ doc/src/man/rmr_alloc_msg.3.xfm | 153 +++++++ doc/src/man/rmr_bytes2meid.3.xfm | 111 +++++ doc/src/man/rmr_bytes2payload.3.xfm | 101 +++++ doc/src/man/rmr_bytes2xact.3.xfm | 108 +++++ doc/src/man/rmr_call.3.xfm | 185 ++++++++ doc/src/man/rmr_close.3.xfm | 88 ++++ doc/src/man/rmr_free_msg.3.xfm | 87 ++++ doc/src/man/rmr_get_meid.3.xfm | 112 +++++ doc/src/man/rmr_get_rcvfd.3.xfm | 150 +++++++ doc/src/man/rmr_init.3.xfm | 134 ++++++ doc/src/man/rmr_payload_size.3.xfm | 89 ++++ doc/src/man/rmr_rcv_msg.3.xfm | 127 ++++++ doc/src/man/rmr_ready.3.xfm | 86 ++++ doc/src/man/rmr_rts_msg.3.xfm | 151 +++++++ doc/src/man/rmr_send_msg.3.xfm | 188 +++++++++ doc/src/man/rmr_str2meid.3.xfm | 108 +++++ doc/src/man/rmr_str2xact.3.xfm | 107 +++++ doc/src/man/rmr_support.3.xfm | 158 +++++++ doc/src/man/rmr_torcv_msg.3.xfm | 145 +++++++ doc/src/man/rmr_wh_close.3.xfm | 90 ++++ doc/src/man/rmr_wh_open.3.xfm | 131 ++++++ doc/src/man/rmr_wh_send_msg.3.xfm | 204 +++++++++ doc/src/roff.im | 78 ++++ doc/src/rst.im | 100 +++++ examples/Makefile | 14 + examples/README | 6 + examples/receiver.c | 83 ++++ examples/sender.c | 120 ++++++ src/README | 13 + src/STYLE | 126 ++++++ src/common/CMakeLists.txt | 38 ++ src/common/include/RIC_message_types.h | 54 +++ src/common/include/rmr.h | 173 ++++++++ src/common/include/rmr_agnostic.h | 187 ++++++++ src/common/include/rmr_symtab.h | 52 +++ src/common/src/README | 14 + src/common/src/mbuf_api.c | 217 ++++++++++ src/common/src/ring_static.c | 143 +++++++ src/common/src/rt_generic_static.c | 466 ++++++++++++++++++++ src/common/src/rtc_static.c | 223 ++++++++++ src/common/src/symtab.c | 470 +++++++++++++++++++++ src/common/src/tools_static.c | 383 +++++++++++++++++ src/common/src/wormholes.c | 313 ++++++++++++++ src/nanomsg/CMakeLists.txt | 35 ++ src/nanomsg/include/rmr_private.h | 151 +++++++ src/nanomsg/src/rmr.c | 636 ++++++++++++++++++++++++++++ src/nanomsg/src/rtable_static.c | 272 ++++++++++++ src/nanomsg/src/sr_static.c | 290 +++++++++++++ src/nng/CMakeLists.txt | 35 ++ src/nng/include/rmr_nng_private.h | 120 ++++++ src/nng/src/rmr_nng.c | 750 +++++++++++++++++++++++++++++++++ src/nng/src/rtable_nng_static.c | 333 +++++++++++++++ src/nng/src/sr_nng_static.c | 454 ++++++++++++++++++++ test/.gitignore | 4 + test/.targets | 6 + test/Makefile | 24 ++ test/README | 82 ++++ test/ring_test.c | 159 +++++++ test/symtab_test.c | 139 ++++++ test/test_support.c | 134 ++++++ test/tools_test.c | 199 +++++++++ test/unit_test.ksh | 416 ++++++++++++++++++ 72 files changed, 11164 insertions(+) create mode 100644 BUILD create mode 100644 CMakeLists.txt create mode 100644 LICENSE.txt create mode 100644 README create mode 100644 doc/CMakeLists.txt create mode 100644 doc/README create mode 100644 doc/src/generic_ps.im create mode 100644 doc/src/man/.gitignore create mode 100644 doc/src/man/README create mode 100644 doc/src/man/rmr.7.xfm create mode 100644 doc/src/man/rmr_alloc_msg.3.xfm create mode 100644 doc/src/man/rmr_bytes2meid.3.xfm create mode 100644 doc/src/man/rmr_bytes2payload.3.xfm create mode 100644 doc/src/man/rmr_bytes2xact.3.xfm create mode 100644 doc/src/man/rmr_call.3.xfm create mode 100644 doc/src/man/rmr_close.3.xfm create mode 100644 doc/src/man/rmr_free_msg.3.xfm create mode 100644 doc/src/man/rmr_get_meid.3.xfm create mode 100644 doc/src/man/rmr_get_rcvfd.3.xfm create mode 100644 doc/src/man/rmr_init.3.xfm create mode 100644 doc/src/man/rmr_payload_size.3.xfm create mode 100644 doc/src/man/rmr_rcv_msg.3.xfm create mode 100644 doc/src/man/rmr_ready.3.xfm create mode 100644 doc/src/man/rmr_rts_msg.3.xfm create mode 100644 doc/src/man/rmr_send_msg.3.xfm create mode 100644 doc/src/man/rmr_str2meid.3.xfm create mode 100644 doc/src/man/rmr_str2xact.3.xfm create mode 100644 doc/src/man/rmr_support.3.xfm create mode 100644 doc/src/man/rmr_torcv_msg.3.xfm create mode 100644 doc/src/man/rmr_wh_close.3.xfm create mode 100644 doc/src/man/rmr_wh_open.3.xfm create mode 100644 doc/src/man/rmr_wh_send_msg.3.xfm create mode 100644 doc/src/roff.im create mode 100644 doc/src/rst.im create mode 100644 examples/Makefile create mode 100644 examples/README create mode 100644 examples/receiver.c create mode 100644 examples/sender.c create mode 100644 src/README create mode 100644 src/STYLE create mode 100644 src/common/CMakeLists.txt create mode 100644 src/common/include/RIC_message_types.h create mode 100644 src/common/include/rmr.h create mode 100644 src/common/include/rmr_agnostic.h create mode 100644 src/common/include/rmr_symtab.h create mode 100644 src/common/src/README create mode 100644 src/common/src/mbuf_api.c create mode 100644 src/common/src/ring_static.c create mode 100644 src/common/src/rt_generic_static.c create mode 100644 src/common/src/rtc_static.c create mode 100644 src/common/src/symtab.c create mode 100644 src/common/src/tools_static.c create mode 100644 src/common/src/wormholes.c create mode 100644 src/nanomsg/CMakeLists.txt create mode 100644 src/nanomsg/include/rmr_private.h create mode 100644 src/nanomsg/src/rmr.c create mode 100644 src/nanomsg/src/rtable_static.c create mode 100644 src/nanomsg/src/sr_static.c create mode 100644 src/nng/CMakeLists.txt create mode 100644 src/nng/include/rmr_nng_private.h create mode 100644 src/nng/src/rmr_nng.c create mode 100644 src/nng/src/rtable_nng_static.c create mode 100644 src/nng/src/sr_nng_static.c create mode 100644 test/.gitignore create mode 100644 test/.targets create mode 100644 test/Makefile create mode 100644 test/README create mode 100644 test/ring_test.c create mode 100644 test/symtab_test.c create mode 100644 test/test_support.c create mode 100644 test/tools_test.c create mode 100755 test/unit_test.ksh diff --git a/BUILD b/BUILD new file mode 100644 index 0000000..64bd052 --- /dev/null +++ b/BUILD @@ -0,0 +1,103 @@ +# +#================================================================================== +# Copyright (c) 2019 Nokia +# Copyright (c) 2018-2019 AT&T Intellectual Property. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#================================================================================== +# + + +Building RMr + +The RIC Message Router (RMr) is built with CMake, and requires +a modern gcc compiler and make to be installed on the build +system. Typically, installing the following list of packages +in a container (Ubuntu) is all that is needed to craft a +development environment (containerised builds are also the +recommended approach): + + gcc git make vim cmake g++ ksh bash + +Kshell and vi are needed only if you wish to use the container +interactively. Bash is assumed necessary for CMake. + + +Build process +To build RMr, the usual CMake steps are followed: + mkdir build + cd build + cmake .. [options] + make package + +This will create a .deb (provided the system supports this) in +the build directory. It's that simple. + + +Alternatives +To build in a non-Linux environment, or to build with an +alternate install path (or both) read on. + +Instead of using 'make package' as listed above, using +'make install' will build and install on the local system. +By default, the target install is into /usr/local which may +not be desired. To install into an alternate path add +these two options when the 'cmake ..' command is given: + + -DCMAKE_INSTALL_PREFIX=/path/to/dir + -DMAN_PREFIX=/path/to/dir + + +The first will cause the make process to install into the named +directory, which can be in your home directory. The second +defines where manual pages are placed (if not defined +/usr/share/man is the target). Manual pages are generally +NOT built as the required tool has yet to be incorporated into +the build process and generally is not available on most systems. + + +Compiling and Linking +Should the Rmr and NNG/Nano libraries be installed in a directory +outside of the normal system spots (e.g. not in /usr/local) +it might be necessary to define the specific directory for +libraries (.e.g -L) on the command line, or via environment +variables (e.g.. C_INCLUDE_PATH, LD_LIBRARY_PATH, LIBRARY_PATH). +It may also be necessary to have the library directory defined +in the environment at run time. It is difficult to know what +each system needs, but the following linker ooptions work when +libraries are installed in the system spots: + + -lrmr_nng -lnng -lpthread + +Adding -L is one way to compensate when libraries are installed +a different spot (e.g. in $HOME/usr): + + -L $HOME/usr -lrmr_nng -lnng -lpthread + + +Libraries +RMr supports both NNG and Nanomsg as underlying transport. They +are separate beasts, and while an NNG based programme can +communicate with a Nanomsg based programme, their APIs are NOT +compatible. For this reason, and others, RMr generates two +libraries and requires that the underlying transport be selected +at link time rather than run time. The RMr API for both underlying +mechanisms is the same, so generating a NNG and Nanomsg version +of a programme should require no extra work; other than adding +a second link statement and giving it a different name. + +Nanomsg is on its way out with respect to community support. RMr +will continue to support Nanomsg for a short period of time, but +new programmes should NOT use Nanomsg. + + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9f0af5d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,248 @@ + +# +#================================================================================== +# Copyright (c) 2019 Nokia +# Copyright (c) 2018-2019 AT&T Intellectual Property. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#================================================================================== +# + +project( rmr LANGUAGES C ) +cmake_minimum_required( VERSION 3.5 ) + + +set( major_version "1" ) +set( minor_version "0" ) +set( patch_level "15" ) + +set( install_root "${CMAKE_INSTALL_PREFIX}" ) +set( install_lib "lib" ) +set( install_inc "include/rmr" ) +if( MAN_PREFIX ) + set( install_man ${MAN_PREFIX} ) # is there a cmake var for this -- can't find one +else() + set( install_man "/usr/share/man" ) # this needs to be fixed so it's not hard coded +endif() + +# Must use GNUInstallDirs to install libraries into correct +# locations on all platforms. +include( GNUInstallDirs ) + +if( NOT BUILD_LIB ) + set( BUILD_LIB lib ) +endif() + +# ---------------- set version info (not perfect, but better than nothing) ---- +execute_process( + COMMAND bash -c "git rev-parse --short HEAD|awk '{printf\"%s\", $0}'" + OUTPUT_VARIABLE git_id +) + +add_definitions( + -DGIT_ID=${git_id} + -DMAJOR_VER=${major_version} + -DMINOR_VER=${minor_version} + -DPATCH_VER=${patch_level} +) + + +# ---------------- setup nano/nng things --------------------------------------- +if( NOT SKIP_EXTERNALS ) + set( need_ext 1 ) # we force dependences on these for right build order + execute_process( COMMAND git submodule update --init -- ext/nng + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + + execute_process( COMMAND git submodule update --init -- ext/nanomsg + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + if( NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/ext/nng/CMakeLists.txt ) + message( FATAL_ERROR "cannot find nng in our git source as a submodule: Giving up" ) # this will abort which seems wrong, but tdam. + endif() + + include( ExternalProject ) + ExternalProject_Add( + ext_nng + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ext/nng" + CMAKE_ARGS "-DBUILD_SHARED_LIBS=1" + CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}" + BUILD_COMMAND "make" + UPDATE_COMMAND "" + TEST_COMMAND "" + STEP_TARGETS build + ) + ExternalProject_Add( + nanomsg + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ext/nanomsg" + BUILD_COMMAND "make" + UPDATE_COMMAND "" + CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}" + TEST_COMMAND "" + STEP_TARGETS build + ) + + # it seems impossible to install everything that lands in {bin}/lib, so we need to + # hard code (shudder) some things. Even worse, we have to make exceptions for + # builds on apple (osx) since their naming convention wandered off the path. + set( nng_major 1 ) + set( nng_minor 1.0 ) + set( nano_major 5 ) + set( nano_minor 1.0 ) + set( so ${CMAKE_SHARED_LIBRARY_SUFFIX} ) # cmake variables are impossibly long :( + if( NOT APPLE ) # probably breaks in windows, but idc + set( nng_so_suffix ${so} ) + set( nng_so_suffix_m ${so}.${nng_major} ) + set( nng_so_suffix_mm ${so}.${nng_major}.${nng_minor} ) + + set( nano_so_suffix ${so} ) + set( nano_so_suffix_m ${so}.${nano_major} ) + set( nano_so_suffix_mm ${so}.${nano_major}.${nano_minor} ) + else() + # of course apple puts versions before the suffix :( + set( nng_so_suffix ${so} ) # so has a lead dot, so NOT needed + set( nng_so_suffix_m ".${nng_major}${so}" ) # these need leading dots + set( nng_so_suffix_mm ".${nng_major}.${nng_minor}${so}" ) + + set( nano_so_suffix ${so} ) + set( nano_so_suffix_m ".${nano_major}${so}" ) + set( nano_so_suffix_mm ".${nano_major}.${nano_minor}${so}" ) + endif() + + message( "+++ installing nano/nng with: ${nano_major}.${nano_minor} | ${nng_major}.${nng_minor}" ) +else() + set( need_ext 0 ) +endif() +unset( SKIP_EXTERNALS CACHE ) # prevent it from being applied next build unless specifically set on comd line + + +# this gets us round a chicken/egg problem. include files don't exist until make is run +# but Cmake insists on having these exist when we add them to include directories to +# enable rmr code to find them after we build them. +# +execute_process( COMMAND "mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/include/nng" ) +include_directories( "${CMAKE_CURRENT_BINARY_DIR}/include" ) + + +# Compiler flags +# +set( CMAKE_POSITION_INDEPENDENT_CODE ON ) +set( CMAKE_CXX_FLAGS "-g -Wall " ) + +# Include modules +add_subdirectory( src/common ) +add_subdirectory( src/nanomsg ) +add_subdirectory( src/nng ) +add_subdirectory( doc ) # this will auto skip if {X}fm is not available + + +# shared and static libraries built from the same object files +# Nanomsg based library (librmr ) +# library is built by pulling object files from nano and common subdirs +# +add_library( rmr_shared SHARED "$;$" ) +add_library( rmr_static STATIC "$;$" ) + +# both libraries to be named with librmr prefix +set_target_properties( rmr_shared PROPERTIES OUTPUT_NAME "rmr" ) +set_target_properties( rmr_static PROPERTIES OUTPUT_NAME "rmr" ) + + +# NNG based library (librmr_nng ) +# library is built by pulling objects from nng and common subdirs +# +add_library( rmr_nng_shared SHARED "$;$" ) +add_library( rmr_nng_static STATIC "$;$" ) + + + +# if externals need to be built, then we must force them to be built first by depending on them +if( need_ext ) + add_dependencies( rmr_static;rmr_shared nanomsg ) + add_dependencies( rmr_nng_shared;rmr_nng_static ext_nng ) +endif() + + +# both libraries to be named with librmr_nng prefix +# +set_target_properties( rmr_nng_shared PROPERTIES OUTPUT_NAME "rmr_nng" ) +set_target_properties( rmr_nng_static PROPERTIES OUTPUT_NAME "rmr_nng" ) + +# +if( APPLE ) + message( "### apple hack: forcing hard coded library paths for nng/nano dynamic libraries" ) + target_link_libraries( rmr_shared ${CMAKE_CURRENT_BINARY_DIR}/lib/libnanomsg${nano_so_suffix} ) + target_link_libraries( rmr_nng_shared ${CMAKE_CURRENT_BINARY_DIR}/lib/libnng${nng_so_suffix} ) +endif() + +# Define directories where package should drop things when installed +# In CMake speak archive == *.a library == *.so, so both are needed +# Headers from the common directory are forced to install by the local CM file in common. At +# the moment, there are no header files specific to either nano or nng, so to the public +# header directive is moot, but needed if some day there is one. +# +install( TARGETS rmr_nng_shared;rmr_nng_static;rmr_shared;rmr_static EXPORT LibraryConfig + ARCHIVE DESTINATION ${install_lib} + LIBRARY DESTINATION ${install_lib} + PUBLIC_HEADER DESTINATION ${install_inc} +) + + + +# install any nano/nng libraries in to the deb as well, but ONLY if we created them. +# (sure would be nice if FILEs allowed for globbing; sadlyy it does not. +# +if( need_ext ) + message( "including nano and nng libraries in the deb" ) + install( FILES + ${CMAKE_CURRENT_BINARY_DIR}/${BUILD_LIB}/libnanomsg${nano_so_suffix} + ${CMAKE_CURRENT_BINARY_DIR}/${BUILD_LIB}/libnanomsg${nano_so_suffix_m} + ${CMAKE_CURRENT_BINARY_DIR}/${BUILD_LIB}/libnanomsg${nano_so_suffix_mm} + ${CMAKE_CURRENT_BINARY_DIR}/${BUILD_LIB}/libnng${nng_so_suffix} + ${CMAKE_CURRENT_BINARY_DIR}/${BUILD_LIB}/libnng${nng_so_suffix_m} + ${CMAKE_CURRENT_BINARY_DIR}/${BUILD_LIB}/libnng${nng_so_suffix_mm} + + DESTINATION ${install_lib} + ) +endif() + + +IF( EXISTS "${CMAKE_ROOT}/Modules/CPack.cmake" ) + include( InstallRequiredSystemLibraries ) + + set( CPACK_set_DESTDIR "on" ) + set( CPACK_PACKAGING_INSTALL_PREFIX "${install_root}" ) + set( CPACK_GENERATOR "DEB" ) + + set( CPACK_PACKAGE_DESCRIPTION "Thin library for RIC xAPP messaging routed based on message type." ) + set( CPACK_PACKAGE_DESCRIPTION_SUMMARY "RIC message routing library" ) + set( CPACK_PACKAGE_VENDOR "None" ) + set( CPACK_PACKAGE_CONTACT "None" ) + set( CPACK_PACKAGE_VERSION_MAJOR "${major_version}" ) + set( CPACK_PACKAGE_VERSION_MINOR "${minor_version}" ) + set( CPACK_PACKAGE_VERSION_PATCH "${patch_level}" ) + set( CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}_${major_version}.${minor_version}.${CPACK_PACKAGE_VERSION_PATCH}" ) + set( CPACK_SOURCE_PACKAGE_FILE_NAME "vric${CMAKE_PROJECT_NAME}_${major_version}.${minor_version}.${CPACK_PACKAGE_VERSION_PATCH}" ) + + # we build and ship the libraries, so there is NO dependency + #set( CPACK_DEBIAN_PACKAGE_DEPENDS "nanomsg ( >= 1.1.4 ), nng ( >= 1.1.1 )" ) + + set( CPACK_DEBIAN_PACKAGE_PRIORITY "optional" ) + set( CPACK_DEBIAN_PACKAGE_SECTION "ric" ) + set( CPACK_DEBIAN_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR} ) + + # this seems ingnored if included + #set( CPACK_COMPONENTS_ALL Libraries ApplicationData ) + + INCLUDE( CPack ) +ENDIF() diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..e723ccb --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,29 @@ + + Unless otherwise specified, all software contained herein is licensed + under the Apache License, Version 2.0 (the "Software License"); + you may not use this software except in compliance with the Software + License. You may obtain a copy of the Software License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the Software License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the Software License for the specific language governing permissions + and limitations under the Software License. + + + + Unless otherwise specified, all documentation contained herein is licensed + under the Creative Commons License, Attribution 4.0 Intl. (the + "Documentation License"); you may not use this documentation except in + compliance with the Documentation License. You may obtain a copy of the + Documentation License at + + https://creativecommons.org/licenses/by/4.0/ + + Unless required by applicable law or agreed to in writing, documentation + distributed under the Documentation License is distributed on an "AS IS" + BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. See the Documentation License for the specific language governing + permissions and limitations under the Documentation License. diff --git a/README b/README new file mode 100644 index 0000000..86217ce --- /dev/null +++ b/README @@ -0,0 +1,93 @@ +# +#================================================================================== +# Copyright (c) 2019 Nokia +# Copyright (c) 2018-2019 AT&T Intellectual Property. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#================================================================================== +# + + +Source for the RIC Messaging Library -- RMR. + +C does not provide the concept of package names, yet we have +a desire not to maintain all of the static code in a single large +file, we use the following convention: + + .c -- C code which builds separately and generates an object + that is ultimately added to the archive. + + _static.c - File containing nothing but static functions (a.k.a package + only functions). These files should be included by other *.c + files and should not generate object. + + .h Header file that user applications are expected to include + in order to make use of the library + + _inline.h Header files containing inline static functions that the + user application is expected to include. + + _private.h Header file meant only to be included by the package. + +Further, as this code is used to generate both a Nanomsg and NNG based version, +there are some modules which are specific to the underlying transport being +used. The original code was based on Nanomsg, thus any changes resulting from +the port to NNG, are in files with the same name plus _nng (e.g. rtable_static.c +is the original module, and rrable_nng_static.c is the NNG version). + + +External Names +All externally facing function names and constants will start with rmr_ or +RMR_ repsectively (RIC Message Router). For the time being, there is a +set of mappings from the old uta_* names to rmr_* names. The user code must +define UTA_COMPAT to have these ensbled. + +Internal Names +Internal (static) functions have no mandiated convention. There are some +names which are prefixed with uta_. These are left over from the original +prototype libray which had the name Uta. The uta_ prefixes were mostly on +functions which were iniitally external, but were pulled back for this release. + + + +Requirements +To build the RMR libraries, both Nanomsg and NNG must be installed, and if not +installed in the standard places (e.g. /usr/local/include and /usr/local/lib), +then the proper references must be made in C_INCLUDE_PATH, and LD_LIBRARY_PATH. + +To install see the instructions on their html sites: + https://github.com/nanomsg/nng + https://nanomsg.org/download.html + + +Unit Testing +The script ../test/utest.ksh should be used for running unit tests. With no +parameters it will attempt to build any file in this directory which has the +name *_test.c. Build is attempted with either mk or make and enables the +necessary compiler flags to support coverage output (gcov). Once built, the +test programme is executed and if the return code is success (0), the +coverage data is interpreted. + +The test programmes may make use of ../test/tools.c which provide simple +validation check functions. These programmes shouild also directly include +the module(s) under test. This ensures that they are not linked, and are +compiled with the proper coverage flags. In addition, it allows modules that +are not under test to be linked from the archive and (most importantly) not +reported on from a coverage perspective. In cases where two modules depend on +each other, and are static functions, they will need to be tested from a single +unit test programme (see the rt_tool test programme). + +It might be necessary to write a higher level test driver as some of the modules +(e.g. route table) have threaded daemons which might not be easy to drive +completely or at all, and thus the code coverage for a passing test might need +to be lower for this type of module. diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt new file mode 100644 index 0000000..44f0001 --- /dev/null +++ b/doc/CMakeLists.txt @@ -0,0 +1,117 @@ + +# +#================================================================================== +# Copyright (c) 2019 Nokia +# Copyright (c) 2018-2019 AT&T Intellectual Property. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#================================================================================== +# + + +# builds the man pages for the deb file (generates troff from {X}fm). +# also builds postscript files, but leaves them in the current build dir. +# but, ONLY if build_doc variable is true +# + +# look for tfm to build the man pages with. if not found, then we pull +# and build in the current build environment setting tfm/pfm commands to +# point at the correct spot. If the user has {X}fm installed, we just +# use their install. +# +if( BUILD_DOC ) + find_program( tfm NAMES tfm ) + find_program( pfm NAMES pfm ) + + if( "${tfm}" MATCHES "tfm-NOTFOUND" ) # user doesn't have installed; set where we expect them + set( tfm ${CMAKE_CURRENT_BINARY_DIR}/xfm/.build/src/tfm/tfm ) + set( pfm "${CMAKE_CURRENT_BINARY_DIR}/xfm/.build/src/pfm/pfm" ) + + if( NOT EXISTS ${tfm} ) # not yet built here, pull and build + # pull and build {X}fm tools needed to generate manpages + execute_process( + COMMAND "bash" "-c" "git clone https://github.com/ScottDaniels/xfm.git && cd xfm && mkdir .build && cd .build && cmake .. && make" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) + message( "+++ xfm pulled and built" ) + else() + message( "+++ found xfm in the build environment" ) + endif() + + endif() + + # base filenames (with .xfm are input) + set( man_names + rmr.7 + rmr_bytes2meid.3 + rmr_bytes2xact.3 + rmr_bytes2payload.3 + rmr_free_msg.3 + rmr_payload_size.3 + rmr_rts_msg.3 + rmr_wh_close.3 + rmr_alloc_msg.3 + rmr_get_rcvfd.3 + rmr_rcv_msg.3 + rmr_send_msg.3 + rmr_wh_open.3 + rmr_call.3 + rmr_init.3 + rmr_ready.3 + rmr_str2meid.3 + rmr_str2xact.3 + rmr_support.3 + rmr_torcv_msg.3 + rmr_wh_send_msg.3 + ) + + # empty list of roff/troff input files we generated + set( man3_files ) + set( man7_files ) + + # mk is so much easier than this -- grumble + # for each source, build a specific command that runs tfm to generate the + # troff output. Sed is needed to remove the leading blank that tfm likes + # to insert even if indention is 0. + # + foreach( nm IN LISTS man_names ) + set( out ${CMAKE_BINARY_DIR}/${nm} ) + set( in ${CMAKE_SOURCE_DIR}/doc/src/man/${nm}.xfm ) + + add_custom_command( + OUTPUT ${out} + DEPENDS ${in} + COMMAND bash -c "export LIB=${CMAKE_SOURCE_DIR}/doc/src; ${tfm} ${in} stdout | sed 's/^ //' >${out}; ${pfm} ${in} ${out}.ps" + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Building manpage ${out}" + VERBATIM + ) + if( ${out} MATCHES ".*\.3" ) + list( APPEND man3_files ${out} ) + else() + list( APPEND man7_files ${out} ) + endif() + endforeach() + + # we must force these to install + # find all of the man pages in build and add them to the deb + # + install( FILES ${man3_files} DESTINATION ${install_man}/man3/ ) + install( FILES ${man7_files} DESTINATION ${install_man}/man7/ ) + + add_custom_target( man_pages ALL DEPENDS ${man3_files};${man7_files} ) + +else() + message( "+++ not building doc, set -DBULID_DOC on cmake commandline to enable" ) +endif() +unset( BUILD_DOC CACHE ) # prevent it from being applied next build unless specifically set on comd line diff --git a/doc/README b/doc/README new file mode 100644 index 0000000..ea47501 --- /dev/null +++ b/doc/README @@ -0,0 +1,17 @@ + +Documentation for RMr. + +Most (all?) documentation for RMr is built from source ({X)fm, or other text +formatter source, not the RMr code) rather than hand editing the output +(markdown/rst) files. This allows for more control over the output, a wider +variety of output types, and (most importantly) the generation of multiple +output from the same source (e.g. man pages can be rendered in troff which +the man command required, and also rendered as .rst or .md files which some +wikis prefer, and as PDF which can be easily distributed). + +Thus, there is a source file below this directory which has the document +source. There may also be an output directory at this level. If necessary, +the output directory will contain generated documents (.rst files?) which for +some odd reason need to also be committed to the repo (I don't commit binaries +so I'm having a difficult time sorting out the reason why these generated files +might be committed). diff --git a/doc/src/generic_ps.im b/doc/src/generic_ps.im new file mode 100644 index 0000000..ccca9ba --- /dev/null +++ b/doc/src/generic_ps.im @@ -0,0 +1,75 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.** macros compatable with the roff/troff and rts imbed files +.** this is included when generating postscript from the man source. + + .hn off + .dv text_size 10p + .dv ex_size 8p + .dv text_font Helvetica + .dv ital_font Helvetica-Oblique + .dv bold_font Helvetica-Bold + .dv cw_font Courier + .st &text_size + .sf &text_font + .dh 1 f=&bold_font p=12 e=no s=2,1 m=.5i i=0i + .dh 2 f=&bold_font p=12 s=1,.5 m=.5i i=0i + .dh 3 f=&bold_font p=12 s=1,0 m=.5i i=0i + + .dv comment .** ignore + + .dv h1 .h1 $1 + .dv h2 .h2 $1 + .dv h3 .h3 $1 + + .dv fig + .dv set_font_cw .sf ^&cw_font + + .dv nf .nf + .dv fo .fo + + .dv line_len .ll $1 + .dv space .sp 1 + .dv half_space .sp .5 + .dv beg_list .bl $1 + .dv end_list .el + .dv beg_dlist .bd $1 $2 + .dv end_dlist .ed + .dv ditem .di $1 ^: + .dv di .di $1 ^: + .dv li .li + + .dv ex_start .st ^&ex_size .sf ^&cw_font .nf + .dv ex_end .fo on .sf ^&text_font .st ^&text_size .sp 1 + + .** fonts and font macros + .dv ital .tf ^&ital_font ^&text_size $1 ^: + .dv bold .tf ^&bold_font ^&text_size $1 ^: + .dv cw .tf ^&cw_font ^&text_size $1 ^: + .dv set_font_prop .sf ^&text_font + + .cd 1 6.5i + .ll 6i + .pn off + .ju on + .in .5i + + .dv indent .ll -0.5i .in +0.25i + .dv uindent .in -0.25i .ll +0.5i diff --git a/doc/src/man/.gitignore b/doc/src/man/.gitignore new file mode 100644 index 0000000..51e0212 --- /dev/null +++ b/doc/src/man/.gitignore @@ -0,0 +1,6 @@ +*.pdf +*.ps +*.sp +*.ri +*.rst +*- diff --git a/doc/src/man/README b/doc/src/man/README new file mode 100644 index 0000000..8fc0277 --- /dev/null +++ b/doc/src/man/README @@ -0,0 +1,16 @@ + +Various RMr man pages. These are maintained in {X}fm format which allows +them to be rendered into one of these formats: + Postscript + troff + rst + markdown + html + + +Troff is generated and installed into deb packages such that 'man rmr_call' +and the like generate expected man page output. The rst output is likely +copied to some other directory and snarfed for automatic conversion to +html ({X}fm could do that without the extra layer) as a part of the CICD +gizmo. Postscript is a benefit for pretty printing, and markdown is +available if someone adds a markdown macro file in the parent directory. diff --git a/doc/src/man/rmr.7.xfm b/doc/src/man/rmr.7.xfm new file mode 100644 index 0000000..4c3e13a --- /dev/null +++ b/doc/src/man/rmr.7.xfm @@ -0,0 +1,135 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi +.if false + Mnemonic rmr.7.xfm + Abstract The manual page for the whole RMr library + Author E. Scott Daniels + Date 29 January 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** and rst.im will cause rst to be generated depending on OUTPUT_TYPE env +.** var. +.** if formatting with pfm, then pretty postscript will be generated + +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMr Library) +&h2(NAME) + RMr -- Ric Message Router Library + +&h2(DESCRIPTION) +RMr is a library which provides a user application with the ability +to send and receive messages to/from other RMr based applications +without having to understand the underlying messaging transport environment (e.g. Nanomsg) +and without needing to know which other endpoint applications are currently +available and accepting messages. +To do this, RMr depends on a routing table generated by an external source. +This table is used to determine the destination endpoint of each message sent by mapping the +message type T (supplied by the user application) to an endpoint entry. +Once determined, the message is sent directly to the endpoint. +The user application is unaware of which endpoint actually receives the +message, and in some cases whether that message was sent to multiple +applications. + +&space +RMr functions do provide for the ability to respond to the specific source +instance of a message allowing for either a request response, or call +response relationship when needed. + + +&h3(The Route Table) +The library is supplied with a route table which maps message numbers to +endpoint groups such that each time a message of type T is sent, the message +is delivered to one member of each group associated with T. +For example, message type 2 might route to two different groups where +group A consists of worker1 and worker2, while group B consists only of +logger1. + +&space +It is the responsibility of the route table generator to know which endpoints +belong to which groups, and which groups accept which message types. +Once understood, the route table generator publishes a table that is ingested +by RMr and used for mapping messages to end points. + +&h3(Environment) +To enable configuration of the library behaviour outside of direct user application +control, RMr supports a number of environment variables which provide information +to the library. +The following is a list of the various environment variables, what they control +and the defaults which RMr uses if undefined. + +&beg_dlist(.75i : ^&bold_font ) +&di(RMR_BIND_IF) This provides the interface that RMr will bind listen ports to allowing + for a single interface to be used rather than listening across all interfaces. + This should be the IP address assigned to the interface that RMr should listen + on, and if not defined RMr will listen on all interfaces. + +&di(RMR_RTG_SVC) RMr opens a TCP listen socket using the port defined by this + environment variable and expects that the route table generator process + will connect to this port. + If not supplied the port 4561 is used. + +&di(RMR_RTG_ISRAW) Is set to 1 if the route table generator is sending "plain" messages + (not using RMr to send messages, 0 if the rtg is using RMr to send. The default + is 1 as we don't expect the rtg to use RMr. + +&di(RMR_SEED_RT) This is used to supply a static route table which can be used for + debugging, testing, or if no route table generator process is being used to + supply the route table. + If not defined, no static table is used and RMr will not report &ital(ready) + until a table is received. +&end_dlist + + +&h2(SEE ALSO ) +.ju off +rmr_alloc_msg(3), +rmr_call(3), +rmr_free_msg(3), +rmr_init(3), +rmr_payload_size(3), +rmr_rcv_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_ready(3), +rmr_fib(3), +rmr_has_str(3), +rmr_tokenise(3), +rmr_mk_ring(3), +rmr_ring_free(3), +rmr_torcv_msg(3), +rmr_wh_open(3), +rmr_wh_send_msg(3) +.ju on + + +.qu diff --git a/doc/src/man/rmr_alloc_msg.3.xfm b/doc/src/man/rmr_alloc_msg.3.xfm new file mode 100644 index 0000000..04b908b --- /dev/null +++ b/doc/src/man/rmr_alloc_msg.3.xfm @@ -0,0 +1,153 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi +.if false + Mnemonic rmr_alloc_msg.xfm + Abstract The manual page for the rmr_alloc_msg function. + Author E. Scott Daniels + Date 28 January 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + rmr_alloc_msg + +&h2(SYNOPSIS ) +&indent +&ex_start +#include + +rmr_mbuf_t* rmr_alloc_msg( void* a, int size ); +&ex_end +&uindent + +&h2(DESCRIPTION) +The &cw(rmr_alloc_msg) function is used to allocate a buffer which the user +programme can write into and then send through the RMR library. +The buffer is allocated such that sending it requires no additional copying +out of the buffer. +If the value passed in &cw(size) is 0, then the default size supplied on the +&ital(rmr_init) call will be used. +The &ital(a) parameter is the void context pointer that was returned by +the &ital(rmr_init) function. + +&space +The pointer to the message buffer returned is a structure which has some +user application visible fields; the structure is described in &cw(rmr.h,) +and is illustrated below. + +&space +&ex_start +typedef struct { + int state; + int mtype; + int len; + unsigned char* payload; + unsigned char* a; +} rmr_mbuf_t; +&ex_end + +&space +&beg_dlist(.75i : ^&bold_font ) +&ditem(state ) Is the current buffer state. Following a call to &cw(rmr_send_msg) +the state indicates whether the buffer was successfully sent which determines +exactly what the payload points to. If the send failed, the payload referenced +by the buffer is the message that failed to send (allowing the application to +attempt a retransmission). +When the state is &cw(RMR_OK) the buffer represents an empty buffer that the application +may fill in in preparation to send. + +&half_space +&ditem(mtype ) When sending a message, the application is expected to set this field +to the appropriate message type value (as determined by the user programme). Upon send +this value determines how the RMR library will route the message. +For a buffer which has been received, this field will contain the message type that was +set by the sending application. + +&half_space +&ditem(len ) The application using a buffer to send a message is expected to set the +length value to the actual number of bytes that it placed into the message. This +is likely less than the total number of bytes that the message can carry. +For a message buffer that is passed to the application as the result of a receive +call, this will be the value that the sending application supplied and should +indicate the number of bytes in the payload which are valid. + +&half_space +&ditem(payload ) The payload is a pointer to the actual received data. The +user programme may read and write from/to the memory referenced by the payload +up until the point in time that the buffer is used on a &cw(rmr_send, rmr_call) +or &cw(rmr_reply) function call. +Once the buffer has been passed back to a RMR library function the user programme +should &bold(NOT) make use of the payload pointer. + + +&half_space +&ditem(a ) The &ital(a) field is a pointer to a fixed sized area in +the message into which the user may write a transaction ID. +The ID is optional with the exception of when the user application uses the &cw(rmr_call) +function to send a message and wait for the reply; the underlying RMR processing +expects that the matching reply message will also contain the same data in the +&ital(a) field. +&end_dlist + +&h2(RETURN VALUE) +The function returns a pointer to a &cw(rmr_mbuf) structure, or NULL on error. + +&h2(ERRORS) +&beg_dlist(.75i : ^&bold_font ) +&di(ENOMEM) Unable to allocate memory. +&end_dlist + +.** &h2(EXAMPLE) + +&h2(SEE ALSO ) +rmr_mbuf(3) +rmr_call(3), +rmr_free_msg(3), +rmr_init(3), +rmr_payload_size(3), +rmr_send_msg(3), +rmr_rcv_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_ready(3), +rmr_fib(3), +rmr_has_str(3), +rmr_tokenise(3), +rmr_mk_ring(3), +rmr_ring_free(3) + + +.qu + diff --git a/doc/src/man/rmr_bytes2meid.3.xfm b/doc/src/man/rmr_bytes2meid.3.xfm new file mode 100644 index 0000000..fcc3955 --- /dev/null +++ b/doc/src/man/rmr_bytes2meid.3.xfm @@ -0,0 +1,111 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.if false + Mnemonic rmr_bytes2meid.xfm + Abstract The manual page for the rmr_bytes2meid function. + Author E. Scott Daniels + Date 8 March 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + rmr_bytes2meid + +&h2(SYNOPSIS) +&indent +&ex_start +#include + +int rmr_bytes2meid( rmr_mbuf_t* mbuf, unsigned char* src, int len ) +&ex_end + +&uindent + +&h2(DESCRIPTION) +The &cw(rmr_bytes2meid) function will copy up to &ital(len) butes from &ital(src) to the +managed equipment ID (meid) field in the message. +The field is a fixed length, gated by the constant &cw(RMR_MAX_MEID) and if len is larger +than this value, only RMR_MAX_MEID bytes will actually be copied. + + +&h2(RETURN VALUE) +On success, the actual number of bytes copied is returned, or -1 to indicate a hard error. +If the length is less than 0, or not the same as length passed in, &cw(errno) is set +to one of the errors described in the &ital(Errors) section. + +&h2(ERRORS) +If the returned length does not match the length passed in, &cw(errno) will be set to one +of the following constants with the meaning listed below. + +&beg_dlist(.75i : ^&bold_font ) +&half_space +&di(EINVAL) The message, or an internal portion of the message, was corrupted or the pointer was invalid. + +&half_space +&di(EOVERFLOW) The length passed in was larger than the maximum length of the field; only a portion of + the source bytes were copied. +&end_dlist + +&h2(EXAMPLE) + + +&h2(SEE ALSO ) +rmr_alloc_msg(3), +rmr_bytes2xact(3), +rmr_call(3), +rmr_free_msg(3), +rmr_get_rcvfd(3), +rmr_get_meid(3), +rmr_payload_size(3), +rmr_send_msg(3), +rmr_rcv_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_ready(3), +rmr_fib(3), +rmr_has_str(3), +rmr_tokenise(3), +rmr_mk_ring(3), +rmr_ring_free(3), +rmr_str2meid(3), +rmr_str2xact(3), +rmr_wh_open(3), +rmr_wh_send_msg(3) + + + +.qu + diff --git a/doc/src/man/rmr_bytes2payload.3.xfm b/doc/src/man/rmr_bytes2payload.3.xfm new file mode 100644 index 0000000..a8f99e2 --- /dev/null +++ b/doc/src/man/rmr_bytes2payload.3.xfm @@ -0,0 +1,101 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.if false + Mnemonic rmr_bytes2payload.xfm + Abstract The manual page for the rmr_bytes2payload function. + Author E. Scott Daniels + Date 8 March 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + rmr_bytes2payload + +&h2(SYNOPSIS) +&indent +&ex_start +#include + +void rmr_bytes2payload( rmr_mbuf_t* mbuf, unsigned char* src, int len ) +&ex_end + +&uindent + +&h2(DESCRIPTION) +This is a convenience function as some wrapper languages might not have the ability to +directly copy into the payload buffer. +The bytes from &ital( src ) for the length given are copied to the payload. +It is the caller's responsibility to ensure that the payload is large enough. +Upon successfully copy, the &cw( len ) field in the message buffer is updated to +reflect the number of bytes copied. +.sp +There is little error checking, and no error reporting. + + +&h2(RETURN VALUE) +None. + +&h2(EXAMPLE) + + +&h2(SEE ALSO ) +rmr_alloc_msg(3), +rmr_bytes2xact(3), +rmr_bytes2payload(3), +rmr_call(3), +rmr_free_msg(3), +rmr_get_rcvfd(3), +rmr_get_meid(3), +rmr_payload_size(3), +rmr_send_msg(3), +rmr_rcv_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_ready(3), +rmr_fib(3), +rmr_has_str(3), +rmr_tokenise(3), +rmr_mk_ring(3), +rmr_ring_free(3), +rmr_str2meid(3), +rmr_str2xact(3), +rmr_wh_open(3), +rmr_wh_send_msg(3) + + + +.qu + diff --git a/doc/src/man/rmr_bytes2xact.3.xfm b/doc/src/man/rmr_bytes2xact.3.xfm new file mode 100644 index 0000000..dbfe679 --- /dev/null +++ b/doc/src/man/rmr_bytes2xact.3.xfm @@ -0,0 +1,108 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.if false + Mnemonic rmr_bytes2xact.xfm + Abstract The manual page for the rmr_bytes2xact function. + Author E. Scott Daniels + Date 8 March 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + rmr_bytes2xact + +&h2(SYNOPSIS) +&indent +&ex_start +#include + +int rmr_bytes2xact( rmr_mbuf_t* mbuf, unsigned char* src, int len ) +&ex_end + +&uindent + +&h2(DESCRIPTION) +The &cw(rmr_bytes2xact) function will copy up to &ital(len) butes from &ital(src) to the +transaction ID (xaction) field in the message. +The field is a fixed length, gated by the constant &cw(RMR_MAX_XID) and if len is larger +than this value, only RMR_MAX_XID bytes will actually be copied. + +&beg_dlist(.75i : ^&bold_font ) +&h2(RETURN VALUE) +On success, the actual number of bytes copied is returned, or -1 to indicate a hard error. +If the length is less than 0, or not the same as length passed in, &cw(errno) is set +to one of the errors described in the &ital(Errors) section. + +&h2(ERRORS) +If the returned length does not match the length passed in, &cw(errno) will be set to one +of the following constants with the meaning listed below. + +&half_space +&di(EINVAL) The message, or an internal portion of the message, was corrupted or the pointer was invalid. + +&half_space +&di(EOVERFLOW) The length passed in was larger than the maximum length of the field; only a portion of + the source bytes were copied. +&end_dlist + +&h2(EXAMPLE) + + +&h2(SEE ALSO ) +rmr_alloc_msg(3), +rmr_bytes2meid(3), +rmr_call(3), +rmr_free_msg(3), +rmr_get_rcvfd(3), +rmr_payload_size(3), +rmr_send_msg(3), +rmr_rcv_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_ready(3), +rmr_fib(3), +rmr_has_str(3), +rmr_tokenise(3), +rmr_mk_ring(3), +rmr_ring_free(3), +rmr_str2meid(3), +rmr_wh_open(3), +rmr_wh_send_msg(3) + + + +.qu + diff --git a/doc/src/man/rmr_call.3.xfm b/doc/src/man/rmr_call.3.xfm new file mode 100644 index 0000000..6be29d6 --- /dev/null +++ b/doc/src/man/rmr_call.3.xfm @@ -0,0 +1,185 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + + +.if false + Mnemonic rmr_call_man.xfm + Abstract The manual page for the rmr_call function. + Author E. Scott Daniels + Date 28 January 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + rmr_call + +&h2(SYNOPSIS ) +&indent +&ex_start +#include + +extern rmr_mbuf_t* rmr_call( void* vctx, rmr_mbuf_t* msg ); +&ex_end +&uindent + +&h2(DESCRIPTION) +The &cw(rmr_call) function sends the user application message to a remote +endpoint, and waits for a corresponding response message before returning +control to the user application. +The user application supplies a completed message buffer, as it would for +a &cw(rmr_send) call, but unlike with the send, the buffer returned will have +the response from the application that received the message. + +&space +Messages which are received while waiting for the response are queued internally +by RMR, and are returned to the user application when &cw(rmr_rcv_msg) is +invoked. +These messages are returned in th order received, one per call to &cw(rmr_rcv_msg.) + +&h3(Call Timeout) +The &cw(rmr_call) function implements a timeout failsafe to prevent, in most cases, the +function from blocking forever. +The timeout period is &bold(not) based on time (calls to clock are deemed too expensive +for a low latency system level library, but instead the period is based on the number of +received messages which are not the response. +Using a non-time mechanism for &ital(timeout) prevents the async queue from filling +(which would lead to message drops) in an environment where there is heavy message traffic. + +&space +When the threshold number of messages have been queued without receiving a response message, +control is returned to the user application and a NULL pointer is returned to indicate that +no message was received to process. +Currently the threshold is fixed at 20 messages, though in future versions of the library +this might be extended to be a parameter which the user application may set. + +&h2(RETURN VALUE) +The &cw(rmr_call) function returns a pointer to a message buffer with the state set to reflect +the overall state of call processing (see Errors below). +In some cases a NULL pointer will be returned; when this is the case only &ital(errno) +will be available to describe the reason for failure. + +&h2(ERRORS) +These values are reflected in the state field of the returned message. + +&half_space +&beg_dlist(.75i : ^&bold_font ) +&di(RMR_OK) The call was successful and the message buffer references the response message. +&half_space +&di(RMR_ERR_CALLFAILED) The call failed and the value of &ital(errno,) as described below, + should be checked for the specific reason. +&end_dlist + +&space +The global "variable" &ital(errno) will be set to one of the following values if the +overall call processing was not successful. +&half_space + +&beg_dlist(.75i : ^&bold_font ) +&di(ETIMEDOUT) Too many messages were queued before receiving the expected response +&half_space +&di(ENOBUFS) The queued message ring is full, messages were dropped +&half_space +&di(EINVAL) A parameter was not valid +&half_space +&di(EAGAIN) The underlying message system wsa interrupted or the device was busy; + the message was &bold(not) sent, and user application should call + this function with the message again. +&end_dlist + +&h2(EXAMPLE) +The following code bit shows one way of using the &cw(rmr_call) function, and illustrates +how the transaction ID must be set. + +&space +&ex_start + int retries_left = 5; // max retries on dev not available + int retry_delay = 50000; // retry delay (usec) + static rmr_mbuf_t* mbuf = NULL; // response msg + msg_t* pm; // private message (payload) + + // get a send buffer and reference the payload + mbuf = rmr_alloc_msg( mr, RMR_MAX_RCV_BYTES ); + pm = (msg_t*) mbuf->payload; + + // generate an xaction ID and fill in payload with data and msg type + snprintf( mbuf->xaction, RMR_MAX_XID, "%s", gen_xaction() ); + snprintf( pm->req, sizeof( pm->req ), "{ \"req\": \"num users\"}" ); + mbuf->mtype = MT_REQ; + + msg = rmr_call( mr, msg ); + if( ! msg ) { // probably a timeout and no msg received + return NULL; // let errno trickle up + } + + if( mbuf->state != RMR_OK ) { + while( retries_left-- > 0 && // loop as long as eagain + errno == EAGAIN && + (msg = rmr_call( mr, msg )) != NULL && + mbuf->state != RMR_OK ) { + + usleep( retry_delay ); + } + + if( mbuf == NULL || mbuf->state != RMR_OK ) { + rmr_free_msg( mbuf ); // safe if nil + return NULL; + } + } + + // do something with mbuf +&ex_end + + +&h2(SEE ALSO ) +.ju off +rmr_alloc_msg(3), +rmr_free_msg(3), +rmr_init(3), +rmr_payload_size(3), +rmr_send_msg(3), +rmr_rcv_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_ready(3), +rmr_fib(3), +rmr_has_str(3), +rmr_tokenise(3), +rmr_mk_ring(3), +rmr_ring_free(3) +.ju on + + +.qu + diff --git a/doc/src/man/rmr_close.3.xfm b/doc/src/man/rmr_close.3.xfm new file mode 100644 index 0000000..8b05f0b --- /dev/null +++ b/doc/src/man/rmr_close.3.xfm @@ -0,0 +1,88 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.if false + Mnemonic rmr_close.3.xfm + Abstract The manual page for the rmr_close function. + Author E. Scott Daniels + Date 21 February 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + rmr_wh_open + +&h2(SYNOPSIS) +&indent +&ex_start +#include + +void rmr_close( void* vctx ) +&ex_end + +&uindent + +&h2(DESCRIPTION) +The &cw(rmr_close) function closes the listen socket effectively cutting the application +off. +The route table listener is also stopped. +Calls to rmr_rcv_msg() will fail with unpredictable error codes, and calls to rmr_send_msg(), +rmr_call(), and rmr_rts_msg() will have unknown results. + +.sp + + +&h2(SEE ALSO ) +rmr_alloc_msg(3), +rmr_call(3), +rmr_free_msg(3), +rmr_get_rcvfd(3), +rmr_payload_size(3), +rmr_send_msg(3), +rmr_rcv_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_ready(3), +rmr_fib(3), +rmr_has_str(3), +rmr_tokenise(3), +rmr_mk_ring(3), +rmr_ring_free(3), +rmr_wh_open(3), +rmr_wh_send_msg(3) + + +.qu + diff --git a/doc/src/man/rmr_free_msg.3.xfm b/doc/src/man/rmr_free_msg.3.xfm new file mode 100644 index 0000000..e29f8d9 --- /dev/null +++ b/doc/src/man/rmr_free_msg.3.xfm @@ -0,0 +1,87 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.if false + Mnemonic rmr_free_msg_man.xfm + Abstract The manual page for the rmr_free_msg function. + Author E. Scott Daniels + Date 28 January 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + rmr_free_msg + +&h2(SYNOPSIS ) +&indent +&ex_start +#include + +void rmr_free_msg( rmr_mbuf_t* mbuf ); +&ex_end +&uindent + +&h2(DESCRIPTION) +The message buffer is returned to the pool, or the associated memory is +released depending on the needs of the underlying messaging system. +This allows the user application to release a buffer that is not going +to be used. +It is safe to pass a nil pointer to this function, and doing so does +not result in a change to the value of &cw(errrno.) +&space + +After calling, the user application should &bold(not) use any of the +pointers (transaction ID, or payload) which were available. + + +&h2(SEE ALSO ) +rmr_alloc_msg(3), +rmr_call(3), +rmr_init(3), +rmr_payload_size(3), +rmr_send_msg(3), +rmr_rcv_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_ready(3), +rmr_fib(3), +rmr_has_str(3), +rmr_tokenise(3), +rmr_mk_ring(3), +rmr_ring_free(3) + + +.qu + diff --git a/doc/src/man/rmr_get_meid.3.xfm b/doc/src/man/rmr_get_meid.3.xfm new file mode 100644 index 0000000..2dcc943 --- /dev/null +++ b/doc/src/man/rmr_get_meid.3.xfm @@ -0,0 +1,112 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.if false + Mnemonic rmr_get_meid.xfm + Abstract The manual page for the rmr_get_meid function. + Author E. Scott Daniels + Date 8 March 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + rmr_get_meid + +&h2(SYNOPSIS) +&indent +&ex_start +#include + +int rmr_get_meid( rmr_mbuf_t* mbuf, unsigned char* dest ) +&ex_end + +&uindent + +&h2(DESCRIPTION) +The &cw(rmr_get_meid) function will copy the managed equipment ID (meid) field from the message +into the &ital(dest) buffer provided by the user. +The buffer referenced by &ital( dest ) is assumed to be at least &cw(RMR_MAX_MEID) bytes in length. +If &ital( dest ) is NULL, then a buffer is allocated (the calling application is expected +to free when the buffer is no longer needed). + +&h2(RETURN VALUE) +On success, a pointer to the extracted string is returned. +If &ital( dest ) was supplied, then this is just a pointer to the caller's buffer. +If &ital( dest ) was NULL, this is a pointer to the allocated buffer. +If an error occurs, a nil pointer is returned and errno is set as described below. + +&h2(ERRORS) +If an error occurs, the value of the global variable &cw( errno ) will be set to one of +the following with the indicated meaning. + +&beg_dlist(.75i : ^&bold_font ) +&half_space +&di(EINVAL) The message, or an internal portion of the message, was corrupted or the pointer was invalid. + +&half_space +&di(ENOMEM) A nil pointer was passed for &ital( dest, ) however it was not possible to allocate a + buffer using malloc(). +&end_dilist + + + +&h2(SEE ALSO ) +.ju off +rmr_alloc_msg(3), +rmr_bytes2xact(3), +rmr_bytes2meid(3), +rmr_call(3), +rmr_free_msg(3), +rmr_get_rcvfd(3), +rmr_payload_size(3), +rmr_send_msg(3), +rmr_rcv_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_ready(3), +rmr_fib(3), +rmr_has_str(3), +rmr_tokenise(3), +rmr_mk_ring(3), +rmr_ring_free(3), +rmr_str2meid(3), +rmr_str2xact(3), +rmr_wh_open(3), +rmr_wh_send_msg(3) +.ju on + + +.qu + diff --git a/doc/src/man/rmr_get_rcvfd.3.xfm b/doc/src/man/rmr_get_rcvfd.3.xfm new file mode 100644 index 0000000..8300d4e --- /dev/null +++ b/doc/src/man/rmr_get_rcvfd.3.xfm @@ -0,0 +1,150 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.if false + Mnemonic rmr_get_rcvfd.3.xfm + Abstract The manual page for the rmr_get_rcvfd function. + Author E. Scott Daniels + Date 11 February 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + rmr_get_rcvfd + +&h2(SYNOPSIS) +&indent +&ex_start +#include + +void* rmr_get_rcvfd( void* ctx ) +&ex_end + +&uindent + +&h2(DESCRIPTION) +The &cw(rmr_get_rcvfd) function returns a file descriptor which may be given to epoll_wait() +by an application that wishes to use event poll in a single thread rather than block +on the arrival of a message via calls to rmr_rcv_msg(). +When epoll_wait() indicates that this file descriptor is ready, a call to rmr_rcv_msg() +will not block as at least one message has been received. + +&space +The context (ctx) pointer passed in is the pointer returned by the call to rmr_init(). + +&space +&bold(NOTE^:) There is no support for epoll in Nanomsg, thus his function is only supported +when linking with the NNG version of RMr and the file descriptor returned when using the +Nanomsg verfsion will always return an error. + + +&h2(RETURN VALUE) +The &cw(rmr_get_rcvfd) function returns a file descriptor greater or equal to 0 on success +and -1 on error. +If this function is called from a user application linked against the Nanomsg RMr library, +calls will always return -1 with errno set to EINVAL. + +&h2(ERRORS) +The following error values are specifically set by this RMR function. In some cases the +error message of a system call is propagated up, and thus this list might be incomplete. + +&beg_dlist(.75i : ^&bold_font ) +&di(EINVAL) The use of this function is invalid in this environment. +&end_dlist + +&h2(EXAMPLE) +The following short code bit illustrates the use of this function. Error checking has +been omitted for clarity. +&space + +&ex_start +#include +#include +#include +#include + +int main() { + int rcv_fd; // pollable fd + void* mrc; //msg router context + struct epoll_event events[10]; // support 10 events to poll + struct epoll_event epe; // event definition for event to listen to + int ep_fd = -1; + rmr_mbuf_t* msg = NULL; + int nready; + int i; + + mrc = rmr_init( "43086", RMR_MAX_RCV_BYTES, RMRFL_NONE ); + rcv_fd = rmr_get_rcvfd( mrc ); + + ep_fd = epoll_create1( 0 ); // initialise epoll environment + epe.events = EPOLLIN; + epe.data.fd = rcv_fd; + epoll_ctl( ep_fd, EPOLL_CTL_ADD, rcv_fd, &epe ); // add our info to the mix + + while( 1 ) { + nready = epoll_wait( ep_fd, events, 10, -1 ); // -1 == block forever (no timeout) + for( i = 0; i < nready && i < 10; i++ ) { // loop through to find what is ready + if( events[i].data.fd == rcv_fd ) { // RMr has something + msg = rmr_rcv_msg( mrc, msg ); + if( msg ) { + // do something with msg + } + } + + // check for other ready fds.... + } + } +} +&ex_end + +&h2(SEE ALSO ) +rmr_alloc_msg(3), +rmr_call(3), +rmr_free_msg(3), +rmr_payload_size(3), +rmr_send_msg(3), +rmr_rcv_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_ready(3), +rmr_fib(3), +rmr_has_str(3), +rmr_tokenise(3), +rmr_mk_ring(3), +rmr_ring_free(3) + + +.qu + diff --git a/doc/src/man/rmr_init.3.xfm b/doc/src/man/rmr_init.3.xfm new file mode 100644 index 0000000..408c954 --- /dev/null +++ b/doc/src/man/rmr_init.3.xfm @@ -0,0 +1,134 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.if false + Mnemonic rmr_init_man.xfm + Abstract The manual page for the rmr_init function. + Author E. Scott Daniels + Date 28 January 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + rmr_init + +&h2(SYNOPSIS) +&indent +&ex_start +#include + +void* rmr_init( char* proto_port, int max_msg_size, int flags ); +&ex_end + +&uindent + +&h2(DESCRIPTION) +The &cw(rmr_init) function prepares the environment for sending and receiving messages. +It does so by establishing a worker thread (pthread) which subscribes to a route table +generator which provides the necessary routing information for the RMR library to +send messages. + +&space +&ital(Port) is used to listen for connection requests from other RMR based applications. +The value of &ital(max_msg_size) will be used when allocating zero copy send buffers +which must be allocated, possibly, prior to the application knowing the actual size of +a specific message. + +&space +&h2(ENVIRONMENT) +As a part of the initialisation process &cw(rmr_init) will look into the available +environment variables to influence it's setup. +The following variables will be used when found. +&half_space + +&beg_dlist(1i : &bold_font ) +&ditem(RMR_SEED_RT) +Assumes this is the filename of the seed route table file to use. In normal situations, +the library will wait for an update from the route table generator (expected within a few seconds +of initialisation) before being able to send messages. +However, in some situations where a bootstrap table is necessary, this is the means to +supply it to the library. +&half_space + +&ditem(RMR_RTG_SVC) +The route table generator assumes that RMr is listening on a well known port (4561) by +default, but this environment variable can be used to change the listening port if +needed. +The value of the variable is expected to be just the port. +&end_dlist + +&h2(RETURN VALUE) +The &cw(rmr_init) function returns a void pointer (a contex if you will) that is passed +as the first parameter to nearly all other RMR functions. +If &cw(rmr_init) is unable to properly initialise the environment, NULL is returned and +errno is set to an appropriate value. + +&h2(ERRORS) +The following error values are specifically set by this RMR function. In some cases the +error message of a system call is propagated up, and thus this list might be incomplete. + +&beg_dlist(.75i : ^&bold_font ) +&di(ENOMEM) Unable to allocate memory. +&end_dlist + +&h2(EXAMPLE) +&ex_start + void* uh; + rmr_mbuf* buf = NULL; + + uh = rmr_init( "43086", 4096, 0 ); + buf = rmr_rcv_msg( uh, buf ); +&ex_end + +&h2(SEE ALSO ) +rmr_alloc_msg(3), +rmr_call(3), +rmr_free_msg(3), +rmr_get_rcvfd(3), +rmr_payload_size(3), +rmr_send_msg(3), +rmr_rcv_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_ready(3), +rmr_fib(3), +rmr_has_str(3), +rmr_tokenise(3), +rmr_mk_ring(3), +rmr_ring_free(3) + + +.qu + diff --git a/doc/src/man/rmr_payload_size.3.xfm b/doc/src/man/rmr_payload_size.3.xfm new file mode 100644 index 0000000..7dbfa69 --- /dev/null +++ b/doc/src/man/rmr_payload_size.3.xfm @@ -0,0 +1,89 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi +.if false + Mnemonic rmr_payload_size_man.xfm + Abstract The manual page for the rmr_payload_size function. + Author E. Scott Daniels + Date 29 January 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + rmr_payload_size + +&h2(SYNOPSIS ) +&indent +&ex_start +#include + +int rmr_payload_size( rmr_mbuf_t* msg ); +&ex_end +&uindent + +&h2(DESCRIPTION) +Given a message buffer, this function returns the amount of space (bytes) +available for the user application to consume in the message payload. +This is different than the message length available as a field in the +message buffer. + +&h2(RETURN VALUE) +The number of bytes available in the payload. + +&h2(ERRORS) +&beg_dlist(.75i : ^&bold_font ) +&di(INVAL) Parameter(s) passed to the function were not valid. +&end_dlist + + +&h2(SEE ALSO ) +rmr_alloc_msg(3), +rmr_call(3), +rmr_free_msg(3), +rmr_init(3), +rmr_send_msg(3), +rmr_rcv_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_ready(3), +rmr_fib(3), +rmr_has_str(3), +rmr_tokenise(3), +rmr_mk_ring(3), +rmr_ring_free(3) + + +.qu + diff --git a/doc/src/man/rmr_rcv_msg.3.xfm b/doc/src/man/rmr_rcv_msg.3.xfm new file mode 100644 index 0000000..58809fd --- /dev/null +++ b/doc/src/man/rmr_rcv_msg.3.xfm @@ -0,0 +1,127 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi +.if false + Mnemonic rmr_rcv_msg_man.xfm + Abstract The manual page for the rmr_rcv_msg function. + Author E. Scott Daniels + Date 29 January 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + rmr_rcv_msg + +&h2(SYNOPSIS ) +&indent +&ex_start +#include + +rmr_mbuf_t* rmr_rcv_msg( void* vctx, rmr_mbuf_t* old_msg ); +&ex_end +&uindent + +&h2(DESCRIPTION) +The &cw(rmr_rcv_msg) function blocks until a message is received, returning +the message to the caller via a pointer to a &cw(rmr_mbuf_t) structure type. +If messages were queued while waiting for the response to a previous invocation +of &cw(rmr_call,) the oldest message is removed from the queue and returned +without delay. + +&space +The &ital(vctx) pointer is the pointer returned by the &cw(rmr_init) function. +&ital(Old_msg) is a pointer to a previously used message buffer or NULL. +The ability to reuse message buffers helps to avoid alloc/free cycles in the +user application. +When no buffer is available to supply, the receive function will allocate one. + +&h2(RETURN VALUE) +The function returns a pointer to the &cw(rmr_mbuf_t) structure which references +the message information (state, length, payload), or a NULL pointer in the case +of an extreme error. + +&h2(ERRORS) +The &ital(state) field in the message buffer will indicate either &cw(RMR_OK) or +&cw(RMR_ERR_EMPTY) if an empty message was received. +If a nil pointer is returned, or any other state value was set in the message +buffer, &cw(errno) will be set to one of the following: +&space + +&beg_dlist(.75i : ^&bold_font ) +&di(INVAL) Parameter(s) passed to the function were not valid. + +&half_space +&di(EBADF) The underlying message transport is unable to process the request. + +&half_space +&di(ENOTSUP) The underlying message transport is unable to process the request. + +&half_space +&di(EFSM) The underlying message transport is unable to process the request. + +&half_space +&di(EAGAIN) The underlying message transport is unable to process the request. + +&half_space +&di(EINTR) The underlying message transport is unable to process the request. + +&half_space +&di(ETIMEDOUT) The underlying message transport is unable to process the request. + +&half_space +&di(ETERM) The underlying message transport is unable to process the request. +&end_dlist + +&h2(EXAMPLE) + +&h2(SEE ALSO ) +.ju off +rmr_alloc_msg(3), +rmr_call(3), +rmr_free_msg(3), +rmr_get_rcvfd(3), +rmr_init(3), +rmr_mk_ring(3), +rmr_payload_size(3), +rmr_send_msg(3), +rmr_torcv_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_ready(3), +rmr_ring_free(3) +.ju on + + +.qu + diff --git a/doc/src/man/rmr_ready.3.xfm b/doc/src/man/rmr_ready.3.xfm new file mode 100644 index 0000000..9a82010 --- /dev/null +++ b/doc/src/man/rmr_ready.3.xfm @@ -0,0 +1,86 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi +.if false + Mnemonic rmr_ready_man.xfm + Abstract The manual page for the rmr_ready function. + Author E. Scott Daniels + Date 28 January 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + rmr_ready + +&h2(SYNOPSIS ) +&indent +&ex_start +#include + +int rmr_ready( void* vctx ); +&ex_end +&uindent + +&h2(DESCRIPTION) +The &cw(rmr_ready) function checks to see if a routing table has been successfully +received and installed. +The return value indicates the state of readiness. + +&h2(RETURN VALUE) +A return value of 1 (true) indicates that the routing table is in place and +attempts to send messages can be made. +When 0 is returned (false) the routing table has not been received and thus +attempts to send messages will fail with &ital(no endpoint) errors. + +&h2(SEE ALSO ) +.ju off +rmr_alloc_msg(3), +rmr_call(3), +rmr_free_msg(3), +rmr_init(3), +rmr_payload_size(3), +rmr_send_msg(3), +rmr_rcv_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_fib(3), +rmr_has_str(3), +rmr_tokenise(3), +rmr_mk_ring(3), +rmr_ring_free(3) +.ju on + + +.qu + diff --git a/doc/src/man/rmr_rts_msg.3.xfm b/doc/src/man/rmr_rts_msg.3.xfm new file mode 100644 index 0000000..bad1b22 --- /dev/null +++ b/doc/src/man/rmr_rts_msg.3.xfm @@ -0,0 +1,151 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi +.if false + Mnemonic rmr_rts_msg_man.xfm + Abstract The manual page for the rmr_rts_msg function. + Author E. Scott Daniels + Date 28 January 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + rmr_rts_msg + +&h2(SYNOPSIS ) +&indent +&ex_start +#include + +rmr_mbuf_t* rmr_rts_msg( void* vctx, rmr_mbuf_t* msg ); +&ex_end +&uindent + +&h2(DESCRIPTION) +The &cw(rmr_rts_msg) function sends a message returning it to the endpoint +which sent the message rather than selecting an endpoint based on the +message type and routing table. +Other than this small difference, the behaviour is exactly the same as +&cw(rmr_send_msg.) + +&h2(RETURN VALUE) +On success, a new message buffer, with an empty payload, is returned for the application +to use for the next send. +The state in this buffer will reflect the overall send operation state and should be +&cw(RMR_OK.) + +&space +If the state in the returned buffer is anything other than &cw(UT_OK,) the user application +may need to attempt a retransmission of the message, or take other action depending on the +setting of &cw(errno) as described below. + +&space +In the event of extreme failure, a NULL pointer is returned. In this case the value of +&cw(errno) might be of some use, for documentation, but there will be little that the +user application can do other than to move on. + +&h2(ERRORS) +The following values may be passed back in the &ital(state) field of the returned message +buffer. + +&space +&beg_dlist(.75i : ^&bold_font ) +&di(RMR_ERR_BADARG) The message buffer pointer did not refer to a valid message. +&di(RMR_ERR_NOHDR) The header in the message buffer was not valid or corrupted. +&di(RMR_ERR_NOENDPT) The message type in the message buffer did not map to a known endpoint. +&di(RMR_ERR_SENDFAILED) The send failed; &cw(errno) has the possible reason. +&end_dlist + +&space +The following values may be assigned to &cw(errno) on failure. +&beg_dlist(.75i : ^&bold_font ) +&di(INVAL) Parameter(s) passed to the function were not valid, or the underlying message processing environment was unable to interpret the message. + +&half_space +&di(ENOKEY) The header information in the message buffer was invalid. + +&half_space +&di(ENXIO) No known endpoint for the message could be found. + +&half_space +&di(EMSGSIZE) The underlying transport refused to accept the message because of a size value issue (message was not attempted to be sent). + +&half_space +&di(EFAULT) The message referenced by the message buffer is corrupt (NULL pointer or bad internal length). + +&half_space +&di(EBADF) Internal RMR error; information provided to the message transport environment was not valid. + +&half_space +&di(ENOTSUP) Sending was not supported by the underlying message transport. + +&half_space +&di(EFSM) The device is not in a state that can accept the message. + +&half_space +&di(EAGAIN) The device is not able to accept a message for sending. The user application should attempt to resend. + +&half_space +&di(EINTR) The operation was interrupted by delivery of a signal before the message was sent. + +&half_space +&di(ETIMEDOUT) The underlying message environment timed out during the send process. + +&half_space +&di(ETERM) The underlying message environment is in a shutdown state. +&end_dlist + +&h2(EXAMPLE) + +&h2(SEE ALSO ) +.ju off +rmr_alloc_msg(3), +rmr_call(3), +rmr_free_msg(3), +rmr_init(3), +rmr_payload_size(3), +rmr_send_msg(3), +rmr_rcv_msg(3), +rmr_rcv_specific(3), +rmr_ready(3), +rmr_fib(3), +rmr_has_str(3), +rmr_tokenise(3), +rmr_mk_ring(3), +rmr_ring_free(3) +.ju on + + +.qu + diff --git a/doc/src/man/rmr_send_msg.3.xfm b/doc/src/man/rmr_send_msg.3.xfm new file mode 100644 index 0000000..6497781 --- /dev/null +++ b/doc/src/man/rmr_send_msg.3.xfm @@ -0,0 +1,188 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi +.if false + Mnemonic rmr_send_msg_man.xfm + Abstract The manual page for the rmr_send_msg function. + Author E. Scott Daniels + Date 28 January 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + rmr_send_msg + +&h2(SYNOPSIS ) +&indent +&ex_start +#include + +rmr_mbuf_t* rmr_send_msg( void* vctx, rmr_mbuf_t* msg ); +&ex_end +&uindent + +&h2(DESCRIPTION) +The &cw(rmr_send_msg) function accepts a message buffer from the user application +and attempts to send it. +The destination of the message is selected based on the message type specified +in the message buffer, and the matching information in the routing tables +which are currently in use by the RMR library. +This may actually result in the sending of the message to multiple destinations +which could degrade expected overall performance of the user application. +(Limiting excessive sending of messages is the responsibility of the application(s) +responsible for building the routing table used by the RMR library, and not the +responsibility of the library.) + + +&h2(RETURN VALUE) +On success, a new message buffer, with an empty payload, is returned for the application +to use for the next send. +The state in this buffer will reflect the overall send operation state and should be +&cw(RMR_OK.) + +&space +If the state in the returned buffer is anything other than &cw(UT_OK,) the user application +may need to attempt a retransmission of the message, or take other action depending on the +setting of &cw(errno) as described below. + +&space +In the event of extreme failure, a NULL pointer is returned. In this case the value of +&cw(errno) might be of some use, for documentation, but there will be little that the +user application can do other than to move on. + +&h2(ERRORS) +The following values may be passed back in the &ital(state) field of the returned message +buffer. + +&space +&beg_dlist(.75i : ^&bold_font ) +&di(RMR_ERR_BADARG) The message buffer pointer did not refer to a valid message. +&di(RMR_ERR_NOHDR) The header in the message buffer was not valid or corrupted. +&di(RMR_ERR_NOENDPT) The message type in the message buffer did not map to a known endpoint. +&end_dlist + +&space +The following values may be assigned to &cw(errno) on failure. +&beg_dlist(.75i : ^&bold_font ) +&di(INVAL) Parameter(s) passed to the function were not valid, or the underlying message processing environment was unable to interpret the message. + +&half_space +&di(ENOKEY) The header information in the message buffer was invalid. + +&half_space +&di(ENXIO) No known endpoint for the message could be found. + +&half_space +&di(EMSGSIZE) The underlying transport refused to accept the message because of a size value issue (message was not attempted to be sent). + +&half_space +&di(EFAULT) The message referenced by the message buffer is corrupt (NULL pointer or bad internal length). + +&half_space +&di(EBADF) Internal RMR error; information provided to the message transport environment was not valid. + +&half_space +&di(ENOTSUP) Sending was not supported by the underlying message transport. + +&half_space +&di(EFSM) The device is not in a state that can accept the message. + +&half_space +&di(EAGAIN) The device is not able to accept a message for sending. The user application should attempt to resend. + +&half_space +&di(EINTR) The operation was interrupted by delivery of a signal before the message was sent. + +&half_space +&di(ETIMEDOUT) The underlying message environment timed out during the send process. + +&half_space +&di(ETERM) The underlying message environment is in a shutdown state. +&end_dlist + +&h2(EXAMPLE) +The following is a simple example of how the &cw(rmr_send_msg) function is called. +In this example, the send message buffer is saved between calls and reused +eliminating alloc/free cycles. + +&space +&ex_start + static rmr_mbuf_t* send_msg = NULL; // message to send; reused on each call + msg_t* send_pm; // payload for send + msg_t* pm; // our message format in the received payload + + if( send_msg == NULL ) { + send_msg = rmr_alloc_msg( mr, MAX_SIZE ); // new buffer to send + } + + // reference payload and fill in message type + pm = (msg_t*) send_msg->payload; + send_msg->mtype = MT_ANSWER; + + msg->len = generate_data( pm ); // something that fills the payload in + msg = rmr_send_msg( mr, send_msg ); + if( ! msg ) { + return ERROR; + } else { + if( msg->state != RMR_OK ) { + // check for eagain, and resend if needed + // else return error + } + } + + return OK; + +&ex_end + + +&h2(SEE ALSO ) +.ju off +rmr_alloc_msg(3), +rmr_call(3), +rmr_free_msg(3), +rmr_init(3), +rmr_payload_size(3), +rmr_rcv_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_ready(3), +rmr_mk_ring(3), +rmr_ring_free(3), +rmr_torcv_rcv(3), +rmr_wh_send_msg(3) +.ju on + + +.qu + diff --git a/doc/src/man/rmr_str2meid.3.xfm b/doc/src/man/rmr_str2meid.3.xfm new file mode 100644 index 0000000..81ed6c9 --- /dev/null +++ b/doc/src/man/rmr_str2meid.3.xfm @@ -0,0 +1,108 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.if false + Mnemonic rmr_str2meid.xfm + Abstract The manual page for the rmr_str2meid function. + Author E. Scott Daniels + Date 8 March 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + rmr_str2meid + +&h2(SYNOPSIS) +&indent +&ex_start +#include + +int rmr_str2meid( rmr_mbuf_t* mbuf, unsigned char* src, int len ) +&ex_end + +&uindent + +&h2(DESCRIPTION) +The &cw(rmr_str2meid) function will copy the string pointed to by src to the +managed equipment ID (meid) field in the given message. +The field is a fixed length, gated by the constant &cw(RMR_MAX_MEID) and if string length is larger +than this value, then &bold(nothing) will be copied. (Note, this differs slightly from the +behaviour of the &cw( lrmr_bytes2meid() ) function.) + +&h2(RETURN VALUE) +On success, the value RMR_OK is returned. +If the string cannot be copied to the message, the return value will be one of the +errors listed below. + +&h2(ERRORS) +If the return value is not RMR_OK, then it will be set to one of the values below. +&half_space + + +&beg_dlist(.75i : ^&bold_font ) +&di(RMR_ERR_BADARG) The message, or an internal portion of the message, was corrupted or the pointer was invalid. + +&half_space +&di(RMR_ERR_OVERFLOW) The length passed in was larger than the maximum length of the field; only a portion of + the source bytes were copied. +&end_dlist + +&h2(EXAMPLE) + + +&h2(SEE ALSO ) +.ju off +rmr_alloc_msg(3), +rmr_call(3), +rmr_free_msg(3), +rmr_get_meid(3), +rmr_get_rcvfd(3), +rmr_payload_size(3), +rmr_send_msg(3), +rmr_rcv_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_ready(3), +rmr_fib(3), +rmr_has_str(3), +rmr_tokenise(3), +rmr_mk_ring(3), +rmr_ring_free(3), +rmr_bytes2meid(3), +rmr_wh_open(3), +rmr_wh_send_msg(3) +.ju on +.qu + diff --git a/doc/src/man/rmr_str2xact.3.xfm b/doc/src/man/rmr_str2xact.3.xfm new file mode 100644 index 0000000..1f14cd6 --- /dev/null +++ b/doc/src/man/rmr_str2xact.3.xfm @@ -0,0 +1,107 @@ + +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.if false + Mnemonic rmr_str2xact.xfm + Abstract The manual page for the rmr_str2xact function. + Author E. Scott Daniels + Date 8 March 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + rmr_str2xact + +&h2(SYNOPSIS) +&indent +&ex_start +#include + +int rmr_str2xact( rmr_mbuf_t* mbuf, unsigned char* src, int len ) +&ex_end + +&uindent + +&h2(DESCRIPTION) +The &cw(rmr_str2xact) function will copy the string pointed to by src to the +transaction ID (xaction) field in the given message. +The field is a fixed length, gated by the constant &cw(RMR_MAX_XID) and if string length is larger +than this value, then &bold(nothing) will be copied. (Note, this differs slightly from the +behaviour of the &cw( lrmr_bytes2xact() ) function.) + +&beg_dlist(.75i : ^&bold_font ) +&h2(RETURN VALUE) +On success, the value RMR_OK is returned. +If the string cannot be copied to the message, the return value will be one of the +errors listed below. + +&h2(ERRORS) +If the return value is not RMR_OK, then it will be set to one of the values below. + +&half_space +&di(RMR_ERR_BADARG) The message, or an internal portion of the message, was corrupted or the pointer was invalid. + +&half_space +&di(RMR_ERR_OVERFLOW) The length passed in was larger than the maximum length of the field; only a portion of + the source bytes were copied. +&end_dlist + +&h2(EXAMPLE) + + +&h2(SEE ALSO ) +rmr_alloc_msg(3), +rmr_bytes2meid(3), +rmr_bytes2xact(3), +rmr_call(3), +rmr_free_msg(3), +rmr_get_rcvfd(3), +rmr_payload_size(3), +rmr_send_msg(3), +rmr_rcv_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_ready(3), +rmr_fib(3), +rmr_has_str(3), +rmr_tokenise(3), +rmr_mk_ring(3), +rmr_ring_free(3), +rmr_str2meid(3), +rmr_wh_open(3), +rmr_wh_send_msg(3) +.qu + diff --git a/doc/src/man/rmr_support.3.xfm b/doc/src/man/rmr_support.3.xfm new file mode 100644 index 0000000..b4cdd7f --- /dev/null +++ b/doc/src/man/rmr_support.3.xfm @@ -0,0 +1,158 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi +.if false + Mnemonic rmr_support_man.xfm + Abstract The manual page for the rmr_ functions that support the library + and are available to support other applications such as the route + table generator, but are not directly related to message sending + and receiving. These "second class" functions therefore are + likely not to need a dedicated man page, so we lump them all + here. + Author E. Scott Daniels + Date 28 January 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + RMR support functions + +&h2(SYNOPSIS ) +&indent +&ex_start +#include +#include + +char* rmr_fib( char* fname ); +int rmr_has_str( char const* buf, char const* str, char sep, int max ); +int rmr_tokenise( char* buf, char** tokens, int max, char sep ); +void* rmr_mk_ring( int size ); +void rmr_ring_free( void* vr ); + +static inline void* rmr_ring_extract( void* vr ) +static inline int rmr_ring_insert( void* vr, void* new_data ) +&ex_end +&uindent + +&h2(DESCRIPTION) +These functions support the RMR library, and are made available to user applications +as some (e.g. route table generators) might need and/or want to make use of them. + + + +The &cw(rmr_fib) function accepts a file name and reads the entire file into a single buffer. +The intent is to provide an easy way to load a static route table without a lot of +buffered I/O hoops. + +&space +The &cw(rmr_has_str) function accepts a &ital(buffer) containing a set of delimited tokens (e.g. foo,bar,goo) +and returns true if the target string, &ital(str,) matches one of the tokens. +The &ital(sep) parameter provides the separation character in the buffer (e.g a comma) +and &ital(max) indicates the maximum number of tokens to split the buffer into +before checking. + +&space +The &cw(rmr_tokenise) function is a simple tokeniser which splits &ital(buf) into tokens +at each occurrence of &ital(sep). +Multiple occurrences of the separator character (e.g. a,,b) result in a nil token. +Pointers to the tokens are placed into the &ital(tokens) array provided by the caller which +is assumed to have at least enough space for &ital(max) entries. + +&space +The &cw(rmr_mk_ring) function creates a buffer ring with &ital(size) entries. + +&space +The &cw(rmr_ring_free) function accepts a pointer to a ring context and frees the associated memory. + +&space +The &cw(rmr_ring_insert) and &cw(rmr_ring_extract) functions are provided as static inline functions +via the &ital(rmr/ring_inline.h) header file. +These functions both accept the ring &ital(context) returned by &cw(mk_ring,) and either insert a +pointer at the next available slot (tail) or extract the data at the head. + +&h2(RETURN VALUES) +The following are the return values for each of these functions. + +&space +The &cw(rmr_fib) function returns a pointer to the buffer containing the contents of the +file. +The buffer is terminated with a single nil character (0) making it a legitimate C string. +If the file was empty or nonexistent, a buffer with an immediate nil character. +If it is important to the calling programme to know if the file was empty or did not exist, +the caller should use the system stat function call to make that determination. + +&space +The &cw(rmr_has_str) function returns 1 if &ital(buf) contains the token referenced by &ita(str,) +and false (0) if it does not. +On error, a -1 value is returned and &cw(errno) is set accordingly. + +&space +The &cw(rmr_tokenise) function returns the actual number of token pointers placed into &ital(tokens) + +&space +The &cw(rmr_mk_ring) function returns a void pointer which is the &ital(context) for the ring. + +&space +The &cw(rmr_ring_insert) function returns 1 if the data was successfully inserted into the ring, and +0 if the ring is full and the pointer could not be deposited. + +&space +The &cw(rmr_ring_extract) will return the data which is at the head of the ring, or NULL if the +ring is empty. + +&h2(ERRORS) +Not many of these functions set the value in &cw(errno,) however the value may be one of the following: +&beg_dlist(.75i : ^&bold_font ) +&di(INVAL) Parameter(s) passed to the function were not valid. +&end_dlist + +&h2(EXAMPLE) + +&h2(SEE ALSO ) +.ju off +rmr_alloc_msg(3), +rmr_call(3), +rmr_free_msg(3), +rmr_init(3), +rmr_payload_size(3), +rmr_send_msg(3), +rmr_rcv_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_ready(3), +.ju on + + +.qu + diff --git a/doc/src/man/rmr_torcv_msg.3.xfm b/doc/src/man/rmr_torcv_msg.3.xfm new file mode 100644 index 0000000..0482419 --- /dev/null +++ b/doc/src/man/rmr_torcv_msg.3.xfm @@ -0,0 +1,145 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi +.if false + Mnemonic rmr_torcv_msg_man.xfm + Abstract The manual page for the rmr_torcv_msg function. + Author E. Scott Daniels + Date 29 January 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + rmr_torcv_msg + +&h2(SYNOPSIS ) +&indent +&ex_start +#include + +rmr_mbuf_t* rmr_torcv_msg( void* vctx, rmr_mbuf_t* old_msg, int ms_to ); +&ex_end +&uindent + +&h2(DESCRIPTION) +The &cw(rmr_torcv_msg) function will pause for &ital(ms_to) milliseconds waiting +for a message to arrive. +If a message arrives before the timeout expires the message buffer returned +will have a status of RMR_OK and the payload will contain the data received. +If the timeout expires before the message is received, the status will +have the value RMR_ERR_TIMEOUT. +When a received message is returned the message buffer will also contain the +message type and length set by the sender. +If messages were queued while waiting for the response to a previous invocation +of &cw(rmr_call,) the oldest message is removed from the queue and returned +without delay. + +&space +The &ital(vctx) pointer is the pointer returned by the &cw(rmr_init) function. +&ital(Old_msg) is a pointer to a previously used message buffer or NULL. +The ability to reuse message buffers helps to avoid alloc/free cycles in the +user application. +When no buffer is available to supply, the receive function will allocate one. + +&h2(RETURN VALUE) +The function returns a pointer to the &cw(rmr_mbuf_t) structure which references +the message information (state, length, payload), or a NULL pointer in the case +of an extreme error. + +&h2(ERRORS) +The &ital(state) field in the message buffer will be one of the following: +&space +&beg_dlist(.75i : ^&bold_font ) +&di(RMR_OK) The message buffer (payload) references the received data. + +&space +&di(RMR_ERR_TIMEOUT) The timeout expired before a complete message was received. + All other fields in the message buffer are not valid. + +&space +&di(RMR_ERR_EMPTY) A message was received, but it had no payload. All other + fields in the message buffer are not valid. +&end_dlist +&space + +&beg_dlist(.75i : ^&bold_font ) +&di(INVAL) Parameter(s) passed to the function were not valid. + +&half_space +&di(EBADF) The underlying message transport is unable to process the request. + +&half_space +&di(ENOTSUP) The underlying message transport is unable to process the request. + +&half_space +&di(EFSM) The underlying message transport is unable to process the request. + +&half_space +&di(EAGAIN) The underlying message transport is unable to process the request. + +&half_space +&di(EINTR) The underlying message transport is unable to process the request. + +&half_space +&di(ETIMEDOUT) The underlying message transport is unable to process the request. + +&half_space +&di(ETERM) The underlying message transport is unable to process the request. +&end_dlist + +&h2(EXAMPLE) + +&h2(SEE ALSO ) +.ju off +rmr_alloc_msg(3), +rmr_call(3), +rmr_free_msg(3), +rmr_get_rcvfd(3), +rmr_init(3), +rmr_payload_size(3), +rmr_rcv_msg(3), +rmr_send_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_ready(3), +rmr_fib(3), +rmr_has_str(3), +rmr_tokenise(3), +rmr_mk_ring(3), +rmr_ring_free(3) +.ju on + + +.qu + diff --git a/doc/src/man/rmr_wh_close.3.xfm b/doc/src/man/rmr_wh_close.3.xfm new file mode 100644 index 0000000..3ee4b34 --- /dev/null +++ b/doc/src/man/rmr_wh_close.3.xfm @@ -0,0 +1,90 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.if false + Mnemonic rmr_wh_close.xfm + Abstract The manual page for the rmr_wh_close function. + Author E. Scott Daniels + Date 21 February 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + rmr_wh_open + +&h2(SYNOPSIS) +&indent +&ex_start +#include + +void rmr_close( void* vctx, rmr_whid_t whid ) +&ex_end + +&uindent + +&h2(DESCRIPTION) +The &cw(rmr_wh_close) function closes the wormhole associated with the wormhole id passed +in. +Future calls to &cw(rmr_wh_send_msg) with this ID will fail. + +&space +The underlying TCP connection to the remote endpoint is &bold(not) closed as this +session may be reqruired for regularlly routed messages (messages routed based on message +type). +There is no way to force a TCP session to be closed at this point in time. + + +&h2(SEE ALSO ) +rmr_alloc_msg(3), +rmr_call(3), +rmr_free_msg(3), +rmr_get_rcvfd(3), +rmr_payload_size(3), +rmr_send_msg(3), +rmr_rcv_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_ready(3), +rmr_fib(3), +rmr_has_str(3), +rmr_tokenise(3), +rmr_mk_ring(3), +rmr_ring_free(3), +rmr_wh_open(3), +rmr_wh_send_msg(3) + + +.qu + diff --git a/doc/src/man/rmr_wh_open.3.xfm b/doc/src/man/rmr_wh_open.3.xfm new file mode 100644 index 0000000..9bd15a4 --- /dev/null +++ b/doc/src/man/rmr_wh_open.3.xfm @@ -0,0 +1,131 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.if false + Mnemonic rmr_wh_open.xfm + Abstract The manual page for the rmr_wh_open function. + Author E. Scott Daniels + Date 20 February 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + rmr_wh_open + +&h2(SYNOPSIS) +&indent +&ex_start +#include + +void* rmr_open( void* vctx, char* target ) +&ex_end + +&uindent + +&h2(DESCRIPTION) +The &cw(rmr_wh_open) function creates a direct link for sending, a wormhole, to another +RMr based process. +Sending messages through a wormhole requires that the connection be established overtly +by the user application (via this function), and that the ID returned by &cw(rmr_wh_open) +be passed to the &cw(rmr_wh_send_msg) function. + +&space +&ital(Target) is the &ital(name:port,) or &ital(IP-address:port,) combination of the +processess that the wormhole should be connected to. +&ital(Vctx) is the RMr void context pointer that was returned by the &cw(rmr_init) function. + +&space +When invoked, this function immediatly attempts to connect to the target process. +If the connection cannot be established, an error is returned to the caller, and no +direct messages can be sent to the target. +Once a wormhole is connected, the underlying transport mechanism (e.g. NNG) will provide +reconnects should the connection be lost, however the handling of messages sent when +a connection is broken is undetermined as each underlying transport mechanism may handle +buffering and retries differently. + + +&h2(RETURN VALUE) +The &cw(rmr_wh_open) function returns a type &cw(rmr_whid_t) which must be passed to +the &cw(rmr_wh_send_msg) function when sending a message. +The id may also be tested to determine success or failure of the connection by +using the RMR_WH_CONNECTED macro and passing the ID as the parameter; a result of +1 indicates that the connection was esablished and that the ID is valid. + +&h2(ERRORS) +The following error values are specifically set by this RMR function. In some cases the +error message of a system call is propagated up, and thus this list might be incomplete. + +&beg_dlist(.75i : ^&bold_font ) +&di(EINVAL) A parameter passed was not valid. +&di(EACCESS) The user applicarion does not have the ability to establish a wormhole to the + indicated target (or maybe any target). +&di(ECONNREFUSED) The connection was refused. +&end_dlist + +&h2(EXAMPLE) +&ex_start + void* rmc; + rmr_whid_t wh; + + rmc = rmr_init( "43086", 4096, 0 ); // init context + wh = rmr_open( rmc, "localhost:6123" ); + if( !RMR_WH_CONNECTED( wh ) ) { + fprintf( stderr, "unable to connect wormhole: %s\n", + strerror( errno ) ); + } +&ex_end + +&h2(SEE ALSO ) +rmr_alloc_msg(3), +rmr_call(3), +rmr_free_msg(3), +rmr_get_rcvfd(3), +rmr_payload_size(3), +rmr_send_msg(3), +rmr_rcv_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_ready(3), +rmr_fib(3), +rmr_has_str(3), +rmr_tokenise(3), +rmr_mk_ring(3), +rmr_ring_free(3), +rmr_wh_send_msg(3), +rmr_wh_close(3) + + +.qu + diff --git a/doc/src/man/rmr_wh_send_msg.3.xfm b/doc/src/man/rmr_wh_send_msg.3.xfm new file mode 100644 index 0000000..5a014d6 --- /dev/null +++ b/doc/src/man/rmr_wh_send_msg.3.xfm @@ -0,0 +1,204 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi +.if false + Mnemonic rmr_wh_send_msg_man.xfm + Abstract The manual page for the rmr_wh_send_msg function. + Author E. Scott Daniels + Date 28 January 2019 +.fi + +.** if formatting with tfm, the roff.im will cause roff output to be generated +.** if formatting with pfm, then pretty postscript will be generated +.gv e LIB lib +.if pfm + .im &{lib}/generic_ps.im +.ei + .gv e OUTPUT_RST use_rst + .if .ev &use_rst 1 = + .im &{lib}/rst.im + .ei + .im &{lib}/roff.im + .fi +.fi + +&line_len(6i) + +&h1(RMR Library Functions) +&h2(NAME) + rmr_wh_send_msg + +&h2(SYNOPSIS ) +&indent +&ex_start +#include + +rmr_mbuf_t* rmr_wh_send_msg( void* vctx, rmr_whid_t id, rmr_mbuf_t* msg ); +&ex_end +&uindent + +&h2(DESCRIPTION) +The &cw(rmr_wh_send_msg) function accepts a message buffer from the user application +and attempts to send it using the wormhole ID provided (id). +Unlike &ital(rmr_send_msg,) this function attempts to send the message directly +to a process at the other end of a wormhole which was created with &ital(rmr_wh-open().) +When sending message via wormholes, the normal RMr routing based on message type is +ignored, and the caller may leave the message type unspecified in the message buffer +(unless it is needed by the reeciving process). + +.sp +The message buffer (msg) used to send is the same format as used for regular RMr +send and reply to sender operations, thus any buffer allocated by these means, or +calls to &ital(rmr_rcv_msg()) can be passed to this function. + + +&h2(RETURN VALUE) +On success, a new message buffer, with an empty payload, is returned for the application +to use for the next send. +The state in this buffer will reflect the overall send operation state and should be +&cw(RMR_OK.) + +&space +If the state in the returned buffer is anything other than &cw(RMR_OK,) the user application +may need to attempt a retransmission of the message, or take other action depending on the +setting of &cw(errno) as described below. + +&space +In the event of extreme failure, a NULL pointer is returned. In this case the value of +&cw(errno) might be of some use, for documentation, but there will be little that the +user application can do other than to move on. + +&h2(ERRORS) +The following values may be passed back in the &ital(state) field of the returned message +buffer. + +&space +&beg_dlist(.75i : ^&bold_font ) +&di(RMR_ERR_WHID) The wormhole ID passed in was not associated with an open wormhole, or was out of range for a valid ID. +&di(RMR_ERR_NOWHOPEN) No wormholes exist, further attempt to validate the ID are skipped. +&di(RMR_ERR_BADARG) The message buffer pointer did not refer to a valid message. +&di(RMR_ERR_NOHDR) The header in the message buffer was not valid or corrupted. +&end_dlist + +&space +The following values may be assigned to &cw(errno) on failure. +&beg_dlist(.75i : ^&bold_font ) +&di(INVAL) Parameter(s) passed to the function were not valid, or the underlying message processing environment was unable to interpret the message. + +&half_space +&di(ENOKEY) The header information in the message buffer was invalid. + +&half_space +&di(ENXIO) No known endpoint for the message could be found. + +&half_space +&di(EMSGSIZE) The underlying transport refused to accept the message because of a size value issue (message was not attempted to be sent). + +&half_space +&di(EFAULT) The message referenced by the message buffer is corrupt (NULL pointer or bad internal length). + +&half_space +&di(EBADF) Internal RMR error; information provided to the message transport environment was not valid. + +&half_space +&di(ENOTSUP) Sending was not supported by the underlying message transport. + +&half_space +&di(EFSM) The device is not in a state that can accept the message. + +&half_space +&di(EAGAIN) The device is not able to accept a message for sending. The user application should attempt to resend. + +&half_space +&di(EINTR) The operation was interrupted by delivery of a signal before the message was sent. + +&half_space +&di(ETIMEDOUT) The underlying message environment timed out during the send process. + +&half_space +&di(ETERM) The underlying message environment is in a shutdown state. +&end_dlist + +&h2(EXAMPLE) +The following is a simple example of how the a wormhole is created (rmr_wh_open) and then +how &cw(rmr_wh_send_msg) function is used to send messages. +Some error checking is omitted for clarity. + +&space +&ex_start + +#include // system headers omitted for clarity + +int main() { + rmr_whid_t whid = -1; // wormhole id for sending + void* mrc; //msg router context + int i; + rmr_mbuf_t* sbuf; // send buffer + int count = 0; + + mrc = rmr_init( "43086", RMR_MAX_RCV_BYTES, RMRFL_NONE ); + if( mrc == NULL ) { + fprintf( stderr, "[FAIL] unable to initialise RMr environment\n" ); + exit( 1 ); + } + + while( ! rmr_ready( mrc ) ) { // wait for routing table info + sleep( 1 ); + } + + sbuf = rmr_alloc_msg( mrc, 2048 ); + + while( 1 ) { + if( whid < 0 ) { + whid = rmr_wh_open( mrc, "localhost:6123" ); // open fails if endpoint refuses conn + if( RMR_WH_CONNECTED( wh ) ) { + snprintf( sbuf->payload, 1024, "periodic update from sender: %d", count++ ); + sbuf->len = strlen( sbuf->payload ); + sbuf = rmr_wh_send_msg( mrc, whid, sbuf ); + } + } + + sleep( 5 ); + } +} +&ex_end + + +&h2(SEE ALSO ) +.ju off +rmr_alloc_msg(3), +rmr_call(3), +rmr_free_msg(3), +rmr_init(3), +rmr_payload_size(3), +rmr_rcv_msg(3), +rmr_rcv_specific(3), +rmr_rts_msg(3), +rmr_ready(3), +rmr_fib(3), +rmr_has_str(3), +rmr_tokenise(3), +rmr_mk_ring(3), +rmr_ring_free(3), +rmr_wh_open(3), +rmr_wh_close(3) +.ju on + + +.qu + diff --git a/doc/src/roff.im b/doc/src/roff.im new file mode 100644 index 0000000..27831da --- /dev/null +++ b/doc/src/roff.im @@ -0,0 +1,78 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.if false + Mnemonic: troff.im + Abstract: This file provides macros allowing {X}fm source to generate + troff input from {X}fm source when the doc is passed through + tfm, and to generate postscirpt output when passed through + pfm. About the only real use of this is to be able to maintain + manual pages in the more easily read {X}fm source format. + + Author: E. Scott Daniels + Date: 27 January 2019 + + Some roff information was gleanded from this page: + http://cmd.inp.nsk.su/old/cmd2/manuals/unix/UNIX_Unleashed/ch08.htm +.fi + +.if tfm + .** assume that we're generating roff output when tfm is used. These macros + .** convert {X}fm input into troff. + + .dv h1 .sp 1 ^&bold($1) .br ^&space + .dv h2 .sp 1 ^&bold($1) .br ^&space + .dv h3 .sp 1 ^&bold($1) .br ^&space + + .dv fig + .dv set_font_cw .br ^^.ft CW + + .dv nf ^^.nf .br .nf + .dv fo .fo ^^.fi .br + + .dv line_len ^^.ll $1 + .dv space .br ^^.sp 1 .br + .dv half_space .br ^^.sp 1 .br + .dv beg_list + .dv end_list + .dv beg_dlist .dv udi -$1 ^: ^^.in +$1 .br ^^.sp 1 .br + .dv end_dlist .br ^^.in &udi .br + .dv ditem ^^.ti ^&udi .br ^&space .br \fB$1\fR .br + .dv item ^&space \(bu + .** .dv di ^&ditem + .dv di ^^.ti ^&udi .br ^&space .br \fB$1\fR .br + + .dv ex_start ^^.IP .br ^^.nf .br ^^.ft CW .br .nf + .dv ex_end .fo on .br ^^.ft P .br ^^.fi .br ^&space + + .** fonts and font macros + .dv ital \fI$1\fR + .dv bold \fB$1\fR + .dv cw \f(CW$1\fP + .dv set_font_prop .br ^^.ft P + + .ju off + .in 0 + + .dv indent ^^.ll -0.5i &space ^^.in +0.25i + .dv uindent ^^.in -0.25i &space ^^.ll +0.5i +.ei + .** for postscript output we just pass through to pfm commands + .im generic_ps.im +.fi diff --git a/doc/src/rst.im b/doc/src/rst.im new file mode 100644 index 0000000..8662137 --- /dev/null +++ b/doc/src/rst.im @@ -0,0 +1,100 @@ +.if false +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.if false + Mnemonic: rts.im + Abstract: This file provides macros allowing {X}fm source to generate + rts input from {X}fm source when the doc is passed through + tfm, and to generate postscirpt output when passed through + pfm. Simalar to the roff.im macro set that allows the generation + of troff input for man pages. + + Author: E. Scott Daniels + Date: 7 February 2019 + + + Maybe useful (but doesn't explain why real formatters aren't being used) + http://docutils.sourceforge.net/docs/user/rst/quickref.html +.fi + + +.if tfm + .** assume that we're generating rts output when tfm is used. These macros + .** convert {X}fm input into rts. + .** post processing is needed to strip the leading space that tfm insists on adding. + + .** bloody rst has no consistant marking character, and each header level must be different and as long as the text. + .** and of course they don't generate tags in the resulting HTML, but
tags. WTF? + .dv h1 .sp 1 $1 .br ============================================================================================ .sp 1 + .dv h2 .sp 1 $1 .br -------------------------------------------------------------------------------------------- .sp 1 + .dv h3 .sp 1 $1 .br ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .sp 1 + .dv h4 **$1** + .** .dv h1 === $1 .br === .sp 1 + .** .dv h2 === $1 .br === .sp 1 + .** .dv h3 === $1 .br === .sp 1 + + .dv fig + .dv set_font_cw + + .dv nf .sp 1 ^:^: .br .ll -2 .in +2 + .dv fo .in -2 .ll +2 .sp 1 + + .dv indent + .dv uindent + + .dv lic1 + + .dv lic2 - + .dv lic3 * + + .in 0i .** bloody rst is indention sensitive like markdown; sheesh + + .dv line_len .ll $1 + .dv space .sp 1 + .dv half_space .sp 1 + .dv beg_list .sp 1 .dv lic $1 ^: + .dv end_list .sp 1 + + .dv beg_dlist .sp 1 + .dv end_dlist .br .in 0i + + .** for now we allow only a single layer of defitems + .dv di .in 0i .br $1 .br .in +.25i + .dv diitem .in 0i .br $1 .br .in +.25i + .dv item .br &lic + + .dv ex_start .sp 1 ^:^: .br .ll -2 .in +2 .nf + .dv ex_end .fo on .in -2 .ll +2 .sp 1 + + .** fonts and font macros + .dv ital *$1* + .dv bold **$1** + .dv cw $1 + .dv set_font_prop + + .dv table .sp 1 ^[table not supported in rst output] .if false + .dv tab_cell + .dv tab_row + .dv end_table .fi + + .ju off +.ei + .** for postscript output we just need to set macros up to mimic those above; same for + .** what ever alternate crap we're generating, so defined once: + .im generic_ps.im +.fi diff --git a/examples/Makefile b/examples/Makefile new file mode 100644 index 0000000..6314ea3 --- /dev/null +++ b/examples/Makefile @@ -0,0 +1,14 @@ +# The demo programmes assume that RMr (along with nng) is installed +# It may be necessary to set LD_LIBRARY_PATH=/usr/local/lib + + +.PHONY: all +all: sender receiver + +receiver: receiver.c + gcc $< -g -o $@ -lrmr_nng -lnng -lpthread -lm + +sender: sender.c + gcc $< -g -o $@ -lrmr_nng -lnng -lpthread -lm + + diff --git a/examples/README b/examples/README new file mode 100644 index 0000000..4b63eee --- /dev/null +++ b/examples/README @@ -0,0 +1,6 @@ + +This directory contains a few sample programmes which demonstrate +various aspects of how a user programme can use RMr to send +and/or receive messages. These programmes are fairly simple +in nature, and in most cases error checking is not performed +to keep the code simple. diff --git a/examples/receiver.c b/examples/receiver.c new file mode 100644 index 0000000..c78c710 --- /dev/null +++ b/examples/receiver.c @@ -0,0 +1,83 @@ +// :vim ts=4 sw=4 noet: +/* + Mnemonic: rmr_rcvr.c + Abstract: This is a very simple receiver that does nothing but listen + for messages and write stats every so often to the tty. + + Define these environment variables to have some control: + RMR_SEED_RT -- path to the static routing table + RMR_RTG_SVC -- host:port of the route table generator + + Parms: Two positional parameters are recognised on the command line: + [port [stats-freq]] + + where port is the port number to listen on and the stats frequency + is the number of messages received which causes stats to be + generated. If not supplied the listen port default is 4560 + and the stats frequency is every 10 messages. + + Date: 1 April 2019 + Author: E. Scott Daniels +*/ + +#include +#include +#include +#include +#include + +#include + + +int main( int argc, char** argv ) { + void* mrc; // msg router context + long long total = 0; + rmr_mbuf_t* msg = NULL; // message received + int stat_freq = 10; // write stats after reciving this many messages + int i; + char* listen_port; + long long count = 0; + long long bad = 0; + long long empty = 0; + + if( argc > 1 ) { + listen_port = argv[1]; + } + if( argc > 2 ) { + stat_freq = atoi( argv[2] ); + } + fprintf( stderr, " listening on port: %s\n", listen_port ); + fprintf( stderr, " stats will be reported every %d messages\n", stat_freq ); + + mrc = rmr_init( listen_port, RMR_MAX_RCV_BYTES, RMRFL_NONE ); // start your engines! + if( mrc == NULL ) { + fprintf( stderr, " ABORT: unable to initialise RMr\n" ); + exit( 1 ); + } + + while( ! rmr_ready( mrc ) ) { // wait for RMr to load a route table + fprintf( stderr, " waiting for ready\n" ); + sleep( 3 ); + } + fprintf( stderr, " rmr now shows ready\n" ); + + while( 1 ) { // forever; ctl-c, kill -15, etc to end + msg = rmr_rcv_msg( mrc, msg ); // block until one arrives + + if( msg ) { + if( msg->state == RMR_OK ) { + count++; // messages received for stats output + } else { + bad++; + } + } else { + empty++; + } + + if( (count % stat_freq) == 0 ) { + fprintf( stderr, " total msg received: %lld errors: %lld empty: %lld\n", count, bad, empty ); + } + + } +} + diff --git a/examples/sender.c b/examples/sender.c new file mode 100644 index 0000000..0fa5343 --- /dev/null +++ b/examples/sender.c @@ -0,0 +1,120 @@ +// :vim ts=4 sw=4 noet: + +/* + Mnemonic: sender.c + Abstract: Very simple test sender. Sends messages with a given delay between each. + The sender also uses epoll_wait() to ensure that any received messages + don't clog the queue. + + Parms: The following positional parameters are recognised on the command line: + [listen_port [delay [stats-freq] [msg-type]]]] + + Defaults: + listen_port 43086 + delay (mu-sec) 1000000 (1 sec) + stats-freq 10 + msg-type 0 + + Date: 1 April 2019 +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include + +int main( int argc, char** argv ) { + void* mrc; //msg router context + struct epoll_event events[1]; // list of events to give to epoll + struct epoll_event epe; // event definition for event to listen to + int ep_fd = -1; // epoll's file des (given to epoll_wait) + int rcv_fd; // file des that NNG tickles -- give this to epoll to listen on + int nready; // number of events ready for receive + rmr_mbuf_t* sbuf; // send buffer + rmr_mbuf_t* rbuf; // received buffer + int count = 0; + int rcvd_count = 0; + char* listen_port = "43086"; + int delay = 1000000; // mu-sec delay between messages + int mtype = 0; + int stats_freq = 100; + + if( argc > 1 ) { + listen_port = argv[1]; + } + if( argc > 2 ) { + delay = atoi( argv[2] ); + } + if( argc > 3 ) { + mtype = atoi( argv[3] ); + } + + fprintf( stderr, " listen port: %s; mtype: %d; delay: %d\n", listen_port, mtype, delay ); + + if( (mrc = rmr_init( listen_port, 1400, RMRFL_NONE )) == NULL ) { + fprintf( stderr, " unable to initialise RMr\n" ); + exit( 1 ); + } + + rcv_fd = rmr_get_rcvfd( mrc ); // set up epoll things, start by getting the FD from MRr + if( rcv_fd < 0 ) { + fprintf( stderr, " unable to set up polling fd\n" ); + exit( 1 ); + } + if( (ep_fd = epoll_create1( 0 )) < 0 ) { + fprintf( stderr, "[FAIL] unable to create epoll fd: %d\n", errno ); + exit( 1 ); + } + epe.events = EPOLLIN; + epe.data.fd = rcv_fd; + + if( epoll_ctl( ep_fd, EPOLL_CTL_ADD, rcv_fd, &epe ) != 0 ) { + fprintf( stderr, "[FAIL] epoll_ctl status not 0 : %s\n", strerror( errno ) ); + exit( 1 ); + } + + sbuf = rmr_alloc_msg( mrc, 256 ); // alloc first send buffer; subsequent buffers allcoated on send + rbuf = NULL; // don't need to alloc receive buffer + + while( ! rmr_ready( mrc ) ) { // must have a route table before we can send; wait til RMr say it has one + sleep( 1 ); + } + fprintf( stderr, " rmr is ready\n" ); + + + while( 1 ) { // send messages until the cows come home + snprintf( sbuf->payload, 200, "count=%d received= %d ts=%lld %d stand up and cheer!", // create the payload + count, rcvd_count, (long long) time( NULL ), rand() ); + + sbuf->mtype = mtype; // fill in the message bits + sbuf->len = strlen( sbuf->payload ) + 1; // our receiver likely wants a nice acsii-z string + sbuf->state = 0; + sbuf = rmr_send_msg( mrc, sbuf ); // send it (send returns an empty payload on success, or the original payload on fail/retry) + while( sbuf->state == RMR_ERR_RETRY ) { // soft failure (device busy?) retry + sbuf = rmr_send_msg( mrc, sbuf ); // retry send until it's good (simple test; real programmes should do better) + } + count++; + + while( (nready = epoll_wait( ep_fd, events, 1, 0 )) > 0 ) { // if something ready to receive (non-blocking check) + if( events[0].data.fd == rcv_fd ) { // we only are waiting on 1 thing, so [0] is ok + errno = 0; + rbuf = rmr_rcv_msg( mrc, rbuf ); + if( rbuf ) { + rcvd_count++; + } + } + } + + if( (count % stats_freq) == 0 ) { + fprintf( stderr, " sent %d received %d\n", count, rcvd_count ); + } + + usleep( delay ); + } +} + diff --git a/src/README b/src/README new file mode 100644 index 0000000..420b935 --- /dev/null +++ b/src/README @@ -0,0 +1,13 @@ + +Top level source directory. Each subdirectory contains specific code +which supports the indicated transport mechanism. The common subdirectory +contains modules which are transport mechanism agnostic. + +Each sub directory should contain the following directories: + src - Actual C source modules + include - Header files + test - Unit test code and scripts to drive when necessary + +Also at this level in the file tree, a test directory contains code +which supports all of the specific unit test code in each of the other +three directories. diff --git a/src/STYLE b/src/STYLE new file mode 100644 index 0000000..bcc9992 --- /dev/null +++ b/src/STYLE @@ -0,0 +1,126 @@ + +Coding conventions/style for the ricmsg library. + +Line width +A hard line width will not be enforced, but a soft maximum of 150 +characters is preferred. + + +Indention +Indention is with TABS. Tab width is 4. Please ensure the line + // :vi ts=4 sw=4 noet: +is include in any new source file. + + +Comments +A "two column" approach is preferred so as to prevent small comments from +disrupting the "flow" of the logic; for example: + + switch( *(tokens[0]) ) { + case 'n': // newrt|{start|end} + if( strcmp( tokens[1], "end" ) == 0 ) { // wrap up the table we were building + if( nrt ) { + uta_rt_drop( ctx->old_rtable ); // time to drop one that was previously replaced + ctx->old_rtable = ctx->rtable; // currently active becomes old and allowed to 'drain' + ctx->rtable = nrt; // one we've been adding to becomes active + nrt = NULL; + } else { + nrt = NULL; + } + } else { // start a new table. + if( nrt != NULL ) { // one in progress? this forces it out + uta_rt_drop( nrt ); + } + +Comment Blocks +Major sections of code may be commented with multi-line blocks. These comments +should be enclosed in /* and */, indented to match the current top line of the block. +Each line should NOT contain a leading * or #, and a row of dashes at the top +and bottom should only be used for critical comments. For example: + + /* + Return true if routing table is initialised etc. and app can send/receive. + */ + +Function Headers +All functions should have a header comment block which describes: + - the purpose of the function + - any non-obvious parameters + - parameter limits/values where applicable + - general caution or warning statement to future programmers + + +Parenthesess and Brackets +When parentheses are used on function calls, if and while statements, there should be +a single space between the opening paraen and first token, and a similar space +between the end of the last token and the closing paren; this makes the code easier +to read in a monochrome environment. + +Parentheses used for expressions and type casting should abutt the tokens in the expression, +and further helps to make the code readable when an expression is used in an if/while. +To illustrate: + + if( (ctx = (uta_ctx_t *) vctx) == NULL ) { // this is readable + if((ctx=(uta_ctx_t *)vctx) == NULL) { // this is not + +Opening parens should NOT be separated from the function or keyword token; 'if(' not +'if ('. + +Return is NOT a function, and thus the return value should NOT be placed in parens. + + +Curly Brace Enclosed Blocks +Go's enforced curly brace policy actually makes senes, so that is used here. The bodies +of ALL if statements, even when just a single line, are to be enclosed in curly braces. +Further, curly braces are placed on the same line as the if and else tokens. The final +closing curly brace is aligned with the corresponding if. For example: + + if( key_len < 10 ) { // TESTING -- use dummy seed function as nn_rcv likley never to find a publisher + mlen = dummy_nn_rtg_rcv( mbuf ); // TESTING ONLY -- get a seed table + key_len = 16; // after seeding, we can wait + } else { + mlen = nn_recv( nn_sock, mbuf, sizeof( mbuf )-1, 0 ); // blocks until next buffer + } + +Functions +Function types should be placed on the same line as the function name as this allows +for simple generation of prototype statements in the header files. + +Variable Declaration and Type Specification +C's variable declaration opens the programmer to a world of accdental maintence bugs +when multple varlables are defined using a comma operator. Therefore, one variable +definition, per line is to be used here. Further, pointer types are to be declared +as 'type* var' rather than 'type *var' because the variable type is a pointer and +declaring it this way envforces it. Yes, this can lead to issues as 'int* k,n' +isn't what you probalby want, but is exactly why using multiple declarations on a +single line is considered bad. Again, Go's approach to variable declaration got +this correct. + +When declaring types (typedef) the preferred convention is to add "_t" to the type +name; e.g. msg_t. On the other hand, variable names should NOT indicate the type +(state_b is wrong as state might not always be boolean). + +All variables for a function should be declared at the TOP of the function. This makes +maintenence easier, and modern compilers are good at allocating variables as they +enter/leave scope, so there is no reason to allocate variables "script style". +It is also wise to initialise all variables at allocation; use your best judgement. + +Camel Case +Sucks; use underbars. + This_is_easy_to_read + ThisIsNotEasyToRead + +External names and contants +All outward facing external names and constants will begin with rmr_ or RMR_ as approprate. + + +Mk and Makefile confentions +Not much here other than there should be 2 "cleanup" rules: clean and nuke. Clean should +remove all intermediate files, leaving desired output (libraries, .ps or .pdf files). The +nuke rule should remove everything that can be built including libraries, binaries, .pdf +files etc. + +Mk is preferred as the recipes are easier to define and maintain (no silly end of line +continuation, mkfile variables are passed, etc.). However, both mkfiles and Makefiles +should be created so as not to require mk. Yes this is duplicate work, and dropping +make support is certainly acceptable :) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt new file mode 100644 index 0000000..ebccd3b --- /dev/null +++ b/src/common/CMakeLists.txt @@ -0,0 +1,38 @@ + +# +#================================================================================== +# Copyright (c) 2019 Nokia +# Copyright (c) 2018-2019 AT&T Intellectual Property. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#================================================================================== +# + + +add_library( common_objects OBJECT + src/symtab.c + src/mbuf_api.c +) + +target_include_directories (common_objects PUBLIC + $ + $ + PRIVATE src) + +# we have to force headers to install +install( FILES + include/rmr.h + include/rmr_symtab.h + include/RIC_message_types.h + DESTINATION ${install_root}/${install_inc} +) diff --git a/src/common/include/RIC_message_types.h b/src/common/include/RIC_message_types.h new file mode 100644 index 0000000..82495c8 --- /dev/null +++ b/src/common/include/RIC_message_types.h @@ -0,0 +1,54 @@ +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + + + + +/* Header file defining message types + for various RMR messages + + + ------------------ + WORK IN PROGRESS + ------------------ + +*/ + +#define RIC_UNDEFINED -1 + +/* E2 Related messages should be in the range + 10000 to 99999 +*/ + +#define RIC_X2_SETUP 10000 +#define RIC_X2_RESPONSE 10001 +#define RIC_X2_RESOURCE_STATUS_REQUEST 10002 +#define RIC_X2_RESOURCE_STATUS_RESPONSE 10003 +#define RIC_X2_LOAD_INFORMATION 10004 +#define RIC_E2_TERMINATION_HC_REQUEST 10005 +#define RIC_E2_TERMINATION_HC_RESPONSE 10006 +#define RIC_E2_MANAGER_HC_REQUEST 10007 +#define RIC_E2_MANAGER_HC_RESPONSE 10008 + + +/* A1 Related messages should be in the range + 100000 to 999999 +*/ +#define RIC_CONTROL_XAPP_CONFIG_REQUEST 100000 +#define RIC_CONTROL_XAPP_CONFIG_RESPONSE 100001 diff --git a/src/common/include/rmr.h b/src/common/include/rmr.h new file mode 100644 index 0000000..2f58eaf --- /dev/null +++ b/src/common/include/rmr.h @@ -0,0 +1,173 @@ +// : vi ts=4 sw=4 noet : +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + +/* + Mnemonic: rmr.h + Abstract: General (public) header file for the uta message routing library + Author: E. Scott Daniels + Date: 27 November 2018 +*/ + +#ifndef _rmr_h +#define _rmr_h + +#include // broken on mac + +#ifdef __cplusplus +extern "C" { +#endif + + +#define RMR_MAX_XID 32 // space in header reserved for user xaction id +#define RMR_MAX_SID 32 // spece in header reserved for sender id +#define RMR_MAX_MEID 32 // spece in header reserved for managed element id +#define RMR_MAX_SRC 16 // max length of hostname +#define RMR_MAX_RCV_BYTES 4096 // max bytes we support in a receive message + + // various flags for function calls +#define RMRFL_NONE 0x00 // no flags +#define RMRFL_AUTO_ALLOC 0x01 // send auto allocates a zerocopy buffer + +#define RMR_DEF_SIZE 0 // pass as size to have msg allocation use the default msg size + + +#define RMR_OK 0 // state is good +#define RMR_ERR_BADARG 1 // argument passd to function was unusable +#define RMR_ERR_NOENDPT 2 // send/call could not find an endpoint based on msg type +#define RMR_ERR_EMPTY 3 // msg received had no payload; attempt to send an empty message +#define RMR_ERR_NOHDR 4 // message didn't contain a valid header +#define RMR_ERR_SENDFAILED 5 // send failed; errno has nano reason +#define RMR_ERR_CALLFAILED 6 // unable to send call() message +#define RMR_ERR_NOWHOPEN 7 // no wormholes are open +#define RMR_ERR_WHID 8 // wormhole id was invalid +#define RMR_ERR_OVERFLOW 9 // operation would have busted through a buffer/field size +#define RMR_ERR_RETRY 10 // request (send/call/rts) failed, but caller should retry (EAGAIN for wrappers) +#define RMR_ERR_RCVFAILED 11 // receive failed (hard error) +#define RMR_ERR_TIMEOUT 12 // message processing call timed out +#define RMR_ERR_UNSET 13 // the message hasn't been populated with a transport buffer +#define RMR_ERR_TRUNC 14 // received message likely truncated + +#define RMR_WH_CONNECTED(a) (a>=0) // for now whid is integer; it could be pointer at some future date + +/* + General message buffer. Passed to send and returned by receive. + + (All fields are exposed such that if a wrapper needs to dup the storage as it passes + into or out of their environment they dup it all, not just what we choose to expose.) +*/ +typedef struct { + int state; // state of processing + int mtype; // message type + int len; // length of data in the payload (send or received) + unsigned char* payload; // transported data + unsigned char* xaction; // pointer to fixed length transaction id bytes + + // these things are off limits to the user application + void* tp_buf; // underlying transport allocated pointer (e.g. nng message) + void* header; // internal message header (whole buffer: header+payload) + unsigned char* id; // if we need an ID in the message separate from the xaction id + int flags; // various MFL_ (private) flags as needed + int alloc_len; // the length of the allocated space (hdr+payload) +} rmr_mbuf_t; + + +typedef int rmr_whid_t; // wormhole identifier returned by rmr_wh_open(), passed to rmr_wh_send_msg() + + +// ---- library message specific prototypes ------------------------------------------------------------ +extern rmr_mbuf_t* rmr_alloc_msg( void* vctx, int size ); +extern rmr_mbuf_t* rmr_call( void* vctx, rmr_mbuf_t* msg ); +extern void rmr_close( void* vctx ); +extern void* rmr_init( char* proto_port, int max_msg_size, int flags ); +extern int rmr_payload_size( rmr_mbuf_t* msg ); +extern rmr_mbuf_t* rmr_send_msg( void* vctx, rmr_mbuf_t* msg ); +extern rmr_mbuf_t* rmr_mtosend_msg( void* vctx, rmr_mbuf_t* msg, int max_to ); +extern rmr_mbuf_t* rmr_rcv_msg( void* vctx, rmr_mbuf_t* old_msg ); +extern rmr_mbuf_t* rmr_rcv_specific( void* uctx, rmr_mbuf_t* msg, char* expect, int allow2queue ); +extern rmr_mbuf_t* rmr_rts_msg( void* vctx, rmr_mbuf_t* msg ); +extern int rmr_ready( void* vctx ); +extern int rmr_set_rtimeout( void* vctx, int time ); +extern int rmr_set_stimeout( void* vctx, int time ); +extern int rmr_get_rcvfd( void* vctx ); // only supported with nng +extern rmr_mbuf_t* rmr_torcv_msg( void* vctx, rmr_mbuf_t* old_msg, int ms_to ); +extern rmr_whid_t rmr_wh_open( void* vctx, char const* target ); +extern rmr_mbuf_t* rmr_wh_send_msg( void* vctx, rmr_whid_t whid, rmr_mbuf_t* msg ); +extern void rmr_wh_close( void* vctx, int whid ); + + +// ----- msg buffer operations (no context needed) ------------------------------------------------------ +extern int rmr_bytes2meid( rmr_mbuf_t* mbuf, unsigned char const* src, int len ); +extern void rmr_bytes2payload( rmr_mbuf_t* mbuf, unsigned char const* src, int len ); +extern int rmr_bytes2xact( rmr_mbuf_t* mbuf, unsigned char const* src, int len ); +extern void rmr_free_msg( rmr_mbuf_t* mbuf ); +extern unsigned char* rmr_get_meid( rmr_mbuf_t* mbuf, unsigned char* dest ); +extern int rmr_str2meid( rmr_mbuf_t* mbuf, unsigned char const* str ); +extern void rmr_str2payload( rmr_mbuf_t* mbuf, unsigned char const* str ); +extern void rmr_str2payload( rmr_mbuf_t* mbuf, unsigned char const* str ); +extern int rmr_str2xact( rmr_mbuf_t* mbuf, unsigned char const* str ); + + +extern int rmr_rcv_to( void* vctx, int time ); // DEPRECATED -- replaced with set_rtimeout +extern int rmr_send_to( void* vctx, int time ); // DEPRECATED -- replaced with set_stimeout + + +// --- uta compatability defs if needed user should define UTA_COMPAT ---------------------------------- +#ifdef UTA_COMPAT + +#define UTA_MAX_XID RMR_MAX_XID +#define UTA_MAX_SID RMR_MAX_SID +#define UTA_MAX_SRC RMR_MAX_SRC +#define UTA_MAX_RCV_BYTES RMR_MAX_RCV_BYTES + +#define UTAFL_NONE RMRFL_NONE +#define UTAFL_AUTO_ALLOC RMRFL_AUTO_ALLOC + +#define UTA_DEF_SIZE RMRFL_AUTO_ALLOC + +#define UTA_OK RMR_OK +#define UTA_ERR_BADARG RMR_ERR_BADARG +#define UTA_ERR_NOENDPT RMR_ERR_NOENDPT +#define UTA_ERR_EMPTY RMR_ERR_EMPTY +#define UTA_ERR_NOHDR RMR_ERR_NOHDR +#define UTA_ERR_SENDFAILED RMR_ERR_SENDFAILED +#define UTA_ERR_CALLFAILED RMR_ERR_CALLFAILED + +#define uta_mbuf_t rmr_mbuf_t + +#define uta_alloc_msg rmr_alloc_msg +#define uta_call rmr_call +#define uta_free_msg rmr_free_msg +#define uta_init rmr_init +#define uta_payload_size rmr_payload_size +#define uta_send_msg rmr_send_msg +#define uta_rcv_msg rmr_rcv_msg +#define uta_rcv_specific rmr_rcv_specific +#define uta_rcv_to rmr_rcv_to +#define uta_rts_msg rmr_rts_msg +#define uta_ready rmr_ready +#define uta_send_to rmr_send_to +#endif // uta compat + + +#ifdef __cplusplus + } +#endif + +#endif // dup include prevention diff --git a/src/common/include/rmr_agnostic.h b/src/common/include/rmr_agnostic.h new file mode 100644 index 0000000..687330b --- /dev/null +++ b/src/common/include/rmr_agnostic.h @@ -0,0 +1,187 @@ +// : vi ts=4 sw=4 noet : +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + +/* + Mnemonic: rmr_agnostic.h + Abstract: Header file for things that are agnostic to the underlying transport + mechanism. + Author: E. Scott Daniels + Date: 28 February 2018 +*/ + +#ifndef _rmr_agnostic_h +#define _rmr_agnostic_h + +typedef struct endpoint endpoint_t; // place holder for structs defined in nano/nng private.h +typedef struct uta_ctx uta_ctx_t; + +// allow testing to override without changing this +#ifndef DEBUG +#define DEBUG 0 +#endif + +#define FALSE 0 +#define TRUE 1 + +#define QUOTE(a) #a // allow a constant to be quoted +#define QUOTE_DEF(a) QUOTE(a) // allow a #define value to be quoted (e.g. QUOTE(MAJOR_VERSION) ) + + +#define RMR_MSG_VER 1 // potental to treat messages differently if from backlevel version + + // environment variable names we'll suss out +#define ENV_BIND_IF "RMR_BIND_IF" // the interface to bind to for both normal comma and RTG (0.0.0.0 if missing) +#define ENV_RTG_PORT "RMR_RTG_SVC" // the port we'll listen on for rtg connections +#define ENV_SEED_RT "RMR_SEED_RT" // where we expect to find the name of the seed route table +#define ENV_RTG_RAW "RMR_RTG_ISRAW" // if > 0 we expect route table gen messages as raw (not sent from an RMr application) +#define ENV_VERBOSE_FILE "RMR_VCTL_FILE" // file where vlevel may be managed for some (non-time critical) functions + +#define NO_FLAGS 0 // no flags to pass to a function + +#define FL_NOTHREAD 0x01 // do not start an additional thread (must be 'user land' to support rtg +#define UFL_MASK 0xff // mask applied to some flag parms passed by the user to remove any internal flags + // internal flags, must be > than UFLAG_MASK +//#define IFL_.... + + // msg buffer flags +#define MFL_ZEROCOPY 0x01 // the message is an allocated zero copy message and can be sent. +#define MFL_NOALLOC 0x02 // send should NOT allocate a new buffer before returning +#define MFL_ADDSRC 0x04 // source must be added on send +#define MFL_RAW 0x08 // message is 'raw' and not from an RMr based sender (no header) + +#define MAX_EP_GROUP 32 // max number of endpoints in a group +#define MAX_RTG_MSG_SZ 2048 // max expected message size from route generator + +//#define DEF_RTG_MSGID "" // default to pick up all messages from rtg +#define DEF_RTG_PORT "tcp:4561" // default port that we accept rtg connections on +#define DEF_COMM_PORT "tcp:4560" // default port we use for normal communications + +/* + Message header; interpreted by the other side, but never seen by + the user application. + + DANGER: Add new fields AT THE END of the struct. Adding them any where else + will break any code that is currently running. +*/ +typedef struct { + int32_t mtype; // message type ("long" network integer) + int32_t plen; // payload length + int32_t rmr_ver; // our internal message version number + unsigned char xid[RMR_MAX_XID]; // space for user transaction id or somesuch + unsigned char sid[RMR_MAX_SID]; // sender ID for return to sender needs + unsigned char src[RMR_MAX_SRC]; // name of the sender (source) + unsigned char meid[RMR_MAX_MEID]; // managed element id. + struct timespec ts; // timestamp ??? +} uta_mhdr_t; + +/* + Round robin group. +*/ +typedef struct { + int ep_idx; // next endpoint to send to + int nused; // number of endpoints in the list + int nendpts; // number allocated + endpoint_t **epts; // the list of endpoints that we RR over +} rrgroup_t; + +/* + Routing table entry. This is a list of endpoints that can be sent + messages of the given mtype. If there is more than one, we will + round robin messags across the list. +*/ +typedef struct { + int mtype; // the message type for this list + int nrrgroups; // number of rr groups to send to + rrgroup_t** rrgroups; // one or more set of endpoints to round robin messages to +} rtable_ent_t; + +/* + The route table. +*/ +typedef struct { + void* hash; // hash table. +} route_table_t; + +/* + A wormhole is a direct connection between two endpoints that the user app can + send to without message type based routing. +*/ +typedef struct { + int nalloc; // number of ep pointers allocated + endpoint_t** eps; // end points directly referenced +} wh_mgt_t; + + +/* + This manages an array of pointers to IP addresses that are associated with one of our interfaces. + For now, we don't need to map the addr to a specific interface, just know that it is one of ours. +*/ +typedef struct { + char** addrs; // all ip addresses we found + int naddrs; // num actually used +} if_addrs_t; + + + +// --------------- ring things ------------------------------------------------- +typedef struct ring { + uint16_t head; // index of the head of the ring (insert point) + uint16_t tail; // index of the tail (extract point) + uint16_t nelements; // number of elements in the ring + void** data; // the ring data (pointers to blobs of stuff) +} ring_t; + + + +// -------------- common static prototypes -------------------------------------- + +//---- tools ---------------------------------- +static int has_myip( char const* buf, if_addrs_t* list, char sep, int max ); +static int uta_tokenise( char* buf, char** tokens, int max, char sep ); +static char* uta_h2ip( char const* hname ); +static int uta_lookup_rtg( uta_ctx_t* ctx ); +static int uta_has_str( char const* buf, char const* str, char sep, int max ); + +// --- message ring -------------------------- +static void* uta_mk_ring( int size ); +static void uta_ring_free( void* vr ); +static inline void* uta_ring_extract( void* vr ); +static inline int uta_ring_insert( void* vr, void* new_data ); + +// --- message and context management -------- +static int ie_test( void* r, int i_factor, long inserts ); + + +// ----- route table generic static things --------- +static void collect_things( void* st, void* entry, char const* name, void* thing, void* vthing_list ); +static void del_rte( void* st, void* entry, char const* name, void* thing, void* data ); +static char* uta_fib( char* fname ); +static route_table_t* uta_rt_init( ); +static route_table_t* uta_rt_clone( route_table_t* srt ); +static void uta_rt_drop( route_table_t* rt ); +static endpoint_t* uta_add_ep( route_table_t* rt, rtable_ent_t* rte, char* ep_name, int group ); +static rtable_ent_t* uta_add_rte( route_table_t* rt, int mtype, int nrrgroups ); +static endpoint_t* uta_get_ep( route_table_t* rt, char const* ep_name ); +static void read_static_rt( uta_ctx_t* ctx, int vlevel ); +static void parse_rt_rec( uta_ctx_t* ctx, char* buf, int vlevel ); +static void* rtc( void* vctx ); +static endpoint_t* rt_ensure_ep( route_table_t* rt, char const* ep_name ); + +#endif diff --git a/src/common/include/rmr_symtab.h b/src/common/include/rmr_symtab.h new file mode 100644 index 0000000..95fa153 --- /dev/null +++ b/src/common/include/rmr_symtab.h @@ -0,0 +1,52 @@ +// : vi ts=4 sw=4 noet : +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + +/* + Mnemonic: rmr_symtab.h + Abstract: Header file for the symbol table. + Date: 4 February 2019 + Author: E. Scott Daniels +*/ + +#ifndef _rmr_symtab_h +#define _rmr_symtab_h + +/* --------- symtab ---------------- */ +#define UT_FL_NOCOPY 0x00 /* use user pointer */ +#define UT_FL_COPY 0x01 /* make a copy of the string data */ +#define UT_FL_FREE 0x02 /* free val when deleting */ + + +/* ------------ symtab ----------------------------- */ +extern void rmr_sym_clear( void *s ); +extern void rmr_sym_dump( void *s ); +extern void *rmr_sym_alloc( int size ); +extern void rmr_sym_del( void *s, const char *name, unsigned int class ); +extern void *rmr_sym_ndel( void *vtable, int key ); +extern void rmr_sym_free( void *vtable ); +extern void *rmr_sym_get( void *s, const char *name, unsigned int class ); +extern int rmr_sym_put( void *s, const char *name, unsigned int class, void *val ); +extern int rmr_sym_map( void *s, unsigned int key, void *val ); +extern void *rmr_sym_pull( void *vtable, int key ); +extern void rmr_sym_stats( void *s, int level ); +extern void rmr_sym_foreach_class( void *vst, unsigned int class, void (* user_fun)( void*, void*, const char*, void*, void* ), void *user_data ); + + +#endif diff --git a/src/common/src/README b/src/common/src/README new file mode 100644 index 0000000..dd04864 --- /dev/null +++ b/src/common/src/README @@ -0,0 +1,14 @@ + +The common modules are mostly static functions which are included +directly by the main API modules (rmr.c and rmr_nng.c). The +static approach is to isolate them from the user application. Code +is reused as though it were linked from a library, but is only +available to our code. + +Wormholes was designed to be compiled on its own and included +in the library as it contains the wormhole API functions, however +Cmake seems unable to generate (easily) two, differently named, +object modules from the same source, so we resort to including +it directly as though it were a static module. The symtab code +is a standalone compile, and is also available to the user +application. diff --git a/src/common/src/mbuf_api.c b/src/common/src/mbuf_api.c new file mode 100644 index 0000000..930d74d --- /dev/null +++ b/src/common/src/mbuf_api.c @@ -0,0 +1,217 @@ +// : vi ts=4 sw=4 noet : +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + +/* + Mnemonic: mbuf_api.c + Abstract: These are common functions which work only on the mbuf and + thus (because they do not touch an endpoint or context) + can be agnostic to the underlying transport. + + Author: E. Scott Daniels + Date: 8 February 2019 +*/ + +#include +#include // uint* types +#include +#include +#include + +#include "rmr.h" // things the users see +#include "rmr_agnostic.h" // agnostic things (must be included before private) + + +// ---------- some wrappers need explicit copy-in functions, also header field setters ----- + +/* + Allow the user programme to set the meid in the header. The meid is a fixed + length buffer in the header and thus we must ensure it is not overrun. If + the user gives a source buffer that is too large, we truncate. The return + value is the number of bytes copied, or -1 for absolute failure (bad pointer + etc.). Errno is set: + EINVAL id poitner, buf or buf header are bad. + EOVERFLOW if the bytes given would have overrun + +*/ +extern int rmr_bytes2meid( rmr_mbuf_t* mbuf, unsigned char const* src, int len ) { + uta_mhdr_t* hdr; + + if( src == NULL || mbuf == NULL || mbuf->header == NULL ) { + errno = EINVAL; + return -1; + } + + errno = 0; + if( len > RMR_MAX_MEID ) { + len = RMR_MAX_MEID; + errno = EOVERFLOW; + } + + hdr = (uta_mhdr_t *) mbuf->header; + memcpy( hdr->meid, src, len ); + + return len; +} + +/* + Allows the user programme to set the meid from a string. The end of string + (nil) will be included UNLESS the total length including the end of string + would exceed the size of the space in the header for the meid. The return + value is RMR_OK for success and !RMR_OK on failure. Errno will be set + on error. +*/ +extern int rmr_str2meid( rmr_mbuf_t* mbuf, unsigned char const* str ) { + int len; // len moved -- we do validate + + if( str == NULL || mbuf == NULL || mbuf->header == NULL ) { + errno = EINVAL; + return RMR_ERR_BADARG; + } + + errno = 0; + if( (len = strlen( (char *) str )) > RMR_MAX_MEID-1 ) { + errno = EOVERFLOW; + return RMR_ERR_OVERFLOW; + } + + rmr_bytes2meid( mbuf, str, len+1 ); + return RMR_OK; +} + + + +/* + This will copy n bytes from source into the payload. If len is larger than + the payload only the bytes which will fit are copied, The user should + check errno on return to determine success or failure. +*/ +extern void rmr_bytes2payload( rmr_mbuf_t* mbuf, unsigned char const* src, int len ) { + if( src == NULL || mbuf == NULL || mbuf->payload == NULL ) { + errno = EINVAL; + return; + } + + errno = 0; + mbuf->state = RMR_OK; + if( len > mbuf->alloc_len - sizeof( uta_mhdr_t ) ) { + mbuf->state = RMR_ERR_OVERFLOW; + errno = EMSGSIZE; + len = mbuf->alloc_len - sizeof( uta_mhdr_t ); + } + + mbuf->len = len; + memcpy( mbuf->payload, src, len ); +} + +/* + This will copy a nil terminated string to the mbuf payload. The buffer length + is set to the string length. +*/ +extern void rmr_str2payload( rmr_mbuf_t* mbuf, unsigned char const* str ) { + rmr_bytes2payload( mbuf, str, strlen( (char *) str ) + 1 ); +} + + +/* + Allow the user programme to set the xaction field in the header. The xaction + is a fixed length buffer in the header and thus we must ensure it is not overrun. + If the user gives a source buffer that is too large, we truncate. The return + value is the number of bytes copied, or -1 for absolute failure (bad pointer + etc.). Errno is set: + EINVAL id poitner, buf or buf header are bad. + EOVERFLOW if the bytes given would have overrun + +*/ +extern int rmr_bytes2xact( rmr_mbuf_t* mbuf, unsigned char const* src, int len ) { + uta_mhdr_t* hdr; + + if( src == NULL || mbuf == NULL || mbuf->header == NULL ) { + errno = EINVAL; + return -1; + } + + errno = 0; + if( len > RMR_MAX_XID ) { + len = RMR_MAX_XID; + errno = EOVERFLOW; + } + + hdr = (uta_mhdr_t *) mbuf->header; + memcpy( hdr->xid, src, len ); + + return len; +} + + + +/* + Allows the user programme to set the xaction (xid) field from a string. The end + of string (nil) will be included UNLESS the total length including the end of string + would exceed the size of the space in the header for the xaction. The return + value is RMR_OK for success and !RMR_OK on failure. Errno will be set + on error. +*/ +extern int rmr_str2xact( rmr_mbuf_t* mbuf, unsigned char const* str ) { + int len; // len moved -- we do validate + + if( str == NULL || mbuf == NULL || mbuf->header == NULL ) { + errno = EINVAL; + return RMR_ERR_BADARG; + } + + errno = 0; + if( (len = strlen( (char *) str )) > RMR_MAX_XID-1 ) { + errno = EOVERFLOW; + return RMR_ERR_OVERFLOW; + } + + rmr_bytes2xact( mbuf, str, len+1 ); + return RMR_OK; +} + +/* + Extracts the meid (managed equipment) from the header and copies the bytes + to the user supplied area. If the user supplied pointer is nil, then + a buffer will be allocated and it is the user's responsibilty to free. + A pointer is returned to the destination memory (allocated or not) + for consistency. If the user programme supplies a destination it is + the responsibility of the programme to ensure that the space is large + enough. +*/ +extern unsigned char* rmr_get_meid( rmr_mbuf_t* mbuf, unsigned char* dest ) { + uta_mhdr_t* hdr; + + if( mbuf == NULL || mbuf->header == NULL ) { + errno = EINVAL; + return NULL; + } + + if( ! dest ) { + if( (dest = (unsigned char *) malloc( sizeof( unsigned char ) * RMR_MAX_MEID )) == NULL ) { + errno = ENOMEM; + return NULL; + } + } + + hdr = (uta_mhdr_t *) mbuf->header; + memcpy( dest, hdr->meid, RMR_MAX_XID ); + + return dest; +} diff --git a/src/common/src/ring_static.c b/src/common/src/ring_static.c new file mode 100644 index 0000000..2d29dbd --- /dev/null +++ b/src/common/src/ring_static.c @@ -0,0 +1,143 @@ +// :vi sw=4 ts=4 noet: +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ +/* + Mnemonic: ring_static.c + Abstract: Implements a ring of information (probably to act as a + message queue). + Author: E. Scott Daniels + Date: 31 August 2017 +*/ + +#ifndef _ring_static_c +#define _ring_static_c + +#include +#include +#include +#include +#include +#include +#include + +#define RING_FAST 1 // when set we skip nil pointer checks on the ring pointer + +/* + Make a new ring. +*/ +static void* uta_mk_ring( int size ) { + ring_t* r; + uint16_t max; + + if( size <= 0 || (r = (ring_t *) malloc( sizeof( *r ) )) == NULL ) { + return NULL; + } + + r->head = r->tail = 0; + + max = (r->head - 1); + if( size >= max ) { + size--; + } + + r->nelements = size; // because we always have an empty element when full + if( (r->data = (void **) malloc( sizeof( void** ) * (r->nelements + 1) )) == NULL ) { + free( r ); + return NULL; + } + + memset( r->data, 0, sizeof( void** ) * r->nelements ); + return (void *) r; +} + +/* + Ditch the ring. The caller is responsible for extracting any remaining + pointers and freeing them as needed. +*/ +static void uta_ring_free( void* vr ) { + ring_t* r; + + if( (r = (ring_t*) vr) == NULL ) { + return; + } + + free( r ); +} + + +/* + Pull the next data pointer from the ring; null if there isn't + anything to be pulled. +*/ +static inline void* uta_ring_extract( void* vr ) { + ring_t* r; + uint16_t ti; // real index in data + + if( !RING_FAST ) { // compiler should drop the conditional when always false + if( (r = (ring_t*) vr) == NULL ) { + return 0; + } + } else { + r = (ring_t*) vr; + } + + if( r->tail == r->head ) { // empty ring + return NULL; + } + + ti = r->tail; + r->tail++; + if( r->tail >= r->nelements ) { + r->tail = 0; + } + + return r->data[ti]; +} + +/* + Insert the pointer at the next open space in the ring. + Returns 1 if the inert was ok, and 0 if the ring is full. +*/ +static inline int uta_ring_insert( void* vr, void* new_data ) { + ring_t* r; + + if( !RING_FAST ) { // compiler should drop the conditional when always false + if( (r = (ring_t*) vr) == NULL ) { + return 0; + } + } else { + r = (ring_t*) vr; + } + + if( r->head+1 == r->tail || (r->head+1 >= r->nelements && !r->tail) ) { // ring is full + return 0; + } + + r->data[r->head] = new_data; + r->head++; + if( r->head >= r->nelements ) { + r->head = 0; + } + + return 1; +} + + + +#endif diff --git a/src/common/src/rt_generic_static.c b/src/common/src/rt_generic_static.c new file mode 100644 index 0000000..26e907e --- /dev/null +++ b/src/common/src/rt_generic_static.c @@ -0,0 +1,466 @@ +// :vi sw=4 ts=4 noet: +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + +/* + Mnemonic: rt_generic_static.c + Abstract: These are route table functions which are not specific to the + underlying protocol. rtable_static, and rtable_nng_static + have transport provider specific code. + + This file must be included before the nng/nano specific file as + it defines types. + + Author: E. Scott Daniels + Date: 5 February 2019 +*/ + +#ifndef rt_generic_static_c +#define rt_generic_static_c + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + Passed to a symtab foreach callback to construct a list of pointers from + a current symtab. +*/ +typedef struct thing_list { + int nalloc; + int nused; + void** things; +} thing_list_t; + + +/* + Given a message type create a route table entry and add to the hash keyed on the + message type. Once in the hash, endpoints can be added with uta_add_ep. Size + is the number of group slots to allocate in the entry. +*/ +static rtable_ent_t* uta_add_rte( route_table_t* rt, int mtype, int nrrgroups ) { + rtable_ent_t* rte; + + if( rt == NULL ) { + return NULL; + } + + if( (rte = (rtable_ent_t *) malloc( sizeof( *rte ) )) == NULL ) { + fprintf( stderr, "rmr_add_rte: malloc failed for entry\n" ); + return NULL; + } + memset( rte, 0, sizeof( *rte ) ); + + if( nrrgroups <= 0 ) { + nrrgroups = 10; + } + + if( (rte->rrgroups = (rrgroup_t **) malloc( sizeof( rrgroup_t * ) * nrrgroups )) == NULL ) { + fprintf( stderr, "rmr_add_rte: malloc failed for rrgroup array\n" ); + free( rte ); + return NULL; + } + memset( rte->rrgroups, 0, sizeof( rrgroup_t *) * nrrgroups ); + rte->nrrgroups = nrrgroups; + + rmr_sym_map( rt->hash, mtype, rte ); // add to hash using numeric mtype as key + + if( DEBUG ) fprintf( stderr, "[DBUG] route table entry created: mt=%d groups=%d\n", mtype, nrrgroups ); + return rte; +} + +/* + Parse a single record recevied from the route table generator, or read + from a static route table file. Start records cause a new table to + be started (if a partial table was received it is discarded. Table + entry records are added to the currenly 'in progress' table, and an + end record causes the in progress table to be finalised and the + currently active table is replaced. +*/ +static void parse_rt_rec( uta_ctx_t* ctx, char* buf, int vlevel ) { + int i; + int ntoks; // number of tokens found in something + int ngtoks; + int grp; // group number + rtable_ent_t* rte; // route table entry added + char* tokens[128]; + char* gtokens[64]; // groups + char* tok; // pointer into a token or string + + if( ! buf ) { + return; + } + + while( *buf && isspace( *buf ) ) { // skip leading whitespace + buf++; + } + for( tok = buf + (strlen( buf ) - 1); tok > buf && isspace( *tok ); tok-- ); // trim trailing spaces too + *(tok+1) = 0; + + if( (ntoks = uta_tokenise( buf, tokens, 128, '|' )) > 0 ) { + switch( *(tokens[0]) ) { + case 0: // ignore blanks + // fallthrough + case '#': // and comment lines + break; + + case 'n': // newrt|{start|end} + if( strcmp( tokens[1], "end" ) == 0 ) { // wrap up the table we were building + if( ctx->new_rtable ) { + uta_rt_drop( ctx->old_rtable ); // time to drop one that was previously replaced + ctx->old_rtable = ctx->rtable; // currently active becomes old and allowed to 'drain' + ctx->rtable = ctx->new_rtable; // one we've been adding to becomes active + ctx->new_rtable = NULL; + if( DEBUG > 1 || (vlevel > 1) ) fprintf( stderr, "[DBUG] end of route table noticed\n" ); + } else { + if( DEBUG > 1 ) fprintf( stderr, "[DBUG] end of route table noticed, but one was not started!\n" ); + ctx->new_rtable = NULL; + } + } else { // start a new table. + if( ctx->new_rtable != NULL ) { // one in progress? this forces it out + if( DEBUG > 1 || (vlevel > 1) ) fprintf( stderr, "[DBUG] new table; dropping incomplete table\n" ); + uta_rt_drop( ctx->new_rtable ); + } + + if( ctx->rtable ) { + ctx->new_rtable = uta_rt_clone( ctx->rtable ); // create by cloning endpoint entries from active table + } else { + ctx->new_rtable = uta_rt_init( ); // don't have one yet, just crate empty + } + if( DEBUG > 1 || (vlevel > 1) ) fprintf( stderr, "[DBUG] start of route table noticed\n" ); + } + break; + + case 'r': // assume rt entry + if( ! ctx->new_rtable ) { // bad sequence, or malloc issue earlier; ignore siliently + break; + } + + if( ((tok = strchr( tokens[1], ',' )) == NULL ) || // no sender names + (uta_has_str( tokens[1], ctx->my_name, ',', 127) >= 0) || // our name isn't in the list + has_myip( tokens[1], ctx->ip_list, ',', 127 ) ) { // the list has one of our IP addresses + + if( DEBUG > 1 || (vlevel > 1) ) fprintf( stderr, "[DBUG] create rte for mtype=%s\n", tokens[1] ); + + if( (ngtoks = uta_tokenise( tokens[2], gtokens, 64, ';' )) > 0 ) { // split last field by groups first + rte = uta_add_rte( ctx->new_rtable, atoi( tokens[1] ), ngtoks ); // get/create entry for message type + for( grp = 0; grp < ngtoks; grp++ ) { + if( (ntoks = uta_tokenise( gtokens[grp], tokens, 64, ',' )) > 0 ) { + for( i = 0; i < ntoks; i++ ) { + if( DEBUG > 1 || (vlevel > 1)) fprintf( stderr, "[DBUG] add endpoint %s\n", tokens[i] ); + uta_add_ep( ctx->new_rtable, rte, tokens[i], grp ); + } + } + } + } + } else { + if( DEBUG || (vlevel > 2) ) + fprintf( stderr, "entry not included, sender not matched: %s\n", tokens[1] ); + } + + break; + + default: + if( DEBUG ) fprintf( stderr, "[WRN] rmr_rtc: unrecognised request: %s\n", tokens[0] ); + break; + } + } +} + +/* + This function attempts to open a static route table in order to create a 'seed' + table during initialisation. The environment variable RMR_SEED_RT is expected + to contain the necessary path to the file. If missing, or if the file is empty, + no route table will be available until one is received from the generator. + + This function is probably most useful for testing situations, or extreme + cases where the routes are static. +*/ +static void read_static_rt( uta_ctx_t* ctx, int vlevel ) { + int i; + char* fname; + char* fbuf; // buffer with file contents + char* rec; // start of the record + char* eor; // end of the record + int rcount = 0; // record count for debug + + if( (fname = getenv( ENV_SEED_RT )) == NULL ) { + return; + } + + if( (fbuf = uta_fib( fname ) ) == NULL ) { // read file into a single buffer + fprintf( stderr, "[WRN] seed route table could not be opened: %s: %s\n", fname, strerror( errno ) ); + return; + } + + if( DEBUG ) fprintf( stderr, "[INFO] seed route table successfully opened: %s\n", fname ); + for( rec = fbuf; rec && *rec; rec = eor+1 ) { + rcount++; + if( (eor = strchr( rec, '\n' )) != NULL ) { + *eor = 0; + } + + parse_rt_rec( ctx, rec, vlevel ); + } + + if( DEBUG ) fprintf( stderr, "[INFO] seed route table successfully parsed: %d records\n", rcount ); + free( fbuf ); +} + +/* + Callback driven for each named thing in a symtab. We collect the pointers to those + things for later use (cloning). +*/ +static void collect_things( void* st, void* entry, char const* name, void* thing, void* vthing_list ) { + thing_list_t* tl; + + if( (tl = (thing_list_t *) vthing_list) == NULL ) { + return; + } + + if( thing == NULL ) { + return; + } + + tl->things[tl->nused++] = thing; // save a reference to the thing +} + +/* + Called to delete a route table entry struct. We delete the array of endpoint + pointers, but NOT the endpoints referenced as those are referenced from + multiple entries. +*/ +static void del_rte( void* st, void* entry, char const* name, void* thing, void* data ) { + rtable_ent_t* rte; + int i; + + if( (rte = (rtable_ent_t *) thing) == NULL ) { + return; + } + + if( rte->rrgroups ) { // clean up the round robin groups + for( i = 0; i < rte->nrrgroups; i++ ) { + if( rte->rrgroups[i] ) { + free( rte->rrgroups[i]->epts ); // ditch list of endpoint pointers (end points are reused; don't trash them) + } + } + + free( rte->rrgroups ); + } + + free( rte ); // finally, drop the potato +} + +/* + Read an entire file into a buffer. We assume for route table files + they will be smallish and so this won't be a problem. + Returns a pointer to the buffer, or nil. Caller must free. + Terminates the buffer with a nil character for string processing. + + If we cannot stat the file, we assume it's empty or missing and return + an empty buffer, as opposed to a nil, so the caller can generate defaults + or error if an empty/missing file isn't tolerated. +*/ +static char* uta_fib( char* fname ) { + struct stat stats; + off_t fsize = 8192; // size of the file + off_t nread; // number of bytes read + int fd; + char* buf; // input buffer + + if( (fd = open( fname, O_RDONLY )) >= 0 ) { + if( fstat( fd, &stats ) >= 0 ) { + if( stats.st_size <= 0 ) { // empty file + close( fd ); + fd = -1; + } else { + fsize = stats.st_size; // stat ok, save the file size + } + } else { + fsize = 8192; // stat failed, we'll leave the file open and try to read a default max of 8k + } + } + + if( fd < 0 ) { // didn't open or empty + if( (buf = (char *) malloc( sizeof( char ) * 1 )) == NULL ) { + return NULL; + } + + *buf = 0; + return buf; + } + + // add a size limit check here + + if( (buf = (char *) malloc( sizeof( char ) * fsize + 2 )) == NULL ) { // enough to add nil char to make string + close( fd ); + errno = ENOMEM; + return NULL; + } + + nread = read( fd, buf, fsize ); + if( nread < 0 || nread > fsize ) { // failure of some kind + free( buf ); + errno = EFBIG; // likely too much to handle + close( fd ); + return NULL; + } + + buf[nread] = 0; + + close( fd ); + return buf; +} + +/* + Create and initialise a route table; Returns a pointer to the table struct. +*/ +static route_table_t* uta_rt_init( ) { + route_table_t* rt; + + if( (rt = (route_table_t *) malloc( sizeof( route_table_t ) )) == NULL ) { + return NULL; + } + + if( (rt->hash = rmr_sym_alloc( 509 )) == NULL ) { // modest size, prime + free( rt ); + return NULL; + } + + return rt; +} + +/* + Clone (sort of) an existing route table. This is done to preserve the endpoint + names referenced in a table (and thus existing sessions) when a new set + of message type to endpoint name mappings is received. A new route table + with only endpoint name references is returned based on the active table in + the context. +*/ +static route_table_t* uta_rt_clone( route_table_t* srt ) { + endpoint_t* ep; // an endpoint + route_table_t* nrt; // new route table + route_table_t* art; // active route table + void* sst; // source symtab + void* nst; // new symtab + thing_list_t things; + int i; + + if( srt == NULL ) { + return NULL; + } + + if( (nrt = (route_table_t *) malloc( sizeof( *nrt ) )) == NULL ) { + return NULL; + } + + if( (nrt->hash = rmr_sym_alloc( 509 )) == NULL ) { // modest size, prime + free( nrt ); + return NULL; + } + + things.nalloc = 2048; + things.nused = 0; + things.things = (void **) malloc( sizeof( void * ) * things.nalloc ); + if( things.things == NULL ) { + free( nrt->hash ); + free( nrt ); + return NULL; + } + + sst = srt->hash; // convenience pointers (src symtab) + nst = nrt->hash; + + rmr_sym_foreach_class( sst, 1, collect_things, &things ); // collect the named endpoints in the active table + + for( i = 0; i < things.nused; i++ ) { + ep = (endpoint_t *) things.things[i]; + rmr_sym_put( nst, ep->name, 1, ep ); // slam this one into the new table + } + + free( things.things ); + return nrt; +} + +/* + Given a name, find the endpoint struct in the provided route table. +*/ +static endpoint_t* uta_get_ep( route_table_t* rt, char const* ep_name ) { + + if( rt == NULL || rt->hash == NULL || ep_name == NULL || *ep_name == 0 ) { + return NULL; + } + + return rmr_sym_get( rt->hash, ep_name, 1 ); +} + +/* + Drop the given route table. Purge all type 0 entries, then drop the symtab itself. +*/ +static void uta_rt_drop( route_table_t* rt ) { + if( rt == NULL ) { + return; + } + + rmr_sym_foreach_class( rt->hash, 0, del_rte, NULL ); // free each rte referenced by the hash, but NOT the endpoints + rmr_sym_free( rt->hash ); // free all of the hash related data + free( rt ); +} + +/* + Look up and return the pointer to the endpoint stuct matching the given name. + If not in the hash, a new endpoint is created, added to the hash. Should always + return a pointer. +*/ +static endpoint_t* rt_ensure_ep( route_table_t* rt, char const* ep_name ) { + endpoint_t* ep; + + if( !rt || !ep_name || ! *ep_name ) { + fprintf( stderr, "[WARN] rmr: rt_ensure: internal mishap, something undefined rt=%p ep_name=%p\n", rt, ep_name ); + errno = EINVAL; + return NULL; + } + + if( (ep = uta_get_ep( rt, ep_name )) == NULL ) { // not there yet, make + if( (ep = (endpoint_t *) malloc( sizeof( *ep ) )) == NULL ) { + fprintf( stderr, "[WARN] rmr: rt_ensure: malloc failed for endpoint creation: %s\n", ep_name ); + errno = ENOMEM; + return NULL; + } + + ep->open = 0; // not connected + ep->addr = uta_h2ip( ep_name ); + ep->name = strdup( ep_name ); + + rmr_sym_put( rt->hash, ep_name, 1, ep ); + } + + return ep; +} + + +#endif diff --git a/src/common/src/rtc_static.c b/src/common/src/rtc_static.c new file mode 100644 index 0000000..6b9c707 --- /dev/null +++ b/src/common/src/rtc_static.c @@ -0,0 +1,223 @@ +// : vi ts=4 sw=4 noet : +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + +/* + Mnemonic: rt_collector.c + Abstract: The route table collector is started as a separate pthread and + is responsible for listening for route table updates from a + route manager or route table generator process. + + Author: E. Scott Daniels + Date: 29 November 2018 (extracted to common 13 March 2019) +*/ + +#ifndef _rt_collector_c +#define _rt_collector_c + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + Route Table Collector + A side thread which opens a socket and subscribes to a routing table generator. + It may do other things along the way (latency measurements?). + + The pointer is a pointer to the context. + + Listens for records from the route table generation publisher, expecting + one of the following, newline terminated, ASCII records: + rte|msg-type||]name:port,name:port,...;name:port,... // route table entry with one or more groups of endpoints + new|start // start of new table + new|end // end of new table; complete + + Name must be a host name which can be looked up via gethostbyname() (DNS). + + Multiple endpoints (name:port) may be given separated by a comma; an endpoint is selected using round robin + for each message of the type that is sent. + + Multiple endpoint groups can be given as a comma separated list of endpoints, separated by semicolons: + group1n1:port,group1n2:port,group1n3:port;group2n1:port,group2n2:port + + If multiple groups are given, when send() is called for the cooresponding message type, + the message will be sent to one endpoint in each group. + + msg-type is the numeric message type (e.g. 102). If it is given as n,name then it is assumed + that the entry applies only to the instance running with the hostname 'name.' + + Buffers received from the route table generator can contain multiple newline terminated + records, but each buffer must be less than 4K in length, and the last record in a + buffere may NOT be split across buffers. + +*/ +static void* rtc( void* vctx ) { + uta_ctx_t* ctx; // context user has -- where we pin the route table + uta_ctx_t* pvt_cx; // private context for session with rtg + rmr_mbuf_t* msg = NULL; // message from rtg + char* payload; // payload in the message + size_t mlen; + size_t clen; // length to copy and mark + char* port; // a port number we listen/connect to + char* fport; // pointer to the real buffer to free + size_t buf_size; // nng needs var pointer not just size? + char* nextr; // pointer at next record in the message + char* curr; // current record + int i; + long blabber = 0; // time of last blabber so we don't flood if rtg goes bad + int cstate = -1; // connection state to rtg + int state; // processing state of some nng function + char* tokens[128]; + char wbuf[128]; + char* pbuf; + int pbuf_size = 0; // number allocated in pbuf + int ntoks; + int raw_interface = 1; // rtg is using raw NNG/Nano not RMr to send updates + int vfd = -1; // verbose file des if we have one + int vlevel = 0; // how chatty we should be 0== no nattering allowed + char* eptr; + + if( (ctx = (uta_ctx_t *) vctx) == NULL ) { + fprintf( stderr, "[CRI] rmr_rtc: internal mishap: context passed in was nil\n" ); + return NULL; + } + + if( (eptr = getenv( ENV_VERBOSE_FILE )) != NULL ) { + vfd = open( eptr, O_RDONLY ); + if( vfd >= 0 ) { + wbuf[0] = 0; + lseek( vfd, 0, 0 ); + read( vfd, wbuf, 10 ); + vlevel = atoi( wbuf ); + } + } + + read_static_rt( ctx, vlevel ); // seed the route table if one provided + + if( (port = getenv( ENV_RTG_PORT )) == NULL || ! *port ) { // port we need to open to listen for RTG connections + port = strdup( DEF_RTG_PORT ); + } else { + port = strdup( port ); + } + + if( (curr = getenv( ENV_RTG_RAW )) != NULL ) { + raw_interface = atoi( curr ) > 0; // if > 0 we assume that rtg messages are NOT coming from an RMr based process + } + + fport = port; // must hold to free + + ntoks = uta_tokenise( port, tokens, 120, ':' ); // assume tcp:port, but it could be port or old style host:port + switch( ntoks ) { + case 1: + port = tokens[0]; // just the port + break; + + case 2: + port = tokens[1]; // tcp:port or :port + break; + + default: + port = DEF_RTG_PORT; // this shouldn't happen, but parnioia is good + break; + } + + if( (pvt_cx = init( port, MAX_RTG_MSG_SZ, FL_NOTHREAD )) == NULL ) { // open a private context + fprintf( stderr, "[CRI] rmr_rtc: unable to initialise listen port for RTG (pvt_cx)\n" ); + free( fport ); + return NULL; + } + + if( DEBUG ) fprintf( stderr, "[DBUG] rtc thread is running and listening; listening for rtg conns on %s\n", port ); + free( fport ); + + // future: if we need to register with the rtg, then build a message and send it through a wormhole here + + blabber = 0; + while( 1 ) { // until the cows return, pigs fly, or somesuch event likely not to happen + if( raw_interface ) { + msg = (rmr_mbuf_t *) rcv_payload( pvt_cx, msg ); // receive from non-RMr sender + } else { + msg = rmr_rcv_msg( pvt_cx, msg ); // receive from an RMr sender + } + + if( vfd >= 0 ) { // if changed since last go round + wbuf[0] = 0; + lseek( vfd, 0, 0 ); + read( vfd, wbuf, 10 ); + vlevel = atoi( wbuf ); + } + + if( msg != NULL && msg->len > 0 ) { + payload = msg->payload; + mlen = msg->len; // usable bytes in the payload + if( vlevel > 1 ) { + fprintf( stderr, "[DBUG] rmr_rtc: received rt message; %d bytes (%s)\n", (int) mlen, msg->payload ); + } else { + if( DEBUG > 1 || (vlevel > 0) ) fprintf( stderr, "[DBUG] rmr_rtc: received rt message; %d bytes\n", (int) mlen ); + } + + if( pbuf_size <= mlen ) { + if( pbuf ) { + free( pbuf ); + } + pbuf = (char *) malloc( sizeof( char ) * mlen *2 ); + pbuf_size = mlen * 2; + } + memcpy( pbuf, payload, mlen ); + pbuf[mlen] = 0; // don't depend on sender making this a legit string + + curr = pbuf; + while( curr ) { // loop over each record in the buffer + nextr = strchr( curr, '\n' ); // allow multiple newline records, find end of current and mark + + if( nextr ) { + *(nextr++) = 0; + } + + if( vlevel > 1 ) { + fprintf( stderr, "[DBUG] rmr_rtc: processing (%s)\n", curr ); + } + parse_rt_rec( ctx, curr, vlevel ); // parse record and add to in progress table + + curr = nextr; + } + + if( ctx->shutdown ) { // mostly for testing, but allows user app to close us down if rmr_*() function sets this + break; + } + } else { + if( time( NULL ) > blabber ) { + fprintf( stderr, "[WRN] rmr_rtc: nil buffer, or 0 len msg, received from rtg\n" ); + blabber = time( NULL ) + 180; // limit to 1 every 3 min or so + } + } + } + + return NULL; // unreachable, but some compilers don't see that and complain. +} + + +#endif diff --git a/src/common/src/symtab.c b/src/common/src/symtab.c new file mode 100644 index 0000000..b780d9e --- /dev/null +++ b/src/common/src/symtab.c @@ -0,0 +1,470 @@ +// : vi ts=4 sw=4 noet : +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + +/* +------------------------------------------------------------------------------ +Mnemonic: symtab.c +Abstract: Symbol table -- slightly streamlined from it's original 2000 version + (a part of the {X}fm opensource code), though we must retain the + original copyright. + + Things changed for the Ric Msg implemention (Nov 2018): + - no concept of copy/free of the user data (functions removed) + - add ability to support an integer key (class 0) + - externally visible names given a rmr_ extension as it's being + incorporated into the RIC msg routing library and will be + available to user applications. + +Date: 11 Feb 2000 +Author: E. Scott Daniels + +Mod: 2016 23 Feb - converted Symtab refs so that caller need only a + void pointer to use and struct does not need to be exposed. + 2018 30 Nov - Augmented from original form (see above). +------------------------------------------------------------------------------ +*/ + +#include +#include +#include +#include +#include + +#include "rmr_symtab.h" + +//----------------------------------------------------------------------------------------------- + +typedef struct Sym_ele +{ + struct Sym_ele *next; /* pointer at next element in list */ + struct Sym_ele *prev; /* larger table, easier deletes */ + const char *name; /* symbol name */ + unsigned int nkey; // the numeric key + void *val; /* user data associated with name */ + unsigned long mcount; /* modificaitons to value */ + unsigned long rcount; /* references to symbol */ + //unsigned int flags; + unsigned int class; /* helps divide things up and allows for duplicate names */ +} Sym_ele; + +typedef struct Sym_tab { + Sym_ele **symlist; /* pointer to list of element pointerss */ + long inhabitants; /* number of active residents */ + long deaths; /* number of deletes */ + long size; +} Sym_tab; + +// -------------------- internal ------------------------------------------------------------------ + +static int sym_hash( const char *n, long size ) +{ + const char *p; + long t = 0; + unsigned long tt = 0; + unsigned long x = 79; + + for( p = n; *p; p++ ) /* a bit of magic */ + t = (t * 79 ) + *p; + + if( t < 0 ) + t = ~t; + + return (int ) (t % size); +} + +/* delete element pointed to by eptr at hash loc hv */ +static void del_ele( Sym_tab *table, int hv, Sym_ele *eptr ) +{ + Sym_ele **sym_tab; + + sym_tab = table->symlist; + + if( eptr ) /* unchain it */ + { + if( eptr->prev ) + eptr->prev->next = eptr->next; + else + sym_tab[hv] = eptr->next; + + if( eptr->next ) + eptr->next->prev = eptr->prev; + + if( eptr->class && eptr->name ) { // class 0 entries are numeric, so name is NOT a pointer + free( (void *) eptr->name ); // and if free fails, what? panic? + } + + free( eptr ); + + table->deaths++; + table->inhabitants--; + } +} + +/* + Determine if these are the same. +*/ +static inline int same( unsigned int c1, unsigned int c2, const char *s1, const char* s2 ) +{ + if( c1 != c2 ) + return 0; /* different class - not the same */ + + if( *s1 == *s2 && strcmp( s1, s2 ) == 0 ) + return 1; + return 0; +} + +/* + Generic routine to put something into the table + called by sym_map or sym_put since the logic for each is pretty + much the same. +*/ +static int putin( Sym_tab *table, const char *name, unsigned int class, void *val ) { + Sym_ele *eptr; /* pointer into hash table */ + Sym_ele **sym_tab; /* pointer into hash table */ + int hv; /* hash value */ + int rc = 0; /* assume it existed */ + unsigned int nkey = 0; // numeric key if class == 0 + + sym_tab = table->symlist; + + if( class ) { // string key + hv = sym_hash( name, table->size ); // hash it + for( eptr=sym_tab[hv]; eptr && ! same( class, eptr->class, eptr->name, name); eptr=eptr->next ); + } else { + nkey = *((int *) name); + hv = nkey % table->size; // just hash the number + for( eptr=sym_tab[hv]; eptr && eptr->nkey != nkey; eptr=eptr->next ); + } + + if( ! eptr ) { // not found above, so add + rc++; + table->inhabitants++; + + eptr = (Sym_ele *) malloc( sizeof( Sym_ele) ); + if( ! eptr ) { + fprintf( stderr, "[FAIL] symtab/putin: out of memory\n" ); + return -1; + } + + eptr->prev = NULL; + eptr->class = class; + eptr->mcount = eptr->rcount = 0; /* init counters */ + eptr->val = NULL; + eptr->nkey = nkey; + if( class ) { + eptr->name = strdup( name ); + } else { + eptr->name = NULL; // for a numeric key, just save the value + } + eptr->next = sym_tab[hv]; // add to head of list + sym_tab[hv] = eptr; + if( eptr->next ) + eptr->next->prev = eptr; /* chain back to new one */ + } + + eptr->mcount++; + + eptr->val = val; + + return rc; +} + +// -------------------- visible ------------------------------------------------------------------ + +/* delete all elements in the table */ +extern void rmr_sym_clear( void *vtable ) +{ + Sym_tab *table; + Sym_ele **sym_tab; + int i; + + table = (Sym_tab *) vtable; + sym_tab = table->symlist; + + for( i = 0; i < table->size; i++ ) + while( sym_tab[i] ) + del_ele( table, i, sym_tab[i] ); +} + +/* + Clear and then free the whole thing. +*/ +extern void rmr_sym_free( void *vtable ) { + Sym_tab *table; + + table = (Sym_tab *) vtable; + + if( table == NULL ) + return; + + rmr_sym_clear( vtable ); + free( table->symlist ); + free( table ); +} + +extern void rmr_sym_dump( void *vtable ) +{ + Sym_tab *table; + int i; + Sym_ele *eptr; + Sym_ele **sym_tab; + + table = (Sym_tab *) vtable; + sym_tab = table->symlist; + + for( i = 0; i < table->size; i++ ) + { + if( sym_tab[i] ) + for( eptr = sym_tab[i]; eptr; eptr = eptr->next ) + { + if( eptr->val && eptr->class ) { + fprintf( stderr, "key=%s val@=%p\n", eptr->name, eptr->val ); + } else { + fprintf( stderr, "nkey=%d val@=%p\n", eptr->nkey, eptr->val ); + } + } + } +} + +/* + Allocate a table the size requested - best if size is prime. + Returns a pointer to the management block (handle) or NULL on failure. +*/ +extern void *rmr_sym_alloc( int size ) +{ + int i; + Sym_tab *table; + + if( size < 11 ) /* provide a bit of sanity */ + size = 11; + + if( (table = (Sym_tab *) malloc( sizeof( Sym_tab ))) == NULL ) + { + fprintf( stderr, "rmr_sym_alloc: unable to get memory for symtable (%d elements)", size ); + return NULL; + } + + memset( table, 0, sizeof( *table ) ); + + if((table->symlist = (Sym_ele **) malloc( sizeof( Sym_ele *) * size ))) + { + memset( table->symlist, 0, sizeof( Sym_ele *) * size ); + table->size = size; + } + else + { + fprintf( stderr, "sym_alloc: unable to get memory for %d elements", size ); + return NULL; + } + + return (void *) table; /* user might want to know what the size is */ +} + +/* + Delete an element given name/class or numeric key (class 0). +*/ +extern void rmr_sym_del( void *vtable, const char *name, unsigned int class ) +{ + Sym_tab *table; + Sym_ele **sym_tab; + Sym_ele *eptr; /* pointer into hash table */ + int hv; /* hash value */ + unsigned int nkey; // class 0, name points to integer not string + + table = (Sym_tab *) vtable; + sym_tab = table->symlist; + + if( class ) { + hv = sym_hash( name, table->size ); + for(eptr=sym_tab[hv]; eptr && ! same(class, eptr->class, eptr->name, name); eptr=eptr->next ); + } else { + nkey = *((int *) name); + hv = nkey % table->size; // just hash the number + for( eptr=sym_tab[hv]; eptr && eptr->nkey != nkey; eptr=eptr->next ); + } + + del_ele( table, hv, eptr ); /* ignors null ptr, so safe to always call */ +} + +/* + Delete element by numberic key. +*/ +extern void *rmr_sym_ndel( void *vtable, int key ) { + rmr_sym_del( vtable, (const char *) &key, 0 ); +} + + +extern void *rmr_sym_get( void *vtable, const char *name, unsigned int class ) +{ + Sym_tab *table; + Sym_ele **sym_tab; + Sym_ele *eptr; // element from table + int hv; // hash value of key + unsigned int nkey; // numeric key if class 0 + + table = (Sym_tab *) vtable; + sym_tab = table->symlist; + + if( class ) { + hv = sym_hash( name, table->size ); + for(eptr=sym_tab[hv]; eptr && ! same(class, eptr->class, eptr->name, name); eptr=eptr->next ); + } else { + nkey = *((int *) name); + hv = nkey % table->size; // just hash the number + for( eptr=sym_tab[hv]; eptr && eptr->nkey != nkey; eptr=eptr->next ); + } + + if( eptr ) + { + eptr->rcount++; + return eptr->val; + } + + return NULL; +} + +/* + Retrieve the data referenced by a numerical key. +*/ +extern void *rmr_sym_pull( void *vtable, int key ) { + return rmr_sym_get( vtable, (const char *) &key, 0 ); +} + +/* + Put an element with a string key into the table. Replaces the element + if it was already there. Class must be >0 and if not 1 will be forced. + (class 0 keys are numeric). + Returns 1 if new, 0 if existed. +*/ +extern int rmr_sym_put( void *vtable, const char *name, unsigned int class, void *val ) +{ + Sym_tab *table; + + if( class == 0 ) { + class = 1; + } + + table = (Sym_tab *) vtable; + return putin( table, name, class, val ); +} + +/* + Add a new entry assuming that the key is an unsigned integer. + + Returns 1 if new, 0 if existed +*/ +extern int rmr_sym_map( void *vtable, unsigned int key, void *val ) { + Sym_tab *table; + + table = (Sym_tab *) vtable; + return putin( table, (const char *) &key, 0, val ); +} + +/* + Dump some statistics to stderr dev. Higher level is the more info dumpped +*/ +extern void rmr_sym_stats( void *vtable, int level ) +{ + Sym_tab *table; + Sym_ele *eptr; /* pointer into the elements */ + Sym_ele **sym_tab; + int i; + int empty = 0; + long ch_count; + long max_chain = 0; + int maxi = 0; + int twoper = 0; + + table = (Sym_tab *) vtable; + sym_tab = table->symlist; + + for( i = 0; i < table->size; i++ ) + { + ch_count = 0; + if( sym_tab[i] ) + { + for( eptr = sym_tab[i]; eptr; eptr = eptr->next ) + { + ch_count++; + if( level > 3 ) { + if( eptr->class ) { // a string key + fprintf( stderr, "sym: (%d) key=%s val@=%p ref=%ld mod=%lu\n", i, eptr->name, eptr->val, eptr->rcount, eptr->mcount ); + } else { + fprintf( stderr, "sym: (%d) key=%d val@=%p ref=%ld mod=%lu\n", i, eptr->nkey, eptr->val, eptr->rcount, eptr->mcount ); + } + } + } + } + else + empty++; + + if( ch_count > max_chain ) + { + max_chain = ch_count; + maxi = i; + } + if( ch_count > 1 ) + twoper++; + + if( level > 2 ) + fprintf( stderr, "sym: (%d) chained=%ld\n", i, ch_count ); + } + + if( level > 1 ) + { + fprintf( stderr, "sym: longest chain: idx=%d has %ld elsements):\n", maxi, max_chain ); + for( eptr = sym_tab[maxi]; eptr; eptr = eptr->next ) { + if( eptr->class ) { + fprintf( stderr, "\t%s\n", eptr->name ); + } else { + fprintf( stderr, "\t%d (numeric key)\n", eptr->nkey ); + } + } + } + + fprintf( stderr, "sym:%ld(size) %ld(inhab) %ld(occupied) %ld(dead) %ld(maxch) %d(>2per)\n", + table->size, table->inhabitants, table->size - empty, table->deaths, max_chain, twoper ); +} + +/* + Drive a user callback function for each entry in a class. It is safe for + the user to delete the element as we capture a next pointer before + calling their function. +*/ +extern void rmr_sym_foreach_class( void *vst, unsigned int class, void (* user_fun)( void*, void*, const char*, void*, void* ), void *user_data ) +{ + Sym_tab *st; + Sym_ele **list; + Sym_ele *se; + Sym_ele *next; /* allows user to delete the node(s) we return */ + int i; + + st = (Sym_tab *) vst; + + if( st && (list = st->symlist) != NULL && user_fun != NULL ) + for( i = 0; i < st->size; i++ ) + for( se = list[i]; se; se = next ) /* using next allows user to delet via this */ + { + next = se->next; + if( class == se->class ) { + user_fun( st, se, se->name, se->val, user_data ); + } + } +} diff --git a/src/common/src/tools_static.c b/src/common/src/tools_static.c new file mode 100644 index 0000000..8d41b09 --- /dev/null +++ b/src/common/src/tools_static.c @@ -0,0 +1,383 @@ +// :vi sw=4 ts=4 noet: +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + +/* + Mnemonic: tools_static.c + Abstract: A small set of very simple tools to support Uta == RMR. + uta_tokenise -- simple string tokeniser + uta_h2ip -- look up host name and return an ip address + uta_lookup_rtg -- looks in env for rtg host:port + uta_has_str -- searches buffer of tokens for a string + + uta_link2 -- establish a nanomsg connection to a host + + Author: E. Scott Daniels + Date: 30 November 2018 +*/ + +#ifndef _tools_static_c +#define _tools_static_c + +#include +#include +#include +#include +#include +#include +#include +#include + +#include // these are needed to suss out ip addresses from interfaces +#include +#include +#include +#include + + +/* + Simple tokeniser. Split a null terminated string into tokens recording the + pointers in the tokens array provided. Tokens MUST be large enough. Max is + the max number of tokens to split into. Returns the actual number of tokens + recorded in the pointer array. + + CAUTION: this modifies the string passed in!! +*/ +static int uta_tokenise( char* buf, char** tokens, int max, char sep ) { + char* end; // end of token + int n = 0; + + if( !buf || ! tokens || !(*buf) ) { + return 0; + } + + tokens[n++] = buf; + end = buf; + while( n < max && *end && (end = strchr( end, sep )) != NULL ) { + *end = 0; + tokens[n++] = ++end; + } + + return n; +} + +/* + Xlate hostname (expected to be name:port) to an IP address that nano will tolerate. + We'll use the first address from the list to keep it simple. If the first character + of the name is a digit, we assume it's really an IP address and just return that. + + Return is a string which the caller must free. Even if the string passed in is already + an IP address, a duplicate will be returend so that it can always be freed. + On error a nil pointer is returned. +*/ +static char* uta_h2ip( char const* hname ) { + char buf[120]; + struct hostent* hent; + unsigned int octs[4]; + unsigned int a; + int i; + char* tok; + char* dname; // duplicated name for distruction + + dname = strdup( hname ); + + if( isdigit( *dname ) || *dname == '[' ) { // hostnames can't start with digit, or ipv6 [; assume ip address + return dname; + } + + if( (tok = strchr( dname, ':' )) != NULL ) { + *(tok++) = 0; + } + + hent = gethostbyname( dname ); + if( hent == NULL || hent->h_addr_list == NULL ) { + //fprintf( stderr, "[WARN] h2ip: dns lookup failed for: %s\n", dname ); + free( dname ); + return NULL; + } + + a = ntohl( *((unsigned int *)hent->h_addr_list[0]) ); + for( i = 3; i >= 0; i-- ) { + octs[i] = a & 0xff; + a = a >> 8; + } + + if( tok ) { // if :port was given, smash it back on + snprintf( buf, sizeof( buf ), "%d.%d.%d.%d:%s", octs[0], octs[1], octs[2], octs[3], tok ); + } else { + snprintf( buf, sizeof( buf ), "%d.%d.%d.%d", octs[0], octs[1], octs[2], octs[3] ); + } + + free( dname ); + return strdup( buf ); +} + + +/* + Looks for the environment variable RMR_RTG_SVC which we assume to be name[:port], and + does a dns lookup on the name. If the env does not have such a variable, we default to + "rtg" and a port of 5656. + + Returns true (1) if lookup found something; + + CAUTION: this is ONLY used if the RTG is a pub and we are using pub/sub to get updates. + There are issues with some underlying transport pub/sub implementations so this + is likley NOT needed/used. +*/ +static int uta_lookup_rtg( uta_ctx_t* ctx ) { + char* ev; // pointer to the env value + char* def_port = "5656"; + char* port = NULL; + char* dstr = NULL; + + if( ctx == NULL ) { + return 0; + } + + + if( ctx->rtg_addr ) { + free( ctx->rtg_addr ); + } + + if( (ev = getenv( "RMR_RTG_SVC" )) == NULL ) { + ev = "rtg"; + port = def_port; + } else { + dstr = strdup( ev ); // copy so we can trash it + if( (port = strchr( dstr, ':' )) == NULL ) { + port = def_port; + } else { + *port = 0; + port++; // point at the first digit + } + ev = dstr; // all references below assume ev + } + + ctx->rtg_addr = uta_h2ip( ev ); // convert name to IP addr + ctx->rtg_port = atoi( port ); + if( dstr ) { + free( dstr ); + } + + return ctx->rtg_addr != NULL; +} + + +/* + Expects a buffer of 'sep' separated tokens and looks to see if + the given string is one of those tokens. Returns the token + index (0 - n-1) if the string is found; -1 otherwise. The max + parameter supplies the maximum number of tokens to search in + the buffer. + + On failure (-1) errno will be set in cases where memory cannot + be alocated (is this even possible any more?). If errno is 0 + and failure is returned, then the caller should assume that + the token isn't in the list, or the list had no elements. +*/ +static int uta_has_str( char const* buf, char const* str, char sep, int max ) { + char* dbuf; // duplicated buf so we can trash + char** tokens; // pointer to tokens from the string + int ntokens; // number of tokens buf split into + int i; + int rc; // return code + + if( max < 2 ) { + return -1; + } + + dbuf = strdup( buf ); + if( dbuf == NULL ) { + errno = ENOMEM; + return -1; + } + + if( (tokens = (char **) malloc( sizeof( char * ) * max )) == NULL ) { + errno = ENOMEM; + free( dbuf ); + return -1; + } + + ntokens = uta_tokenise( dbuf, tokens, max, sep ); + errno = 0; + rc = -1; + for( i = 0; rc < 0 && i < ntokens; i++ ) { + if( tokens[i] ) { + if( strcmp( tokens[i], str ) == 0 ) { + rc = i; + } + } + } + + free( dbuf ); + free( tokens ); + return rc; +} + +/* + Generate a list of all IP address associated with the interfaces available. + For now we capture them all, but we may need to limit. The port is smashed + onto each IP we find so that we can do a direct compare against the addr + that could be in the route table. + + If the environment variable which limits the binding of our listen port + to a single interface (ENV_BIND_IF) then ONLY that address is added to + the list so that we don't pick up entries from the rtable that are for other + processes listening on different interfaces. +*/ +if_addrs_t* mk_ip_list( char* port ) { + if_addrs_t* l; + struct ifaddrs *ifs; // pointer to head + struct ifaddrs *ele; // pointer into the list + char octs[NI_MAXHOST+1]; + char wbuf[NI_MAXHOST+128]; + char* fmt; + char* envp; // at the environment var if there + + + + if( (l = (if_addrs_t *) malloc( sizeof( if_addrs_t ) )) == NULL ) { + return NULL; + } + memset( l, 0, sizeof( if_addrs_t ) ); + l->addrs = (char **) malloc( sizeof( char* ) * 128 ); + if( l->addrs == NULL ) { + free( l ); + return NULL; + } + + if( (envp = getenv( ENV_BIND_IF )) != NULL ) { + snprintf( wbuf, sizeof( wbuf ), "%s:%s", envp, port ); // smash port onto the addr as is + l->addrs[l->naddrs] = strdup( wbuf ); + l->naddrs++; + if( DEBUG ) fprintf( stderr, "[INFO] rmr: using only specific bind interface when searching specific RT entries: %s\n", wbuf ); + return l; + } + + getifaddrs( &ifs ); + for( ele = ifs; ele; ele = ele->ifa_next ) { + *octs = 0; + + if( ele && strcmp( ele->ifa_name, "lo" ) ) { + if( ele->ifa_addr->sa_family == AF_INET ) { + getnameinfo( ele->ifa_addr, sizeof( struct sockaddr_in ), octs, NI_MAXHOST, NULL, 0, NI_NUMERICHOST ); + fmt = "%s:%s"; + } else { + if( ele->ifa_addr->sa_family == AF_INET6 ) { + getnameinfo( ele->ifa_addr, sizeof( struct sockaddr_in6 ), octs, NI_MAXHOST, NULL, 0, NI_NUMERICHOST ); + fmt = "[%s]:%s"; + } + } + + if( *octs ) { + if( l->naddrs < 128 ) { + snprintf( wbuf, sizeof( wbuf ), fmt, octs, port ); // smash port onto the addr + l->addrs[l->naddrs] = strdup( wbuf ); + l->naddrs++; + } + } + } + } + + if( ifs ) { + freeifaddrs( ifs ); + } + + return l; +} + +/* + Check the address:port passed in and return true if it matches + one of the addresses we saw when we built the list. Right now + this isn't a speed intensive part of our processing, so we just + do a straight search through the list. We don't expect this to + ever be a higly driven functions so not bothering to optimise. +*/ +int is_this_myip( if_addrs_t* l, char* addr ) { + int i; + + if( l == NULL ) { + return 0; + } + + for( i = 0; i < l->naddrs; i++ ) { + if( strcmp( addr, l->addrs[i] ) == 0 ) { + return 1; + } + } + + return 0; +} + +/* + Expects a buffer containing "sep" separated tokens, and a list of + IP addresses anchored by ip_list. Searches the tokens to see if + any are an ip address:port which is in the ip list. Returns true + (1) if a token is in the list, false otherwise. +*/ +static int has_myip( char const* buf, if_addrs_t* list, char sep, int max ) { + char* dbuf; // duplicated buf so we can trash + char** tokens; // pointer to tokens from the string + int ntokens; // number of tokens buf split into + int i; + int rc = 0; // return code + + if( max < 2 ) { + return 0; + } + + if( buf == NULL ) { + return 0; + } + + if( list == NULL ) { + return 0; + } + + + dbuf = strdup( buf ); // get a copy we can mess with + if( dbuf == NULL ) { + errno = ENOMEM; + return 0; + } + + if( (tokens = (char **) malloc( sizeof( char * ) * max )) == NULL ) { + errno = ENOMEM; + free( dbuf ); + return 0; + } + + ntokens = uta_tokenise( dbuf, tokens, max, sep ); + errno = 0; + rc = 0; + for( i = 0; ! rc && i < ntokens; i++ ) { + if( tokens[i] ) { + if( is_this_myip( list, tokens[i] ) ) { + rc = 1; + break; + } + } + } + + free( dbuf ); + free( tokens ); + return rc; +} + +#endif diff --git a/src/common/src/wormholes.c b/src/common/src/wormholes.c new file mode 100644 index 0000000..8011ddc --- /dev/null +++ b/src/common/src/wormholes.c @@ -0,0 +1,313 @@ +// :vi sw=4 ts=4 noet: +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + +/* + Mnemonic: wormholes.c + Abstract: All functions (internal and external) needed to manage wormholes. + + Wormholes allow a user application to send directly to an endpoint. + The application must first "open" the wormhole which allows us to + provide the application with an ID that can be used on a wh_send() + call. It also does the validation (future) which might not allow + the application to open any wormholes, or may allow them only to + specific targets. + + Author: E. Scott Daniels + Date: 13 February 2019 +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rmr.h" +#include "rmr_symtab.h" + +/* +#ifdef NNG +#include +#include +#include +#include +#include + +#include "rmr_nng_private.h" +#include "rt_generic_static.c" +#include "rtable_nng_static.c" +#include "sr_nng_static.c" + +#else +#include +#include +#include +#include +#include + +#include "rmr_private.h" +#include "rt_generic_static.c" +#include "rtable_static.c" +#include "sr_static.c" +#endif +*/ + +#include "tools_static.c" + + + +// ----------------------- internal stuff ----------------------------------------------- + +/* + This function returns true if the current application is permitted to open a wormhole + to the desired target. + + This is a place holder for future functionality. +*/ +static int wh_can_open( uta_ctx_t* ctx, char const* target ) { + return 1; +} + +/* + Allocate and initialise the wormholes list; point context at it. +*/ +static int wh_init( uta_ctx_t* ctx ) { + wh_mgt_t* whm; + size_t alloc_sz; + + if( ctx == NULL ) { + errno = EINVAL; + return 0; + } + + if( ctx->wormholes != NULL ) { // already allocated, do nothing but signal all is well + return 1; + } + + if( (whm = malloc( sizeof( *whm ) )) == NULL ) { + fprintf( stderr, "mem alloc failed for whm: alloc %d bytes\n", (int) sizeof( *whm ) ); + errno = ENOMEM; + return 0; + } + + whm->nalloc = 16; + alloc_sz = whm->nalloc * sizeof( endpoint_t ); + if( (whm->eps = (endpoint_t **) malloc( alloc_sz )) == NULL ) { + fprintf( stderr, "mem alloc failed: alloc %d bytes\n", (int) alloc_sz ); + free( whm ); + errno = ENOMEM; + return 0; + } + + memset( whm->eps, 0, alloc_sz ); + + ctx->wormholes = whm; + errno = 0; + return 1; +} + +/* + Realloc the wormhole endpoint list. + Returns 0 if failure with errno set; !0 on success. +*/ +static int wh_extend( wh_mgt_t* whm ) { + int i; + int j; + size_t alloc_sz; + + i = whm->nalloc; // starting point for initialisation after realloc + whm->nalloc += 16; + + alloc_sz = whm->nalloc * sizeof( endpoint_t ); + if( (whm->eps = (endpoint_t **) realloc( whm->eps, alloc_sz )) == NULL ) { + errno = ENOMEM; + return 0; + } + + for( j = 0; j < 16; j++ ) { + whm->eps[i++] = NULL; // must init the new stuff + } + + errno = 0; + return 1; +} + +/* + Mostly for leak analysis during testing. +*/ +static void wh_nuke( uta_ctx_t* ctx ) { + if( ctx == NULL ) { + return; + } + + if( ctx->wormholes ) { + if( ctx->wormholes->eps ) { + free( ctx->wormholes->eps ); + } + free( ctx->wormholes ); + } + + ctx->wormholes = NULL; +} + +// ----------------------- visible stuff ------------------------------------------------ + +/* + Opens a direct wormhole connection to the named target. Target is expected to be + either hostname:port or IP:port. If we don't have an endpoint in our hash, we'll + create one. + Unlike 'regular' connections to endpoints which are connected on the first send + attempt, when a wormhole is opened we connect immediatly. In the NNG world this + could result in a delay and immediate failure. With nanomsg the failure may not + be detected as the connect doesn't block. +*/ +extern rmr_whid_t rmr_wh_open( void* vctx, char const* target ) { + endpoint_t* ep; // endpoint that represents the target + uta_ctx_t* ctx = NULL; + rmr_whid_t whid = -1; // wormhole id is the index into the list + wh_mgt_t* whm; // easy reference to wh mgt stuff + int i; + + + if( (ctx = (uta_ctx_t *) vctx) == NULL || target == NULL || *target == 0 ) { + errno = EINVAL; + return whid; + } + + if( ! wh_can_open( ctx, target ) ) { + errno = EACCES; + return whid; + } + + if( ctx->wormholes == NULL ) { + if( ! wh_init( ctx ) ) { // first call, we need to set things up + return whid; // fail with errno set by init + } + } + + whm = ctx->wormholes; + + + if( (ep = rt_ensure_ep( ctx->rtable, target )) == NULL ) { // get pointer to ep if there, create new if not + fprintf( stderr, "ensure ep returned bad; setting no memory error\n" ); + return -1; // ensure sets errno + } + + whid = whm->nalloc; + for( i = 0; i < whm->nalloc; i++ ) { // look for a pointer to the ep, and find first open spot + if( whid == whm->nalloc && !whm->eps[i] ) { + whid = i; // save first open slot should we need it + } + + if( whm->eps[i] == ep ) { + return i; // we're already pointing to it, just send it back again + } + } + + if( whid >= whm->nalloc ) { + if( ! wh_extend( whm ) ) { // add some; whid will point to the right place + return -1; + } + } + + if( !rt_link2_ep( ep ) ) { // start a connection if open already + errno = ECONNREFUSED; + return -1; + } + + whm->eps[whid] = ep; + return whid; +} + + +/* + Send a message directly to an open wormhole. + As with the other send functions in RMr, we return a new zero copy buffer for the + user application to fill in. +*/ +extern rmr_mbuf_t* rmr_wh_send_msg( void* vctx, rmr_whid_t whid, rmr_mbuf_t* msg ) { + uta_ctx_t* ctx; + endpoint_t* ep; // enpoint that wormhole ID references + wh_mgt_t *whm; + + if( (ctx = (uta_ctx_t *) vctx) == NULL || msg == NULL ) { // bad stuff, bail fast + errno = EINVAL; // if msg is null, this is their clue + if( msg != NULL ) { + msg->state = RMR_ERR_BADARG; + errno = EINVAL; // must ensure it's not eagain + } + return msg; + } + + msg->state = RMR_OK; + + if( (whm = ctx->wormholes) == NULL ) { + errno = EINVAL; // no wormholes open + msg->state = RMR_ERR_NOWHOPEN; + return msg; + } + + if( whid < 0 || whid >= whm->nalloc || whm->eps[whid] == NULL ) { + errno = EINVAL; // no wormholes open + msg->state = RMR_ERR_WHID; + return msg; + } + + errno = 0; // nng seems not to set errno any longer, so ensure it's clear + if( msg->header == NULL ) { + fprintf( stderr, "[ERR] rmr_wh_send_msg: message had no header\n" ); + msg->state = RMR_ERR_NOHDR; + errno = EBADMSG; // must ensure it's not eagain + return msg; + } + + ep = whm->eps[whid]; + return send2ep( ctx, ep, msg ); // send directly to the endpoint +} + +/* + This will "close" a wormhole. We don't actually drop the session as that might be needed + by others, but we do pull the ep reference from the list. +*/ +extern void rmr_wh_close( void* vctx, int whid ) { + uta_ctx_t* ctx; + wh_mgt_t *whm; + + if( (ctx = (uta_ctx_t *) vctx) == NULL ) { + return; + } + + if( (whm = ctx->wormholes) == NULL || whm->eps == NULL ) { + return; + } + + if( whid >= whm->nalloc || whid < 0 ) { + return; + } + + if( whm->eps[whid] == NULL ) { + return; + } + + whm->eps[whid] = NULL; +} diff --git a/src/nanomsg/CMakeLists.txt b/src/nanomsg/CMakeLists.txt new file mode 100644 index 0000000..23be082 --- /dev/null +++ b/src/nanomsg/CMakeLists.txt @@ -0,0 +1,35 @@ + +# +#================================================================================== +# Copyright (c) 2019 Nokia +# Copyright (c) 2018-2019 AT&T Intellectual Property. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#================================================================================== +# + + +# for clarity: this generates object, not a lib as the CM command implies +add_library( nano_objects OBJECT src/rmr.c ) + +if( need_ext ) + add_dependencies( nano_objects nanomsg ) # force external things to build first +endif() + +target_include_directories (nano_objects PUBLIC + $ + $ + $ + $ + PRIVATE src) + diff --git a/src/nanomsg/include/rmr_private.h b/src/nanomsg/include/rmr_private.h new file mode 100644 index 0000000..3b5d3d3 --- /dev/null +++ b/src/nanomsg/include/rmr_private.h @@ -0,0 +1,151 @@ +// : vi ts=4 sw=4 noet : +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + +/* + Mnemonic: rmr_private.h + Abstract: Private header information for the uta library functions. + This should contain only things which are specific to nanomsg; + anything else is defined in the common/rmr_agnostic.h header. + + Author: E. Scott Daniels + Date: 27 November 2018 + + Mods: 28 February 2019 - moved most of the crap here to agnosic. +*/ + +#ifndef _rmr_private_h +#define _rmr_private_h + +/* + Manages an endpoint. Typedef for this is defined in agnostic.h +*/ +struct endpoint { + char* name; // end point name (symtab reference) + char* proto; // connection proto (should only be TCP, but future might bring others) + char* addr; // address used for connection + int nn_sock; // the nano-msg socket to write to for this entry + int open; // true if we've established the connection +}; + +/* + Context describing our world. Should be returned to user programme on + call to initialise, and passed as first parm on all calls to other + visible functions. + + The typedef for ctx is in the agnostic header +*/ +struct uta_ctx { + char* my_name; // dns name of this host to set in sender field of a message + int shutdown; // threads should exit if this is set + int max_mlen; // max message length payload+header + int max_plen; // max payload length + int flags; // CTXFL_ constants + int nrtele; // number of elements in the routing table + int nn_sock; // our general listen socket + route_table_t* rtable; // the active route table + route_table_t* old_rtable; // the previously used rt, sits here to allow for draining + route_table_t* new_rtable; // route table under construction + if_addrs_t* ip_list; // list manager of the IP addresses that are on our known interfaces + void* mring; // ring where msgs are queued while waiting for a call response msg + + char* rtg_addr; // addr/port of the route table generation publisher + int rtg_port; // the port that the rtg listens on + + wh_mgt_t* wormholes; // wormhole management + pthread_t rtc_th; // thread info for the rtc listener +}; + + +/* + Prototypes of the functions which are defined in our static modules (nothing + from common should be here). +*/ + +// ---- housekeeping and initialisation ---------- +static void* init( char* usr_port, int max_mlen, int flags ); +static void free_ctx( uta_ctx_t* ctx ); + +// --- message and context management -------- +static rmr_mbuf_t* send_msg( uta_ctx_t* ctx, rmr_mbuf_t* msg, int nn_sock ); +static void* rcv_payload( uta_ctx_t* ctx, rmr_mbuf_t* old_msg ); + + +// ---- route table and connection management --------------- + +static int uta_link2( char* target ); +static int rt_link2_ep( endpoint_t* ep ); +static endpoint_t* uta_add_ep( route_table_t* rt, rtable_ent_t* rte, char* ep_name, int group ); +static int uta_epsock_byname( route_table_t* rt, char* ep_name ); +static int uta_epsock_rr( route_table_t *rt, int mtype, int group, int* more ); + +// ------ msg ------------------------------------------------ +static rmr_mbuf_t* alloc_zcmsg( uta_ctx_t* ctx, rmr_mbuf_t* msg, int size, int state ); +static inline rmr_mbuf_t* clone_msg( rmr_mbuf_t* old_msg ); +static rmr_mbuf_t* rcv_msg( uta_ctx_t* ctx, rmr_mbuf_t* old_msg ); +static void* rcv_payload( uta_ctx_t* ctx, rmr_mbuf_t* old_msg ); +static rmr_mbuf_t* send_msg( uta_ctx_t* ctx, rmr_mbuf_t* msg, int nn_sock ); +static rmr_mbuf_t* send2ep( uta_ctx_t* ctx, endpoint_t* ep, rmr_mbuf_t* msg ); + + + +/* +// --- message ring -------------------------- +static void* uta_mk_ring( int size ); +static void uta_ring_free( void* vr ); +static inline void* uta_ring_extract( void* vr ); +static inline int uta_ring_insert( void* vr, void* new_data ); + +// --- message and context management -------- +static int ie_test( void* r, int i_factor, long inserts ); +static void free_ctx( uta_ctx_t* ctx ); +static rmr_mbuf_t* alloc_zcmsg( uta_ctx_t* ctx, rmr_mbuf_t* msg, int size, int state ); +static inline rmr_mbuf_t* clone_msg( rmr_mbuf_t* old_msg ); +static rmr_mbuf_t* rcv_msg( uta_ctx_t* ctx, rmr_mbuf_t* old_msg ); + +// ----- route table static things --------- +static void collect_things( void* st, void* entry, char const* name, void* thing, void* vthing_list ); +static void del_rte( void* st, void* entry, char const* name, void* thing, void* data ); +static char* uta_fib( char* fname ); +static route_table_t* uta_rt_init( ); +static route_table_t* uta_rt_clone( route_table_t* srt ); +static void uta_rt_drop( route_table_t* rt ); +static endpoint_t* uta_add_ep( route_table_t* rt, rtable_ent_t* rte, char* ep_name, int group ); +static rtable_ent_t* uta_add_rte( route_table_t* rt, int mtype, int nrrgroups ); +static endpoint_t* uta_get_ep( route_table_t* rt, char const* ep_name ); +static int uta_epsock_byname( route_table_t* rt, char* ep_name ); +static int uta_epsock_rr( route_table_t *rt, int mtype, int group, int* more ); +static void read_static_rt( uta_ctx_t* ctx, int vlevel ); +static void parse_rt_rec( uta_ctx_t* ctx, char* buf, int vlevel ); +static void* rtc( void* vctx ); +static endpoint_t* rt_ensure_ep( route_table_t* rt, char const* ep_name ); + + +// --- tools_static protos ------------------ +static int uta_tokenise( char* buf, char** tokens, int max, char sep ); +static char* uta_h2ip( char const* hname ); +static int uta_link2( char* target ); +static int uta_lookup_rtg( uta_ctx_t* ctx ); +static int uta_has_str( char const* buf, char const* str, char sep, int max ); +*/ + +static int rt_link2_ep( endpoint_t* ep ); +static rmr_mbuf_t* send2ep( uta_ctx_t* ctx, endpoint_t* ep, rmr_mbuf_t* msg ); + +#endif diff --git a/src/nanomsg/src/rmr.c b/src/nanomsg/src/rmr.c new file mode 100644 index 0000000..3fe4247 --- /dev/null +++ b/src/nanomsg/src/rmr.c @@ -0,0 +1,636 @@ +// :vi sw=4 ts=4 noet: +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + +/* + Mnemonic: rmr.c + Abstract: The bulk of the ric message routing library which is built upon + the older nanomsg messaging transport mehhanism. + + To "hide" internal functions the choice was made to implement them + all as static functions. This means that we include nearly + all of our modules here as 90% of the library is not visible to + the outside world. + + Author: E. Scott Daniels + Date: 28 November 2018 +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "rmr.h" // things the users see +#include "rmr_agnostic.h" // headers agnostic to the underlying transport mechanism +#include "rmr_private.h" // things that we need too +#include "rmr_symtab.h" + +#include "ring_static.c" // message ring support +#include "rt_generic_static.c" // generic route table (not nng/nano specific) +#include "rtable_static.c" // route table things (nano specific) +#include "rtc_static.c" // common rt collector +#include "tools_static.c" +#include "sr_static.c" // send/receive static functions +#include "wormholes.c" // external wormhole api, and it's static functions (must be LAST) + +// ------------------------------------------------------------------------------------------------------ + +/* + Clean up a context. +*/ +static void free_ctx( uta_ctx_t* ctx ) { + if( ctx ) { + if( ctx->rtg_addr ) { + free( ctx->rtg_addr ); + } + } +} + +// --------------- public functions -------------------------------------------------------------------------- + +/* + Set the receive timeout to time. If time >1000 we assume the time is milliseconds, + else we assume seconds. Setting -1 is always block. + Returns the nn value (0 on success <0 on error). +*/ +extern int rmr_set_rtimeout( void* vctx, int time ) { + uta_ctx_t* ctx; + + if( (ctx = (uta_ctx_t *) vctx) == NULL ) { + errno = EINVAL; + return -1; + } + + if( time > 0 ) { + if( time < 1000 ) { + time = time * 1000; // assume seconds, nn wants ms + } + } + + return nn_setsockopt( ctx->nn_sock, NN_SOL_SOCKET, NN_RCVTIMEO, &time, sizeof( time ) ); +} + +/* + Deprecated -- use rmr_set_rtimeout() +*/ +extern int rmr_rcv_to( void* vctx, int time ) { + return rmr_rcv_to( vctx, time ); +} + + +/* + Set the send timeout to time. If time >1000 we assume the time is milliseconds, + else we assume seconds. Setting -1 is always block. + Returns the nn value (0 on success <0 on error). +*/ +extern int rmr_set_stimeout( void* vctx, int time ) { + uta_ctx_t* ctx; + + if( (ctx = (uta_ctx_t *) vctx) == NULL ) { + errno = EINVAL; + return -1; + } + + if( time > 0 ) { + if( time < 1000 ) { + time = time * 1000; // assume seconds, nn wants ms + } + } + + return nn_setsockopt( ctx->nn_sock, NN_SOL_SOCKET, NN_SNDTIMEO, &time, sizeof( time ) ); +} + +/* + Deprecated -- use rmr_set_stimeout() +*/ +extern int rmr_send_to( void* vctx, int time ) { + return rmr_send_to( vctx, time ); +} + +/* + Returns the size of the payload (bytes) that the msg buffer references. + Len in a message is the number of bytes which were received, or should + be transmitted, however, it is possible that the mbuf was allocated + with a larger payload space than the payload length indicates; this + function returns the absolute maximum space that the user has available + in the payload. On error (bad msg buffer) -1 is returned and errno should + indicate the rason. +*/ +extern int rmr_payload_size( rmr_mbuf_t* msg ) { + if( msg == NULL || msg->header == NULL ) { + errno = EINVAL; + return -1; + } + + errno = 0; + return msg->alloc_len - sizeof( uta_mhdr_t ); // figure size should we not have a msg buffer +} + +/* + Allocates a send message as a zerocopy message allowing the underlying message protocol + to send the buffer without copy. +*/ +extern rmr_mbuf_t* rmr_alloc_msg( void* vctx, int size ) { + uta_ctx_t* ctx; + rmr_mbuf_t* m; + + if( (ctx = (uta_ctx_t *) vctx) == NULL ) { + return NULL; + } + + m = alloc_zcmsg( ctx, NULL, size, 0 ); + return m; +} + +/* + Return the message to the available pool, or free it outright. +*/ +extern void rmr_free_msg( rmr_mbuf_t* mbuf ) { + if( mbuf == NULL ) { + return; + } + + if( mbuf->header ) { + if( mbuf->flags & MFL_ZEROCOPY ) { + nn_freemsg( mbuf->header ); // must let nano free it + } else { + free( mbuf->header ); + } + } + + free( mbuf ); +} + +/* + Accept a message and send it to an endpoint based on message type. + Allocates a new message buffer for the next send. If a message type has + more than one group of endpoints defined, then the message will be sent + in round robin fashion to one endpoint in each group. + + CAUTION: this is a non-blocking send. If the message cannot be sent, then + it will return with an error and errno set to eagain. If the send is + a limited fanout, then the returned status is the status of the last + send attempt. +*/ +extern rmr_mbuf_t* rmr_send_msg( void* vctx, rmr_mbuf_t* msg ) { + int nn_sock; // endpoint socket for send + uta_ctx_t* ctx; + int group; // selected group to get socket for + int send_again; // true if the message must be sent again + rmr_mbuf_t* clone_m; // cloned message for an nth send + + if( (ctx = (uta_ctx_t *) vctx) == NULL || msg == NULL ) { // bad stuff, bail fast + errno = EINVAL; // if msg is null, this is their clue + if( msg != NULL ) { + msg->state = RMR_ERR_BADARG; + errno = EINVAL; // must ensure it's not eagain + } + return msg; + } + + errno = 0; // clear; nano might set, but ensure it's not left over if it doesn't + if( msg->header == NULL ) { + fprintf( stderr, "[ERR] rmr_send_msg: message had no header\n" ); + msg->state = RMR_ERR_NOHDR; + errno = EBADMSG; // must ensure it's not eagain + return msg; + } + + send_again = 1; // force loop entry + group = 0; // always start with group 0 + + while( send_again ) { + nn_sock = uta_epsock_rr( ctx->rtable, msg->mtype, group, &send_again ); // round robin select endpoint; again set if mult groups + if( DEBUG ) fprintf( stderr, "[DBUG] send msg: type=%d again=%d group=%d socket=%d len=%d\n", + msg->mtype, send_again, group, nn_sock, msg->len ); + group++; + + if( nn_sock < 0 ) { + msg->state = RMR_ERR_NOENDPT; + errno = ENXIO; // must ensure it's not eagain + return msg; // caller can resend (maybe) or free + } + + if( send_again ) { + clone_m = clone_msg( msg ); // must make a copy as once we send this message is not available + if( DEBUG ) fprintf( stderr, "[DBUG] msg cloned: type=%d len=%d\n", msg->mtype, msg->len ); + msg->flags |= MFL_NOALLOC; // send should not allocate a new buffer + msg = send_msg( ctx, msg, nn_sock ); // do the hard work, msg should be nil on success + /* + if( msg ) { + // error do we need to count successes/errors, how to report some success, esp if last fails? + } + */ + + msg = clone_m; // clone will be the next to send + } else { + msg = send_msg( ctx, msg, nn_sock ); // send the last, and allocate a new buffer; drops the clone if it was + } + } + + return msg; // last message caries the status of last/only send attempt +} + +/* + Return to sender allows a message to be sent back to the endpoint where it originated. + The source information in the message is used to select the socket on which to write + the message rather than using the message type and round-robin selection. This + should return a message buffer with the state of the send operation set. On success + (state is RMR_OK, the caller may use the buffer for another receive operation), and on + error it can be passed back to this function to retry the send if desired. On error, + errno will liklely have the failure reason set by the nanomsg send processing. + The following are possible values for the state in the message buffer: + + Message states returned: + RMR_ERR_BADARG - argument (context or msg) was nil or invalid + RMR_ERR_NOHDR - message did not have a header + RMR_ERR_NOENDPT- an endpoint to send the message to could not be determined + RMR_ERR_SENDFAILED - send failed; errno has nano error code + RMR_ERR_RETRY - operation failed, but caller should retry + + A nil message as the return value is rare, and generally indicates some kind of horrible + failure. The value of errno might give a clue as to what is wrong. + + CAUTION: + Like send_msg(), this is non-blocking and will return the msg if there is an errror. + The caller must check for this and handle. +*/ +extern rmr_mbuf_t* rmr_rts_msg( void* vctx, rmr_mbuf_t* msg ) { + int nn_sock; // endpoint socket for send + uta_ctx_t* ctx; + int state; + uta_mhdr_t* hdr; + char* hold_src; // we need the original source if send fails + + if( (ctx = (uta_ctx_t *) vctx) == NULL || msg == NULL ) { // bad stuff, bail fast + errno = EINVAL; // if msg is null, this is their clue + if( msg != NULL ) { + msg->state = RMR_ERR_BADARG; + } + return msg; + } + + errno = 0; // at this point any bad state is in msg returned + if( msg->header == NULL ) { + fprintf( stderr, "rmr_send_msg: ERROR: message had no header\n" ); + msg->state = RMR_ERR_NOHDR; + return msg; + } + + nn_sock = uta_epsock_byname( ctx->rtable, (char *) ((uta_mhdr_t *)msg->header)->src ); // socket of specific endpoint + if( nn_sock < 0 ) { + msg->state = RMR_ERR_NOENDPT; + return msg; // preallocated msg can be reused since not given back to nn + } + + hold_src = strdup( (char *) ((uta_mhdr_t *)msg->header)->src ); // the dest where we're returning the message to + strncpy( (char *) ((uta_mhdr_t *)msg->header)->src, ctx->my_name, RMR_MAX_SID ); // must overlay the source to be ours + msg = send_msg( ctx, msg, nn_sock ); + if( msg ) { + strncpy( (char *) ((uta_mhdr_t *)msg->header)->src, hold_src, RMR_MAX_SID ); // always return original source so rts can be called again + msg->flags |= MFL_ADDSRC; // if msg given to send() it must add source + } + + free( hold_src ); + return msg; +} + +/* + Call sends the message based on message routing using the message type, and waits for a + response message to arrive with the same transaction id that was in the outgoing message. + If, while wiating for the expected response, messages are received which do not have the + desired transaction ID, they are queued. Calls to uta_rcv_msg() will dequeue them in the + order that they were received. + + Normally, a message struct pointer is returned and msg->state must be checked for RMR_OK + to ensure that no error was encountered. If the state is UTA_BADARG, then the message + may be resent (likely the context pointer was nil). If the message is sent, but no + response is received, a nil message is returned with errno set to indicate the likley + issue: + ETIMEDOUT -- too many messages were queued before reciving the expected response + ENOBUFS -- the queued message ring is full, messages were dropped + EINVAL -- A parameter was not valid + EAGAIN -- the underlying message system wsa interrupted or the device was busy; + user should call this function with the message again. + + + QUESTION: should user specify the number of messages to allow to queue? +*/ +extern rmr_mbuf_t* rmr_call( void* vctx, rmr_mbuf_t* msg ) { + uta_ctx_t* ctx; + unsigned char expected_id[RMR_MAX_XID+1]; // the transaction id in the message; we wait for response with same ID + + if( (ctx = (uta_ctx_t *) vctx) == NULL || msg == NULL ) { // bad stuff, bail fast + if( msg != NULL ) { + msg->state = RMR_ERR_BADARG; + } + return msg; + } + + memcpy( expected_id, msg->xaction, RMR_MAX_XID ); + expected_id[RMR_MAX_XID] = 0; // ensure it's a string + if( DEBUG > 1 ) fprintf( stderr, "[DBUG] rmr_call is making call, waiting for (%s)\n", expected_id ); + errno = 0; + msg->flags |= MFL_NOALLOC; // we don't need a new buffer from send + + msg = rmr_send_msg( ctx, msg ); + if( msg ) { // msg should be nil, if not there was a problem; return buffer to user + if( msg->state != RMR_ERR_RETRY ) { + msg->state = RMR_ERR_CALLFAILED; // don't stomp if send_msg set retry + } + return msg; + } + + return rmr_rcv_specific( ctx, NULL, (char *) expected_id, 20 ); // wait for msg allowing 20 to queue ahead +} + +/* + The outward facing receive function. When invoked it will pop the oldest message + from the receive ring, if any are queued, and return it. If the ring is empty + then the receive function is invoked to wait for the next message to arrive (blocking). + + If old_msg is provided, it will be populated (avoiding lots of free/alloc cycles). If + nil, a new one will be allocated. However, the caller should NOT expect to get the same + struct back (if a queued message is returned the message struct will be different). +*/ +extern rmr_mbuf_t* rmr_rcv_msg( void* vctx, rmr_mbuf_t* old_msg ) { + uta_ctx_t* ctx; + rmr_mbuf_t* qm; // message that was queued on the ring + + if( (ctx = (uta_ctx_t *) vctx) == NULL ) { + if( old_msg != NULL ) { + old_msg->state = RMR_ERR_BADARG; + } + errno = EINVAL; + return old_msg; + } + errno = 0; + + qm = (rmr_mbuf_t *) uta_ring_extract( ctx->mring ); // pop if queued + if( qm != NULL ) { + if( old_msg ) { + rmr_free_msg( old_msg ); // future: push onto a free list??? + } + + return qm; + } + + return rcv_msg( ctx, old_msg ); // nothing queued, wait for one +} + +/* + Receive with a timeout. This is a convenience function when sitting on top of + nanomsg as it just sets the rcv timeout and calls rmr_rcv_msg(). +*/ +extern rmr_mbuf_t* rmr_torcv_msg( void* vctx, rmr_mbuf_t* old_msg, int ms_to ) { + rmr_set_rtimeout( vctx, ms_to ); + return rmr_rcv_msg( vctx, old_msg ); +} + + +/* + This blocks until the message with the 'expect' ID is received. Messages which are received + before the expected message are queued onto the message ring. The function will return + a nil message and set errno to ETIMEDOUT if allow2queue messages are received before the + expected message is received. If the queued message ring fills a nil pointer is returned + and errno is set to ENOBUFS. + + Generally this will be invoked only by the call() function as it waits for a response, but + it is exposed to the user application as three is no reason not to. +*/ +extern rmr_mbuf_t* rmr_rcv_specific( void* vctx, rmr_mbuf_t* msg, char* expect, int allow2queue ) { + uta_ctx_t* ctx; + int queued = 0; // number we pushed into the ring + int exp_len = 0; // length of expected ID + + if( (ctx = (uta_ctx_t *) vctx) == NULL ) { + if( msg != NULL ) { + msg->state = RMR_ERR_BADARG; + } + errno = EINVAL; + return msg; + } + + errno = 0; + + if( expect == NULL || ! *expect ) { // nothing expected if nil or empty string, just receive + return rmr_rcv_msg( ctx, msg ); + } + + exp_len = strlen( expect ); + if( exp_len > RMR_MAX_XID ) { + exp_len = RMR_MAX_XID; + } + if( DEBUG ) fprintf( stderr, "[DBUG] rcv_specific waiting for id=%s\n", expect ); + + while( queued < allow2queue ) { + msg = rcv_msg( ctx, msg ); // hard wait for next + if( msg->state == RMR_OK ) { + if( memcmp( msg->xaction, expect, exp_len ) == 0 ) { // got it -- return it + if( DEBUG ) fprintf( stderr, "[DBUG] rcv-specific matched (%s); %d messages were queued\n", msg->xaction, queued ); + return msg; + } + + if( ! uta_ring_insert( ctx->mring, msg ) ) { // just queue, error if ring is full + if( DEBUG > 1 ) fprintf( stderr, "[DBUG] rcv_specific ring is full\n" ); + errno = ENOBUFS; + return NULL; + } + + if( DEBUG ) fprintf( stderr, "[DBUG] rcv_specific queued message type=%d\n", msg->mtype ); + queued++; + msg = NULL; + } + } + + if( DEBUG ) fprintf( stderr, "[DBUG] rcv_specific timeout waiting for %s\n", expect ); + errno = ETIMEDOUT; + return NULL; +} + + +/* + Initialise the message routing environment. Flags are one of the UTAFL_ + constants. Proto_port is a protocol:port string (e.g. tcp:1234). If default protocol + (tcp) to be used, then :port is all that is needed. + + At the moment it seems that TCP really is the only viable protocol, but + we'll allow flexibility. + + The return value is a void pointer which must be passed to most uta functions. On + error, a nil pointer is returned and errno should be set. +*/ +static void* init( char* uproto_port, int max_msg_size, int flags ) { + uta_ctx_t* ctx = NULL; + char bind_info[NN_SOCKADDR_MAX]; // bind info + char* proto = "tcp"; // pointer into the proto/port string user supplied + char* port; + char* proto_port; + char wbuf[1024]; // work buffer + char* tok; // pointer at token in a buffer + int state; + char* interface = NULL; // interface to bind to pulled from RMR_BIND_IF if set + + fprintf( stderr, "[INFO] ric message routing library on nanomsg (%s %s.%s.%s built: %s)\n", + QUOTE_DEF(GIT_ID), QUOTE_DEF(MAJOR_VER), QUOTE_DEF(MINOR_VER), QUOTE_DEF(PATCH_VER), __DATE__ ); + + errno = 0; + if( uproto_port == NULL ) { + proto_port = strdup( "tcp:4567" ); + } else { + proto_port = strdup( uproto_port ); // so we can modify it + } + + if( (ctx = (uta_ctx_t *) malloc( sizeof( uta_ctx_t ) )) == NULL ) { + errno = ENOMEM; + return NULL; + } + memset( ctx, 0, sizeof( uta_ctx_t ) ); + + + ctx->mring = uta_mk_ring( 128 ); // message ring to hold asynch msgs received while waiting for call response + + ctx->max_plen = RMR_MAX_RCV_BYTES + sizeof( uta_mhdr_t ); // default max buffer size + if( max_msg_size > 0 ) { + if( max_msg_size <= ctx->max_plen ) { // user defined len can be smaller + ctx->max_plen = max_msg_size; + } else { + fprintf( stderr, "[WARN] rmr_init: attempt to set max payload len > than allowed maximum; capped at %d bytes\n", ctx->max_plen ); + } + } + + ctx->max_mlen = ctx->max_plen + sizeof( uta_mhdr_t ); + + uta_lookup_rtg( ctx ); // attempt to fill in rtg info; rtc will handle missing values/errors + + ctx->nn_sock = nn_socket( AF_SP, NN_PULL ); // our 'listen' socket should allow multiple senders to connect + if( ctx->nn_sock < 0 ) { + fprintf( stderr, "[CRIT] rmr_init: unable to initialise nanomsg listen socket: %d\n", errno ); + free_ctx( ctx ); + return NULL; + } + + if( (port = strchr( proto_port, ':' )) != NULL ) { + if( port == proto_port ) { // ":1234" supplied; leave proto to default and point port correctly + port++; + } else { + *(port++) = 0; // term proto string and point at port string + proto = proto_port; // user supplied proto so point at it rather than default + } + } else { + port = proto_port; // assume something like "1234" was passed + } + + if( (gethostname( wbuf, sizeof( wbuf ) )) < 0 ) { + fprintf( stderr, "[CRIT] rmr_init: cannot determine localhost name: %s\n", strerror( errno ) ); + return NULL; + } + if( (tok = strchr( wbuf, '.' )) != NULL ) { + *tok = 0; // we don't keep domain portion + } + ctx->my_name = (char *) malloc( sizeof( char ) * RMR_MAX_SID ); + if( snprintf( ctx->my_name, RMR_MAX_SID, "%s:%s", wbuf, port ) >= RMR_MAX_SID ) { // our registered name is host:port + fprintf( stderr, "[CRIT] rmr_init: hostname + port must be less than %d characters; %s:%s is not\n", RMR_MAX_SID, wbuf, port ); + return NULL; + } + + if( (interface = getenv( ENV_BIND_IF )) == NULL ) { + interface = "0.0.0.0"; + } + snprintf( bind_info, sizeof( bind_info ), "%s://%s:%s", proto, interface, port ); + if( nn_bind( ctx->nn_sock, bind_info ) < 0) { // bind and automatically accept client sessions + fprintf( stderr, "[CRIT] rmr_init: unable to bind nanomsg listen socket for %s: %s\n", bind_info, strerror( errno ) ); + nn_close( ctx->nn_sock ); + free_ctx( ctx ); + return NULL; + } + + if( ! (flags & FL_NOTHREAD) ) { // skip if internal context that does not need rout table thread + if( pthread_create( &ctx->rtc_th, NULL, rtc, (void *) ctx ) ) { // kick the rt collector thread + fprintf( stderr, "[WARN] rmr_init: unable to start route table collector thread: %s", strerror( errno ) ); + } + } + + free( proto_port ); + return (void *) ctx; +} + + +/* + Publicly facing initialisation function. Wrapper for the init() funcion above + as it needs to ensure internal flags are masked off before calling the + real workhorse. +*/ +extern void* rmr_init( char* uproto_port, int max_msg_size, int flags ) { + return init( uproto_port, max_msg_size, flags & UFL_MASK ); +} + +/* + Return true if routing table is initialised etc. and app can send/receive. +*/ +extern int rmr_ready( void* vctx ) { + uta_ctx_t *ctx; + + if( (ctx = (uta_ctx_t *) vctx) == NULL ) { + return FALSE; + } + + if( ctx->rtable != NULL ) { + return TRUE; + } + + return FALSE; +} + +/* + Provides a non-fatal (compile) interface for the nng only function. + Not supported on top of nano, so this always returns -1. +*/ +extern int rmr_get_rcvfd( void* vctx ) { + errno = ENOTSUP; + return -1; +} + +/* + Compatability (mostly) with NNG. +*/ +extern void rmr_close( void* vctx ) { + uta_ctx_t *ctx; + + if( (ctx = (uta_ctx_t *) vctx) == NULL ) { + return; + } + + nn_close( ctx->nn_sock ); +} diff --git a/src/nanomsg/src/rtable_static.c b/src/nanomsg/src/rtable_static.c new file mode 100644 index 0000000..2233da4 --- /dev/null +++ b/src/nanomsg/src/rtable_static.c @@ -0,0 +1,272 @@ +// : vi ts=4 sw=4 noet : +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + +/* + Mnemonic: rtable_static.c + Abstract: Route table management functions. + Author: E. Scott Daniels + Date: 29 November 2018 +*/ + +#ifndef rtable_static_c +#define rtable_static_c + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +/* + Establish a TCP connection to the indicated target (IP address). + Target assumed to be address:port. Requires a separate nano socket; + the socket number (for future sends) is returned or -1 on error. +*/ +static int uta_link2( char* target ) { + char conn_info[NN_SOCKADDR_MAX]; // string to give to nano to make the connection + int nn_sock; // the nano socket for this link + char* addr; + + if( target == NULL || (addr = strchr( target, ':' )) == NULL ) { // bad address:port + fprintf( stderr, "[INFO] rmr: rmr_link2: unable to create link: invalid target: %s\n", target == NULL ? "" : target ); + return -1; + } + + nn_sock = nn_socket( AF_SP, NN_PUSH ); // the socket we'll use to connect to the target + if( nn_sock < 0 ) { + fprintf( stderr, "[WARN] rmr: link2: unable to create socket for link to target: %s: %d\n", target, errno ); + return -1; + } + + snprintf( conn_info, sizeof( conn_info ), "tcp://%s", target ); + if( nn_connect( nn_sock, conn_info ) < 0 ) { // connect failed + fprintf( stderr, "[WARN] rmr: link2: unable to create link to target: %s: %d\n", target, errno ); + nn_close( nn_sock ); + return -1; + } + + return nn_sock; +} + +/* + This provides a protocol independent mechanism for establishing the connection to an endpoint. + Returns true on success; false otherwise. +*/ +static int rt_link2_ep( endpoint_t* ep ) { + if( ep == NULL ) { + return FALSE; + } + + if( ep->open ) { + return TRUE; + } + + ep->nn_sock = uta_link2( ep->addr ) >= 0; // open if a valid socket returned + ep->open = ep->nn_sock >= 0; + return ep->open; +} + +/* + Add an endpoint to a route table entry for the group given. If the endpoint isn't in the + hash we add it and create the endpoint struct. + + The caller must supply the specific route table (we assume it will be pending, but they + could live on the edge and update the active one, though that's not at all a good idea). +*/ +static endpoint_t* uta_add_ep( route_table_t* rt, rtable_ent_t* rte, char* ep_name, int group ) { + endpoint_t* ep; + rrgroup_t* rrg; // pointer at group to update + + if( ! rte || ! rt ) { + fprintf( stderr, "[WARN] rmr_add_ep didn't get a valid rt and/or rte pointer\n" ); + return NULL; + } + + if( rte->nrrgroups <= group ) { + fprintf( stderr, "[WARN] rmr_add_ep group out of range: %d (max == %d)\n", group, rte->nrrgroups ); + return NULL; + } + + if( (rrg = rte->rrgroups[group]) == NULL ) { + if( (rrg = (rrgroup_t *) malloc( sizeof( *rrg ) )) == NULL ) { + fprintf( stderr, "[WARN] rmr_add_ep: malloc failed for round robin group: group=%d\n", group ); + return NULL; + } + memset( rrg, 0, sizeof( *rrg ) ); + + if( (rrg->epts = (endpoint_t **) malloc( sizeof( endpoint_t ) * MAX_EP_GROUP )) == NULL ) { + fprintf( stderr, "[WARN] rmr_add_ep: malloc failed for group endpoint array: group=%d\n", group ); + return NULL; + } + memset( rrg->epts, 0, sizeof( endpoint_t ) * MAX_EP_GROUP ); + + rte->rrgroups[group] = rrg; + + rrg->ep_idx = 0; // next to send to + rrg->nused = 0; // number populated + rrg->nendpts = MAX_EP_GROUP; // number allocated + } + + if( (ep = uta_get_ep( rt, ep_name )) == NULL ) { // not there yet, make + if( (ep = (endpoint_t *) malloc( sizeof( *ep ) )) == NULL ) { + fprintf( stderr, "uta: [WARN] malloc failed for endpoint creation: %s\n", ep_name ); + return NULL; + } + + ep->nn_sock = -1; // not connected + ep->addr = uta_h2ip( ep_name ); + ep->name = strdup( ep_name ); + + rmr_sym_put( rt->hash, ep_name, 1, ep ); + } + + if( rrg != NULL ) { + if( rrg->nused >= rrg->nendpts ) { + // future: reallocate + fprintf( stderr, "[WARN] endpoint array for mtype/group %d/%d is full!\n", rte->mtype, group ); + return NULL; + } + + rrg->epts[rrg->nused] = ep; + rrg->nused++; + } + + if( DEBUG > 1 ) fprintf( stderr, "[DBUG] endpoint added to mtype/group: %d/%d %s\n", rte->mtype, group, ep_name ); + return ep; +} + +/* + Given a name, find the nano socket needed to send to it. Returns the socket number if + found; -1 on error. +*/ +static int uta_epsock_byname( route_table_t* rt, char* ep_name ) { + endpoint_t* ep; + + if( rt == NULL ) { + return -1; + } + + ep = rmr_sym_get( rt->hash, ep_name, 1 ); + if( ep == NULL ) { + return -1; + } + + if( !ep->open ) { // not connected; must connect now + if( ep->addr == NULL ) { // name didn't resolve before, try again + ep->addr = uta_h2ip( ep->name ); + } + ep->nn_sock = uta_link2( ep->addr ); + ep->open = ep->nn_sock >= 0; + if( DEBUG ) fprintf( stderr, "[DBUG] epsock_bn: connection state: %s %s\n", ep->nn_sock >= 0 ? "[OK]" : "[FAIL]", ep->name ); + } + + return ep->nn_sock; +} + +/* + Make a round robin selection within a round robin group for a route table + entry. Returns the nanomsg socket number if there is a rte for the message + type, and group is defined, else returns -1. + + The group is the group number to select from. + + The user supplied integer 'more' will be set if there are additional groups + defined to the matching route table entry which have a higher group number. + This assumes the caller is making a sequential pass across groups starting + with group 0. If more is set, the caller may increase the group number and + invoke this function again to make a selection against that group. If there + are no more groups, more is set to 0. +*/ +static int uta_epsock_rr( route_table_t *rt, int mtype, int group, int* more ) { + rtable_ent_t* rte; // matching rt entry + endpoint_t* ep; // seected end point + int nn_sock = -1; + int dummy; + rrgroup_t* rrg; + + if( ! more ) { // eliminate cheks each time we need to user + more = &dummy; + } + + if( rt == NULL ) { + *more = 0; + return -1; + } + + if( (rte = rmr_sym_pull( rt->hash, mtype )) == NULL ) { + *more = 0; + //if( DEBUG ) fprintf( stderr, ">>>> rte not found for type = %d\n", mtype ); + return -1; + } + + if( group < 0 || group >= rte->nrrgroups ) { + //if( DEBUG ) fprintf( stderr, ">>>> group out of range: mtype=%d group=%d max=%d\n", mtype, group, rte->nrrgroups ); + *more = 0; + return -1; + } + + if( (rrg = rte->rrgroups[group]) == NULL ) { + //if( DEBUG ) fprintf( stderr, ">>>> rrg not found for type = %d\n", mtype ); + *more = 0; // groups are inserted contig, so nothing should be after a nil pointer + return -1; + } + + *more = group < rte->nrrgroups-1 ? (rte->rrgroups[group+1] != NULL): 0; // more if something in next group slot + + switch( rrg->nused ) { + case 0: // nothing allocated, just punt + //if( DEBUG ) fprintf( stderr, ">>>> nothing allocated for the rrg\n" ); + return -1; + + case 1: // exactly one, no rr to deal with and more is not possible even if fanout > 1 + nn_sock = rrg->epts[0]->nn_sock; + ep = rrg->epts[0]; + break; + + default: // need to pick one and adjust rr counts + ep = rrg->epts[rrg->ep_idx]; + nn_sock = rrg->epts[rrg->ep_idx++]->nn_sock; + if( rrg->ep_idx >= rrg->nused ) { + rrg->ep_idx = 0; + } + break; + } + + if( ! ep->open ) { // not connected + if( ep->addr == NULL ) { // name didn't resolve before, try again + ep->addr = uta_h2ip( ep->name ); + } + ep->nn_sock = nn_sock = uta_link2( ep->addr ); + ep->open = ep->nn_sock >= 0; + if( DEBUG ) fprintf( stderr, "[DBUG] epsock_rr: connection state to %s: %s\n", ep->name, nn_sock >= 0 ? "[OK]" : "[FAIL]" ); + } + + return nn_sock; +} + + +#endif diff --git a/src/nanomsg/src/sr_static.c b/src/nanomsg/src/sr_static.c new file mode 100644 index 0000000..cddb662 --- /dev/null +++ b/src/nanomsg/src/sr_static.c @@ -0,0 +1,290 @@ +// :vi sw=4 ts=4 noet: +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + +/* + Mnemonic: sr_static.c + Abstract: These are static send/receive related functions. + + (broken out of rmr.c) + Author: E. Scott Daniels + Date: 13 February 2019 +*/ + +#ifndef _sr_static_c +#define _sr_static_c + +/* +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "rmr.h" // things the users see +#include "rmr_private.h" // things that we need too +#include "rmr_symtab.h" + +#include "ring_static.c" // message ring support +#include "rt_generic_static.c" // generic route table (not nng/nano specific) +#include "rtable_static.c" // route table things (nano specific) +#include "tools_static.c" +*/ + + +/* + Alloc a new nano zero copy buffer and put into msg. If msg is nil, then we will alloc + a new message struct as well. Size is the size of the zc buffer to allocate (not + including our header). If size is 0, then the buffer allocated is the size previously + allocated (if msg is !nil) or the default size given at initialisation). +*/ +static rmr_mbuf_t* alloc_zcmsg( uta_ctx_t* ctx, rmr_mbuf_t* msg, int size, int state ) { + int mlen; + + mlen = sizeof( uta_mhdr_t ); // figure size should we not have a msg buffer + mlen += (size > 0 ? size : ctx->max_plen); // add user requested size or size set during init + + if( msg == NULL ) { + msg = (rmr_mbuf_t *) malloc( sizeof *msg ); + if( msg == NULL ) { + fprintf( stderr, "[CRIT] rmr_alloc_zc: cannot get memory for message\n" ); + exit( 1 ); + } + } else { + mlen = msg->alloc_len; // msg given, allocate the same size as before + } + + memset( msg, 0, sizeof( *msg ) ); + + if( (msg->header = (uta_mhdr_t *) nn_allocmsg( mlen, 0 )) == NULL ) { // this will be released on send, so DO NOT free + fprintf( stderr, "[CRIT] rmr_alloc_zc: cannot get memory for zero copy buffer: %d\n", errno ); + exit( 1 ); + } + + ((uta_mhdr_t *) msg->header)->rmr_ver = RMR_MSG_VER; // version info should we need to recognised old style messages someday + msg->len = 0; // length of data in the payload + msg->alloc_len = mlen; // length of allocated payload + msg->payload = msg->header + sizeof( uta_mhdr_t ); // point past header to payload (single buffer allocation above) + msg->xaction = ((uta_mhdr_t *)msg->header)->xid; // point at transaction id in header area + msg->state = state; // fill in caller's state (likely the state of the last operation) + msg->flags |= MFL_ZEROCOPY; // this is a zerocopy sendable message + strncpy( (char *) ((uta_mhdr_t *)msg->header)->src, ctx->my_name, RMR_MAX_SID ); + + if( DEBUG > 1 ) fprintf( stderr, "[DBUG] alloc_zcmsg mlen = %d size=%d mpl=%d flags=%02x %p m=%p @%p\n", mlen, size, ctx->max_plen, msg->flags, &msg->flags, msg, msg->header ); + + return msg; +} + +/* + This will clone a message into a new zero copy buffer and return the cloned message. +*/ +static inline rmr_mbuf_t* clone_msg( rmr_mbuf_t* old_msg ) { + rmr_mbuf_t* nm; // new message buffer + int mlen; + + if( old_msg == NULL ) { + return NULL; + } + + nm = (rmr_mbuf_t *) malloc( sizeof *nm ); + if( nm == NULL ) { + fprintf( stderr, "[CRIT] rmr_clone: cannot get memory for message buffer\n" ); + exit( 1 ); + } + memset( nm, 0, sizeof( *nm ) ); + + mlen = old_msg->alloc_len; // length allocated before + if( (nm->header = (uta_mhdr_t *) nn_allocmsg( mlen, 0 )) == NULL ) { // this will be released on send, so DO NOT free + fprintf( stderr, "[CRIT] rmr_clone: cannot get memory for zero copy buffer: %d\n", errno ); + exit( 1 ); + } + + nm->mtype = old_msg->mtype; + nm->len = old_msg->len; // length of data in the payload + nm->alloc_len = mlen; // length of allocated payload + nm->payload = nm->header + sizeof( uta_mhdr_t ); // point past header to payload (single buffer allocation above) + nm->xaction = ((uta_mhdr_t *)nm->header)->xid; // point at transaction id in header area + nm->state = old_msg->state; // fill in caller's state (likely the state of the last operation) + nm->flags |= MFL_ZEROCOPY; // this is a zerocopy sendable message + memcpy( ((uta_mhdr_t *)nm->header)->src, ((uta_mhdr_t *)old_msg->header)->src, RMR_MAX_SID ); + memcpy( nm->payload, old_msg->payload, old_msg->len ); + + return nm; +} + +/* + This is the receive work horse used by the outer layer receive functions. + It waits for a message to be received on our listen socket. If old msg + is passed in, the we assume we can use it instead of allocating a new + one, else a new block of memory is allocated. + + This allocates a zero copy message so that if the user wishes to call + uta_rts_msg() the send is zero copy. +*/ +static rmr_mbuf_t* rcv_msg( uta_ctx_t* ctx, rmr_mbuf_t* old_msg ) { + int nn_sock; // endpoint socket for send + int state; + rmr_mbuf_t* msg = NULL; // msg received + uta_mhdr_t* hdr; + + if( old_msg ) { + msg = old_msg; + } else { + msg = alloc_zcmsg( ctx, NULL, RMR_MAX_RCV_BYTES, RMR_OK ); // will abort on failure, no need to check + } + + msg->state = nn_recv( ctx->nn_sock, msg->header, msg->alloc_len, NO_FLAGS ); // total space (header + payload len) allocated + if( msg->state > (int) sizeof( uta_mhdr_t ) ) { // we need more than just a header here + hdr = (uta_mhdr_t *) msg->header; + msg->len = ntohl( hdr->plen ); // length of data in the payload (likely < payload size) + if( msg->len > msg->state - sizeof( uta_mhdr_t ) ) { + fprintf( stderr, "[WARN] rmr_rcv indicated payload length < rcvd payload: expected %d got %ld\n", + msg->len, msg->state - sizeof( uta_mhdr_t ) ); + } + msg->mtype = ntohl( hdr->mtype ); // capture and convert from network order to local order + msg->state = RMR_OK; + msg->flags |= MFL_ADDSRC; // turn on so if user app tries to send this buffer we reset src + msg->payload = msg->header + sizeof( uta_mhdr_t ); + msg->xaction = &hdr->xid[0]; // provide user with ref to fixed space xaction id + if( DEBUG > 1 ) fprintf( stderr, "[DBUG] rcv_msg: got something: type=%d state=%d len=%d diff=%ld\n", + msg->mtype, msg->state, msg->len, msg->payload - (unsigned char *) msg->header ); + } else { + msg->len = 0; + msg->state = RMR_ERR_EMPTY; + } + + return msg; +} + + +/* + Receives a 'raw' message from a non-RMr sender (no header expected). The returned + message buffer cannot be used to send, and the length information may or may + not be correct (it is set to the length received which might be more than the + bytes actually in the payload). +*/ +static void* rcv_payload( uta_ctx_t* ctx, rmr_mbuf_t* old_msg ) { + int nn_sock; // endpoint socket for send + int state; + rmr_mbuf_t* msg = NULL; // msg received + + if( old_msg ) { + msg = old_msg; + } else { + msg = alloc_zcmsg( ctx, NULL, RMR_MAX_RCV_BYTES, RMR_OK ); // will abort on failure, no need to check + } + + msg->state = nn_recv( ctx->nn_sock, msg->header, msg->alloc_len, NO_FLAGS ); // read and state will be length + if( msg->state >= 0 ) { + msg->xaction = NULL; + msg->mtype = -1; + msg->len = msg->state; // no header; len is the entire thing received + msg->state = RMR_OK; + msg->flags = MFL_RAW; // prevent any sending of this headerless buffer + msg->payload = msg->header; + if( DEBUG > 1 ) fprintf( stderr, "[DBUG] rcv_payload: got something: type=%d state=%d len=%d\n", msg->mtype, msg->state, msg->len ); + } else { + msg->len = 0; + msg->state = RMR_ERR_EMPTY; + msg->payload = NULL; + msg->xaction = NULL; + msg->mtype = -1; + } + + return msg; +} + +/* + This does the hard work of actually sending the message to the given socket. On success, + a new message struct is returned. On error, the original msg is returned with the state + set to a reasonable value. If the message being sent as MFL_NOALLOC set, then a new + buffer will not be allocated and returned (mostly for call() interal processing since + the return message from call() is a received buffer, not a new one). + + Called by rmr_send_msg() and rmr_rts_msg(). +*/ +static rmr_mbuf_t* send_msg( uta_ctx_t* ctx, rmr_mbuf_t* msg, int nn_sock ) { + int state; + uta_mhdr_t* hdr; + + // future: ensure that application did not overrun the XID buffer; last byte must be 0 + + hdr = (uta_mhdr_t *) msg->header; + hdr->mtype = htonl( msg->mtype ); // stash type/len in network byte order for transport + hdr->plen = htonl( msg->len ); + + if( msg->flags & MFL_ADDSRC ) { // buffer was allocated as a receive buffer; must add our source + strncpy( (char *) ((uta_mhdr_t *)msg->header)->src, ctx->my_name, RMR_MAX_SID ); // must overlay the source to be ours + } + + if( msg->flags & MFL_ZEROCOPY ) { // faster sending with zcopy buffer + if( (state = nn_send( nn_sock, &msg->header, NN_MSG, NN_DONTWAIT )) < 0 ) { + msg->state = state; + } else { + msg->header = NULL; // nano frees; don't risk accessing later by mistake + } + } else { + if( (state = nn_send( nn_sock, msg->header, sizeof( uta_mhdr_t ) + msg->len, NN_DONTWAIT )) < 0 ) { + msg->state = state; + } + } + + // future: if nano sends bytes, but less than mlen, then what to do? + if( msg->state >= 0 ) { // successful send + if( !(msg->flags & MFL_NOALLOC) ) { // if noalloc is set, then caller doesn't want a new buffer + return alloc_zcmsg( ctx, msg, 0, RMR_OK ); // preallocate a zero-copy buffer and return msg + } else { + rmr_free_msg( msg ); // not wanting a meessage back, trash this one + return NULL; + } + } else { // send failed -- return original message + if( errno == EAGAIN ) { + msg->state = RMR_ERR_RETRY; // some wrappers can't see errno, make this obvious + } else { + msg->state = RMR_ERR_SENDFAILED; // errno will have nano reason + } + if( DEBUG ) fprintf( stderr, "[DBUG] send failed: %s\n", strerror( errno ) ); + } + + return msg; +} + + +/* + A generic wrapper to the real send to keep wormhole stuff agnostic. + We assume the wormhole function vetted the buffer so we don't have to. +*/ +static rmr_mbuf_t* send2ep( uta_ctx_t* ctx, endpoint_t* ep, rmr_mbuf_t* msg ) { + return send_msg( ctx, msg, ep->nn_sock ); +} + +#endif diff --git a/src/nng/CMakeLists.txt b/src/nng/CMakeLists.txt new file mode 100644 index 0000000..511d0bb --- /dev/null +++ b/src/nng/CMakeLists.txt @@ -0,0 +1,35 @@ + +# +#================================================================================== +# Copyright (c) 2019 Nokia +# Copyright (c) 2018-2019 AT&T Intellectual Property. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#================================================================================== +# + +# just to be clear, this generates object files, not a library as the CM command implies +add_library( nng_objects OBJECT src/rmr_nng.c ) + +if( need_ext ) + add_dependencies( nng_objects ext_nng ) # if we are building, ensure built first +endif() + +target_include_directories (nng_objects PUBLIC + $ + $ + $ + $ + PRIVATE src) + +# $ diff --git a/src/nng/include/rmr_nng_private.h b/src/nng/include/rmr_nng_private.h new file mode 100644 index 0000000..83db500 --- /dev/null +++ b/src/nng/include/rmr_nng_private.h @@ -0,0 +1,120 @@ +// : vi ts=4 sw=4 noet : +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + +/* + Mnemonic: uta_nng_private.h + Abstract: Private header information for the uta nng library functions. + These are structs which have specific NNG types; anything that + does not can be included in the common/rmr_agnostic.h header. + + Author: E. Scott Daniels + Date: 31 November 2018 + + Mods: 28 Feb 2019 -- moved the majority to the agnostic header. +*/ + +#ifndef _uta_private_h +#define _uta_private_h + +/* + Manages an endpoint. Type def for this is defined in agnostic. +*/ +struct endpoint { + char* name; // end point name (symtab reference) + char* proto; // connection proto (should only be TCP, but future might bring others) + char* addr; // address used for connection + nng_socket nn_sock; // the nano-msg socket to write to for this entry + nng_dialer dialer; // the connection specific information (retry timout etc) + int open; // set to true if we've connected as socket cannot be checked directly) +}; + +/* + Epoll information needed for the rmr_torcv_msg() funciton +*/ +typedef struct epoll_stuff { + struct epoll_event events[1]; // wait on 10 possible events + struct epoll_event epe; // event definition for event to listen to + int ep_fd; // file des from nng + int nng_fd; // fd from nng +} epoll_stuff_t; + +/* + Context describing our world. Should be returned to user programme on + call to initialise, and passed as first parm on all calls to other + visible functions. + + The typedef is declared in the agnostic header. +*/ +struct uta_ctx { + char* my_name; // dns name of this host to set in sender field of a message + int shutdown; // thread notification if we need to tell them to stop + int max_mlen; // max message length payload+header + int max_plen; // max payload length + int flags; // CTXFL_ constants + int nrtele; // number of elements in the routing table + int send_retries; // number of retries send_msg() should attempt if eagain/timeout indicated by nng + nng_socket nn_sock; // our general listen socket + route_table_t* rtable; // the active route table + route_table_t* old_rtable; // the previously used rt, sits here to allow for draining + route_table_t* new_rtable; // route table under construction + if_addrs_t* ip_list; // list manager of the IP addresses that are on our known interfaces + void* mring; // ring where msgs are queued while waiting for a call response msg + + char* rtg_addr; // addr/port of the route table generation publisher + int rtg_port; // the port that the rtg listens on + + wh_mgt_t* wormholes; // management of user opened wormholes + epoll_stuff_t* eps; // epoll information needed for the rcv with timeout call + + pthread_t rtc_th; // thread info for the rtc listener +}; + + + +/* + Static prototypes for functions located here. All common protos are in the + agnostic header file. +*/ + +// --- initialisation and housekeeping ------- +static void* init( char* uproto_port, int max_msg_size, int flags ); +static void free_ctx( uta_ctx_t* ctx ); + +// --- rt table things --------------------------- +static int uta_link2( char* target, nng_socket* nn_sock, nng_dialer* dialer ); +static int rt_link2_ep( endpoint_t* ep ); +static int uta_epsock_byname( route_table_t* rt, char* ep_name, nng_socket* nn_sock ); +static int uta_epsock_rr( route_table_t *rt, int mtype, int group, int* more, nng_socket* nn_sock ); +static inline int xlate_nng_state( int state, int def_state ); + + +// --- msg --------------------------------------- +static rmr_mbuf_t* alloc_zcmsg( uta_ctx_t* ctx, rmr_mbuf_t* msg, int size, int state ); +static rmr_mbuf_t* alloc_mbuf( uta_ctx_t* ctx, int state ); +static void ref_tpbuf( rmr_mbuf_t* msg, size_t alen ) ; +static inline rmr_mbuf_t* clone_msg( rmr_mbuf_t* old_msg ); +static rmr_mbuf_t* rcv_msg( uta_ctx_t* ctx, rmr_mbuf_t* old_msg ); +static void* rcv_payload( uta_ctx_t* ctx, rmr_mbuf_t* old_msg ); +static rmr_mbuf_t* send_msg( uta_ctx_t* ctx, rmr_mbuf_t* msg, nng_socket nn_sock, int retries ); +static rmr_mbuf_t* send2ep( uta_ctx_t* ctx, endpoint_t* ep, rmr_mbuf_t* msg ); + + + +#endif diff --git a/src/nng/src/rmr_nng.c b/src/nng/src/rmr_nng.c new file mode 100644 index 0000000..9a9a043 --- /dev/null +++ b/src/nng/src/rmr_nng.c @@ -0,0 +1,750 @@ +// : vi ts=4 sw=4 noet : +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + +/* + Mnemonic: rmr_nng.c + Abstract: This is the compile point for the nng version of the rmr + library (formarly known as uta, so internal function names + are likely still uta_*) + + With the exception of the symtab portion of the library, + RMr is built with a single compile so as to "hide" the + internal functions as statics. Because they interdepend + on each other, and CMake has issues with generating two + different wormhole objects from a single source, we just + pull it all together with a centralised comple using + includes. + + Future: the API functions at this point can be separated + into a common source module. + + Author: E. Scott Daniels + Date: 1 February 2019 +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +#include "rmr.h" // things the users see +#include "rmr_agnostic.h" // agnostic things (must be included before private) +#include "rmr_nng_private.h" // things that we need too +#include "rmr_symtab.h" + +#include "ring_static.c" // message ring support +#include "rt_generic_static.c" // route table things not transport specific +#include "rtable_nng_static.c" // route table things -- transport specific +#include "rtc_static.c" // route table collector +#include "tools_static.c" +#include "sr_nng_static.c" // send/receive static functions +#include "wormholes.c" // wormhole api externals and related static functions (must be LAST!) + + +//------------------------------------------------------------------------------ + + +/* + Clean up a context. +*/ +static void free_ctx( uta_ctx_t* ctx ) { + if( ctx ) { + if( ctx->rtg_addr ) { + free( ctx->rtg_addr ); + } + } +} + +// --------------- public functions -------------------------------------------------------------------------- + +/* + Returns the size of the payload (bytes) that the msg buffer references. + Len in a message is the number of bytes which were received, or should + be transmitted, however, it is possible that the mbuf was allocated + with a larger payload space than the payload length indicates; this + function returns the absolute maximum space that the user has available + in the payload. On error (bad msg buffer) -1 is returned and errno should + indicate the rason. +*/ +extern int rmr_payload_size( rmr_mbuf_t* msg ) { + if( msg == NULL || msg->header == NULL ) { + errno = EINVAL; + return -1; + } + + errno = 0; + return msg->alloc_len - sizeof( uta_mhdr_t ); // figure size should we not have a msg buffer +} + +/* + Allocates a send message as a zerocopy message allowing the underlying message protocol + to send the buffer without copy. +*/ +extern rmr_mbuf_t* rmr_alloc_msg( void* vctx, int size ) { + uta_ctx_t* ctx; + rmr_mbuf_t* m; + + if( (ctx = (uta_ctx_t *) vctx) == NULL ) { + return NULL; + } + + m = alloc_zcmsg( ctx, NULL, size, 0 ); + return m; +} + +/* + Return the message to the available pool, or free it outright. +*/ +extern void rmr_free_msg( rmr_mbuf_t* mbuf ) { + if( mbuf == NULL ) { + return; + } + + if( mbuf->header ) { + if( mbuf->flags & MFL_ZEROCOPY ) { + //nng_free( (void *) mbuf->header, mbuf->alloc_len ); + if( mbuf->tp_buf ) { + nng_msg_free( mbuf->tp_buf ); + } + } + } + + free( mbuf ); +} + +/* + send message with maximum timeout. + Accept a message and send it to an endpoint based on message type. + If NNG reports that the send attempt timed out, or should be retried, + RMr will retry for approximately max_to microseconds; rounded to the next + higher value of 10. + + Allocates a new message buffer for the next send. If a message type has + more than one group of endpoints defined, then the message will be sent + in round robin fashion to one endpoint in each group. + + CAUTION: this is a non-blocking send. If the message cannot be sent, then + it will return with an error and errno set to eagain. If the send is + a limited fanout, then the returned status is the status of the last + send attempt. + +*/ +extern rmr_mbuf_t* rmr_mtosend_msg( void* vctx, rmr_mbuf_t* msg, int max_to ) { + nng_socket nn_sock; // endpoint socket for send + uta_ctx_t* ctx; + int group; // selected group to get socket for + int send_again; // true if the message must be sent again + rmr_mbuf_t* clone_m; // cloned message for an nth send + int sock_ok; // got a valid socket from round robin select + + if( (ctx = (uta_ctx_t *) vctx) == NULL || msg == NULL ) { // bad stuff, bail fast + errno = EINVAL; // if msg is null, this is their clue + if( msg != NULL ) { + msg->state = RMR_ERR_BADARG; + errno = EINVAL; // must ensure it's not eagain + } + return msg; + } + + errno = 0; // clear; nano might set, but ensure it's not left over if it doesn't + if( msg->header == NULL ) { + fprintf( stderr, "rmr_send_msg: ERROR: message had no header\n" ); + msg->state = RMR_ERR_NOHDR; + errno = EBADMSG; // must ensure it's not eagain + return msg; + } + + if( max_to < 0 ) { + max_to = ctx->send_retries; // convert to retries + } + + send_again = 1; // force loop entry + group = 0; // always start with group 0 + + while( send_again ) { + sock_ok = uta_epsock_rr( ctx->rtable, msg->mtype, group, &send_again, &nn_sock ); // round robin sel epoint; again set if mult groups + if( DEBUG ) fprintf( stderr, "[DBUG] send msg: type=%d again=%d group=%d len=%d sock_ok=%d\n", + msg->mtype, send_again, group, msg->len, sock_ok ); + group++; + + if( ! sock_ok ) { + msg->state = RMR_ERR_NOENDPT; + errno = ENXIO; // must ensure it's not eagain + return msg; // caller can resend (maybe) or free + } + + if( send_again ) { + clone_m = clone_msg( msg ); // must make a copy as once we send this message is not available + if( DEBUG ) fprintf( stderr, "[DBUG] msg cloned: type=%d len=%d\n", msg->mtype, msg->len ); + msg->flags |= MFL_NOALLOC; // send should not allocate a new buffer + msg = send_msg( ctx, msg, nn_sock, max_to ); // do the hard work, msg should be nil on success + /* + if( msg ) { + // error do we need to count successes/errors, how to report some success, esp if last fails? + } + */ + + msg = clone_m; // clone will be the next to send + } else { + msg = send_msg( ctx, msg, nn_sock, max_to ); // send the last, and allocate a new buffer; drops the clone if it was + } + } + + return msg; // last message caries the status of last/only send attempt +} + +/* + Send with default max timeout as is set in the context. + See rmr_mtosend_msg() for more details on the parameters. + See rmr_stimeout() for info on setting the default timeout. +*/ +extern rmr_mbuf_t* rmr_send_msg( void* vctx, rmr_mbuf_t* msg ) { + return rmr_mtosend_msg( vctx, msg, -1 ); // retries < uses default from ctx +} + +/* + Return to sender allows a message to be sent back to the endpoint where it originated. + The source information in the message is used to select the socket on which to write + the message rather than using the message type and round-robin selection. This + should return a message buffer with the state of the send operation set. On success + (state is RMR_OK, the caller may use the buffer for another receive operation), and on + error it can be passed back to this function to retry the send if desired. On error, + errno will liklely have the failure reason set by the nng send processing. + The following are possible values for the state in the message buffer: + + Message states returned: + RMR_ERR_BADARG - argument (context or msg) was nil or invalid + RMR_ERR_NOHDR - message did not have a header + RMR_ERR_NOENDPT- an endpoint to send the message to could not be determined + RMR_ERR_SENDFAILED - send failed; errno has nano error code + RMR_ERR_RETRY - the reqest failed but should be retried (EAGAIN) + + A nil message as the return value is rare, and generally indicates some kind of horrible + failure. The value of errno might give a clue as to what is wrong. + + CAUTION: + Like send_msg(), this is non-blocking and will return the msg if there is an errror. + The caller must check for this and handle. +*/ +extern rmr_mbuf_t* rmr_rts_msg( void* vctx, rmr_mbuf_t* msg ) { + nng_socket nn_sock; // endpoint socket for send + uta_ctx_t* ctx; + int state; + uta_mhdr_t* hdr; + char* hold_src; // we need the original source if send fails + int sock_ok; // true if we found a valid endpoint socket + + if( (ctx = (uta_ctx_t *) vctx) == NULL || msg == NULL ) { // bad stuff, bail fast + errno = EINVAL; // if msg is null, this is their clue + if( msg != NULL ) { + msg->state = RMR_ERR_BADARG; + } + return msg; + } + + errno = 0; // at this point any bad state is in msg returned + if( msg->header == NULL ) { + fprintf( stderr, "[ERR] rmr_send_msg: message had no header\n" ); + msg->state = RMR_ERR_NOHDR; + return msg; + } + + sock_ok = uta_epsock_byname( ctx->rtable, (char *) ((uta_mhdr_t *)msg->header)->src, &nn_sock ); // socket of specific endpoint + if( ! sock_ok ) { + msg->state = RMR_ERR_NOENDPT; + return msg; // preallocated msg can be reused since not given back to nn + } + + msg->state = RMR_OK; // ensure it is clear before send + hold_src = strdup( (char *) ((uta_mhdr_t *)msg->header)->src ); // the dest where we're returning the message to + strncpy( (char *) ((uta_mhdr_t *)msg->header)->src, ctx->my_name, RMR_MAX_SID ); // must overlay the source to be ours + msg = send_msg( ctx, msg, nn_sock, -1 ); + if( msg ) { + strncpy( (char *) ((uta_mhdr_t *)msg->header)->src, hold_src, RMR_MAX_SID ); // always return original source so rts can be called again + msg->flags |= MFL_ADDSRC; // if msg given to send() it must add source + } + + free( hold_src ); + return msg; +} + +/* + Call sends the message based on message routing using the message type, and waits for a + response message to arrive with the same transaction id that was in the outgoing message. + If, while wiating for the expected response, messages are received which do not have the + desired transaction ID, they are queued. Calls to uta_rcv_msg() will dequeue them in the + order that they were received. + + Normally, a message struct pointer is returned and msg->state must be checked for RMR_OK + to ensure that no error was encountered. If the state is UTA_BADARG, then the message + may be resent (likely the context pointer was nil). If the message is sent, but no + response is received, a nil message is returned with errno set to indicate the likley + issue: + ETIMEDOUT -- too many messages were queued before reciving the expected response + ENOBUFS -- the queued message ring is full, messages were dropped + EINVAL -- A parameter was not valid + EAGAIN -- the underlying message system wsa interrupted or the device was busy; + user should call this function with the message again. + + + QUESTION: should user specify the number of messages to allow to queue? +*/ +extern rmr_mbuf_t* rmr_call( void* vctx, rmr_mbuf_t* msg ) { + uta_ctx_t* ctx; + unsigned char expected_id[RMR_MAX_XID+1]; // the transaction id in the message; we wait for response with same ID + + if( (ctx = (uta_ctx_t *) vctx) == NULL || msg == NULL ) { // bad stuff, bail fast + if( msg != NULL ) { + msg->state = RMR_ERR_BADARG; + } + return msg; + } + + memcpy( expected_id, msg->xaction, RMR_MAX_XID ); + expected_id[RMR_MAX_XID] = 0; // ensure it's a string + if( DEBUG > 1 ) fprintf( stderr, "[DBUG] rmr_call is making call, waiting for (%s)\n", expected_id ); + errno = 0; + msg->flags |= MFL_NOALLOC; // we don't need a new buffer from send + + msg = rmr_send_msg( ctx, msg ); + if( msg ) { // msg should be nil, if not there was a problem; return buffer to user + if( msg->state != RMR_ERR_RETRY ) { + msg->state = RMR_ERR_CALLFAILED; // errno not available to all wrappers; don't stomp if marked retry + } + return msg; + } + + return rmr_rcv_specific( ctx, NULL, (char *) expected_id, 20 ); // wait for msg allowing 20 to queue ahead +} + +/* + The outward facing receive function. When invoked it will pop the oldest message + from the receive ring, if any are queued, and return it. If the ring is empty + then the receive function is invoked to wait for the next message to arrive (blocking). + + If old_msg is provided, it will be populated (avoiding lots of free/alloc cycles). If + nil, a new one will be allocated. However, the caller should NOT expect to get the same + struct back (if a queued message is returned the message struct will be different). +*/ +extern rmr_mbuf_t* rmr_rcv_msg( void* vctx, rmr_mbuf_t* old_msg ) { + uta_ctx_t* ctx; + rmr_mbuf_t* qm; // message that was queued on the ring + + if( (ctx = (uta_ctx_t *) vctx) == NULL ) { + if( old_msg != NULL ) { + old_msg->state = RMR_ERR_BADARG; + } + errno = EINVAL; + return old_msg; + } + errno = 0; + + qm = (rmr_mbuf_t *) uta_ring_extract( ctx->mring ); // pop if queued + if( qm != NULL ) { + if( old_msg ) { + rmr_free_msg( old_msg ); // future: push onto a free list??? + } + + return qm; + } + + return rcv_msg( ctx, old_msg ); // nothing queued, wait for one +} + +/* + This implements a receive with a timeout via epoll. Mostly this is for + wrappers as native C applications can use epoll directly and will not have + to depend on this. +*/ +extern rmr_mbuf_t* rmr_torcv_msg( void* vctx, rmr_mbuf_t* old_msg, int ms_to ) { + struct epoll_stuff* eps; // convience pointer + uta_ctx_t* ctx; + rmr_mbuf_t* qm; // message that was queued on the ring + int nready; + rmr_mbuf_t* msg; + + if( (ctx = (uta_ctx_t *) vctx) == NULL ) { + if( old_msg != NULL ) { + old_msg->state = RMR_ERR_BADARG; + } + errno = EINVAL; + return old_msg; + } + + qm = (rmr_mbuf_t *) uta_ring_extract( ctx->mring ); // pop if queued + if( qm != NULL ) { + if( old_msg ) { + rmr_free_msg( old_msg ); // future: push onto a free list??? + } + + return qm; + } + + if( (eps = ctx->eps) == NULL ) { // set up epoll on first call + eps = malloc( sizeof *eps ); + + if( (eps->ep_fd = epoll_create1( 0 )) < 0 ) { + fprintf( stderr, "[FAIL] unable to create epoll fd: %d\n", errno ); + free( eps ); + return NULL; + } + + eps->nng_fd = rmr_get_rcvfd( ctx ); + eps->epe.events = EPOLLIN; + eps->epe.data.fd = eps->nng_fd; + + if( epoll_ctl( eps->ep_fd, EPOLL_CTL_ADD, eps->nng_fd, &eps->epe ) != 0 ) { + fprintf( stderr, "[FAIL] epoll_ctl status not 0 : %s\n", strerror( errno ) ); + free( eps ); + return NULL; + } + + ctx->eps = eps; + } + + if( old_msg ) { + msg = old_msg; + } else { + msg = alloc_zcmsg( ctx, NULL, RMR_MAX_RCV_BYTES, RMR_OK ); // will abort on failure, no need to check + } + + if( ms_to < 0 ) { + ms_to = 0; + } + + nready = epoll_wait( eps->ep_fd, eps->events, 1, ms_to ); // block until something or timedout + if( nready <= 0 ) { // we only wait on ours, so we assume ready means it's ours + msg->state = RMR_ERR_TIMEOUT; + } else { + return rcv_msg( ctx, msg ); // receive it and return it + } + + return msg; // return empty message with state set +} + +/* + This blocks until the message with the 'expect' ID is received. Messages which are received + before the expected message are queued onto the message ring. The function will return + a nil message and set errno to ETIMEDOUT if allow2queue messages are received before the + expected message is received. If the queued message ring fills a nil pointer is returned + and errno is set to ENOBUFS. + + Generally this will be invoked only by the call() function as it waits for a response, but + it is exposed to the user application as three is no reason not to. +*/ +extern rmr_mbuf_t* rmr_rcv_specific( void* vctx, rmr_mbuf_t* msg, char* expect, int allow2queue ) { + uta_ctx_t* ctx; + int queued = 0; // number we pushed into the ring + int exp_len = 0; // length of expected ID + + if( (ctx = (uta_ctx_t *) vctx) == NULL ) { + if( msg != NULL ) { + msg->state = RMR_ERR_BADARG; + } + errno = EINVAL; + return msg; + } + + errno = 0; + + if( expect == NULL || ! *expect ) { // nothing expected if nil or empty string, just receive + return rmr_rcv_msg( ctx, msg ); + } + + exp_len = strlen( expect ); + if( exp_len > RMR_MAX_XID ) { + exp_len = RMR_MAX_XID; + } + if( DEBUG ) fprintf( stderr, "[DBUG] rcv_specific waiting for id=%s\n", expect ); + + while( queued < allow2queue ) { + msg = rcv_msg( ctx, msg ); // hard wait for next + if( msg->state == RMR_OK ) { + if( memcmp( msg->xaction, expect, exp_len ) == 0 ) { // got it -- return it + if( DEBUG ) fprintf( stderr, "[DBUG] rcv-specific matched (%s); %d messages were queued\n", msg->xaction, queued ); + return msg; + } + + if( ! uta_ring_insert( ctx->mring, msg ) ) { // just queue, error if ring is full + if( DEBUG > 1 ) fprintf( stderr, "[DBUG] rcv_specific ring is full\n" ); + errno = ENOBUFS; + return NULL; + } + + if( DEBUG ) fprintf( stderr, "[DBUG] rcv_specific queued message type=%d\n", msg->mtype ); + queued++; + msg = NULL; + } + } + + if( DEBUG ) fprintf( stderr, "[DBUG] rcv_specific timeout waiting for %s\n", expect ); + errno = ETIMEDOUT; + return NULL; +} + +// CAUTION: these are not supported as they must be set differently (between create and open) in NNG. +// until those details are worked out, these generate a warning. +/* + Set send timeout. The value time is assumed to be microseconds. The timeout is the + rough maximum amount of time that RMr will block on a send attempt when the underlying + mechnism indicates eagain or etimeedout. All other error conditions are reported + without this delay. Setting a timeout of 0 causes no retries to be attempted in + RMr code. Setting a timeout of 1 causes RMr to spin up to 10K retries before returning, + but without issuing a sleep. If timeout is > 1, then RMr will issue a sleep (1us) + after every 10K send attempts until the time value is reached. Retries are abandoned + if NNG returns anything other than NNG_AGAIN or NNG_TIMEDOUT. + + The default, if this function is not used, is 1; meaning that RMr will retry, but will + not enter a sleep. In all cases the caller should check the status in the message returned + after a send call. + + Returns -1 if the context was invalid; RMR_OK otherwise. +*/ +extern int rmr_set_stimeout( void* vctx, int time ) { + uta_ctx_t* ctx; + + if( (ctx = (uta_ctx_t *) vctx) == NULL ) { + return -1; + } + + if( time < 0 ) { + time = 0; + } + + ctx->send_retries = time; + return RMR_OK; +} + +/* + Set receive timeout -- not supported in nng implementation +*/ +extern int rmr_set_rtimeout( void* vctx, int time ) { + fprintf( stderr, "[WRN] Current implementation of RMR ontop of NNG does not support setting a receive timeout\n" ); + return 0; +} + + +/* + This is the actual init workhorse. The user visible function meerly ensures that the + calling programme does NOT set any internal flags that are supported, and then + invokes this. Internal functions (the route table collector) which need additional + open ports without starting additional route table collectors, will invoke this + directly with the proper flag. +*/ +static void* init( char* uproto_port, int max_msg_size, int flags ) { + static int announced = 0; + uta_ctx_t* ctx = NULL; + char bind_info[NNG_MAXADDRLEN]; // bind info + char* proto = "tcp"; // pointer into the proto/port string user supplied + char* port; + char* interface = NULL; // interface to bind to (from RMR_BIND_IF, 0.0.0.0 if not defined) + char* proto_port; + char wbuf[1024]; // work buffer + char* tok; // pointer at token in a buffer + int state; + + if( ! announced ) { + fprintf( stderr, "[INFO] ric message routing library on NNG (%s %s.%s.%s built: %s)\n", + QUOTE_DEF(GIT_ID), QUOTE_DEF(MAJOR_VER), QUOTE_DEF(MINOR_VER), QUOTE_DEF(PATCH_VER), __DATE__ ); + announced = 1; + } + + errno = 0; + if( uproto_port == NULL ) { + proto_port = strdup( DEF_COMM_PORT ); + } else { + proto_port = strdup( uproto_port ); // so we can modify it + } + + if( (ctx = (uta_ctx_t *) malloc( sizeof( uta_ctx_t ) )) == NULL ) { + errno = ENOMEM; + return NULL; + } + memset( ctx, 0, sizeof( uta_ctx_t ) ); + + ctx->send_retries = 1; // default is not to sleep at all; RMr will retry about 10K times before returning + ctx->mring = uta_mk_ring( 128 ); // message ring to hold asynch msgs received while waiting for call response + + ctx->max_plen = RMR_MAX_RCV_BYTES + sizeof( uta_mhdr_t ); // default max buffer size + if( max_msg_size > 0 ) { + if( max_msg_size <= ctx->max_plen ) { // user defined len can be smaller + ctx->max_plen = max_msg_size; + } else { + fprintf( stderr, "[WRN] rmr_init: attempt to set max payload len > than allowed maximum; capped at %d bytes\n", ctx->max_plen ); + } + } + + ctx->max_mlen = ctx->max_plen + sizeof( uta_mhdr_t ); + + // we're using a listener to get rtg updates, so we do NOT need this. + //uta_lookup_rtg( ctx ); // attempt to fill in rtg info; rtc will handle missing values/errors + + if( nng_pull0_open( &ctx->nn_sock ) != 0 ) { // and assign the mode + fprintf( stderr, "[CRI] rmr_init: unable to initialise nng listen (pull) socket: %d\n", errno ); + free_ctx( ctx ); + return NULL; + } + + if( (port = strchr( proto_port, ':' )) != NULL ) { + if( port == proto_port ) { // ":1234" supplied; leave proto to default and point port correctly + port++; + } else { + *(port++) = 0; // term proto string and point at port string + proto = proto_port; // user supplied proto so point at it rather than default + } + } else { + port = proto_port; // assume something like "1234" was passed + } + + if( (gethostname( wbuf, sizeof( wbuf ) )) != 0 ) { + fprintf( stderr, "[CRI] rmr_init: cannot determine localhost name: %s\n", strerror( errno ) ); + return NULL; + } + if( (tok = strchr( wbuf, '.' )) != NULL ) { + *tok = 0; // we don't keep domain portion + } + ctx->my_name = (char *) malloc( sizeof( char ) * RMR_MAX_SID ); + if( snprintf( ctx->my_name, RMR_MAX_SID, "%s:%s", wbuf, port ) >= RMR_MAX_SID ) { // our registered name is host:port + fprintf( stderr, "[CRI] rmr_init: hostname + port must be less than %d characters; %s:%s is not\n", RMR_MAX_SID, wbuf, port ); + return NULL; + } + + ctx->ip_list = mk_ip_list( port ); // suss out all IP addresses we can find on the box, and bang on our port for RT comparisons + + + + if( (interface = getenv( ENV_BIND_IF )) == NULL ) { + interface = "0.0.0.0"; + } + // NOTE: if there are options that might need to be configured, the listener must be created, options set, then started + // rather than using this generic listen() call. + snprintf( bind_info, sizeof( bind_info ), "%s://%s:%s", proto, interface, port ); + if( (state = nng_listen( ctx->nn_sock, bind_info, NULL, NO_FLAGS )) != 0 ) { + fprintf( stderr, "[CRIT] rmr_init: unable to start nng listener for %s: %s\n", bind_info, nng_strerror( state ) ); + nng_close( ctx->nn_sock ); + free_ctx( ctx ); + return NULL; + } + + if( !(flags & FL_NOTHREAD) ) { // skip if internal function that doesnt need an rtc + if( pthread_create( &ctx->rtc_th, NULL, rtc, (void *) ctx ) ) { // kick the rt collector thread + fprintf( stderr, "[WARN] rmr_init: unable to start route table collector thread: %s", strerror( errno ) ); + } + } + + free( proto_port ); + return (void *) ctx; +} + +/* + Initialise the message routing environment. Flags are one of the UTAFL_ + constants. Proto_port is a protocol:port string (e.g. tcp:1234). If default protocol + (tcp) to be used, then :port is all that is needed. + + At the moment it seems that TCP really is the only viable protocol, but + we'll allow flexibility. + + The return value is a void pointer which must be passed to most uta functions. On + error, a nil pointer is returned and errno should be set. + + Flags: + No user flags supported (needed) at the moment, but this provides for extension + without drastically changing anything. The user should invoke with RMRFL_NONE to + avoid any misbehavour as there are internal flags which are suported +*/ +extern void* rmr_init( char* uproto_port, int max_msg_size, int flags ) { + return init( uproto_port, max_msg_size, flags & UFL_MASK ); // ensure any internal flags are off +} + +/* + Return true if routing table is initialised etc. and app can send/receive. +*/ +extern int rmr_ready( void* vctx ) { + uta_ctx_t *ctx; + + if( (ctx = (uta_ctx_t *) vctx) == NULL ) { + return FALSE; + } + + if( ctx->rtable != NULL ) { + return TRUE; + } + + return FALSE; +} + +/* + Returns a file descriptor which can be used with epoll() to signal a receive + pending. The file descriptor should NOT be read from directly, nor closed, as NNG + does not support this. +*/ +extern int rmr_get_rcvfd( void* vctx ) { + uta_ctx_t* ctx; + int fd; + int state; + + if( (ctx = (uta_ctx_t *) vctx) == NULL ) { + return -1; + } + + if( (state = nng_getopt_int( ctx->nn_sock, NNG_OPT_RECVFD, &fd )) != 0 ) { + fprintf( stderr, ">>> cannot get recv fd: %s\n", nng_strerror( state ) ); + return -1; + } + + return fd; +} + + +/* + Clean up things. + + There isn't an nng_flush() per se, but we can pause, generate + a context switch, which should allow the last sent buffer to + flow. There isn't exactly an nng_term/close either, so there + isn't much we can do. +*/ +extern void rmr_close( void* vctx ) { + uta_ctx_t *ctx; + + if( (ctx = (uta_ctx_t *) vctx) == NULL ) { + return; + } + + ctx->shutdown = 1; + nng_close( ctx->nn_sock ); +} + + + diff --git a/src/nng/src/rtable_nng_static.c b/src/nng/src/rtable_nng_static.c new file mode 100644 index 0000000..8b3bf67 --- /dev/null +++ b/src/nng/src/rtable_nng_static.c @@ -0,0 +1,333 @@ +// : vi ts=4 sw=4 noet : +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + +/* + Mnemonic: rtable_nng_static.c + Abstract: Route table management functions which depend on the underlying + transport mechanism and thus cannot be included with the generic + route table functions. + + This module is designed to be included by any module (main) needing + the static/private stuff. + + Author: E. Scott Daniels + Date: 29 November 2018 +*/ + +#ifndef rtable_static_c +#define rtable_static_c + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// ----------------------------------------------------------------------------------------------------- + +/* + Establish a TCP connection to the indicated target (IP address). + Target assumed to be address:port. The new socket is returned via the + user supplied pointer so that a success/fail code is returned directly. + Return value is 0 (false) on failure, 1 (true) on success. +*/ +static int uta_link2( char* target, nng_socket* nn_sock, nng_dialer* dialer ) { + char conn_info[NNG_MAXADDRLEN]; // string to give to nano to make the connection + char* addr; + int state = FALSE; + + if( target == NULL || (addr = strchr( target, ':' )) == NULL ) { // bad address:port + fprintf( stderr, "rmr: link2: unable to create link: bad target: %s\n", target == NULL ? "" : target ); + return FALSE; + } + + if( nn_sock == NULL ) { + errno = EINVAL; + return FALSE; + } + + if( nng_push0_open( nn_sock ) != 0 ) { // and assign the mode + fprintf( stderr, "[CRI] rmr: link2: unable to initialise nanomsg push socket to: %s\n", target ); + return FALSE; + } + + snprintf( conn_info, sizeof( conn_info ), "tcp://%s", target ); + if( (state = nng_dialer_create( dialer, *nn_sock, conn_info )) != 0 ) { + fprintf( stderr, "[WARN] rmr: link2: unable to create dialer for link to target: %s: %d\n", target, errno ); + nng_close( *nn_sock ); + return FALSE; + } + + nng_dialer_setopt_ms( *dialer, NNG_OPT_RECONNMAXT, 2000 ); // cap backoff on retries to reasonable amount (2s) + nng_dialer_setopt_ms( *dialer, NNG_OPT_RECONNMINT, 100 ); // start retry 100m after last failure with 2s cap + + if( (state = nng_dialer_start( *dialer, NO_FLAGS )) != 0 ) { // can fail immediatly (unlike nanomsg) + fprintf( stderr, "[WARN] rmr: unable to create link to target: %s: %s\n", target, nng_strerror( state ) ); + nng_close( *nn_sock ); + return FALSE; + } + + if( DEBUG ) fprintf( stderr, "[INFO] rmr_link2l: dial was successful: %s\n", target ); + + return TRUE; +} + +/* + This provides a protocol independent mechanism for establishing the connection to an endpoint. + Return is true (1) if the link was opened; false on error. +*/ +static int rt_link2_ep( endpoint_t* ep ) { + if( ep == NULL ) { + return FALSE; + } + + if( ep->open ) { // already open, do nothing + return TRUE; + } + + ep->open = uta_link2( ep->addr, &ep->nn_sock, &ep->dialer ); + return ep->open; +} + + +/* + Add an endpoint to a route table entry for the group given. If the endpoint isn't in the + hash we add it and create the endpoint struct. + + The caller must supply the specific route table (we assume it will be pending, but they + could live on the edge and update the active one, though that's not at all a good idea). +*/ +extern endpoint_t* uta_add_ep( route_table_t* rt, rtable_ent_t* rte, char* ep_name, int group ) { + endpoint_t* ep; + rrgroup_t* rrg; // pointer at group to update + + if( ! rte || ! rt ) { + fprintf( stderr, "[WARN] uda_add_ep didn't get a valid rt and/or rte pointer\n" ); + return NULL; + } + + if( rte->nrrgroups <= group ) { + fprintf( stderr, "[WARN] uda_add_ep group out of range: %d (max == %d)\n", group, rte->nrrgroups ); + return NULL; + } + + if( (rrg = rte->rrgroups[group]) == NULL ) { + if( (rrg = (rrgroup_t *) malloc( sizeof( *rrg ) )) == NULL ) { + fprintf( stderr, "[WARN] rmr_add_ep: malloc failed for round robin group: group=%d\n", group ); + return NULL; + } + memset( rrg, 0, sizeof( *rrg ) ); + + if( (rrg->epts = (endpoint_t **) malloc( sizeof( endpoint_t ) * MAX_EP_GROUP )) == NULL ) { + fprintf( stderr, "[WARN] rmr_add_ep: malloc failed for group endpoint array: group=%d\n", group ); + return NULL; + } + memset( rrg->epts, 0, sizeof( endpoint_t ) * MAX_EP_GROUP ); + + rte->rrgroups[group] = rrg; + + rrg->ep_idx = 0; // next endpoint to send to + rrg->nused = 0; // number populated + rrg->nendpts = MAX_EP_GROUP; // number allocated + } + + ep = rt_ensure_ep( rt, ep_name ); // get the ep and create one if not known + /* + if( (ep = uta_get_ep( rt, ep_name )) == NULL ) { // not there yet, make + if( (ep = (endpoint_t *) malloc( sizeof( *ep ) )) == NULL ) { + fprintf( stderr, "uta: [WARN] malloc failed for endpoint creation: %s\n", ep_name ); + return NULL; + } + + ep->open = 0; // not connected + ep->addr = uta_h2ip( ep_name ); + ep->name = strdup( ep_name ); + + rmr_sym_put( rt->hash, ep_name, 1, ep ); + } + */ + + if( rrg != NULL ) { + if( rrg->nused >= rrg->nendpts ) { + // future: reallocate + fprintf( stderr, "[WARN] endpoint array for mtype/group %d/%d is full!\n", rte->mtype, group ); + return NULL; + } + + rrg->epts[rrg->nused] = ep; + rrg->nused++; + } + + if( DEBUG > 1 ) fprintf( stderr, "[DBUG] endpoint added to mtype/group: %d/%d %s\n", rte->mtype, group, ep_name ); + return ep; +} + + +/* + Given a name, find the nano socket needed to send to it. Returns the socket via + the user pointer passed in and sets the return value to true (1). If the + endpoint cannot be found false (0) is returned. +*/ +static int uta_epsock_byname( route_table_t* rt, char* ep_name, nng_socket* nn_sock ) { + endpoint_t* ep; + int state = FALSE; + + if( rt == NULL ) { + return FALSE; + } + + + ep = rmr_sym_get( rt->hash, ep_name, 1 ); + if( ep == NULL ) { + if( DEBUG ) fprintf( stderr, "[DBUG] get ep by name for %s not in hash!\n", ep_name ); + return FALSE; + } + + if( ! ep->open ) { // not open -- connect now + if( DEBUG ) fprintf( stderr, "[DBUG] get ep by name for %s session not started... starting\n", ep_name ); + if( ep->addr == NULL ) { // name didn't resolve before, try again + ep->addr = uta_h2ip( ep->name ); + } + if( uta_link2( ep->addr, &ep->nn_sock, &ep->dialer ) ) { // find entry in table and create link + state = TRUE; + ep->open = TRUE; + *nn_sock = ep->nn_sock; // pass socket back to caller + } + if( DEBUG ) fprintf( stderr, "[DBUG] epsock_bn: connection state: %s %s\n", state ? "[OK]" : "[FAIL]", ep->name ); + } else { + *nn_sock = ep->nn_sock; + state = TRUE; + } + + return state; +} + +/* + Make a round robin selection within a round robin group for a route table + entry. Returns the nanomsg socket if there is a rte for the message + type, and group is defined. Socket is returned via pointer in the parm + list (nn_sock). + + The group is the group number to select from. + + The user supplied (via pointer to) integer 'more' will be set if there are + additional groups beyond the one selected. This allows the caller to + to easily iterate over the group list -- more is set when the group should + be incremented and the function invoked again. Groups start at 0. + + The return value is true (>0) if the socket was found and *nn_sock was updated + and false (0) if there is no associated socket for the msg type, group combination. + We return the index+1 from the round robin table on success so that we can verify + during test that different entries are being seleted; we cannot depend on the nng + socket being different as we could with nano. +*/ +static int uta_epsock_rr( route_table_t *rt, int mtype, int group, int* more, nng_socket* nn_sock ) { + rtable_ent_t* rte; // matching rt entry + endpoint_t* ep; // seected end point + int state = FALSE; // processing state + int dummy; + rrgroup_t* rrg; + + + if( ! more ) { // eliminate cheks each time we need to user + more = &dummy; + } + + if( ! nn_sock ) { // user didn't supply a pointer + errno = EINVAL; + *more = 0; + return FALSE; + } + + if( rt == NULL ) { + *more = 0; + return FALSE; + } + + if( (rte = rmr_sym_pull( rt->hash, mtype )) == NULL ) { + *more = 0; + //if( DEBUG ) fprintf( stderr, ">>>> rte not found for type = %d\n", mtype ); + return FALSE; + } + + if( group < 0 || group >= rte->nrrgroups ) { + //if( DEBUG ) fprintf( stderr, ">>>> group out of range: mtype=%d group=%d max=%d\n", mtype, group, rte->nrrgroups ); + *more = 0; + return FALSE; + } + + if( (rrg = rte->rrgroups[group]) == NULL ) { + //if( DEBUG ) fprintf( stderr, ">>>> rrg not found for type = %d\n", mtype ); + *more = 0; // groups are inserted contig, so nothing should be after a nil pointer + return FALSE; + } + + *more = group < rte->nrrgroups-1 ? (rte->rrgroups[group+1] != NULL): 0; // more if something in next group slot + + switch( rrg->nused ) { + case 0: // nothing allocated, just punt + //if( DEBUG ) fprintf( stderr, ">>>> nothing allocated for the rrg\n" ); + return FALSE; + + case 1: // exactly one, no rr to deal with and more is not possible even if fanout > 1 + //*nn_sock = rrg->epts[0]->nn_sock; + ep = rrg->epts[0]; + //if( DEBUG ) fprintf( stderr, ">>>> _rr returning socket with one choice in group \n" ); + state = TRUE; + break; + + default: // need to pick one and adjust rr counts + ep = rrg->epts[rrg->ep_idx++]; // select next endpoint + //if( DEBUG ) fprintf( stderr, ">>>> _rr returning socket with multiple choices in group idx=%d \n", rrg->ep_idx ); + if( rrg->ep_idx >= rrg->nused ) { + rrg->ep_idx = 0; + } + state = rrg->ep_idx+1; + break; + } + + if( state ) { // end point selected, open if not, get socket either way + if( ! ep->open ) { // not connected + if( ep->addr == NULL ) { // name didn't resolve before, try again + ep->addr = uta_h2ip( ep->name ); + } + + if( uta_link2( ep->addr, &ep->nn_sock, &ep->dialer ) ) { // find entry in table and create link + ep->open = TRUE; + *nn_sock = ep->nn_sock; // pass socket back to caller + } else { + state = FALSE; + } + if( DEBUG ) fprintf( stderr, "[DBUG] epsock_rr: connection attempted with %s: %s\n", ep->name, state ? "[OK]" : "[FAIL]" ); + } else { + *nn_sock = ep->nn_sock; + } + } + + return state; +} + +#endif diff --git a/src/nng/src/sr_nng_static.c b/src/nng/src/sr_nng_static.c new file mode 100644 index 0000000..8e326e3 --- /dev/null +++ b/src/nng/src/sr_nng_static.c @@ -0,0 +1,454 @@ +// : vi ts=4 sw=4 noet : +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + +/* + Mnemonic: sr_nng_static.c + Abstract: These are static send/receive primatives which (sadly) + differ based on the underlying protocol (nng vs nanomsg). + Split from rmr_nng.c for easier wormhole support. + + Author: E. Scott Daniels + Date: 13 February 2019 +*/ + +#ifndef _sr_nng_static_c +#define _sr_nng_static_c + +#include +#include +#include +#include +#include + + +/* + Translates the nng state passed in to one of ours that is suitable to put + into the message, and sets errno to something that might be useful. + If we don't have a specific RMr state, then we return the default (e.g. + receive failed). +*/ +static inline int xlate_nng_state( int state, int def_state ) { + + switch( state ) { + case 0: + errno = 0; + state = RMR_OK; + break; + + case NNG_EAGAIN: // soft errors get retry as the RMr error + state = RMR_ERR_RETRY; + errno = EAGAIN; + break; + + case NNG_ETIMEDOUT: + state = RMR_ERR_RETRY; + errno = EAGAIN; + break; + + case NNG_ENOTSUP: + errno = ENOTSUP; + state = def_state; + break; + + case NNG_EINVAL: + errno = EINVAL; + state = def_state; + break; + + case NNG_ENOMEM: + errno = ENOMEM; + state = def_state; + break; + + case NNG_ESTATE: + errno = EBADFD; // file des not in a good state for the operation + state = def_state; + break; + + case NNG_ECLOSED: + errno = EBADFD; // file des not in a good state for the operation + state = def_state; + break; + + default: + errno = EBADE; + state = def_state; + break; + } + + return state; +} + +/* + Alloc a new nano zero copy buffer and put into msg. If msg is nil, then we will alloc + a new message struct as well. Size is the size of the zc buffer to allocate (not + including our header). If size is 0, then the buffer allocated is the size previously + allocated (if msg is !nil) or the default size given at initialisation). + + NOTE: while accurate, the nng doc implies that both the msg buffer and data buffer + are zero copy, however ONLY the message is zero copy. We now allocate and use + nng messages. +*/ +static rmr_mbuf_t* alloc_zcmsg( uta_ctx_t* ctx, rmr_mbuf_t* msg, int size, int state ) { + size_t mlen; + uta_mhdr_t* hdr; // convenience pointer + + mlen = sizeof( uta_mhdr_t ); // figure size should we not have a msg buffer + mlen += (size > 0 ? size : ctx->max_plen); // add user requested size or size set during init + + if( msg == NULL ) { + msg = (rmr_mbuf_t *) malloc( sizeof *msg ); + if( msg == NULL ) { + fprintf( stderr, "[CRI] rmr_alloc_zc: cannot get memory for message\n" ); + exit( 1 ); + } + } else { + mlen = msg->alloc_len; // msg given, allocate the same size as before + } + + memset( msg, 0, sizeof( *msg ) ); + + if( (state = nng_msg_alloc( (nng_msg **) &msg->tp_buf, mlen )) != 0 ) { + fprintf( stderr, "[CRI] rmr_alloc_zc: cannot get memory for zero copy buffer: %d\n", ENOMEM ); + abort( ); // toss out a core file for this + } + + msg->header = nng_msg_body( msg->tp_buf ); + hdr = (uta_mhdr_t *) msg->header; + hdr->rmr_ver = RMR_MSG_VER; // version info should we need to recognised old style messages someday + msg->len = 0; // length of data in the payload + msg->alloc_len = mlen; // length of allocated payload + msg->payload = msg->header + sizeof( uta_mhdr_t ); // point past header to payload (single buffer allocation above) + msg->xaction = ((uta_mhdr_t *)msg->header)->xid; // point at transaction id in header area + msg->state = state; // fill in caller's state (likely the state of the last operation) + msg->flags |= MFL_ZEROCOPY; // this is a zerocopy sendable message + strncpy( (char *) ((uta_mhdr_t *)msg->header)->src, ctx->my_name, RMR_MAX_SID ); + + if( DEBUG > 1 ) fprintf( stderr, "[DBUG] alloc_zcmsg mlen=%ld size=%d mpl=%d flags=%02x\n", (long) mlen, size, ctx->max_plen, msg->flags ); + + return msg; +} + +/* + Allocates only the mbuf and does NOT allocate an underlying transport buffer since + NNG receive must allocate that on its own. +*/ +static rmr_mbuf_t* alloc_mbuf( uta_ctx_t* ctx, int state ) { + size_t mlen; + uta_mhdr_t* hdr; // convenience pointer + rmr_mbuf_t* msg; + + msg = (rmr_mbuf_t *) malloc( sizeof *msg ); + if( msg == NULL ) { + fprintf( stderr, "[CRI] rmr_alloc_zc: cannot get memory for message\n" ); + exit( 1 ); + } + + memset( msg, 0, sizeof( *msg ) ); + + msg->tp_buf = NULL; + msg->header = NULL; + msg->len = -1; // no payload; invalid len + msg->alloc_len = -1; + msg->payload = NULL; + msg->xaction = NULL; + msg->state = RMR_ERR_UNSET; + msg->flags = 0; + + return msg; +} + +/* + This accepts a message with the assumption that only the tp_buf pointer is valid. It + sets all of the various header/payload/xaction pointers in the mbuf to the proper + spot in the transport layer buffer. The len in the header is assumed to be the + allocated len (a receive buffer that nng created); + + The alen parm is the assumed allocated length; assumed because it's a value likely + to have come from nng receive and the actual alloc len might be larger, but we + can only assume this is the total usable space. +*/ +static void ref_tpbuf( rmr_mbuf_t* msg, size_t alen ) { + uta_mhdr_t* hdr; + + msg->header = nng_msg_body( msg->tp_buf ); // header is the start of the transport buffer + + hdr = (uta_mhdr_t *) msg->header; + hdr->rmr_ver = RMR_MSG_VER; // version info should we need to recognised old style messages someday + msg->len = ntohl( hdr->plen ); // length sender says is in the payload (received length could be larger) + msg->alloc_len = alen; // length of whole tp buffer (including header) + msg->payload = msg->header + sizeof( uta_mhdr_t ); // point past header to payload (single buffer allocation above) + msg->xaction = ((uta_mhdr_t *)msg->header)->xid; // point at transaction id in header area + msg->flags |= MFL_ZEROCOPY; // this is a zerocopy sendable message + msg->mtype = ntohl( hdr->mtype ); // capture and convert from network order to local order + msg->state = RMR_OK; +} + +/* + This will clone a message into a new zero copy buffer and return the cloned message. +*/ +static inline rmr_mbuf_t* clone_msg( rmr_mbuf_t* old_msg ) { + rmr_mbuf_t* nm; // new message buffer + size_t mlen; + int state; + + nm = (rmr_mbuf_t *) malloc( sizeof *nm ); + if( nm == NULL ) { + fprintf( stderr, "[CRI] rmr_clone: cannot get memory for message buffer\n" ); + exit( 1 ); + } + memset( nm, 0, sizeof( *nm ) ); + + mlen = old_msg->alloc_len; // length allocated before + if( (state = nng_msg_alloc( (nng_msg **) &nm->tp_buf, mlen )) != 0 ) { + fprintf( stderr, "[CRI] rmr_clone: cannot get memory for zero copy buffer: %d\n", ENOMEM ); + exit( 1 ); + } + + nm->header = nng_msg_body( nm->tp_buf ); + nm->mtype = old_msg->mtype; + nm->len = old_msg->len; // length of data in the payload + nm->alloc_len = mlen; // length of allocated payload + nm->payload = nm->header + sizeof( uta_mhdr_t ); // point past header to payload (single buffer allocation above) + nm->xaction = ((uta_mhdr_t *)nm->header)->xid; // point at transaction id in header area + nm->state = old_msg->state; // fill in caller's state (likely the state of the last operation) + nm->flags |= MFL_ZEROCOPY; // this is a zerocopy sendable message + + memcpy( ((uta_mhdr_t *)nm->header)->src, ((uta_mhdr_t *)old_msg->header)->src, RMR_MAX_SID ); + memcpy( nm->payload, old_msg->payload, old_msg->len ); + + return nm; +} + +/* + This is the receive work horse used by the outer layer receive functions. + It waits for a message to be received on our listen socket. If old msg + is passed in, the we assume we can use it instead of allocating a new + one, else a new block of memory is allocated. + + This allocates a zero copy message so that if the user wishes to call + rmr_rts_msg() the send is zero copy. + + The nng timeout on send is at the ms level which is a tad too long for + our needs. So, if NNG returns eagain or timedout (we don't set one) + we will loop up to 5 times with a 10 microsecond delay between each + attempt. If at the end of this set of retries NNG is still saying + eagain/timeout we'll return to the caller with that set in errno. + Right now this is only for zero-copy buffers (they should all be zc + buffers now). + + + In the NNG msg world it must allocate the receive buffer rather + than accepting one that we allocated from their space and could + reuse. They have their reasons I guess. Thus, we will free + the old transport buffer if user passes the message in; at least + our mbuf will be reused. +*/ +static rmr_mbuf_t* rcv_msg( uta_ctx_t* ctx, rmr_mbuf_t* old_msg ) { + int state; + rmr_mbuf_t* msg = NULL; // msg received + uta_mhdr_t* hdr; + size_t rsize; // nng needs to write back the size received... grrr + + if( old_msg ) { + msg = old_msg; + if( msg->tp_buf != NULL ) { + nng_msg_free( msg->tp_buf ); + } + + msg->tp_buf = NULL; + } else { + //msg = alloc_zcmsg( ctx, NULL, RMR_MAX_RCV_BYTES, RMR_OK ); // will abort on failure, no need to check + msg = alloc_mbuf( ctx, RMR_OK ); // msg without a transport buffer + } + + msg->len = 0; + msg->payload = NULL; + msg->xaction = NULL; + + //rsize = msg->alloc_len; // set to max, and we'll get len back here too + //msg->state = nng_recv( ctx->nn_sock, msg->header, &rsize, NO_FLAGS ); // total space (header + payload len) allocated + msg->state = nng_recvmsg( ctx->nn_sock, (nng_msg **) &msg->tp_buf, NO_FLAGS ); // blocks hard until received + if( (msg->state = xlate_nng_state( msg->state, RMR_ERR_RCVFAILED )) != RMR_OK ) { + return msg; + } + + if( msg->tp_buf == NULL ) { // if state is good this _should_ not be nil, but parninoia says check anyway + msg->state = RMR_ERR_EMPTY; + return msg; + } + + rsize = nng_msg_len( msg->tp_buf ); + if( rsize >= sizeof( uta_mhdr_t ) ) { // we need at least a full header here + + ref_tpbuf( msg, rsize ); // point payload, header etc to the just received tp buffer + hdr = (uta_mhdr_t *) msg->header; + msg->flags |= MFL_ADDSRC; // turn on so if user app tries to send this buffer we reset src + if( msg->len > (msg->alloc_len - sizeof( uta_mhdr_t )) ) { // way more than we should have had room for; error + msg->state = RMR_ERR_TRUNC; + } + + if( DEBUG > 1 ) fprintf( stderr, "[DBUG] rcv_msg: got something: type=%d state=%d len=%d diff=%ld\n", + msg->mtype, msg->state, msg->len, msg->payload - (unsigned char *) msg->header ); + } else { + msg->len = 0; + msg->state = RMR_ERR_EMPTY; + } + + return msg; +} + +/* + Receives a 'raw' message from a non-RMr sender (no header expected). The returned + message buffer cannot be used to send, and the length information may or may + not be correct (it is set to the length received which might be more than the + bytes actually in the payload). + + Mostly this supports the route table collector, but could be extended with an + API external function. +*/ +static void* rcv_payload( uta_ctx_t* ctx, rmr_mbuf_t* old_msg ) { + int state; + rmr_mbuf_t* msg = NULL; // msg received + size_t rsize; // nng needs to write back the size received... grrr + + if( old_msg ) { + msg = old_msg; + } else { + msg = alloc_zcmsg( ctx, NULL, RMR_MAX_RCV_BYTES, RMR_OK ); // will abort on failure, no need to check + } + + msg->state = nng_recvmsg( ctx->nn_sock, (nng_msg **) &msg->tp_buf, NO_FLAGS ); // blocks hard until received + if( (msg->state = xlate_nng_state( msg->state, RMR_ERR_RCVFAILED )) != RMR_OK ) { + return msg; + } + rsize = nng_msg_len( msg->tp_buf ); + + // do NOT use ref_tpbuf() here! Must fill these in manually. + msg->header = nng_msg_body( msg->tp_buf ); + msg->len = rsize; // len is the number of bytes received + msg->alloc_len = rsize; + msg->mtype = -1; // raw message has no type + msg->state = RMR_OK; + msg->flags = MFL_RAW; + msg->payload = msg->header; // payload is the whole thing; no header + msg->xaction = NULL; + + if( DEBUG > 1 ) fprintf( stderr, "[DBUG] rcv_payload: got something: type=%d state=%d len=%d\n", msg->mtype, msg->state, msg->len ); + + return msg; +} + +/* + This does the hard work of actually sending the message to the given socket. On success, + a new message struct is returned. On error, the original msg is returned with the state + set to a reasonable value. If the message being sent as MFL_NOALLOC set, then a new + buffer will not be allocated and returned (mostly for call() interal processing since + the return message from call() is a received buffer, not a new one). + + Called by rmr_send_msg() and rmr_rts_msg(), etc. and thus we assume that all pointer + validation has been done prior. +*/ +static rmr_mbuf_t* send_msg( uta_ctx_t* ctx, rmr_mbuf_t* msg, nng_socket nn_sock, int retries ) { + int state; + uta_mhdr_t* hdr; + int nng_flags = NNG_FLAG_NONBLOCK; // if we need to set any nng flags (zc buffer) add it to this + int spin_retries = 1000; // if eagain/timeout we'll spin this many times before giving up the CPU + + // future: ensure that application did not overrun the XID buffer; last byte must be 0 + + hdr = (uta_mhdr_t *) msg->header; + hdr->mtype = htonl( msg->mtype ); // stash type/len in network byte order for transport + hdr->plen = htonl( msg->len ); + + if( msg->flags & MFL_ADDSRC ) { // buffer was allocated as a receive buffer; must add our source + strncpy( (char *) ((uta_mhdr_t *)msg->header)->src, ctx->my_name, RMR_MAX_SID ); // must overlay the source to be ours + } + + errno = 0; + msg->state = RMR_OK; + if( msg->flags & MFL_ZEROCOPY ) { // faster sending with zcopy buffer + //nng_flags |= NNG_FLAG_ALLOC; // indicate a zc buffer that nng is expected to free + + do { + if( (state = nng_sendmsg( nn_sock, (nng_msg *) msg->tp_buf, nng_flags )) != 0 ) { // must check and retry some if transient failure + msg->state = state; + if( retries > 0 && (state == NNG_EAGAIN || state == NNG_ETIMEDOUT) ) { + if( --spin_retries <= 0 ) { // don't give up the processor if we don't have to + retries--; + usleep( 1 ); // sigh, give up the cpu and hope it's just 1 miscrosec + spin_retries = 1000; + } + } else { + state = 0; // don't loop + //if( DEBUG ) fprintf( stderr, ">>>>> send failed: %s\n", nng_strerror( state ) ); + } + } else { + state = 0; + msg->state = RMR_OK; + msg->header = NULL; // nano frees; don't risk accessing later by mistake + msg->tp_buf = NULL; + } + } while( state && retries > 0 ); + } else { + msg->state = RMR_ERR_SENDFAILED; + errno = ENOTSUP; + return msg; + /* + NOT SUPPORTED + if( (state = nng_send( nn_sock, msg->header, sizeof( uta_mhdr_t ) + msg->len, nng_flags )) != 0 ) { + msg->state = state; + //if( DEBUG ) fprintf( stderr, ">>>>> copy buffer send failed: %s\n", nng_strerror( state ) ); + } + */ + } + + if( msg->state == RMR_OK ) { // successful send + if( !(msg->flags & MFL_NOALLOC) ) { // allocate another sendable zc buffer unless told otherwise + return alloc_zcmsg( ctx, msg, 0, RMR_OK ); // preallocate a zero-copy buffer and return msg + } else { + rmr_free_msg( msg ); // not wanting a meessage back, trash this one + return NULL; + } + } else { // send failed -- return original message + if( msg->state == NNG_EAGAIN || msg->state == NNG_ETIMEDOUT ) { + errno = EAGAIN; + msg->state = RMR_ERR_RETRY; // errno will have nano reason + } else { + msg->state = xlate_nng_state( msg->state, RMR_ERR_SENDFAILED ); // xlate to our state and set errno + //errno = -msg->state; + //msg->state = RMR_ERR_SENDFAILED; // errno will have nano reason + } + + if( DEBUG ) fprintf( stderr, "[DBUG] send failed: %d %s\n", (int) msg->state, strerror( msg->state ) ); + } + + return msg; +} + +/* + A generic wrapper to the real send to keep wormhole stuff agnostic. + We assume the wormhole function vetted the buffer so we don't have to. +*/ +static rmr_mbuf_t* send2ep( uta_ctx_t* ctx, endpoint_t* ep, rmr_mbuf_t* msg ) { + return send_msg( ctx, msg, ep->nn_sock, -1 ); +} + +#endif diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..6770301 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,4 @@ +*.gcov +*.dcov +*.gcno +*.gcda diff --git a/test/.targets b/test/.targets new file mode 100644 index 0000000..0825648 --- /dev/null +++ b/test/.targets @@ -0,0 +1,6 @@ + +# Coverage target values for modules which are not expected to meet the +# default standard. Lines are with module name +# starting in column 0; e.g. ../src/common/src/foo.c 65 + +# there are no alternate coverage targets at the moment diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..20d461e --- /dev/null +++ b/test/Makefile @@ -0,0 +1,24 @@ + + +CC = gcc +coverage_opts = -ftest-coverage -fprofile-arcs + +libs = ../build/librmr_nng.a -L ../build/lib -lnng -lpthread -lm + +%.o:: %.c + $(CC) -g $< -c + +%:: %.c + $(CC) $(coverage_opts) -fPIC -g $< -o $@ $(libs) + +# catch all +all: + echo "run unit_test.ksh to make and run things here" + +# remove intermediates +clean: + rm -f *.gcov *.gcda *.dcov *.gcno + +# remove anything that can be builts +nuke: clean + rm -f ring_test symtab_test diff --git a/test/README b/test/README new file mode 100644 index 0000000..85f96ed --- /dev/null +++ b/test/README @@ -0,0 +1,82 @@ + +Unit test + +The means to unit testing the RMr library is contained in +this directory. It is somewhat difficult to accurately generate +coverage information for parts of the library because the library +is a fair amount of static functions (to keep them from being +visible to the user programme). + +To run the tests: + ksh unit_test.sh [specific-test] + +If a specific test (e.g. ring_test.c) is not given on the command line, +all *_test.c files are sussed out and an attempt to build and run them +is made by the script. + +Output is an interpretation of the resulting gcov output (in more useful +and/or easier to read format). For example: + +unit_test.ksh ring_test.c +ring_test.c -------------------------------------- +[OK] 100% uta_ring_insert +[OK] 100% uta_ring_extract +[OK] 100% uta_ring_free +[LOW] 76% uta_mk_ring +[PASS] 91% ../src/common/src/ring_static.c + + +The output shows, for each function, the coverage (column 2) and an +interpretation (ok or low) wthin an overall pass or fail. + + +File Names +The unit test script will find all files named *_test.c and assume that +they can be compiled and executed using the local Makefile. Files +which are needed by these programmes (e.g. common functions) are expected +to reside in this directory as test_*.c and test_*.h files and should +be directly included by the test programmes (not built and linked). This +allows the unit test script to isngore the functions, and files, when +generating coverage reports. + + +Discounting +The unit test script makes a discount pass on low coverage files in +attempt to discount the coverage rate by ignoring what are considered +to be difficult to reach blocks in the code. Currently, these blocks +are limited to what appear to be tests for memory allocation, failure +and/or nil pointer handling. If code blocks of this sort are found, +they are not counted against the coverage for the module. If the -v +option is given, an augmented coverage listing is saved in .dcov which +shows the discounted lines with a string of equal signs (====) rather +than the gcov hash string (###). + +The discount check is applied only if an entire module has a lower +than accepted coverage rate, and can be forced for all modules with +the -f option. + +To illustrate, the following code checks the return from the system +library strdup() call which is very unlikely to fail under test without +going to extremes and substituting for the system lib. Thus, the +block which checks for a nil pointer has been discounted: + + -: 354: + 1: 355: dbuf = strdup( buf ); + 1: 356: if( dbuf == NULL ) { + =====: 357: errno = ENOMEM; + =====: 358: return 0; + -: 359: } + + +Target Coverage +By default, a target coverage of 80% is used. For some modules this may +be impossible to achieve, so to prevent always failing these modules +may be listed in the .targets file with their expected minimum coverage. +Module names need to be qualified (e.g. ../src/common/src/foo.c. + + +----------------------------------------------------------------------- +A note about ksh (A.K.A Korn shell, or kshell) +Ksh is preferred for more complex scripts such as the unit test +script as it does not have some of the limitations that bash +(and other knock-offs) have. diff --git a/test/ring_test.c b/test/ring_test.c new file mode 100644 index 0000000..331f976 --- /dev/null +++ b/test/ring_test.c @@ -0,0 +1,159 @@ +// : vi ts=4 sw=4 noet : +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + +/* + Mmemonic: ring_test.c + Abstract: Test the ring funcitons. + Author: E. Scott Daniels + Date: 31 July 2017 +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "../src/common/include/rmr.h" +#include "../src/common/include/rmr_agnostic.h" +#include "../src/common/src/ring_static.c" + + +/* + Conduct a series of interleaved tests inserting i-factor + values before beginning to pull values (i-factor must be + size - 2 smaller than the ring. + Returns 0 on success, 1 on insert failure and 2 on pull failure. +*/ +static int ie_test( void* r, int i_factor, long inserts ) { + int i; + int* dp; + int data[29]; + + for( i = 0; i < inserts; i++ ) { + data[i%29] = i; + if( ! uta_ring_insert( r, &data[i%29] ) ) { + fprintf( stderr, "[FAIL] interleaved insert failed on ifactor=%d i=%d\n", i_factor, i ); + return 1; + } + if( i > i_factor-1 ) { + dp = uta_ring_extract( r ); + if( *dp != data[(i-i_factor)%29] ) { + fprintf( stderr, "[FAIL] interleaved exctract failed on ifactor=%d i=%d expected=%d got=%d\n", i_factor, i, data[(i-i_factor)%29], *dp ); + return 2; + } + } + } + //fprintf( stderr, "[OK] interleaved insert/extract test passed for insert factor %d\n", i_factor ); + + return 0; +} + +int main( void ) { + void* r; + int i; + int j; + int data[20]; + int* dp; + int size = 18; + + r = uta_mk_ring( 0 ); // should return nil + if( r != NULL ) { + fprintf( stderr, "[FAIL] attempt to make a ring with size 0 returned a pointer\n" ); + exit( 1 ); + } + r = uta_mk_ring( -1 ); // should also return nil + if( r != NULL ) { + fprintf( stderr, "[FAIL] attempt to make a ring with size <0 returned a pointer\n" ); + exit( 1 ); + } + + r = uta_mk_ring( 18 ); + if( r == NULL ) { + fprintf( stderr, "[FAIL] unable to make ring with 17 entries\n" ); + exit( 1 ); + } + + for( i = 0; i < 20; i++ ) { // test to ensure it reports full when head/tail start at 0 + data[i] = i; + if( ! uta_ring_insert( r, &data[i] ) ) { + break; + } + } + + if( i > size ) { + fprintf( stderr, "[FAIL] didn not report table full: i=%d\n", i ); + exit( 1 ); + } + + fprintf( stderr, "[OK] reported table full at i=%d as expected\n", i ); + + + for( i = 0; i < size + 3; i++ ) { // ensure they all come back in order, and we don't get 'extras' + if( (dp = uta_ring_extract( r )) == NULL ) { + if( i < size-1 ) { + fprintf( stderr, "[FAIL] nil pointer at i=%d\n", i ); + exit( 1 ); + } else { + break; + } + } + + if( *dp != i ) { + fprintf( stderr, "[FAIL] data at i=% isnt right; expected %d got %d\n", i, i, *dp ); + } + } + if( i > size ) { + fprintf( stderr, "[FAIL] got too many values on extract: %d\n", i ); + exit( 1 ); + } + fprintf( stderr, "[OK] extracted values were sane, got: %d\n", i-1 ); + + uta_ring_free( NULL ); // ensure this doesn't blow up + uta_ring_free( r ); + for( i = 2; i < 15; i++ ) { + r = uta_mk_ring( 16 ); + if( ie_test( r, i, 101 ) != 0 ) { // modest number of inserts + exit( 1 ); + } + + uta_ring_free( r ); + } + fprintf( stderr, "[OK] all modest insert/exctract tests pass\n" ); + + size = 5; + for( j = 0; j < 20; j++ ) { + for( i = 2; i < size - 2; i++ ) { + r = uta_mk_ring( size ); + if( ie_test( r, i, 66000 ) != 0 ) { // should force the 16bit head/tail indexes to roll over + exit( 1 ); + } + + uta_ring_free( r ); + } + fprintf( stderr, "[OK] all large insert/exctract tests pass ring size=%d\n", size ); + + size++; + } + + return 0; +} diff --git a/test/symtab_test.c b/test/symtab_test.c new file mode 100644 index 0000000..76b2765 --- /dev/null +++ b/test/symtab_test.c @@ -0,0 +1,139 @@ +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + + +/* + Mnemonic: symtab_test.c + Abstract: This is the unit test module that will drive tests against + the symbol table portion of RMr. Run with: + ksh unit_test.ksh symtab_test.c + Date: 1 April 2019 + Author: E. Scott Daniels +*/ + +#include "../src/common/include/rmr_symtab.h" +#include "../src/common/src/symtab.c" + +#include "test_support.c" + +int state = GOOD; // overall pass/fail state 0==fail +int counter; // global counter for for-each tests + + + +static void fetch( void* st, char* key, int class, int expected ) { + char* val; + + val = rmr_sym_get( st, key, class ); + if( val ) { + fprintf( stderr, "[%s] get returns key=%s val=%s\n", !expected ? "FAIL" : "OK", key, val ); + if( !expected ) { + state = BAD; + } + + } else { + fprintf( stderr, "[%s] string key fetch return nil\n", expected ? "FAIL" : "OK" ); + if( expected ) { + state = BAD; + } + } +} + +static void nfetch( void* st, int key, int expected ) { + char* val; + + val = rmr_sym_pull( st, key ); + if( val ) { + fprintf( stderr, "[%s] get returns key=%d val=%s\n", !expected ? "FAIL" : "OK", key, val ); + if( !expected ) { + state = BAD; + } + } else { + fprintf( stderr, "[%s] get return nil for key=%d\n", expected ? "FAIL" : "OK", key ); + if( expected ) { + state = BAD; + } + } +} + + +/* + Driven by foreach class -- just incr the counter. +*/ +static void each_counter( void* a, void* b, const char* c, void* d, void* e ) { + counter++; +} + +int main( ) { + void* st; + char* foo = "foo"; + char* bar = "bar"; + char* goo = "goo"; // name not in symtab + int i; + int class = 1; + int s; + void* p; + + st = rmr_sym_alloc( 10 ); // alloc with small value to force adjustment inside + fail_if_nil( st, "symtab pointer" ); + + s = rmr_sym_put( st, foo, class, bar ); // add entry with string key; returns 1 if it was inserted + fail_if_false( s, "insert foo existed" ); + + s = rmr_sym_put( st, foo, class+1, bar ); // add to table with a different class + fail_if_false( s, "insert foo existed" ); + + s = rmr_sym_put( st, foo, class, bar ); // inserted above, should return not inserted (0) + fail_if_true( s, "insert foo existed" ); + + fetch( st, foo, class, 1 ); + fetch( st, goo, class, 0 ); // fetch non existant + rmr_sym_stats( st, 4 ); // early stats at verbose level 4 so chatter is minimised + rmr_sym_dump( st ); + + for( i = 2000; i < 3000; i++ ) { // bunch of dummy things to force chains in the table + rmr_sym_map( st, i, foo ); // add entry with unsigned integer key + } + rmr_sym_stats( st, 0 ); // just the small facts to verify the 1000 we stuffed in + rmr_sym_ndel( st, 2001 ); // force a numeric key delete + rmr_sym_ndel( st, 12001 ); // delete numeric key not there + + s = rmr_sym_map( st, 1234, foo ); // add known entries with unsigned integer key + fail_if_false( s, "numeric add of key 1234 should not have existed" ); + s = rmr_sym_map( st, 2345, bar ); + fail_if_true( s, "numeric add of key 2345 should have existed" ); + + counter = 0; + rmr_sym_foreach_class( st, 0, each_counter, NULL ); + fail_if_false( counter, "expected counter after foreach to be non-zero" ); + + nfetch( st, 1234, 1 ); + nfetch( st, 2345, 1 ); + + + rmr_sym_del( st, foo, 0 ); + + rmr_sym_stats( st, 0 ); + + rmr_sym_free( NULL ); // ensure it doesn't barf when given a nil pointer + rmr_sym_free( st ); + + return state; +} + diff --git a/test/test_support.c b/test/test_support.c new file mode 100644 index 0000000..d9a5f47 --- /dev/null +++ b/test/test_support.c @@ -0,0 +1,134 @@ +// : vi ts=4 sw=4 noet : +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + +/* + Mnemonic: test_tools.c + Abstract: Functions for test applications to make their life a bit easier. + This file is probably compiled to a .o, and then included on + the cc command for the test. + Author: E. Scott Daniels + Date: 6 January 2019 +*/ + +#include +#include +#include +#include + +#ifndef BAD +#define BAD 1 // these are exit codes unless user overrides +#define GOOD 0 +#endif + +/* + Snag the optional positional parameter at pp, return defval if not there. +*/ +static char* snag_pp( int pp, int argc, char** argv, char* defval ) { + + if( pp < argc ) { + return argv[pp]; + } + + return defval; +} + +/* + Signal handler -- inside of the tests we will exit cleanly for hup/temp/intr + signals so that the coverage stuff will generate the needed data files. If + we inter/term the process they don't drive. +*/ + +void sig_clean_exit( int sign ) { + fprintf( stderr, "signal trapped for clean exit: %d\n", sign ); + exit( 0 ); +} + +/* + Setup all of the signal handling for signals that we want to force a clean exit: + term, intr, hup, quit, usr1/2 alarm, etc. All others we'll let default. +*/ +static void set_signals( void ) { + struct sigaction sa; + int sig_list[] = { SIGINT, SIGQUIT, SIGILL, SIGALRM, SIGTERM, SIGUSR1 , SIGUSR2 }; + int i; + int nele; // number of elements in the list + + nele = (int) ( sizeof( sig_list )/sizeof( int ) ); // convert raw size to the number of elements + for( i = 0; i < nele; i ++ ) { + memset( &sa, 0, sizeof( sa ) ); + sa.sa_handler = sig_clean_exit; + sigaction( sig_list[i], &sa, NULL ); + } +} + + +static int fail_if_nil( void* p, char* what ) { + if( !p ) { + fprintf( stderr, "[FAIL] pointer to '%s' was nil\n", what ); + } + return p ? GOOD : BAD; +} + +static int fail_not_nil( void* p, char* what ) { + if( p ) { + fprintf( stderr, "[FAIL] pointer to '%s' was not nil\n", what ); + } + return !p ? GOOD : BAD; +} + +static int fail_if_false( int bv, char* what ) { + if( !bv ) { + fprintf( stderr, "[FAIL] boolean was false (%d) %s\n", bv, what ); + } + + return bv ? GOOD : BAD; +} + +static int fail_if_true( int bv, char* what ) { + if( bv ) { + fprintf( stderr, "[FAIL] boolean was true (%d) %s\n", bv, what ); + } + return bv ? BAD : GOOD; +} + +/* + Same as fail_if_true(), but reads easier in the test code. +*/ +static int fail_if( int bv, char* what ) { + + if( bv ) { + fprintf( stderr, "[FAIL] boolean was true (%d) %s\n", bv, what ); + } + return bv ? BAD : GOOD; +} + +static int fail_not_equal( int a, int b, char* what ) { + if( a != b ) { + fprintf( stderr, "[FAIL] %s values were not equal a=%d b=%d\n", what, a, b ); + } + return a == b ? GOOD : BAD; // user may override good/bad so do NOT return a==b directly! +} + +static int fail_if_equal( int a, int b, char* what ) { + if( a == b ) { + fprintf( stderr, "[FAIL] %s values were equal a=%d b=%d\n", what, a, b ); + } + return a != b ? GOOD : BAD; // user may override good/bad so do NOT return a==b directly! +} diff --git a/test/tools_test.c b/test/tools_test.c new file mode 100644 index 0000000..786d2e6 --- /dev/null +++ b/test/tools_test.c @@ -0,0 +1,199 @@ +// : vi ts=4 sw=4 noet : +/* +================================================================================== + Copyright (c) 2019 Nokia + Copyright (c) 2018-2019 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +*/ + + +/* + Mnemonic: tools_testh.c + Abstract: Unit tests for the RMr tools module. + Author: E. Scott Daniels + Date: 21 January 2019 +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include + +/* +#include +#include +#include +#include +#include +*/ + +#include "../src/common/include/rmr.h" +#include "../src/common/include/rmr_agnostic.h" +#include "test_support.c" // our private library of test tools + +// ===== dummy context for tools testing so we don't have to pull in all of the nano/nng specific stuff ===== +struct uta_ctx { + char* my_name; // dns name of this host to set in sender field of a message + int shutdown; // thread notification if we need to tell them to stop + int max_mlen; // max message length payload+header + int max_plen; // max payload length + int flags; // CTXFL_ constants + int nrtele; // number of elements in the routing table + int send_retries; // number of retries send_msg() should attempt if eagain/timeout indicated by nng + //nng_socket nn_sock; // our general listen socket + route_table_t* rtable; // the active route table + route_table_t* old_rtable; // the previously used rt, sits here to allow for draining + route_table_t* new_rtable; // route table under construction + if_addrs_t* ip_list; // list manager of the IP addresses that are on our known interfaces + void* mring; // ring where msgs are queued while waiting for a call response msg + + char* rtg_addr; // addr/port of the route table generation publisher + int rtg_port; // the port that the rtg listens on + + wh_mgt_t* wormholes; // management of user opened wormholes + //epoll_stuff_t* eps; // epoll information needed for the rcv with timeout call + + //pthread_t rtc_th; // thread info for the rtc listener +}; + + +#include "../src/common/src/tools_static.c" + + +int main( ) { + int i; + int j; + int errors = 0; + char* tokens[127]; + char* buf = "2,Fred,Wilma,Barney,Betty,Dino,Pebbles,Bambam,Mr. Slate,Gazoo"; + char* dbuf; // duplicated buf since C marks a const string is unumtable + char* hname; + uta_ctx_t ctx; // context for uta_lookup test + void* if_list; + + + // ------------------ tokenise tests ----------------------------------------------------------- + dbuf = strdup( buf ); + i = uta_tokenise( dbuf, tokens, 127, ',' ); + errors += fail_not_equal( i, 10, "unexpected number of tokens returned (comma sep)" ); + for( j = 0; j < i; j++ ) { + //fprintf( stderr, ">>>> [%d] (%s)\n", j, tokens[j] ); + errors += fail_if_nil( tokens[j], "token from buffer" ); + } + errors += fail_not_equal( strcmp( tokens[4], "Betty" ), 0, "4th token wasn't 'Betty'" ); + + free( dbuf ); + dbuf = strdup( buf ); + i = uta_tokenise( dbuf, tokens, 127, '|' ); + errors += fail_not_equal( i, 1, "unexpected number of tokens returned (bar sep)" ); + free( dbuf ); + + // ------------ has str tests ----------------------------------------------------------------- + j = uta_has_str( buf, "Mr. Slate", ',', 1 ); // should fail (-1) because user should use strcmp in this situation + errors += fail_if_true( j >= 0, "test to ensure has str rejects small max" ); + + j = uta_has_str( buf, "Mr. Slate", ',', 27 ); + errors += fail_if_true( j < 0, "has string did not find Mr. Slate" ); + + j = uta_has_str( buf, "Mrs. Slate", ',', 27 ); + errors += fail_if_true( j >= 0, "has string not found Mrs. Slate" ); + + // ------------ host name 2 ip tests --------------------------------------------------------- + hname = uta_h2ip( "192.168.1.2" ); + errors += fail_not_equal( strcmp( hname, "192.168.1.2" ), 0, "h2ip did not return IP address when given address" ); + errors += fail_if_nil( hname, "h2ip did not return a pointer" ); + + hname = uta_h2ip( "yahoo.com" ); + errors += fail_if_nil( hname, "h2ip did not return a pointer" ); + + hname = uta_h2ip( "yahoo.com:1234" ); // should ignore the port + errors += fail_if_nil( hname, "h2ip did not return a pointer" ); + + // ------------ rtg lookup test ------------------------------------------------------------- + ctx.rtg_port = 0; + ctx.rtg_addr = NULL; + + i = uta_lookup_rtg( NULL ); // ensure it handles a nil context + errors += fail_if_true( i, "rtg lookup returned that it found something when not expected to (nil context)" ); + + setenv( "RMR_RTG_SVC", "localhost:1234", 1); + i = uta_lookup_rtg( &ctx ); + errors += fail_if_false( i, "rtg lookup returned that it did not find something when expected to" ); + errors += fail_if_nil( ctx.rtg_addr, "rtg lookup did not return a pointer (with port)" ); + errors += fail_not_equal( ctx.rtg_port, 1234, "rtg lookup did not capture the port" ); + + setenv( "RMR_RTG_SVC", "localhost", 1); // test ability to generate default port + uta_lookup_rtg( &ctx ); + errors += fail_if_nil( ctx.rtg_addr, "rtg lookup did not return a pointer (no port)" ); + errors += fail_not_equal( ctx.rtg_port, 5656, "rtg lookup did not return default port" ); + + unsetenv( "RMR_RTG_SVC" ); // this should fail as the default name (rtg) will be unknown during testing + i = uta_lookup_rtg( &ctx ); + errors += fail_if_true( i, "rtg lookup returned that it found something when not expected to" ); + +/* +//==== moved out of generic tools ========== + // -------------- test link2 stuff ---------------------------------------------------------- + i = uta_link2( "bad" ); // should fail + errors += fail_if_true( i >= 0, "uta_link2 didn't fail when given bad address" ); + + i = uta_link2( "nohost:-1234" ); + errors += fail_if_true( i >= 0, "uta_link2 did not failed when given a bad (negative) port " ); + + i = uta_link2( "nohost:1234" ); // nn should go off and set things up, but it will never successd, but uta_ call should + errors += fail_if_true( i < 0, "uta_link2 failed when not expected to" ); +*/ + + // ------------ my ip stuff ----------------------------------------------------------------- + + if_list = mk_ip_list( "1235" ); + errors += fail_if_nil( if_list, "mk_ip_list returned nil pointer" ); + + i = has_myip( NULL, NULL, ',', 128 ); // should be false if pointers are nil + errors += fail_if_true( i, "has_myip returned true when given nil buffer" ); + + i = has_myip( "buffer contents not valid", NULL, ',', 128 ); // should be false if pointers are nil + errors += fail_if_true( i, "has_myip returned true when given nil list" ); + + i = has_myip( "buffer contents not valid", NULL, ',', 1 ); // should be false if max < 2 + errors += fail_if_true( i, "has_myip returned true when given small max value" ); + + i = has_myip( "buffer.contents.not.valid", if_list, ',', 128 ); // should be false as there is nothing valid in the list + errors += fail_if_true( i, "has_myip returned true when given a buffer with no valid info" ); + + + setenv( "RMR_BIND_IF", "192.168.4.30", 1 ); // drive the case where we have a hard set interface; and set known interface in list + if_list = mk_ip_list( "1235" ); + errors += fail_if_nil( if_list, "mk_ip_list with env set returned nil pointer" ); + + i = has_myip( "192.168.1.2:1235,192.168.4.30:1235,192.168.2.19:4567", if_list, ',', 128 ); // should find our ip in middle + errors += fail_if_false( i, "has_myip did not find IP in middle of list" ); + + i = has_myip( "192.168.4.30:1235,192.168.2.19:4567,192.168.2.19:2222", if_list, ',', 128 ); // should find our ip at head + errors += fail_if_false( i, "has_myip did not find IP at head of list" ); + + i = has_myip( "192.168.23.45:4444,192.168.1.2:1235,192.168.4.30:1235", if_list, ',', 128 ); // should find our ip at end + errors += fail_if_false( i, "has_myip did not find IP at tail of list" ); + + i = has_myip( "192.168.4.30:1235", if_list, ',', 128 ); // should find our ip when only in list + errors += fail_if_false( i, "has_myip did not find IP when only one in list" ); + + return errors > 0; // overall exit code bad if errors +} diff --git a/test/unit_test.ksh b/test/unit_test.ksh new file mode 100755 index 0000000..fb5fa2a --- /dev/null +++ b/test/unit_test.ksh @@ -0,0 +1,416 @@ +#!/usr/bin/env ksh +# this will fail if run with bash! + +#================================================================================== +# Copyright (c) 2019 Nokia +# Copyright (c) 2018-2019 AT&T Intellectual Property. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#================================================================================== + + +# +# Mnemonic: unit_test.ksh +# Abstract: Execute unit test(s) in the directory and produce a more +# meaningful summary than gcov gives by default (exclude +# coverage on the unit test functions). +# +# Test files must be named *_test.c, or must explicitly be +# supplied on the command line. Functions in the test +# files will not be reported on provided that they have +# their prototype (all on the SAME line) as: +# static type name() { +# +# Functions with coverage less than 80% will be reported as +# [LOW] in the output. A file is considered to pass if the +# overall execution percentage for the file is >= 80% regardless +# of the number of functions that reported low. +# +# Test programmes are built prior to execution. Plan-9 mk is +# the preferred builder, but as it's not widly adopted (sigh) +# make is assumed and -M will shift to Plan-9. Use -C xxx to +# invoke a customised builder. +# +# For a module which does not pass, we will attempt to boost +# the coverage by discounting the unexecuted lines which are +# inside of if() statements that are checking return from +# (m)alloc() calls or are checking for nil pointers as these +# cases are likely impossible to drive. When discount testing +# is done both the failure message from the original analysis +# and a pass/fail message from the discount test are listed, +# but only the result of the discount test is taken into +# consideration with regard to overall success. +# +# Date: 16 January 2018 +# Author: E. Scott Daniels +# ------------------------------------------------------------------------- + +function usage { + echo "usage: $0 [-G|-M|-C custom-command-string] [-c cov-target] [-f] [-v] [files]" + echo " if -C is used to provide a custom build command then it must " + echo " contain a %s which will be replaced with the unit test file name." + echo ' e.g.: -C "mk -a %s"' + echo " -c allows user to set the target coverage for a module to pass; default is 80" + echo " -f forces a discount check (normally done only if coverage < target)" + echo " -v will write additional information to the tty and save the disccounted file if discount run or -f given" +} + +# read through the given file and add any functions that are static to the +# ignored list. Only test and test tools files should be parsed. +# +function add_ignored_func { + if [[ ! -r $1 ]] + then + return + fi + + typeset f="" + grep "^static.*(.*).*{" $1 | awk ' # get list of test functions to ignore + { + gsub( "[(].*", "" ) + print $3 + } + ' | while read f + do + iflist+="$f " + done +} + +# +# Parse the .gcov file and discount any unexecuted lines which are in if() +# blocks that are testing the result of alloc/malloc calls, or testing for +# nil pointers. The feeling is that these might not be possible to drive +# and shoudn't contribute to coverage deficencies. +# +# In verbose mode, the .gcov file is written to stdout and any unexecuted +# line which is discounted is marked with ===== replacing the ##### marking +# that gcov wrote. +# +# The return value is 0 for pass; non-zero for fail. +function discount_an_checks { + typeset f="$1" + + mct=$( get_mct ${1%.gcov} ) # see if a special coverage target is defined for this + + if [[ ! -f $1 ]] + then + if [[ -f ${1##*/} ]] + then + f=${1##*/} + else + echo "cant find: $f" + return + fi + fi + + awk -v module_cov_target=$mct \ + -v full_name="${1}" \ + -v module="${f%.*}" \ + -v chatty=$verbose \ + ' + function spit_line( ) { + if( chatty ) { + printf( "%s\n", $0 ) + } + } + + /-:/ { # skip unexecutable lines + spit_line() + seq++ # allow blank lines in a sequence group + next + } + + { + nexec++ # number of executable lines + } + + /#####:/ { + unexec++; + if( $2+0 != seq+1 ) { + prev_malloc = 0 + prev_if = 0 + seq = 0 + spit_line() + next + } + + if( prev_if && prev_malloc ) { + if( prev_malloc ) { + #printf( "allow discount: %s\n", $0 ) + if( chatty ) { + gsub( "#####", "=====", $0 ) + } + discount++; + } + } + + seq++;; + spit_line() + next; + } + + /if[(].*alloc.*{/ { # if( (x = malloc( ... )) != NULL ) or if( (p = sym_alloc(...)) != NULL ) + seq = $2+0 + prev_malloc = 1 + prev_if = 1 + spit_line() + next + } + + /if[(].* == NULL/ { # a nil check likely not easily forced if it wasnt driven + prev_malloc = 1 + prev_if = 1 + spit_line() + seq = $2+0 + next + } + + /if[(]/ { + if( seq+1 == $2+0 && prev_malloc ) { // malloc on previous line + prev_if = 1 + } else { + prev_malloc = 0 + prev_if = 0 + } + spit_line() + next + } + + /alloc[(]/ { + seq = $2+0 + prev_malloc = 1 + spit_line() + next + } + + { + spit_line() + } + + END { + net = unexec - discount + orig_cov = ((nexec-unexec)/nexec)*100 # original coverage + adj_cov = ((nexec-net)/nexec)*100 # coverage after discount + pass_fail = adj_cov < module_cov_target ? "FAIL" : "PASS" + rc = adj_cov < module_cov_target ? 1 : 0 + if( chatty ) { + printf( "[%s] %s executable=%d unexecuted=%d discounted=%d net_unex=%d cov=%d% ==> %d%%% target=%d%%\n", + pass_fail, full_name ? full_name : module, nexec, unexec, discount, net, orig_cov, adj_cov, module_cov_target ) + } else { + printf( "[%s] %d%% (%d%%) %s\n", pass_fail, adj_cov, orig_cov, full_name ? full_name : module ) + } + + exit( rc ) + } + ' $f +} + +# Given a file name ($1) see if it is in the ./.targets file. If it is +# return the coverage listed, else return (echo) the default $module_cov_target +# +function get_mct { + typeset v=$module_cov_target + + if [[ -f ./.targets ]] + then + grep "^$1 " ./.targets | head -1 | read junk tv + fi + + echo ${tv:-$v} +} + + +# ------------------------------------------------------------------------ + +export C_INCLUDE_PATH="../src/common/include" + +module_cov_target=80 +builder="make -B %s" # default to plain ole make +verbose=0 +trigger_discount_str="FAIL" + +while [[ $1 == "-"* ]] +do + case $1 in + -C) builder="$2"; shift;; # custom build command + -G) builder="gmake %s";; + -M) builder="mk -a %s";; # use plan-9 mk (better, but sadly not widly used) + + -c) module_cov_target=$2; shift;; + -f) force_discounting=1; + trigger_discount_str="FAIL|PASS" # check all outcomes for each module + ;; + + -v) (( verbose++ ));; + + -h) usage; exit 0;; + --help) usage; exit 0;; + -\?) usage; exit 0;; + + *) echo "unrecognised option: $1" >&2 + usage >&2 + exit 1 + ;; + esac + + shift +done + +if [[ -z $1 ]] +then + flist="" + for tfile in *_test.c + do + flist+="$tfile " + done +else + flist="$@" +fi + +errors=0 +for tfile in $flist +do + echo "$tfile --------------------------------------" + bcmd=$( printf "$builder" "${tfile%.c}" ) + if ! $bcmd >/tmp/PID$$.log 2>&1 + then + echo "[FAIL] cannot build $tfile" + cat /tmp/PID$$.log + rm -f /tmp/PID$$ + exit 1 + fi + + iflist="main sig_clean_exit " # ignore external functions from our tools + add_ignored_func $tfile # ignore all static functions in our test driver + add_ignored_func test_support.c # ignore all static functions in our test tools + + if ! ${tfile%.c} >/tmp/PID$$.log 2>&1 + then + echo "[FAIL] unit test failed for: $tfile" + cat /tmp/PID$$.log + continue + fi + + ( + touch ./.targets + sed '/^#/ d; /^$/ d; s/^/TARGET: /' ./.targets + gcov -f ${tfile%.c} | sed "s/'//g" + ) | awk \ + -v ignore_list="$iflist" \ + -v module_cov_target=$module_cov_target \ + -v chatty=$verbose \ + ' + BEGIN { + nignore = split( ignore_list, ignore, " " ) + for( i = 1; i <= nignore; i++ ) { + imap[ignore[i]] = 1 + } + + exit_code = 0 # assume good + } + + /^TARGET:/ { + if( NF > 1 ) { + target[$2] = $NF + } + next; + } + + /File.*_test/ || /File.*test_/ { # dont report on test files + skip = 1 + file = 1 + fname = $2 + next + } + + /File/ { + skip = 0 + file = 1 + fname = $2 + next + } + + /Function/ { + fname = $2 + file = 0 + if( imap[fname] ) { + fname = "skipped: " fname # should never see and make it smell if we do + skip = 1 + } else { + skip = 0 + } + next + } + + skip { next } + + /Lines executed/ { + split( $0, a, ":" ) + pct = a[2]+0 + + if( file ) { + if( target[fname] ) { + mct = target[fname] + } else { + mct = module_cov_target + } + if( chatty ) { + printf( "[INFO] target coverage for %s is %d%%\n", fname, mct ) + } + if( pct < mct ) { + printf( "[FAIL] %3d%% %s\n\n", pct, fname ) # CAUTION: write only 3 things here + exit_code = 1 + } else { + printf( "[PASS] %3d%% %s\n\n", pct, fname ) + } + } else { + if( pct < 80 ) { + printf( "[LOW] %3d%% %s\n", pct, fname ) + } else { + printf( "[OK] %3d%% %s\n", pct, fname ) + } + } + } + + END { + exit( exit_code ) + } + ' >/tmp/PID$$.log # capture output to run discount on failures + rc=$? + cat /tmp/PID$$.log + if (( rc || force_discounting )) # didn't pass, or forcing, see if discounting helps + then + egrep "$trigger_discount_str" /tmp/PID$$.log | while read state junk name + do + echo "[INFO] checking to see if discounting improves coverage for $name" + if ! discount_an_checks $name.gcov >/tmp/PID$$.disc + then + (( errors++ )) + fi + tail -1 /tmp/PID$$.disc + if (( verbose )) # updated file was generated, keep here + then + echo "[INFO] discounted coverage info in: ${tfile##*/}.dcov" + mv /tmp/PID$$.disc ${tfile##*/}.dcov + fi + done + fi +done + +rm -f /tmp/PID$$.* +if (( errors )) +then + exit 1 +fi +exit 0 + -- 2.16.6