Initial commit of RMR Library 30/30/1
authorAshwin Sridharan <hiltunen@att.com>
Wed, 3 Apr 2019 20:47:02 +0000 (16:47 -0400)
committerAshwin Sridharan <hiltunen@att.com>
Wed, 3 Apr 2019 20:47:06 +0000 (16:47 -0400)
Change-Id: Ic4c998b056e8759f4a47a9a8c50c77e88df0f325
Signed-off-by: Ashwin Sridharan <hiltunen@att.com>
72 files changed:
BUILD [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
LICENSE.txt [new file with mode: 0644]
README [new file with mode: 0644]
doc/CMakeLists.txt [new file with mode: 0644]
doc/README [new file with mode: 0644]
doc/src/generic_ps.im [new file with mode: 0644]
doc/src/man/.gitignore [new file with mode: 0644]
doc/src/man/README [new file with mode: 0644]
doc/src/man/rmr.7.xfm [new file with mode: 0644]
doc/src/man/rmr_alloc_msg.3.xfm [new file with mode: 0644]
doc/src/man/rmr_bytes2meid.3.xfm [new file with mode: 0644]
doc/src/man/rmr_bytes2payload.3.xfm [new file with mode: 0644]
doc/src/man/rmr_bytes2xact.3.xfm [new file with mode: 0644]
doc/src/man/rmr_call.3.xfm [new file with mode: 0644]
doc/src/man/rmr_close.3.xfm [new file with mode: 0644]
doc/src/man/rmr_free_msg.3.xfm [new file with mode: 0644]
doc/src/man/rmr_get_meid.3.xfm [new file with mode: 0644]
doc/src/man/rmr_get_rcvfd.3.xfm [new file with mode: 0644]
doc/src/man/rmr_init.3.xfm [new file with mode: 0644]
doc/src/man/rmr_payload_size.3.xfm [new file with mode: 0644]
doc/src/man/rmr_rcv_msg.3.xfm [new file with mode: 0644]
doc/src/man/rmr_ready.3.xfm [new file with mode: 0644]
doc/src/man/rmr_rts_msg.3.xfm [new file with mode: 0644]
doc/src/man/rmr_send_msg.3.xfm [new file with mode: 0644]
doc/src/man/rmr_str2meid.3.xfm [new file with mode: 0644]
doc/src/man/rmr_str2xact.3.xfm [new file with mode: 0644]
doc/src/man/rmr_support.3.xfm [new file with mode: 0644]
doc/src/man/rmr_torcv_msg.3.xfm [new file with mode: 0644]
doc/src/man/rmr_wh_close.3.xfm [new file with mode: 0644]
doc/src/man/rmr_wh_open.3.xfm [new file with mode: 0644]
doc/src/man/rmr_wh_send_msg.3.xfm [new file with mode: 0644]
doc/src/roff.im [new file with mode: 0644]
doc/src/rst.im [new file with mode: 0644]
examples/Makefile [new file with mode: 0644]
examples/README [new file with mode: 0644]
examples/receiver.c [new file with mode: 0644]
examples/sender.c [new file with mode: 0644]
src/README [new file with mode: 0644]
src/STYLE [new file with mode: 0644]
src/common/CMakeLists.txt [new file with mode: 0644]
src/common/include/RIC_message_types.h [new file with mode: 0644]
src/common/include/rmr.h [new file with mode: 0644]
src/common/include/rmr_agnostic.h [new file with mode: 0644]
src/common/include/rmr_symtab.h [new file with mode: 0644]
src/common/src/README [new file with mode: 0644]
src/common/src/mbuf_api.c [new file with mode: 0644]
src/common/src/ring_static.c [new file with mode: 0644]
src/common/src/rt_generic_static.c [new file with mode: 0644]
src/common/src/rtc_static.c [new file with mode: 0644]
src/common/src/symtab.c [new file with mode: 0644]
src/common/src/tools_static.c [new file with mode: 0644]
src/common/src/wormholes.c [new file with mode: 0644]
src/nanomsg/CMakeLists.txt [new file with mode: 0644]
src/nanomsg/include/rmr_private.h [new file with mode: 0644]
src/nanomsg/src/rmr.c [new file with mode: 0644]
src/nanomsg/src/rtable_static.c [new file with mode: 0644]
src/nanomsg/src/sr_static.c [new file with mode: 0644]
src/nng/CMakeLists.txt [new file with mode: 0644]
src/nng/include/rmr_nng_private.h [new file with mode: 0644]
src/nng/src/rmr_nng.c [new file with mode: 0644]
src/nng/src/rtable_nng_static.c [new file with mode: 0644]
src/nng/src/sr_nng_static.c [new file with mode: 0644]
test/.gitignore [new file with mode: 0644]
test/.targets [new file with mode: 0644]
test/Makefile [new file with mode: 0644]
test/README [new file with mode: 0644]
test/ring_test.c [new file with mode: 0644]
test/symtab_test.c [new file with mode: 0644]
test/test_support.c [new file with mode: 0644]
test/tools_test.c [new file with mode: 0644]
test/unit_test.ksh [new file with mode: 0755]

diff --git a/BUILD b/BUILD
new file mode 100644 (file)
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 (file)
index 0000000..9f0af5d
--- /dev/null
@@ -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 "$<TARGET_OBJECTS:nano_objects>;$<TARGET_OBJECTS:common_objects>" )
+add_library( rmr_static STATIC "$<TARGET_OBJECTS:nano_objects>;$<TARGET_OBJECTS:common_objects>" )
+
+# 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 "$<TARGET_OBJECTS:nng_objects>;$<TARGET_OBJECTS:common_objects>" )
+add_library( rmr_nng_static STATIC "$<TARGET_OBJECTS:nng_objects>;$<TARGET_OBJECTS:common_objects>" )
+
+
+
+# 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 (file)
index 0000000..e723ccb
--- /dev/null
@@ -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 (file)
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:
+
+       <name>.c --             C code which builds separately and generates an object
+                                       that is ultimately added to the archive.
+
+       <name>_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.
+
+       <name>.h                Header file that user applications are expected to include
+                                       in order to make use of the library
+
+       <name>_inline.h Header files containing inline static functions that the
+                                       user application is expected to include.
+
+       <name>_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 (file)
index 0000000..44f0001
--- /dev/null
@@ -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 (file)
index 0000000..ea47501
--- /dev/null
@@ -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 (file)
index 0000000..ccca9ba
--- /dev/null
@@ -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 (file)
index 0000000..51e0212
--- /dev/null
@@ -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 (file)
index 0000000..8fc0277
--- /dev/null
@@ -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 (file)
index 0000000..4c3e13a
--- /dev/null
@@ -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 (file)
index 0000000..04b908b
--- /dev/null
@@ -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/rmr.h>
+
+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 (file)
index 0000000..fcc3955
--- /dev/null
@@ -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 <rmr/rmr.h>
+
+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 (file)
index 0000000..a8f99e2
--- /dev/null
@@ -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 <rmr/rmr.h>
+
+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 (file)
index 0000000..dbfe679
--- /dev/null
@@ -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 <rmr/rmr.h>
+
+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 (file)
index 0000000..6be29d6
--- /dev/null
@@ -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 <rmr/rmr.h>
+
+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 (file)
index 0000000..8b05f0b
--- /dev/null
@@ -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 <rmr/rmr.h>
+
+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 (file)
index 0000000..e29f8d9
--- /dev/null
@@ -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 <rmr/rmr.h>
+
+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 (file)
index 0000000..2dcc943
--- /dev/null
@@ -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 <rmr/rmr.h>
+
+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 (file)
index 0000000..8300d4e
--- /dev/null
@@ -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 <rmr/rmr.h>
+
+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 <stdio.h>
+#include <stdlib.h>
+#include <sys/epoll.h>
+#include <rmr/rmr.h>
+
+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 (file)
index 0000000..408c954
--- /dev/null
@@ -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 <rmr/rmr.h>
+
+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 (file)
index 0000000..7dbfa69
--- /dev/null
@@ -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 <rmr/rmr.h>
+
+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 (file)
index 0000000..58809fd
--- /dev/null
@@ -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/rmr.h>
+
+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 (file)
index 0000000..9a82010
--- /dev/null
@@ -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 <rmr/rmr.h>
+
+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 (file)
index 0000000..bad1b22
--- /dev/null
@@ -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/rmr.h>
+
+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 (file)
index 0000000..6497781
--- /dev/null
@@ -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/rmr.h>
+
+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 (file)
index 0000000..81ed6c9
--- /dev/null
@@ -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 <rmr/rmr.h>
+
+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 (file)
index 0000000..1f14cd6
--- /dev/null
@@ -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 <rmr/rmr.h>
+
+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 (file)
index 0000000..b4cdd7f
--- /dev/null
@@ -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 <rmr/rmr.h>
+#include <rmr/ring_inline.h>
+
+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 (file)
index 0000000..0482419
--- /dev/null
@@ -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/rmr.h>
+
+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 (file)
index 0000000..3ee4b34
--- /dev/null
@@ -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 <rmr/rmr.h>
+
+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 (file)
index 0000000..9bd15a4
--- /dev/null
@@ -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 <rmr/rmr.h>
+
+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 (file)
index 0000000..5a014d6
--- /dev/null
@@ -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/rmr.h>
+
+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 <rmr/rmr.h>   // 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 (file)
index 0000000..27831da
--- /dev/null
@@ -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 (file)
index 0000000..8662137
--- /dev/null
@@ -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 <hx> tags in the resulting HTML, but <section> 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 (file)
index 0000000..6314ea3
--- /dev/null
@@ -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 (file)
index 0000000..4b63eee
--- /dev/null
@@ -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 (file)
index 0000000..c78c710
--- /dev/null
@@ -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 <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include <rmr/rmr.h>
+
+
+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, "<DEMO> listening on port: %s\n", listen_port );
+       fprintf( stderr, "<DEMO> 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, "<DEMO> ABORT:  unable to initialise RMr\n" );
+               exit( 1 );
+       }
+
+       while( ! rmr_ready( mrc ) ) {                                                           // wait for RMr to load a route table
+               fprintf( stderr, "<DEMO> waiting for ready\n" );
+               sleep( 3 );
+       }
+       fprintf( stderr, "<DEMO> 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, "<DEMO> 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 (file)
index 0000000..0fa5343
--- /dev/null
@@ -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 <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/epoll.h>
+#include <time.h>
+
+#include <rmr/rmr.h>
+
+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, "<DEMO> listen port: %s; mtype: %d; delay: %d\n", listen_port, mtype, delay );
+
+    if( (mrc = rmr_init( listen_port, 1400, RMRFL_NONE )) == NULL ) {
+               fprintf( stderr, "<DEMO> 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, "<DEMO> 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, "<DEMO> 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, "<DEMO> sent %d   received %d\n", count, rcvd_count );
+               }
+
+               usleep( delay );
+    }
+}
+
diff --git a/src/README b/src/README
new file mode 100644 (file)
index 0000000..420b935
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..ebccd3b
--- /dev/null
@@ -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 
+       $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+       $<INSTALL_INTERFACE:include>
+       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 (file)
index 0000000..82495c8
--- /dev/null
@@ -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 (file)
index 0000000..2f58eaf
--- /dev/null
@@ -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 <sys/epoll.h>         // 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 (file)
index 0000000..687330b
--- /dev/null
@@ -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 (file)
index 0000000..95fa153
--- /dev/null
@@ -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 (file)
index 0000000..dd04864
--- /dev/null
@@ -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 (file)
index 0000000..930d74d
--- /dev/null
@@ -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 <stdlib.h>
+#include <netdb.h>                     // uint* types
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 (file)
index 0000000..2d29dbd
--- /dev/null
@@ -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 <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+
+#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 (file)
index 0000000..26e907e
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <errno.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+/*
+       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 (file)
index 0000000..6b9c707
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <errno.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+/*
+       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 (file)
index 0000000..b780d9e
--- /dev/null
@@ -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 <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <memory.h>
+
+#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 (file)
index 0000000..8d41b09
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <errno.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+#include <ctype.h>
+
+#include <sys/types.h>         // these are needed to suss out ip addresses from interfaces
+#include <ifaddrs.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+
+/*
+       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 (file)
index 0000000..8011ddc
--- /dev/null
@@ -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 <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+#include <ctype.h>
+
+#include "rmr.h"
+#include "rmr_symtab.h"
+
+/*
+#ifdef NNG
+#include <nng/nng.h>
+#include <nng/protocol/pubsub0/pub.h>
+#include <nng/protocol/pubsub0/sub.h>
+#include <nng/protocol/pipeline0/push.h>
+#include <nng/protocol/pipeline0/pull.h>
+
+#include "rmr_nng_private.h"
+#include "rt_generic_static.c"
+#include "rtable_nng_static.c"
+#include "sr_nng_static.c"
+
+#else
+#include <nanomsg/nn.h>
+#include <nanomsg/tcp.h>
+#include <nanomsg/pair.h>
+#include <nanomsg/pipeline.h>
+#include <nanomsg/pubsub.h>
+
+#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 (file)
index 0000000..23be082
--- /dev/null
@@ -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 
+       $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+       $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../common/include>
+       $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../common/src>
+       $<INSTALL_INTERFACE:include>
+       PRIVATE src)
+
diff --git a/src/nanomsg/include/rmr_private.h b/src/nanomsg/include/rmr_private.h
new file mode 100644 (file)
index 0000000..3b5d3d3
--- /dev/null
@@ -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 (file)
index 0000000..3fe4247
--- /dev/null
@@ -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 <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <errno.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <time.h>
+#include <arpa/inet.h>
+
+#include <nanomsg/nn.h>
+#include <nanomsg/tcp.h>
+#include <nanomsg/pair.h>
+#include <nanomsg/pipeline.h>
+#include <nanomsg/pubsub.h>
+
+#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 (file)
index 0000000..2233da4
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <errno.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+
+/*
+       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 ? "<nil>" : 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 (file)
index 0000000..cddb662
--- /dev/null
@@ -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 <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <errno.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <time.h>
+#include <arpa/inet.h>
+
+#include <nanomsg/nn.h>
+#include <nanomsg/tcp.h>
+#include <nanomsg/pair.h>
+#include <nanomsg/pipeline.h>
+#include <nanomsg/pubsub.h>
+
+#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 (file)
index 0000000..511d0bb
--- /dev/null
@@ -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 
+       $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../common/include>
+        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../common/src>
+        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../rmr/include>
+       PRIVATE src)
+
+#      $<INSTALL_INTERFACE:include>
diff --git a/src/nng/include/rmr_nng_private.h b/src/nng/include/rmr_nng_private.h
new file mode 100644 (file)
index 0000000..83db500
--- /dev/null
@@ -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 (file)
index 0000000..9a9a043
--- /dev/null
@@ -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 <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <errno.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <time.h>
+#include <arpa/inet.h>
+
+#include <nng/nng.h>
+#include <nng/protocol/pubsub0/pub.h>
+#include <nng/protocol/pubsub0/sub.h>
+#include <nng/protocol/pipeline0/push.h>
+#include <nng/protocol/pipeline0/pull.h>
+
+
+#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 (file)
index 0000000..8b3bf67
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <errno.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+// -----------------------------------------------------------------------------------------------------
+
+/*
+       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 ? "<nil>" : 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 (file)
index 0000000..8e326e3
--- /dev/null
@@ -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 <nng/nng.h>
+#include <nng/protocol/pubsub0/pub.h>
+#include <nng/protocol/pubsub0/sub.h>
+#include <nng/protocol/pipeline0/push.h>
+#include <nng/protocol/pipeline0/pull.h>
+
+
+/*
+       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 (file)
index 0000000..6770301
--- /dev/null
@@ -0,0 +1,4 @@
+*.gcov
+*.dcov
+*.gcno
+*.gcda
diff --git a/test/.targets b/test/.targets
new file mode 100644 (file)
index 0000000..0825648
--- /dev/null
@@ -0,0 +1,6 @@
+
+# Coverage target values for modules which are not expected to meet the
+# default standard. Lines are <module-name> <target-pct> 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 (file)
index 0000000..20d461e
--- /dev/null
@@ -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 (file)
index 0000000..85f96ed
--- /dev/null
@@ -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 (file)
index 0000000..331f976
--- /dev/null
@@ -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 <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+
+#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 (file)
index 0000000..76b2765
--- /dev/null
@@ -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 (file)
index 0000000..d9a5f47
--- /dev/null
@@ -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 <signal.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#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 (file)
index 0000000..786d2e6
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <errno.h>
+#include <string.h>
+#include <errno.h>
+#include <pthread.h>
+#include <ctype.h>
+
+/*
+#include <nanomsg/nn.h>
+#include <nanomsg/tcp.h>
+#include <nanomsg/pair.h>
+#include <nanomsg/pipeline.h>
+#include <nanomsg/pubsub.h>
+*/
+
+#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 (executable)
index 0000000..fb5fa2a
--- /dev/null
@@ -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
+