Initial commit of TS xapp 57/3457/1
authorRon Shacham <rshacham@research.att.com>
Fri, 24 Apr 2020 18:46:48 +0000 (14:46 -0400)
committerRon Shacham <rshacham@research.att.com>
Fri, 24 Apr 2020 18:46:48 +0000 (14:46 -0400)
Signed-off-by: Ron Shacham <rshacham@research.att.com>
Change-Id: I1e08ef0cbee769d365f7b4f27a4548161ae4703c

32 files changed:
CMakeLists.txt [new file with mode: 0644]
Dockerfile [new file with mode: 0644]
README [new file with mode: 0644]
build_rmr.sh [new file with mode: 0755]
examples/Makefile [new file with mode: 0644]
examples/ts_xapp.cpp [new file with mode: 0644]
examples/ts_xapp.cpp~ [new file with mode: 0644]
rmr-version.yaml [new file with mode: 0644]
routes.txt [new file with mode: 0755]
run_xapp.sh [new file with mode: 0755]
src/messaging/CMakeLists.txt [new file with mode: 0644]
src/messaging/README [new file with mode: 0644]
src/messaging/callback.cpp [new file with mode: 0644]
src/messaging/callback.hpp [new file with mode: 0644]
src/messaging/default_cb.cpp [new file with mode: 0644]
src/messaging/default_cb.hpp [new file with mode: 0644]
src/messaging/message.cpp [new file with mode: 0644]
src/messaging/message.hpp [new file with mode: 0644]
src/messaging/messenger.cpp [new file with mode: 0644]
src/messaging/messenger.hpp [new file with mode: 0644]
src/messaging/msg_component.hpp [new file with mode: 0644]
src/xapp/CMakeLists.txt [new file with mode: 0644]
src/xapp/README [new file with mode: 0644]
src/xapp/xapp.cpp [new file with mode: 0644]
src/xapp/xapp.hpp [new file with mode: 0644]
test/.gitignore [new file with mode: 0644]
test/Makefile [new file with mode: 0644]
test/parse_gcov.sh [new file with mode: 0755]
test/rmr_em.c [new file with mode: 0644]
test/scrub_gcov.sh [new file with mode: 0755]
test/unit_test.cpp [new file with mode: 0644]
test/unit_test.sh [new file with mode: 0755]

diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..8e65e5f
--- /dev/null
@@ -0,0 +1,265 @@
+#==================================================================================
+#      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.
+#==================================================================================
+#
+
+# This CMake definition supports several -D command line options:
+#
+#      -DDEBUG=n                       Enable debugging level n
+#      -DDEV_PKG=1                     Development package configuration
+#      -DBUILD_DOC=1           Man pages generated
+#      -DPRESERVE_PTYPE=1      Do not change the processor type when naming deb packages
+#      -DGPROF=1                       Enable profiling compile time flags
+#      -DMAN_PREFIX=<path>     Supply a path where man pages are installed (default: /usr/share/man)
+
+project( ricxfcpp )
+cmake_minimum_required( VERSION 3.5 )
+
+set( major_version "0" )               # should be automatically populated from git tag later, but until CI process sets a tag we use this
+set( minor_version "1" )
+set( patch_level "2" )
+
+set( install_root "${CMAKE_INSTALL_PREFIX}" )
+set( install_inc "include/ricxfcpp" )
+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 )
+
+# externals may install using LIBDIR as established by the gnu include; it varies from system
+# to system, and we don't trust that it is always set, so we default to lib if it is missing.
+#
+if( NOT CMAKE_INSTALL_LIBDIR )
+       set( CMAKE_INSTALL_LIBDIR "lib" )
+endif()
+
+set( install_lib "${CMAKE_INSTALL_LIBDIR}" )
+message( "+++ ricxfcpp library install target directory: ${install_lib}" )
+
+# ---------------- extract some things from git ------------------------------
+
+# commit id for the version string
+execute_process(
+       COMMAND bash -c "git rev-parse --short HEAD|awk '{printf\"%s\", $0}'"
+       OUTPUT_VARIABLE git_id
+)
+
+# version information for library names and version string
+execute_process(
+       COMMAND bash -c "git describe --tags --abbrev=0 HEAD 2>/dev/null | awk -v tag=0.0.4095 ' { tag=$1 } END{ print  tag suffix }'|sed 's/\\./;/g' "
+       OUTPUT_VARIABLE mmp_version_str
+       ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE
+)
+message( "+++ mmp version from tag: '${mmp_version_str}'" )
+
+# extra indicator to show that the build was based on modified file(s) and not the true commit
+# (no hope of reproducing the exact library for debugging). Used only for the internal version
+# string.
+execute_process(
+       COMMAND bash -c "git diff --shortstat|awk -v fmt=%s -v r=-rotten '{ s=r } END { printf( fmt, s ) }'"
+       OUTPUT_VARIABLE spoiled_str
+)
+
+# uncomment these lines once CI starts adding a tag on merge
+#set( mmp_version ${mmp_version_str} )
+#list( GET mmp_version 0 major_version )
+#list( GET mmp_version 1 minor_version )
+#list( GET mmp_version 2 patch_level )
+
+if( DEBUG )                                    # if set, we'll set debugging on in the compile
+       set( debugging ${DEBUG} )
+       message( "+++ debugging is being set to ${DEBUG}" )
+else()
+       set( debugging 0 )
+       message( "+++ debugging is set to off" )
+endif()
+unset( DEBUG CACHE )                                   # we don't want this to persist
+
+
+# define constants used in the version string, debugging, etc.
+add_definitions(
+       -DGIT_ID=${git_id}
+       -DMAJOR_VER=${major_version}
+       -DMINOR_VER=${minor_version}
+       -DPATCH_VER=${patch_level}
+       -DDEBUG=${debugging}
+)
+
+# ---------------- suss out pkg gen tools so we don't fail generating packages that the system cannot support --------------
+
+# deb packages use underbars, and package manager(s) seem to flip the *_64 processor type
+# to the old (non-standard) amd64 string, so we do it here for consistency. Set -DPRESERVE_PTYPE=1
+# to prevent the flip. RPM packages will always be given the system generated processor type string.
+#
+if( ${CMAKE_SYSTEM_PROCESSOR} MATCHES "x86_64" )
+       if( NOT PRESERVE_PTYPE )
+               set( deb_sys_name "amd64" )
+       else()
+               set( deb_sys_name ${CMAKE_SYSTEM_PROCESSOR} )
+       endif()
+else()
+       set( deb_sys_name ${CMAKE_SYSTEM_PROCESSOR} )
+endif()
+unset( PRESERVE_PTYPE CACHE )                                  # we don't want this to persist
+
+set( rpm_sys_name ${CMAKE_SYSTEM_PROCESSOR} )
+
+if( DEV_PKG )
+       set( deb_pkg_name "ricxfcpp-dev" )
+       set( rpm_pkg_name "ricxfcpp-devel" )
+else()
+       set( deb_pkg_name "ricxfcpp" )
+       set( rpm_pkg_name "ricxfcpp" )
+endif()
+
+set( pkg_label "ricxfcpp${spoiled_str}-${major_version}.${minor_version}.${patch_level}-${sys_name}" )
+set( rpm_pkg_label "${rpm_pkg_name}${spoiled_str}-${major_version}.${minor_version}.${patch_level}-${rpm_sys_name}" )
+set( deb_pkg_label "${deb_pkg_name}${spoiled_str}_${major_version}.${minor_version}.${patch_level}_${deb_sys_name}" )
+message( "+++ pkg name: ${deb_pkg_label}.deb" )
+
+set( gen_rpm 0 )
+find_program( rpm NAMES rpmbuild )                                     # rpm package gen requires this to be installed
+if( "${rpm}" MATCHES "rpm-NOTFOUND" )                          # cannot build rpm
+       set( pkg_list "DEB" )
+       message( "### make package will generate only deb package; cannot find support to generate rpm packages" )
+else()
+       message( "+++ pkg name: ${rpm_pkg_label}.rpm" )         # debugging if we think we can gen rpm too
+       set( pkg_list "DEB;RPM" )
+       set( gen_rpm 1 )
+       message( "+++ make package will generate both deb and rpm packages" )
+endif()
+
+
+
+# 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 code to find them after we build them.
+#
+include_directories( "${CMAKE_CURRENT_SOURCE_DIR}/src/messaging" )
+
+
+# Compiler flags
+#
+set( CMAKE_POSITION_INDEPENDENT_CODE ON )
+set( CMAKE_C_FLAGS "-g " )
+set( CMAKE_CPP_FLAGS "-g " )
+if( GPROF )                                    # if set, we'll set profiling flag on compiles
+       message( "+++ profiling is on" )
+       set( CMAKE_C_FLAGS "-pg " )
+       set( CMAKE_CPP_FLAGS "-pg " )
+else()
+       message( "+++ profiling is off" )
+endif()
+unset( GPROF CACHE )                                   # we don't want this to persist
+
+# Include modules
+add_subdirectory( src/messaging )
+add_subdirectory( src/xapp )
+#add_subdirectory( doc )                               # this will auto skip if {X}fm is not available
+
+
+# shared and static libraries are built from the same object files.
+#
+add_library( ricxfcpp_shared SHARED "$<TARGET_OBJECTS:message_objects>;$<TARGET_OBJECTS:xapp_objects>" )
+set_target_properties( ricxfcpp_shared
+       PROPERTIES
+       OUTPUT_NAME "ricxfcpp"
+       SOVERSION ${major_version}
+       VERSION ${major_version}.${minor_version}.${patch_level} 
+)
+target_include_directories( ricxfcpp_shared PUBLIC "src/messenger" "src/xapp" )
+
+# we only build/export the static archive (.a) if generating a dev package
+if( DEV_PKG )
+       add_library( ricxfcpp_static STATIC "$<TARGET_OBJECTS:message_objects>;$<TARGET_OBJECTS:xapp_objects>" )
+       set_target_properties( ricxfcpp_static
+               PROPERTIES
+               OUTPUT_NAME "ricxfcpp"
+               SOVERSION ${major_version}
+               VERSION ${major_version}.${minor_version}.${patch_level} 
+       )
+       target_include_directories( ricxfcpp_static PUBLIC "src/messenger" "src/xapp" )
+endif()
+
+# -------- unit testing -------------------------------------------------------
+enable_testing()
+add_test(
+       NAME drive_unit_tests
+       COMMAND bash ../test/unit_test.sh -q
+       WORKING_DIRECTORY ../test
+)
+
+
+# ------------- packaging -----------------------------------------------------
+
+# Define what should be installed, and where they should go. For dev package we install
+# only the RMr headers, man pages and archive (.a) files.  The run-time package gets just
+# the library (.so) files and nothing more.
+#
+if( DEV_PKG )
+       set( target_list "ricxfcpp_static" )
+else()
+       set( target_list "ricxfcpp_shared" )
+endif()
+
+install( TARGETS ${target_list} EXPORT LibraryConfig
+       LIBRARY  DESTINATION ${install_lib}
+       ARCHIVE  DESTINATION ${install_lib}
+       PUBLIC_HEADER DESTINATION ${install_inc}
+)
+
+unset( DEV_PKG  CACHE )                        # prevent from being a hidden setting if user redoes things
+
+IF( EXISTS "${CMAKE_ROOT}/Modules/CPack.cmake" )
+       include( InstallRequiredSystemLibraries )
+
+       set( CPACK_DEBIAN_PACKAGE_NAME ${deb_pkg_name} )
+       set( CPACK_RPM_PACKAGE_NAME ${rpm_pkg_name} )
+
+       set( CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "/usr/local;/usr/local/bin;/usr/local/include;/usr/local/share;/usr/local/lib" )
+
+       set( CPACK_set_DESTDIR "on" )
+       set( CPACK_PACKAGING_INSTALL_PREFIX "${install_root}" )
+       set( CPACK_GENERATOR "${pkg_list}" )
+
+       set( CPACK_PACKAGE_DESCRIPTION "C++ framework for RIC xAPPs based on RMR." )
+       set( CPACK_PACKAGE_DESCRIPTION_SUMMARY "RIC xAPP C++ framework 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 "${pkg_label}" )                                             # generic name for old versions of cpack
+       set( CPACK_DEBIAN_FILE_NAME "${deb_pkg_label}.deb" )
+       set( CPACK_RPM_FILE_NAME "${rpm_pkg_label}.rpm" )
+
+       # Future: define dependencies on RMR and other libs
+
+       set( CPACK_DEBIAN_PACKAGE_PRIORITY "optional" )
+       set( CPACK_DEBIAN_PACKAGE_SECTION "ric" )
+       set( CPACK_DEBIAN_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR} )
+       set( CPACK_RPM_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR} )
+
+       # this seems ingnored if included
+       #set( CPACK_COMPONENTS_ALL Libraries ApplicationData )
+
+       INCLUDE( CPack )
+ENDIF()
diff --git a/Dockerfile b/Dockerfile
new file mode 100644 (file)
index 0000000..a5e2925
--- /dev/null
@@ -0,0 +1,141 @@
+# vim: ts=4 sw=4 noet:
+#==================================================================================
+#      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:       Dockerfile
+#      Abstract:       This can be used to create a base environment for using the xAPP
+#                              framework.  It will install RMR and the framework libraries. It also
+#                              installs make and g++ so that it can be used as a builder environment.
+#
+#                              The unit tests are executed as a part of the build process; if they are
+#                              not passing then the build will fail.
+#
+#                              Building should be as simple as:
+#
+#                                      docker build -f Dockerfile -t ricxfcpp:[version]
+#
+#      Date:           23 March 2020
+#      Author:         E. Scott Daniels
+# --------------------------------------------------------------------------------------
+
+
+FROM nexus3.o-ran-sc.org:10004/bldr-ubuntu18-c-go:6-u18.04-nng as buildenv
+RUN mkdir /playpen
+
+RUN apt-get update && apt-get install -y cmake gcc make git g++ wget
+
+RUN mkdir /playpen/bin /playpen/factory /playpen/factory/src /playpen/factory/test
+ARG SRC=.
+
+WORKDIR /playpen
+# Install RMr (runtime and dev) from debian package cached on packagecloud.io
+ARG RMR_VER=3.6.2
+
+# if package cloud is actually working, this is preferred
+#
+#RUN wget -nv --content-disposition https://packagecloud.io/o-ran-sc/staging/packages/debian/stretch/rmr_${RMR_VER}_amd64.deb/download.deb
+#RUN wget -nv --content-disposition https://packagecloud.io/o-ran-sc/staging/packages/debian/stretch/rmr-dev_${RMR_VER}_amd64.deb/download.deb
+#RUN dpkg -i rmr_${RMR_VER}_amd64.deb
+#RUN dpkg -i rmr-dev_${RMR_VER}_amd64.deb
+#
+# else this:
+#
+RUN git config --global http.proxy http://one.proxy.att.com:8080
+
+
+RUN apt-get install -y cpputest
+RUN apt-get remove -y libboost-all-dev
+RUN apt-get install -y  libboost-all-dev
+RUN apt-get install -y libhiredis-dev
+RUN apt-get install -y valgrind
+
+
+RUN git clone https://gerrit.o-ran-sc.org/r/ric-plt/sdl
+RUN cd sdl && \
+    ./autogen.sh && \
+    ./configure && \
+    make all && \
+    #    make test && \
+    make install
+
+
+COPY ${SRC}/build_rmr.sh /playpen/bin
+RUN bash /playpen/bin/build_rmr.sh -t ${RMR_VER}
+
+COPY ${SRC}/CMakeLists.txt /playpen/factory/
+COPY ${SRC}/src /playpen/factory/src/
+COPY ${SRC}/test /playpen/factory/test/
+#COPY ${SRC}/examples /tmp/examples/
+
+
+COPY ${SRC}/examples /playpen/factory/examples
+COPY ${SRC}/routes.txt /playpen/factory/examples
+
+#
+# Run unit tests
+#
+COPY ${SRC}/test/* /playpen/factory/test/
+RUN cd /playpen/factory/test; bash unit_test.sh
+
+# Go to the factory and build our stuff
+#
+ENV LD_LIBRARY_PATH=/usr/local/lib64:/usr/local/lib:/usr/lib
+ENV C_INCLUDE_PATH=/usr/local/include
+RUN cd /playpen/factory; rm -fr .build; mkdir .build; cd .build; cmake .. -DDEV_PKG=1; make install; cmake .. -DDEV_PKG=0; make install
+
+RUN cd /playpen/factory/examples; make
+
+RUN ls /usr/local/lib
+
+
+# -----  final, smaller image ----------------------------------
+FROM ubuntu:18.04
+
+# must add compile tools to make it a builder environmnent. If a build environment isn't needed 
+# comment out the next line and reduce the image size by more than 180M.
+#
+RUN apt-get update && apt-get install -y --no-install-recommends make g++
+
+# if bash doesn't cut it for run_replay grab a real shell and clean up as much as we can
+RUN apt-get update; apt-get install -y ksh
+RUN rm -fr /var/lib/apt/lists 
+
+RUN mkdir -p /usr/local/include/ricxfcpp
+COPY --from=buildenv /usr/local/lib /usr/local/lib/
+COPY --from=buildenv /usr/local/include/ricxfcpp /usr/local/include/ricxfcpp/
+COPY --from=buildenv /usr/local/include/rmr /usr/local/include/rmr/
+COPY --from=buildenv /usr/local/lib /usr/local/lib
+COPY --from=buildenv /usr/lib /usr/lib/
+
+RUN mkdir -p /examples/
+COPY --from=buildenv /playpen/factory/examples/ts_xapp /examples/
+COPY --from=buildenv /playpen/factory/examples/routes.txt /examples/
+
+
+ENV LD_LIBRARY_PATH=/usr/local/lib64:/usr/local/lib
+ENV C_INCLUDE_PATH=/usr/local/include
+
+ENV RMR_SEED_RT="routes.txt"
+
+#WORKDIR /factory
+
+WORKDIR /examples/
+
+#CMD [ "make" ]
+
+CMD ./ts_xapp
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..e4dc3ff
--- /dev/null
+++ b/README
@@ -0,0 +1,22 @@
+
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 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.
+==================================================================================
+
+xAPP C++ Framework
+
+This repository contains a "framework" on which C++ RIC applications (xAPPs)
+can be built.
diff --git a/build_rmr.sh b/build_rmr.sh
new file mode 100755 (executable)
index 0000000..905a15d
--- /dev/null
@@ -0,0 +1,23 @@
+#!/usr/bin/env ksh
+
+#      Mnemonic:       rmr_build.sh
+#      Abstract:       This will pull RMR from the repo, build and install. This
+#                              is en lieu of using wget to fetch the RMR package from some
+#                              repo and installing it.  The package method is preferred
+#                              but if that breaks this can be used in place of it.
+
+rmr_ver=${1:-3.6.2}
+
+# assume that we're in the proper directory
+set -e
+git clone "https://gerrit.o-ran-sc.org/r/ric-plt/lib/rmr"
+
+cd rmr
+git checkout $ver
+mkdir .build
+cd .build
+cmake .. -DDEV_PKG=1 -DPACK_EXTERNALS=1
+make install
+cmake .. -DDEV_PKG=0
+make install
+
diff --git a/examples/Makefile b/examples/Makefile
new file mode 100644 (file)
index 0000000..07830bc
--- /dev/null
@@ -0,0 +1,30 @@
+# vim: ts=4 sw=4 noet:
+
+#==================================================================================
+#      Copyright (c) 2020 Nokia
+#      Copyright (c) 2020 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.
+#==================================================================================
+
+# simple makefile to build the examples. This assumes that the xapp framework
+# library has been installed or the LD_LIBRARY_PATH and C_INCLUDE_PATH environent
+# variables are set to reference the needed files.
+
+%.o:: %.cpp %.hpp
+       g++ -g ${prereq%% *} -c 
+
+% :: %.cpp
+       g++ $< -g -o $@  -lricxfcpp -lrmr_si -lpthread -lm -lsdl
+
+all:: ts_xapp
diff --git a/examples/ts_xapp.cpp b/examples/ts_xapp.cpp
new file mode 100644 (file)
index 0000000..bd51be9
--- /dev/null
@@ -0,0 +1,346 @@
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 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:       ts_xapp.cpp
+       Abstract:       Traffic Steering xApp;
+                              1. Receives A1 Policy
+                              2. Queries SDL to decide which UE to attempt Traffic Steering for
+                              3. Requests prediction for UE throughput on current and neighbor cells
+                              4. Receives prediction
+                              5. Optionally exercises Traffic Steering action over E2
+
+       Date:           22 April 2020
+       Author:         Ron Shacham
+               
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <memory>
+
+#include <sdl/syncstorage.hpp>
+#include <set>
+#include <map>
+#include <vector>
+#include <string>
+
+#include "ricxfcpp/xapp.hpp"
+
+using Namespace = std::string;
+using Key = std::string;
+using Data = std::vector<uint8_t>;
+using DataMap = std::map<Key, Data>;
+using Keys = std::set<Key>;
+
+
+// ----------------------------------------------------------
+
+std::unique_ptr<Xapp> xfw;
+
+
+void policy_callback( Message& mbuf, int mtype, int subid, int len, Msg_component payload,  void* data ) {
+
+  long now;
+  long total_count;
+
+  int sz;
+  int i;
+
+  int response_to = 0;  // max timeout wating for a response
+
+  int send_mtype = 0;
+  int rmtype;                                                  // received message type
+  int delay = 1000000;                         // mu-sec delay; default 1s
+
+  std::unique_ptr<Message> msg;
+  Msg_component send_payload;                          // special type of unique pointer to the payload
+  
+  fprintf( stderr, "Policy Callback got a message, type=%d , length=%d\n" , mtype, len);
+  fprintf(stderr, "payload is %s\n", payload.get());
+  
+  //fprintf( stderr, "callback 1 got a message type = %d len = %d\n", mtype, len );
+  mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK1\n" ); // validate that we can use the same buffer for 2 rts calls
+  mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK2\n" );
+
+  mtype = 0;
+
+  fprintf(stderr, "cb 1\n");
+
+  msg = xfw->Alloc_msg( 2048 );
+  
+  sz = msg->Get_available_size();  // we'll reuse a message if we received one back; ensure it's big enough
+  if( sz < 2048 ) {
+    fprintf( stderr, "<SNDR> fail: message returned did not have enough size: %d [%d]\n", sz, i );
+    exit( 1 );
+  }
+
+  fprintf(stderr, "cb 2");
+  
+  send_payload = msg->Get_payload(); // direct access to payload
+  snprintf( (char *) send_payload.get(), 2048, "{\"UEPredictionSet\" : [\"222\", \"333\", \"444\"]}", i );     
+
+  fprintf(stderr, "cb 3");    
+  
+  // payload updated in place, nothing to copy from, so payload parm is nil
+  if ( ! msg->Send_msg( mtype, Message::NO_SUBID, strlen( (char *) send_payload.get() )+1, NULL )) {
+    fprintf( stderr, "<SNDR> send failed: %d\n", i );
+  }
+
+  fprintf(stderr, "cb 4");    
+
+  /*
+  msg = xfw->Receive( response_to );
+  if( msg != NULL ) {
+    rmtype = msg->Get_mtype();
+    send_payload = msg->Get_payload();
+    fprintf( stderr, "got: mtype=%d payload=(%s)\n", rmtype, (char *) send_payload.get() );
+  } 
+  */  
+  
+}
+
+
+void prediction_callback( Message& mbuf, int mtype, int subid, int len, Msg_component payload,  void* data ) {
+
+  long now;
+  long total_count;
+
+  int sz;
+  int i;
+
+  int response_to = 0;  // max timeout wating for a response
+
+  int send_mtype = 0;
+  int rmtype;                                                  // received message type
+  int delay = 1000000;                         // mu-sec delay; default 1s
+
+  std::unique_ptr<Message> msg;
+  Msg_component send_payload;                          // special type of unique pointer to the payload
+  
+  fprintf( stderr, "Prediction Callback got a message, type=%d , length=%d\n" , mtype, len);
+  fprintf(stderr, "payload is %s\n", payload.get());
+  
+  mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK1\n" ); // validate that we can use the same buffer for 2 rts calls
+  mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK2\n" );
+
+  mtype = 0;
+
+  fprintf(stderr, "cb 1\n");
+  
+}
+
+
+
+extern int main( int argc, char** argv ) {
+
+  std::unique_ptr<Message> msg;
+  Msg_component payload;                               // special type of unique pointer to the payload
+
+  int nthreads = 1;
+
+  int response_to = 0;  // max timeout wating for a response  
+
+  int delay = 1000000;                         // mu-sec delay; default 1s  
+
+  char*        port = (char *) "4560";
+
+  int ai;  
+  
+  ai = 1;
+  while( ai < argc ) {                         // very simple flag processing (no bounds/error checking)
+    if( argv[ai][0] != '-' )  {
+      break;
+    }
+    
+    switch( argv[ai][1] ) {                    // we only support -x so -xy must be -x -y
+    case 'd':                                  // delay between messages (mu-sec)
+      delay = atoi( argv[ai+1] );
+      ai++;
+      break;
+      
+    case 'p': 
+      port = argv[ai+1];       
+      ai++;
+      break;
+      
+    case 't':                                  // timeout in seconds; we need to convert to ms for rmr calls
+      response_to = atoi( argv[ai+1] ) * 1000;
+      ai++;
+      break;
+    }
+    ai++;
+  }
+  
+  fprintf( stderr, "<XAPP> response timeout set to: %d\n", response_to );
+  fprintf( stderr, "<XAPP> listening on port: %s\n", port );
+  
+  xfw = std::unique_ptr<Xapp>( new Xapp( port, true ) ); // new xAPP thing; wait for a route table
+
+  fprintf(stderr, "code1\n");
+  
+  xfw->Add_msg_cb( 20010, policy_callback, NULL );
+  xfw->Add_msg_cb( 30002, prediction_callback, NULL );
+
+  fprintf(stderr, "code2\n");
+
+  std::string sdl_namespace_u = "TS-UE-metrics";
+  std::string sdl_namespace_c = "TS-cell-metrics";
+
+  fprintf(stderr, "code5\n");
+  
+  std::unique_ptr<shareddatalayer::SyncStorage> sdl(shareddatalayer::SyncStorage::create());
+
+  Namespace nsu(sdl_namespace_u);
+  Namespace nsc(sdl_namespace_c);
+
+    /*
+
+  fprintf(stderr, "before sdl set\n");
+  
+  try{
+    //connecting to the Redis and generating a random key for namespace "hwxapp"
+    fprintf(stderr, "IN SDL Set Data");
+    //    std::string data_string = "{\"rsrp\" : -110}";
+
+
+    std::string data_string = "{\"CellID\": \"310-680-200-555001\", \"MeasTimestampPDCPBytes\": \"2020-03-18 02:23:18.220\", \"MeasPeriodPDCPBytes\": 20, \"PDCPBytesDL\": 2000000, \"PDCPBytesUL\": 1200000, \"MeasTimestampAvailPRB\": \"2020-03-18 02:23:18.220\", \"MeasPeriodAvailPRB\": 20, \"AvailPRBDL\": 30, \"AvailPRBUL\": 50  }";
+      
+    DataMap dmap;
+    //    char key[4]="abc";
+    char key[] = "310-680-200-555001";
+    std::cout << "KEY: "<< key << std::endl;
+    Key k = key;
+    Data d;
+    //    uint8_t num = 101;
+    d.assign(data_string.begin(), data_string.end());
+    //    d.push_back(num);
+    dmap.insert({k,d});
+
+    sdl->set(nsc, dmap);
+
+    data_string = "{ \"CellID\": \"310-680-200-555002\", \"MeasTimestampPDCPBytes\": \"2020-03-18 02:23:18.220\", \"MeasPeriodPDCPBytes\": 20, \"PDCPBytesDL\": 800000, \"PDCPBytesUL\": 400000, \"MeasTimestampAvailPRB\": \"2020-03-18 02:23:18.220\", \"MeasPeriodAvailPRB\": 20, \"AvailPRBDL\": 30, \"AvailPRBUL\": 45  }";
+
+    Data d2;
+    DataMap dmap2;    
+    char key2[] = "310-680-200-555002";
+    std::cout << "KEY: "<< key2 << std::endl;
+    Key k2 = key2;
+    d2.assign(data_string.begin(), data_string.end());
+    //    d.push_back(num);
+    dmap2.insert({k2,d});
+
+    sdl->set(nsc, dmap2);
+
+
+
+    std::string data_string = "{ \"CellID\": \"310-680-200-555003\", \"MeasTimestampPDCPBytes\": \"2020-03-18 02:23:18.220\", \"MeasPeriodPDCPBytes\": 20, \"PDCPBytesDL\": 800000, \"PDCPBytesUL\": 400000, \"MeasTimestampAvailPRB\": \"2020-03-18 02:23:18.220\", \"MeasPeriodAvailPRB\": 20, \"AvailPRBDL\": 30, \"AvailPRBUL\": 45  }";
+
+    Data d3;
+    DataMap dmap3;
+    char key3[] = "310-680-200-555003";
+    std::cout << "KEY: "<< key3 << std::endl;
+    Key k3 = key3;
+    d3.assign(data_string.begin(), data_string.end());
+    //    d.push_back(num);
+    dmap3.insert({k3,d3});
+
+    sdl->set(nsc, dmap3);
+
+
+
+    data_string = "{ \"UEID\": 12345, \"ServingCellID\": \"310-680-200-555002\", \"MeasTimestampUEPDCPBytes\": \"2020-03-18 02:23:18.220\", \"MeasPeriodUEPDCPBytes\": 20,\"UEPDCPBytesDL\": 250000,\"UEPDCPBytesUL\": 100000, \"MeasTimestampUEPRBUsage\": \"2020-03-18 02:23:18.220\", \"MeasPeriodUEPRBUsage\": 20, \"UEPRBUsageDL\": 10, \"UEPRBUsageUL\": 30, \"MeasTimestampRF\": \"2020-03-18 02:23:18.210\",\"MeasPeriodRF\": 40, \"ServingCellRF\": [-115,-16,-5], \"NeighborCellRF\": [  {\"CID\": \"310-680-200-555001\",\"Cell-RF\": [-90,-13,-2.5 ] }, {\"CID\": \"310-680-200-555003\",  \"Cell-RF\": [-140,-17,-6 ] } ] }";
+      
+    Data d4;
+    DataMap dmap4;
+    char key4[] = "12345";
+    std::cout << "KEY: "<< key << std::endl;
+    d4.assign(data_string.begin(), data_string.end());
+    Key k4 = key4;
+    //    d.push_back(num);
+    dmap4.insert({k4,d4});
+
+    sdl->set(nsu, dmap4);
+
+    
+  }
+  catch(...){
+    fprintf(stderr,"SDL Error in Set Data for Namespace");
+    return false;
+  }
+  
+  fprintf(stderr, "after sdl set\n");
+
+    */
+
+  fprintf(stderr, "before sdl get\n");
+
+
+  std::string prefix2="310";
+  Keys K = sdl->findKeys(nsc, prefix2);     // just the prefix
+  DataMap Dk = sdl->get(nsc, K);
+
+  std::cout << "K contains " << K.size() << " elements.\n";  
+
+  fprintf(stderr, "before forloop\n");
+
+  for(auto si=K.begin();si!=K.end();++si){
+    std::vector<uint8_t> val_v = Dk[(*si)]; // 4 lines to unpack a string
+    char val[val_v.size()+1];                               // from Data
+    int i;
+    for(i=0;i<val_v.size();++i) val[i] = (char)(val_v[i]);
+    val[i]='\0';
+    fprintf(stderr, "KEYS and Values %s = %s\n",(*si).c_str(), val);
+  }
+
+
+  std::string prefix3="12";
+  Keys K2 = sdl->findKeys(nsu, prefix3);     // just the prefix
+  DataMap Dk2 = sdl->get(nsu, K2);
+
+  std::cout << "K contains " << K2.size() << " elements.\n";  
+
+  fprintf(stderr, "before forloop\n");
+
+  for(auto si=K2.begin();si!=K2.end();++si){
+    std::vector<uint8_t> val_v = Dk2[(*si)]; // 4 lines to unpack a string
+    char val[val_v.size()+1];                               // from Data
+    int i;
+    for(i=0;i<val_v.size();++i) val[i] = (char)(val_v[i]);
+    val[i]='\0';
+    fprintf(stderr, "KEYS and Values %s = %s\n",(*si).c_str(), val);
+  }  
+  
+
+  fprintf(stderr, "after sdl get\n");
+
+  xfw->Run( nthreads );
+
+  fprintf(stderr, "code3\n");  
+  
+  msg = xfw->Alloc_msg( 2048 );
+
+  fprintf(stderr, "code4\n");    
+
+  
+}
diff --git a/examples/ts_xapp.cpp~ b/examples/ts_xapp.cpp~
new file mode 100644 (file)
index 0000000..9ccd4b5
--- /dev/null
@@ -0,0 +1,346 @@
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 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:       ts_xapp.cpp
+       Abstract:       Traffic Steering xApp;
+                              1. Receives A1 Policy
+                              2. Queries SDL to decide which UE to attempt Traffic Steering for
+                              3. Requests prediction for UE throughput on current and neighbor cells
+                              4. Receives prediction
+                              5. Optionally exercises Traffic Steering action over E2
+
+       Date:           22 April 2020
+       Author:         Ron Shacham
+               
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <iostream>
+#include <memory>
+
+#include <sdl/syncstorage.hpp>
+#include <set>
+#include <map>
+#include <vector>
+#include <string>
+
+#include "ricxfcpp/xapp.hpp"
+
+using Namespace = std::string;
+using Key = std::string;
+using Data = std::vector<uint8_t>;
+using DataMap = std::map<Key, Data>;
+using Keys = std::set<Key>;
+
+
+// ----------------------------------------------------------
+
+std::unique_ptr<Xapp> xfw;
+
+
+void policy_callback( Message& mbuf, int mtype, int subid, int len, Msg_component payload,  void* data ) {
+
+  long now;
+  long total_count;
+
+  int sz;
+  int i;
+
+  int response_to = 0;  // max timeout wating for a response
+
+  int send_mtype = 0;
+  int rmtype;                                                  // received message type
+  int delay = 1000000;                         // mu-sec delay; default 1s
+
+  std::unique_ptr<Message> msg;
+  Msg_component send_payload;                          // special type of unique pointer to the payload
+  
+  fprintf( stderr, "Policy Callback got a message, type=%d , length=%d\n" , mtype, len);
+  fprintf(stderr, "payload is %s\n", payload.get());
+  
+  //fprintf( stderr, "callback 1 got a message type = %d len = %d\n", mtype, len );
+  mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK1\n" ); // validate that we can use the same buffer for 2 rts calls
+  mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK2\n" );
+
+  mtype = 0;
+
+  fprintf(stderr, "cb 1\n");
+
+  msg = xfw->Alloc_msg( 2048 );
+  
+  sz = msg->Get_available_size();  // we'll reuse a message if we received one back; ensure it's big enough
+  if( sz < 2048 ) {
+    fprintf( stderr, "<SNDR> fail: message returned did not have enough size: %d [%d]\n", sz, i );
+    exit( 1 );
+  }
+
+  fprintf(stderr, "cb 2");
+  
+  send_payload = msg->Get_payload(); // direct access to payload
+  snprintf( (char *) send_payload.get(), 2048, "{\"UEPredictionSet\" : [\"222\", \"333\", \"444\"]}", i );     
+
+  fprintf(stderr, "cb 3");    
+  
+  // payload updated in place, nothing to copy from, so payload parm is nil
+  if ( ! msg->Send_msg( mtype, Message::NO_SUBID, strlen( (char *) send_payload.get() )+1, NULL )) {
+    fprintf( stderr, "<SNDR> send failed: %d\n", i );
+  }
+
+  fprintf(stderr, "cb 4");    
+
+  /*
+  msg = xfw->Receive( response_to );
+  if( msg != NULL ) {
+    rmtype = msg->Get_mtype();
+    send_payload = msg->Get_payload();
+    fprintf( stderr, "got: mtype=%d payload=(%s)\n", rmtype, (char *) send_payload.get() );
+  } 
+  */  
+  
+}
+
+
+void prediction_callback( Message& mbuf, int mtype, int subid, int len, Msg_component payload,  void* data ) {
+
+  long now;
+  long total_count;
+
+  int sz;
+  int i;
+
+  int response_to = 0;  // max timeout wating for a response
+
+  int send_mtype = 0;
+  int rmtype;                                                  // received message type
+  int delay = 1000000;                         // mu-sec delay; default 1s
+
+  std::unique_ptr<Message> msg;
+  Msg_component send_payload;                          // special type of unique pointer to the payload
+  
+  fprintf( stderr, "Prediction Callback got a message, type=%d , length=%d\n" , mtype, len);
+  fprintf(stderr, "payload is %s\n", payload.get());
+  
+  mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK1\n" ); // validate that we can use the same buffer for 2 rts calls
+  mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK2\n" );
+
+  mtype = 0;
+
+  fprintf(stderr, "cb 1\n");
+  
+}
+
+
+
+extern int main( int argc, char** argv ) {
+
+  std::unique_ptr<Message> msg;
+  Msg_component payload;                               // special type of unique pointer to the payload
+
+  int nthreads = 1;
+
+  int response_to = 0;  // max timeout wating for a response  
+
+  int delay = 1000000;                         // mu-sec delay; default 1s  
+
+  char*        port = (char *) "4555";
+
+  int ai;  
+  
+  ai = 1;
+  while( ai < argc ) {                         // very simple flag processing (no bounds/error checking)
+    if( argv[ai][0] != '-' )  {
+      break;
+    }
+    
+    switch( argv[ai][1] ) {                    // we only support -x so -xy must be -x -y
+    case 'd':                                  // delay between messages (mu-sec)
+      delay = atoi( argv[ai+1] );
+      ai++;
+      break;
+      
+    case 'p': 
+      port = argv[ai+1];       
+      ai++;
+      break;
+      
+    case 't':                                  // timeout in seconds; we need to convert to ms for rmr calls
+      response_to = atoi( argv[ai+1] ) * 1000;
+      ai++;
+      break;
+    }
+    ai++;
+  }
+  
+  fprintf( stderr, "<XAPP> response timeout set to: %d\n", response_to );
+  fprintf( stderr, "<XAPP> listening on port: %s\n", port );
+  
+  xfw = std::unique_ptr<Xapp>( new Xapp( port, true ) ); // new xAPP thing; wait for a route table
+
+  fprintf(stderr, "code1\n");
+  
+  xfw->Add_msg_cb( 20010, policy_callback, NULL );
+  xfw->Add_msg_cb( 30002, prediction_callback, NULL );
+
+  fprintf(stderr, "code2\n");
+
+  std::string sdl_namespace_u = "TS-UE-metrics";
+  std::string sdl_namespace_c = "TS-cell-metrics";
+
+  fprintf(stderr, "code5\n");
+  
+  std::unique_ptr<shareddatalayer::SyncStorage> sdl(shareddatalayer::SyncStorage::create());
+
+  Namespace nsu(sdl_namespace_u);
+  Namespace nsc(sdl_namespace_c);
+
+    /*
+
+  fprintf(stderr, "before sdl set\n");
+  
+  try{
+    //connecting to the Redis and generating a random key for namespace "hwxapp"
+    fprintf(stderr, "IN SDL Set Data");
+    //    std::string data_string = "{\"rsrp\" : -110}";
+
+
+    std::string data_string = "{\"CellID\": \"310-680-200-555001\", \"MeasTimestampPDCPBytes\": \"2020-03-18 02:23:18.220\", \"MeasPeriodPDCPBytes\": 20, \"PDCPBytesDL\": 2000000, \"PDCPBytesUL\": 1200000, \"MeasTimestampAvailPRB\": \"2020-03-18 02:23:18.220\", \"MeasPeriodAvailPRB\": 20, \"AvailPRBDL\": 30, \"AvailPRBUL\": 50  }";
+      
+    DataMap dmap;
+    //    char key[4]="abc";
+    char key[] = "310-680-200-555001";
+    std::cout << "KEY: "<< key << std::endl;
+    Key k = key;
+    Data d;
+    //    uint8_t num = 101;
+    d.assign(data_string.begin(), data_string.end());
+    //    d.push_back(num);
+    dmap.insert({k,d});
+
+    sdl->set(nsc, dmap);
+
+    data_string = "{ \"CellID\": \"310-680-200-555002\", \"MeasTimestampPDCPBytes\": \"2020-03-18 02:23:18.220\", \"MeasPeriodPDCPBytes\": 20, \"PDCPBytesDL\": 800000, \"PDCPBytesUL\": 400000, \"MeasTimestampAvailPRB\": \"2020-03-18 02:23:18.220\", \"MeasPeriodAvailPRB\": 20, \"AvailPRBDL\": 30, \"AvailPRBUL\": 45  }";
+
+    Data d2;
+    DataMap dmap2;    
+    char key2[] = "310-680-200-555002";
+    std::cout << "KEY: "<< key2 << std::endl;
+    Key k2 = key2;
+    d2.assign(data_string.begin(), data_string.end());
+    //    d.push_back(num);
+    dmap2.insert({k2,d});
+
+    sdl->set(nsc, dmap2);
+
+
+
+    std::string data_string = "{ \"CellID\": \"310-680-200-555003\", \"MeasTimestampPDCPBytes\": \"2020-03-18 02:23:18.220\", \"MeasPeriodPDCPBytes\": 20, \"PDCPBytesDL\": 800000, \"PDCPBytesUL\": 400000, \"MeasTimestampAvailPRB\": \"2020-03-18 02:23:18.220\", \"MeasPeriodAvailPRB\": 20, \"AvailPRBDL\": 30, \"AvailPRBUL\": 45  }";
+
+    Data d3;
+    DataMap dmap3;
+    char key3[] = "310-680-200-555003";
+    std::cout << "KEY: "<< key3 << std::endl;
+    Key k3 = key3;
+    d3.assign(data_string.begin(), data_string.end());
+    //    d.push_back(num);
+    dmap3.insert({k3,d3});
+
+    sdl->set(nsc, dmap3);
+
+
+
+    data_string = "{ \"UEID\": 12345, \"ServingCellID\": \"310-680-200-555002\", \"MeasTimestampUEPDCPBytes\": \"2020-03-18 02:23:18.220\", \"MeasPeriodUEPDCPBytes\": 20,\"UEPDCPBytesDL\": 250000,\"UEPDCPBytesUL\": 100000, \"MeasTimestampUEPRBUsage\": \"2020-03-18 02:23:18.220\", \"MeasPeriodUEPRBUsage\": 20, \"UEPRBUsageDL\": 10, \"UEPRBUsageUL\": 30, \"MeasTimestampRF\": \"2020-03-18 02:23:18.210\",\"MeasPeriodRF\": 40, \"ServingCellRF\": [-115,-16,-5], \"NeighborCellRF\": [  {\"CID\": \"310-680-200-555001\",\"Cell-RF\": [-90,-13,-2.5 ] }, {\"CID\": \"310-680-200-555003\",  \"Cell-RF\": [-140,-17,-6 ] } ] }";
+      
+    Data d4;
+    DataMap dmap4;
+    char key4[] = "12345";
+    std::cout << "KEY: "<< key << std::endl;
+    d4.assign(data_string.begin(), data_string.end());
+    Key k4 = key4;
+    //    d.push_back(num);
+    dmap4.insert({k4,d4});
+
+    sdl->set(nsu, dmap4);
+
+    
+  }
+  catch(...){
+    fprintf(stderr,"SDL Error in Set Data for Namespace");
+    return false;
+  }
+  
+  fprintf(stderr, "after sdl set\n");
+
+    */
+
+  fprintf(stderr, "before sdl get\n");
+
+
+  std::string prefix2="310";
+  Keys K = sdl->findKeys(nsc, prefix2);     // just the prefix
+  DataMap Dk = sdl->get(nsc, K);
+
+  std::cout << "K contains " << K.size() << " elements.\n";  
+
+  fprintf(stderr, "before forloop\n");
+
+  for(auto si=K.begin();si!=K.end();++si){
+    std::vector<uint8_t> val_v = Dk[(*si)]; // 4 lines to unpack a string
+    char val[val_v.size()+1];                               // from Data
+    int i;
+    for(i=0;i<val_v.size();++i) val[i] = (char)(val_v[i]);
+    val[i]='\0';
+    fprintf(stderr, "KEYS and Values %s = %s\n",(*si).c_str(), val);
+  }
+
+
+  std::string prefix3="12";
+  Keys K2 = sdl->findKeys(nsu, prefix3);     // just the prefix
+  DataMap Dk2 = sdl->get(nsu, K2);
+
+  std::cout << "K contains " << K2.size() << " elements.\n";  
+
+  fprintf(stderr, "before forloop\n");
+
+  for(auto si=K2.begin();si!=K2.end();++si){
+    std::vector<uint8_t> val_v = Dk2[(*si)]; // 4 lines to unpack a string
+    char val[val_v.size()+1];                               // from Data
+    int i;
+    for(i=0;i<val_v.size();++i) val[i] = (char)(val_v[i]);
+    val[i]='\0';
+    fprintf(stderr, "KEYS and Values %s = %s\n",(*si).c_str(), val);
+  }  
+  
+
+  fprintf(stderr, "after sdl get\n");
+
+  xfw->Run( nthreads );
+
+  fprintf(stderr, "code3\n");  
+  
+  msg = xfw->Alloc_msg( 2048 );
+
+  fprintf(stderr, "code4\n");    
+
+  
+}
diff --git a/rmr-version.yaml b/rmr-version.yaml
new file mode 100644 (file)
index 0000000..5dd30dc
--- /dev/null
@@ -0,0 +1,3 @@
+# Communicate to CI which version of RMR to install in the build/vet environment
+---
+version: 3.6.2
diff --git a/routes.txt b/routes.txt
new file mode 100755 (executable)
index 0000000..f0242cf
--- /dev/null
@@ -0,0 +1,6 @@
+newrt|start
+mse|20010|20008|4560
+rte|20011|service-ricplt-a1mediator-rmr:10000
+rte|TS_QUE_PREDICTION|4560
+rte|TS_UE_LIST|service-ricxapp-qpd:4560
+newrt|end
diff --git a/run_xapp.sh b/run_xapp.sh
new file mode 100755 (executable)
index 0000000..f65a4b7
--- /dev/null
@@ -0,0 +1,21 @@
+#! /bin/bash
+
+export RMR_SEED_RT="routes.txt"
+export RMR_RTG_SVC="9999"
+export XAPP_NAME="HELLOWORLD_XAPP"
+export HW_PORTS="4560"
+export MSG_MAX_BUFFER="2048"
+export THREADS="1"
+export VERBOSE="0"
+export CONFIG_FILE="config/config-file.json"
+export GNODEB="NYC123"
+export XAPP_ID="3489-er492k-92389"
+export A1_SCHEMA_FILE="schemas/hwxapp-policy.json"
+export VES_SCHEMA_FILE="schemas/hwxapp-ves.json"
+export VES_COLLECTOR_URL="127.0.0.1:6350"
+export VES_MEASUREMENT_INTERVAL="10"
+export LOG_LEVEL="MDCLOG_ERR"
+export OPERATING_MODE="CONTROL"
+
+
+
diff --git a/src/messaging/CMakeLists.txt b/src/messaging/CMakeLists.txt
new file mode 100644 (file)
index 0000000..507da84
--- /dev/null
@@ -0,0 +1,47 @@
+# vim: sw=4 ts=4 noet:
+#
+#==================================================================================
+#   Copyright (c) 2020 Nokia
+#   Copyright (c) 2020 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( message_objects OBJECT
+       callback.cpp    
+       default_cb.cpp
+       message.cpp
+       messenger.cpp
+)
+
+target_include_directories (message_objects PUBLIC
+       $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+       $<INSTALL_INTERFACE:include>
+       PRIVATE src)
+
+# header files should go into .../include/xfcpp/
+if( DEV_PKG )
+       install( FILES
+               callback.hpp  
+               default_cb.hpp  
+               message.hpp  
+               messenger.hpp
+               msg_component.hpp
+               DESTINATION ${install_inc}
+       ) 
+endif()
+
diff --git a/src/messaging/README b/src/messaging/README
new file mode 100644 (file)
index 0000000..0683082
--- /dev/null
@@ -0,0 +1,47 @@
+
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 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.
+==================================================================================
+
+This directory contains the source for the RMR messaging portion of the 
+framework.  Specifially:
+
+       message.*
+               provides a message class from which the user can extract/set information
+               (type, subscription ID, payload, etc.) and provides instance funcitons
+               for sending and replying to the message. User programmes will interact
+               directly with this.
+
+       msg_component.*
+               This is a small type def that allows some of the getter functions in message
+               to return a smart pointer to memory which cannot directly be released (e.g
+               the payload).   This allows the user programme to declare a variable of type
+               Msg_component (which is really a smart pointer with a custom deleter) rather
+               than having to understand the needed smart pointer construct for these things.
+
+       messenger.*
+               This class provides the direct "wrapper" on top of RMR. It supports 
+               high level operations such as Listen and callback management.
+
+       callback.*
+               This provides the callback support to messenger (listen) functions.
+
+       default_cb.*
+               This module contains any default callback functions which are provided
+               by the framework (e.g. health check processing). 
+
+
+       
diff --git a/src/messaging/callback.cpp b/src/messaging/callback.cpp
new file mode 100644 (file)
index 0000000..d4d697d
--- /dev/null
@@ -0,0 +1,58 @@
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 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:       callback.cpp
+       Abstract:       Function defs for the callback managment object
+       Date:           9 March 2020
+       Author:         E. Scott Daniels
+*/
+
+#include <cstdlib>
+
+#include <rmr/rmr.h>
+
+#include "message.hpp"
+//class Messenger;
+
+
+/*
+       Builder.
+*/
+Callback::Callback( user_callback ufun, void* data ) {         // builder
+       user_fun = ufun;
+       udata = data;
+}
+
+/*
+       there is nothing to be done from a destruction perspective, so no
+       destruction function needed at the moment.
+*/
+
+/*
+       Drive_cb will invoke the callback and pass along the stuff passed here.
+*/
+void Callback::Drive_cb( Message& m ) {
+       if( user_fun != NULL ) {
+               user_fun( m, m.Get_mtype(), m.Get_subid(), m.Get_len(), m.Get_payload(),  udata );
+       }
+}
+
+
diff --git a/src/messaging/callback.hpp b/src/messaging/callback.hpp
new file mode 100644 (file)
index 0000000..b8e9dfa
--- /dev/null
@@ -0,0 +1,52 @@
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 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:       callback.hpp
+       Abstract:       class description for a callback managment object.
+       Date:           9 March 2020
+       Author:         E. Scott Daniels
+*/
+
+
+#ifndef _CALLBACK_HPP
+#define _CALLBACK_HPP
+
+#include <memory>
+
+class Messenger;
+class Message;
+#include "message.hpp"
+
+typedef void(*user_callback)( Message& m, int mtype, int subid, int payload_len, Msg_component payload, void* usr_data );
+
+class Callback {
+
+       private:
+               user_callback user_fun;
+               void*   udata;                                                                  // user data
+
+       public:
+               Callback( user_callback, void* data );                                  // builder
+               void Drive_cb( Message& m );                                                    // invoker
+};
+
+
+#endif
diff --git a/src/messaging/default_cb.cpp b/src/messaging/default_cb.cpp
new file mode 100644 (file)
index 0000000..1083293
--- /dev/null
@@ -0,0 +1,54 @@
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 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:       default_cb.cpp
+       Abstract:       This is a set of static functions that are used as
+                               the default Messenger callbacks.
+
+       Date:           11 Mar 2020
+       Author:         E. Scott Daniels
+*/
+
+#include <string.h>
+#include <unistd.h>
+
+#include <rmr/rmr.h>
+#include <rmr/RIC_message_types.h>
+
+
+#include <cstdlib>
+
+#include "messenger.hpp"
+
+/*
+       This is the default health check function that we provide (user
+       may override it).  It will respond to health check messages by
+       sending an OK back to the source.
+
+       The mr paramter is obviously ignored, but to add this as a callback
+       the function sig must match.
+*/
+void Health_ck_cb( Message& mbuf, int mtype, int sid, int len, Msg_component payload, void* data ) {
+       unsigned char response[128];
+
+       snprintf( (char* ) response, sizeof( response ), "OK\n" );
+       mbuf.Send_response( RIC_HEALTH_CHECK_RESP, sid, strlen( (char *) response )+1, response );
+}
diff --git a/src/messaging/default_cb.hpp b/src/messaging/default_cb.hpp
new file mode 100644 (file)
index 0000000..2e96003
--- /dev/null
@@ -0,0 +1,39 @@
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 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:       default_cb.hpp
+       Abstract:       Headers/prototypes for the default callbacks. The
+                               default callbacks are those which we install when
+                               the messenger is created and handles things that
+                               the application might not want to (e.g. health check).
+
+       Date:           11 March 2020
+       Author:         E. Scott Daniels
+*/
+
+#ifndef _DEF_CB_H
+#define _DEF_CB_H
+
+
+void Health_ck_cb( Message& mbuf, int mtype, int sid, int len, Msg_component payload, void* data );
+
+
+#endif
diff --git a/src/messaging/message.cpp b/src/messaging/message.cpp
new file mode 100644 (file)
index 0000000..30a0fbc
--- /dev/null
@@ -0,0 +1,419 @@
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 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:       message.cpp
+       Abstract:       A message wrapper. This should completely hide the
+                               underlying transport (RMR) message structure from
+                               the user application. For the most part, the getters
+                               are used by the framwork; it is unlikely that other
+                               than adding/extracting the MEID, the user app will
+                               be completely unaware of information that is not
+                               presented in the callback parms.
+
+       Date:           12 March 2020
+       Author:         E. Scott Daniels
+*/
+
+#include <string.h>
+#include <unistd.h>
+
+#include <rmr/rmr.h>
+
+#include <iostream>
+
+#include "message.hpp"
+
+// --------------- private ------------------------------------------------
+
+// --------------- builders/operators  -------------------------------------
+
+/*
+       Create a new message wrapper for an existing RMR msg buffer.
+*/
+Message::Message( rmr_mbuf_t* mbuf, void* mrc ) {
+       this->mrc = mrc;                        // the message router context for sends
+       this->mbuf = mbuf;
+}
+
+Message::Message( void* mrc, int payload_len ) {
+       this->mrc = mrc;
+       this->mbuf = rmr_alloc_msg( mrc, payload_len );
+}
+
+/*
+       Copy builder.  Given a source object instance (soi), create a copy.
+       Creating a copy should be avoided as it can be SLOW!
+*/
+Message::Message( const Message& soi ) {
+       int payload_size;
+
+       mrc = soi.mrc;
+       payload_size = rmr_payload_size( soi.mbuf );            // rmr can handle a nil pointer
+       mbuf = rmr_realloc_payload( soi.mbuf, payload_size, RMR_COPY, RMR_CLONE );      
+}
+
+/*
+       Assignment operator. Simiolar to the copycat, but "this" object exists and
+       may have data that needs to be released prior to making a copy of the soi.
+*/
+Message& Message::operator=( const Message& soi ) {
+       int     payload_size;
+
+       if( this != &soi ) {                            // cannot do self assignment
+               if( mbuf != NULL ) {
+                       rmr_free_msg( mbuf );           // release the old one so we don't leak
+               }
+       
+               payload_size = rmr_payload_size( soi.mbuf );            // rmr can handle a nil pointer
+               mrc = soi.mrc;
+               mbuf = rmr_realloc_payload( soi.mbuf, payload_size, RMR_COPY, RMR_CLONE );      
+       }
+
+       return *this;
+}
+
+/*
+       Move builder.  Given a source object instance (soi), move the information from
+       the soi ensuring that the destriction of the soi doesn't trash things from
+       under us.
+*/
+Message::Message( Message&& soi ) {
+       mrc = soi.mrc;
+       mbuf = soi.mbuf;
+
+       soi.mrc = NULL;         // prevent closing of RMR stuff on soi destroy
+       soi.mbuf = NULL;
+}
+
+/*
+       Move Assignment operator. Move the message data to the existing object
+       ensure the object reference is cleaned up, and ensuring that the source
+       object references are removed.
+*/
+Message& Message::operator=( Message&& soi ) {
+       if( this != &soi ) {                            // cannot do self assignment
+               if( mbuf != NULL ) {
+                       rmr_free_msg( mbuf );           // release the old one so we don't leak
+               }
+       
+               mrc = soi.mrc;
+               mbuf = soi.mbuf;
+
+               soi.mrc = NULL;
+               soi.mbuf = NULL;
+       }
+
+       return *this;
+}
+
+
+/*
+       Destroyer.
+*/
+Message::~Message() {
+       if( mbuf != NULL ) {
+               rmr_free_msg( mbuf );
+       }
+
+       mbuf = NULL;
+}
+
+
+// --- getters/setters -----------------------------------------------------
+/*
+       Copy the payload bytes, and return a smart pointer (unique) to it.
+       If the application needs to update the payload in place for a return
+       to sender call, or just to access the payload in a more efficent manner
+       (without the copy), the Get_payload() function should be considered.
+
+       This function will return a NULL pointer if malloc fails.
+*/
+//char* Message::Copy_payload( ){
+std::unique_ptr<unsigned char> Message::Copy_payload( ){
+       unsigned char*  new_payload = NULL;
+
+       if( mbuf != NULL ) {
+               new_payload = (unsigned char *) malloc( sizeof( unsigned char ) * mbuf->len );
+               memcpy( new_payload, mbuf->payload, mbuf->len );
+       }
+
+       return std::unique_ptr<unsigned char>( new_payload );
+}
+
+/*
+       Makes a copy of the MEID and returns a smart pointer to it.
+*/
+std::unique_ptr<unsigned char> Message::Get_meid(){
+       unsigned char* m = NULL;
+
+       m = (unsigned char *) malloc( sizeof( unsigned char ) * RMR_MAX_MEID );
+       rmr_get_meid( mbuf, m );
+
+       return std::unique_ptr<unsigned char>( m );
+}
+
+/*
+       Return the total size of the payload (the amount that can be written to
+       as opposed to the portion of the payload which is currently in use.
+       If mbuf isn't valid (nil, or message has a broken header) the return
+       will be -1.
+*/
+int Message::Get_available_size(){
+       return rmr_payload_size( mbuf );                // rmr can handle a nil pointer
+}
+
+int    Message::Get_mtype(){
+       int rval = INVALID_MTYPE;
+
+       if( mbuf != NULL ) {
+               rval = mbuf->mtype;
+       }
+
+       return rval;
+}
+
+/*
+       Makes a copy of the source field and returns a smart pointer to it.
+*/
+std::unique_ptr<unsigned char> Message::Get_src(){
+       unsigned char* m = NULL;
+
+       m = (unsigned char *) malloc( sizeof( unsigned char ) * RMR_MAX_SRC );
+       memset( m, 0, sizeof( unsigned char ) * RMR_MAX_SRC );
+
+       if( m != NULL ) {
+               rmr_get_src( mbuf, m );
+       }
+
+       return std::unique_ptr<unsigned char>( m );
+}
+
+int    Message::Get_state( ){
+       int state = INVALID_STATUS;
+
+       if( mbuf != NULL ) {
+               state =  mbuf->state;
+       }
+
+       return state;
+}
+
+int    Message::Get_subid(){
+       int     rval = INVALID_SUBID;
+
+       if( mbuf != NULL ) {
+               rval =mbuf->sub_id;
+       }
+
+       return rval;
+}
+
+/*
+       Return the amount of the payload (bytes) which is used. See
+       Get_available_size() to get the total usable space in the payload.
+*/
+int    Message::Get_len(){
+       int rval = 0;
+
+       if( mbuf != NULL ) {
+               rval = mbuf->len;
+       }
+
+       return rval;
+}
+
+/*
+       This returns a smart (unique) pointer to the payload portion of the
+       message. This provides the user application with the means to
+       update the payload in place to avoid multiple copies.  The
+       user programme is responsible to determing the usable payload
+       length by calling Message:Get_available_size(), and ensuring that
+       writing beyond the indicated size does not happen.
+*/
+Msg_component Message::Get_payload(){
+       if( mbuf != NULL ) {
+               return std::unique_ptr<unsigned char, unfreeable>( mbuf->payload );
+       }
+
+       return NULL;
+}
+
+void Message::Set_meid( std::shared_ptr<unsigned char> new_meid ) {
+       if( mbuf != NULL ) {
+               rmr_str2meid( mbuf, (unsigned char *) new_meid.get() );
+       }
+}
+
+void Message::Set_mtype( int new_type ){
+       if( mbuf != NULL ) {
+               mbuf->mtype = new_type;
+       }
+}
+
+void Message::Set_len( int new_len ){
+       if( mbuf != NULL  && new_len >= 0 ) {
+               mbuf->len = new_len;
+       }
+}
+
+void Message::Set_subid( int new_subid ){
+       if( mbuf != NULL ) {
+               mbuf->sub_id = new_subid;
+       }
+}
+
+
+// -------------- send functions ---------------------------------
+
+/*
+       This assumes that the contents of the mbuf were set by either a send attempt that
+       failed with a retry and thus is ready to be processed by RMR.
+       Exposed to the user, but not expected to be frequently used.
+*/
+bool Message::Send( ) {
+       bool state = false;
+
+       if( mbuf != NULL ) {
+               mbuf =  rmr_send_msg( mrc, mbuf );      // send and pick up new mbuf
+               state = mbuf->state == RMR_OK;          // overall state for caller
+       }
+
+       return state;
+}
+
+/*
+       Similar to Send(), this assumes that the message is already set up and this is a retry.
+       Exposed to the user, but not expected to be frequently used.
+*/
+bool Message::Reply( ) {
+       bool state = false;
+
+       if( mbuf != NULL ) {
+               mbuf =  rmr_rts_msg( mrc, mbuf );               // send and pick up new mbuf
+               state = mbuf->state == RMR_OK;                  // state for caller based on send
+       }
+
+       return state;
+}
+
+/*
+       Send workhorse.
+       This will setup the message (type etc.) ensure the message payload space is
+       large enough and copy in the payload (if a new payload is given), then will
+       either send or rts the message based on the stype parm.
+
+       If payload is nil, then we assume the user updated the payload in place and
+       no copy is needed.
+
+       This is public, but most users should use Send_msg or Send_response functions.
+*/
+bool Message::Send( int mtype, int subid, int payload_len, unsigned char* payload, int stype ) {
+       bool state = false;
+
+       if( mbuf != NULL ) {
+               if( mtype != NO_CHANGE ) {
+                       mbuf->mtype = mtype;
+               }
+               if( subid != NO_CHANGE ) {
+                       mbuf->sub_id = subid;
+               }
+
+               if( payload_len != NO_CHANGE ) {
+                       mbuf->len = payload_len;
+               }
+
+               if( payload != NULL ) {                 // if we have a payload, ensure msg has room, realloc if needed, then copy
+                       mbuf = rmr_realloc_payload( mbuf, payload_len, RMR_NO_COPY, RMR_NO_CLONE );             // ensure message is large enough
+                       if( mbuf == NULL ) {
+                               return false;
+                       }
+
+                       memcpy( mbuf->payload, payload, mbuf->len );
+               }
+
+               if( stype == RESPONSE ) {
+                       mbuf = rmr_rts_msg( mrc, mbuf );
+               } else {
+                       mbuf = rmr_send_msg( mrc, mbuf );
+               }
+
+               state = mbuf->state == RMR_OK;
+       }
+
+       return state;
+}
+
+/*
+       Send a response to the endpoint that sent the original message.
+
+       Response can be null and the assumption will be that the message payload
+       was updated in place and no additional copy is needed before sending the message.
+
+       The second form of the call allows for a stack allocated buffer (e.g. char foo[120]) to
+       be passed as the payload.
+*/
+bool Message::Send_response(  int mtype, int subid, int response_len, std::shared_ptr<unsigned char> response ) {
+       return Send( mtype, subid, response_len, response.get(), RESPONSE );
+}
+
+bool Message::Send_response(  int mtype, int subid, int response_len, unsigned char* response ) {
+       return Send( mtype, subid, response_len, response, RESPONSE );
+}
+
+/*
+       These allow a response message to be sent without changing the mtype/subid.
+*/
+bool Message::Send_response(  int response_len, std::shared_ptr<unsigned char> response ) {
+       return Send( NO_CHANGE, NO_CHANGE, response_len, response.get(), RESPONSE );
+}
+
+bool Message::Send_response(  int response_len, unsigned char* response ) {
+       return Send( NO_CHANGE, NO_CHANGE, response_len, response, RESPONSE );
+}
+
+
+/*
+       Send a message based on message type routing.
+
+       Payload can be null and the assumption will be that the message payload
+       was updated in place and no additional copy is needed before sending the message.
+
+       Return is a new mbuf suitable for sending another message, or the original buffer with
+       a bad state sent if there was a failure.
+*/
+bool Message::Send_msg(  int mtype, int subid, int payload_len, std::shared_ptr<unsigned char> payload ) {
+       return Send( mtype, subid, payload_len, payload.get(), MESSAGE );
+}
+
+bool Message::Send_msg(  int mtype, int subid, int payload_len, unsigned char* payload ) {
+       return Send( mtype, subid, payload_len, payload, MESSAGE );
+}
+
+/*
+       Similar send functions that allow the message type/subid to remain unchanged
+*/
+bool Message::Send_msg(  int payload_len, std::shared_ptr<unsigned char> payload ) {
+       return Send( NO_CHANGE, NO_CHANGE, payload_len, payload.get(), MESSAGE );
+}
+
+bool Message::Send_msg(  int payload_len, unsigned char* payload ) {
+       return Send( NO_CHANGE, NO_CHANGE, payload_len, payload, MESSAGE );
+}
diff --git a/src/messaging/message.hpp b/src/messaging/message.hpp
new file mode 100644 (file)
index 0000000..0db0927
--- /dev/null
@@ -0,0 +1,110 @@
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 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:       message.hpp
+       Abstract:       Headers for the message class.
+                               This class provides an interface to the user application
+                               when information from the message is needed.
+
+       Date:           10 March 2020
+       Author:         E. Scott Daniels
+*/
+
+#ifndef _MESSAGE_HPP
+#define _MESSAGE_HPP
+
+
+#include <iostream>
+#include <string>
+
+#include <rmr/rmr.h>
+
+#include "msg_component.hpp"
+#include "callback.hpp"
+#include "default_cb.hpp"
+
+#ifndef RMR_NO_CLONE
+       #define RMR_NO_CLONE    0
+       #define RMR_NO_COPY     0
+       #define RMR_CLONE       1
+       #define RMR_COPY        1
+#endif
+
+
+// ------------------------------------------------------------------------
+
+class Message {
+       private:
+               rmr_mbuf_t*     mbuf;                                   // the underlying RMR message buffer
+               void*           mrc;                                    // message router context
+               std::shared_ptr<char> psp;                      // shared pointer to the payload to give out
+
+       public:
+               static const int        NO_CHANGE = -99;                        // indicates no change to a send/reply parameter
+               static const int        INVALID_MTYPE = -1;
+               static const int        INVALID_STATUS = -1;
+               static const int        INVALID_SUBID = -2;
+               static const int        NO_SUBID = -1;                                  // not all messages have a subid
+
+               static const int        RESPONSE = 0;                                   // send types
+               static const int        MESSAGE = 1;
+
+               Message( rmr_mbuf_t* mbuf, void* mrc );         // builders
+               Message( void* mrc, int payload_len );
+               Message( const Message& soi );                          // copy cat
+               Message& operator=( const Message& soi );       // copy operator
+               Message( Message&& soi );                               // mover
+               Message& operator=( Message&& soi );    // move operator
+               ~Message();                                                                     // destroyer
+
+               std::unique_ptr<unsigned char>  Copy_payload( );                // copy the payload; deletable smart pointer
+
+               std::unique_ptr<unsigned char> Get_meid();                              // returns a copy of the meid bytes
+               int Get_available_size();
+               int     Get_len();
+               int     Get_mtype();
+               Msg_component Get_payload();
+               std::unique_ptr<unsigned char>  Get_src();
+               int     Get_state( );
+               int     Get_subid();
+
+               void Set_meid( std::shared_ptr<unsigned char> new_meid );
+               void Set_mtype( int new_type );
+               void Set_subid( int new_subid );
+               void Set_len( int new_len );
+
+               bool Reply( );
+               bool Send( );
+               bool Send( int mtype, int subid, int payload_len, unsigned char* payload, int stype );
+
+               bool Send_msg( int mtype, int subid, int payload_len, std::shared_ptr<unsigned char> payload );
+               bool Send_msg( int mtype, int subid, int payload_len, unsigned char* payload );
+               bool Send_msg( int payload_len, std::shared_ptr<unsigned char> payload );
+               bool Send_msg( int payload_len, unsigned char* payload );
+
+               bool Send_response( int mtype, int subid, int payload_len, std::shared_ptr<unsigned char> response );
+               bool Send_response( int mtype, int subid, int payload_len, unsigned char* response );
+               bool Send_response( int payload_len, std::shared_ptr<unsigned char> response );
+               bool Send_response( int payload_len, unsigned char* response );
+};
+
+
+#endif
diff --git a/src/messaging/messenger.cpp b/src/messaging/messenger.cpp
new file mode 100644 (file)
index 0000000..24fe164
--- /dev/null
@@ -0,0 +1,279 @@
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 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:       messenger.cpp
+       Abstract:       Message Router Messenger.
+
+       Date:           10 March 2020
+       Author:         E. Scott Daniels
+*/
+
+#include <string.h>
+#include <unistd.h>
+
+#include <rmr/rmr.h>
+#include <rmr/RIC_message_types.h>
+
+
+#include <iostream>
+#include <string>
+#include <map>
+#include <memory>
+#include <mutex>
+
+#include "callback.hpp"
+#include "default_cb.hpp"              // default callback prototypes
+#include "message.hpp"
+#include "messenger.hpp"
+
+
+// --------------- private -----------------------------------------------------
+
+
+// ---------------- C++ buggerd up way of maintining class constants ----------
+const int Messenger::MAX_PAYLOAD = (1024*64);
+const int Messenger::DEFAULT_CALLBACK = -1;
+
+// --------------- builders -----------------------------------------------
+/*
+       If wait4table is true, then the construction of the object does not
+       complete until the underlying transport has a new copy of the route
+       table.
+
+       If port is nil, then the default port is used (4560).
+*/
+Messenger::Messenger( char* port, bool wait4table ) {
+       if( port == NULL ) {
+               port = (char *) "4560";
+       }
+
+       gate = new std::mutex();
+       listen_port = strdup( port );
+       mrc = rmr_init( listen_port, Messenger::MAX_PAYLOAD, 0 );
+
+       if( wait4table ) {
+               this->Wait_for_cts( 0 );
+       }
+
+       Add_msg_cb( RIC_HEALTH_CHECK_REQ, Health_ck_cb, NULL );         // add our default call backs
+
+       ok_2_run = true;
+}
+
+/*
+       Move support. We DO allow the instance to be moved as only one copy 
+       remains following the move.
+       Given a source object instance (soi) we move the information to 
+       the new object, and then DELETE what was moved so that when the
+       user frees the soi, it doesn't destroy what we snarfed.
+*/
+Messenger::Messenger( Messenger&& soi ) {
+       mrc = soi.mrc;
+       listen_port = soi.listen_port;
+       ok_2_run = soi.ok_2_run;
+       gate = soi.gate;
+               cb_hash = soi.cb_hash;                          // this seems dodgy
+       
+       soi.gate = NULL;
+       soi.listen_port = NULL;
+       soi.mrc = NULL;
+}
+
+/*
+       Move operator. Given a source object instance, movee it's contents
+       to this insance.  We must first clean up this instance.
+*/
+Messenger& Messenger::operator=( Messenger&& soi ) {
+       if( this != &soi ) {                            // cannot move onto ourself
+               if( mrc != NULL ) {
+                       rmr_close( mrc );
+               }
+               if( listen_port != NULL ) {
+                       free( listen_port );
+               }
+
+               mrc = soi.mrc;
+               listen_port = soi.listen_port;
+               ok_2_run = soi.ok_2_run;
+               gate = soi.gate;
+                       cb_hash = soi.cb_hash;                          // this seems dodgy
+               
+               soi.gate = NULL;
+               soi.listen_port = NULL;
+               soi.mrc = NULL;
+       }
+
+       return *this;
+}
+
+/*
+       Destroyer.
+*/
+Messenger::~Messenger() {
+       if( mrc != NULL ) {
+               rmr_close( mrc );
+       }
+
+       if( listen_port != NULL ) {
+               free( listen_port );
+       }
+}
+
+/*
+       Allow user to register a callback function invoked when a specific type of
+       message is received.  The user may pass an optional data pointer which
+       will be passed to the function when it is called.  The function signature
+       must be:
+               void fun( Messenger* mr, rmr_mbuf_t* mbuf,  void* data );
+
+       The user can also invoke this function to set the "default" callback by
+       passing Messenger::DEFAULT_CALLBACK as the mtype. If no other callback
+       is defined for a message type, the default callback function is invoked.
+       If a default is not provided, a non-matching message is silently dropped.
+*/
+void Messenger::Add_msg_cb( int mtype, user_callback fun_name, void* data ) {
+       Callback*       cb;
+
+       cb = new Callback( fun_name, data );
+       cb_hash[mtype] = cb;
+
+       callbacks = true;
+}
+
+/*
+       Message allocation for user to send. User must destroy the message when
+       finished, but may keep the message for as long as is necessary
+       and reuse it over and over.
+*/
+//Message* Messenger::Alloc_msg( int payload_size ) {
+std::unique_ptr<Message> Messenger::Alloc_msg( int payload_size ) {
+       return std::unique_ptr<Message>( new Message( mrc, payload_size ) );
+}
+
+void Messenger::Listen( ) {
+       int count = 0;
+       rmr_mbuf_t*     mbuf = NULL;
+       std::map<int,Callback*>::iterator mi;   // map iterator; silly indirect way to point at the value
+       Callback*       dcb = NULL;                                     // default callback so we don't search
+       Callback*       sel_cb;                                         // callback selected to invoke
+       std::unique_ptr<Message>m;
+
+       if( mrc == NULL ) {
+               return;
+       }
+
+       mi = cb_hash.find( DEFAULT_CALLBACK );
+       if( mi != cb_hash.end() ) {
+               dcb = mi->second;                                       // oddly named second field is the address of the callback block
+       }
+
+       while( ok_2_run ) {
+               mbuf = rmr_torcv_msg( mrc, mbuf, 2000 );                // come up for air every 2 sec to check ok2run
+               if( mbuf != NULL ) {
+                       if( mbuf->state == RMR_OK ) {
+                               m = std::unique_ptr<Message>( new Message( mbuf, mrc ) );       // auto delteted when scope terminates
+
+                               sel_cb = dcb;                                                                                   // start with default
+                               if( callbacks  && ((mi = cb_hash.find( mbuf->mtype )) != cb_hash.end()) ) {
+                                       sel_cb = mi->second;                                                            // override with user callback
+                               }
+                               if( sel_cb != NULL ) {
+                                       sel_cb->Drive_cb( *m );                                                 // drive the selected one
+                                       mbuf = NULL;                                                                    // not safe to use after given to cb
+                               }
+                       } else {
+                               if( mbuf->state != RMR_ERR_TIMEOUT ) {
+                                       fprintf( stderr, "<LISTENER> got  bad status: %d\n", mbuf->state );
+                               }
+                       }
+               }
+       }
+}
+
+/*
+       Wait for the next message, up to a max timout, and return the message received.
+*/
+std::unique_ptr<Message>  Messenger::Receive( int timeout ) {
+       rmr_mbuf_t*     mbuf = NULL;
+       std::unique_ptr<Message> m = NULL;
+
+       if( mrc != NULL ) {
+               mbuf = rmr_torcv_msg( mrc, mbuf, timeout );             // future: do we want to reuse the mbuf here?
+               if( mbuf != NULL ) {
+                       m = std::unique_ptr<Message>( new Message( mbuf, mrc ) );
+               }
+       }
+
+       return m;
+}
+
+/*
+       Called to gracefully stop all listeners.
+*/
+void Messenger::Stop( ) {
+       ok_2_run = false;
+}
+
+/*
+       RMR messages must be released by RMR as there might be transport
+       buffers that have to be dealt with. Every callback is expected to
+       call this function when finished with the message.
+void Messenger::Release_mbuf( void* vmbuf ) {
+       rmr_free_msg( (rmr_mbuf_t *)  vmbuf );
+}
+*/
+
+/*
+       Wait for clear to send.
+       Until RMR loads a route table, all sends will fail with a
+       "no endpoint" state.  This function allows the user application
+       to block until RMR has a viable route table.  It does not guarentee
+       that every message that the user app will try to send has an entry.
+
+       The use of this function by the user application allows for the
+       parallel initialisation of the application while waiting for the
+       route table service to generate a table for the application. The
+       initialisation function may be callsed with "no wait" and this
+       function invoked when the application has completed initialisation
+       and is ready to start sending messages.
+
+       The max wait parameter is the maximum number of seconds to block.
+       If RMR never reports ready false is returned.  A true return
+       incidcates all is ready.  If max_wait is 0, then this will only
+       return when RMR is ready to send.
+*/
+bool Messenger::Wait_for_cts( int max_wait ) {
+       bool block_4ever;
+       bool    state = false;
+
+       block_4ever = max_wait == 0;
+       while( block_4ever || max_wait > 0 ) {
+               if( rmr_ready( mrc ) ) {
+                       state = true;
+                       break;
+               }
+
+               sleep( 1 );
+               max_wait--;
+       }
+
+       return state;
+}
diff --git a/src/messaging/messenger.hpp b/src/messaging/messenger.hpp
new file mode 100644 (file)
index 0000000..0810aeb
--- /dev/null
@@ -0,0 +1,82 @@
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 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:       messenger.hpp
+       Abstract:       Headers for the messenger class
+
+       Date:           10 March 2020
+       Author:         E. Scott Daniels
+*/
+
+#ifndef _messenger_hpp
+#define _messenger_hpp
+
+
+#include <iostream>
+#include <string>
+#include <map>
+#include <memory>
+#include <mutex>
+
+#include <rmr/rmr.h>
+
+#include "message.hpp"
+
+#ifndef RMR_FALSE
+       #define RMR_FALSE       0
+       #define RMR_TRUE        1
+#endif
+
+class Messenger {
+
+       private:
+               std::map<int,Callback*> cb_hash;        // callback functions associated with message types
+               std::mutex*     gate;                                   // overall mutex should we need searialisation
+               bool            ok_2_run;
+               bool            callbacks = false;              // true if callbacks are defined
+               void*           mrc;                                    // message router context
+               char*           listen_port;                    // port we ask msg router to listen on
+
+               // copy and assignment are PRIVATE so that they fail if xapp tries; messenger cannot be copied!
+               Messenger( const Messenger& soi );      
+               Messenger& operator=( const Messenger& soi );
+
+       public:
+               // -- constants which need an instance; that happens as a global in the .cpp file (wtf C++)
+               static const int MAX_PAYLOAD;                   // max message size we'll handle
+               static const int DEFAULT_CALLBACK;              // parm for add callback to set default
+
+               Messenger( char* port, bool wait4table );       // builder
+               Messenger( Messenger&& soi );                           // move construction
+               Messenger& operator=( Messenger&& soi );        // move operator
+               ~Messenger();                                                           // destroyer
+
+               void Add_msg_cb( int mtype, user_callback fun_name, void* data );
+               std::unique_ptr<Message> Alloc_msg( int payload_size );                 // message allocation
+               void Listen( );                                                                                                 // lisen driver
+               std::unique_ptr<Message> Receive( int timeout );                                // receive 1 message
+               void Stop( );                                                                                                   // force to stop
+               //void Release_mbuf( void* vmbuf );
+               bool Wait_for_cts( int max_wait );
+};
+
+#endif
diff --git a/src/messaging/msg_component.hpp b/src/messaging/msg_component.hpp
new file mode 100644 (file)
index 0000000..d707257
--- /dev/null
@@ -0,0 +1,57 @@
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 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:       msg_component.hpp
+       Abstract:       Defines a message component type which is needed in order
+                               to use smart pointers (unique) to point at bytes in the
+                               RMR message (which are not directly allocated and cannot
+                               be freed/deleted outside of RMR (require a special destruction
+                               call in the smart pointer).
+
+       Date:           17 March 2020
+       Author:         E. Scott Daniels
+*/
+
+#ifndef _MSG_COMPONENT_HPP
+#define _MSG_COMPONENT_HPP
+
+#include <memory>
+
+//  -------------- smart pointer support  --------------------------------
+/*
+       Pointers to a lot of things in the RMR message aren't directly
+       allocated and thus cannot be directly freed. To return a smart
+       pointer to these we have to ensure that no attempt to free/delete
+       the reference is made.  This struct defines a type with a function
+       pointer (operator) that is 'registered' as the delete function for
+       such a smart pointer, and does _nothing_ when called.
+*/
+typedef struct {
+       void operator()( unsigned char * p ){}
+} unfreeable;
+
+/*
+       A 'generic' smart pointer to a component in the message which cannot
+       be directly freed (e.g. the payload, meid, etc).
+*/
+using Msg_component = std::unique_ptr<unsigned char, unfreeable>;
+
+#endif
diff --git a/src/xapp/CMakeLists.txt b/src/xapp/CMakeLists.txt
new file mode 100644 (file)
index 0000000..093ef6f
--- /dev/null
@@ -0,0 +1,40 @@
+# vim: sw=4 ts=4 noet:
+#
+#==================================================================================
+#   Copyright (c) 2020 Nokia
+#   Copyright (c) 2020 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( xapp_objects OBJECT
+       xapp.cpp        
+)
+
+target_include_directories (xapp_objects PUBLIC
+       $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+       $<INSTALL_INTERFACE:include>
+       PRIVATE src)
+
+# header files should go into .../include/xfcpp/
+if( DEV_PKG )
+       install( FILES
+               xapp.hpp  
+               DESTINATION ${install_inc}
+       ) 
+endif()
+
diff --git a/src/xapp/README b/src/xapp/README
new file mode 100644 (file)
index 0000000..39391b2
--- /dev/null
@@ -0,0 +1,27 @@
+
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 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.
+==================================================================================
+
+This directory contains the source for the overall Xapp class.
+Specifially:
+
+       xapp.*
+               Is the class that the user creates in their application. It provides, by
+               extension, the RMR messaging functions.
+
+
+       
diff --git a/src/xapp/xapp.cpp b/src/xapp/xapp.cpp
new file mode 100644 (file)
index 0000000..8feb52e
--- /dev/null
@@ -0,0 +1,100 @@
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 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:       xapp.cpp
+       Abstract:       The main xapp class.
+                               This class actually extends the messenger class as most of what
+                               would be done here is just passing through to the messenger.
+
+       Date:           13 March 2020
+       Author:         E. Scott Daniels
+*/
+
+#include <string.h>
+#include <unistd.h>
+
+#include <rmr/rmr.h>
+#include <rmr/RIC_message_types.h>
+
+
+#include <iostream>
+#include <thread>
+
+#include <messenger.hpp>
+#include "xapp.hpp"
+
+// --------------- private -----------------------------------------------------
+
+
+
+// --------------- builders -----------------------------------------------
+
+/*
+       If wait4table is true, then the construction of the object does not
+       complete until the underlying transport has a new copy of the route
+       table.
+
+       If port is nil, then the default port is used (4560).
+*/
+Xapp::Xapp( char* port, bool wait4table ) : Messenger( port, wait4table ) {
+       // nothing to do; all handled in Messenger constructor
+}
+
+/*
+       Destroyer.
+*/
+Xapp::~Xapp() {
+       // nothing to destroy; superclass destruction is magically invoked.
+}
+
+/*
+       Start n threads, each running a listener which will drive callbacks.
+
+       The final listener is started in the main thread; in otherwords this
+       function won't return unless that listener crashes.
+*/
+void Xapp::Run( int nthreads ) {
+       int i;
+       std::thread** tinfo;                            // each thread we'll start
+
+       tinfo = new std::thread* [nthreads-1];
+
+       for( i = 0; i < nthreads - 1; i++ ) {                           // thread for each n-1; last runs here
+               tinfo[i] = new std::thread( &Xapp::Listen, this );
+       }
+
+       this->Listen();                                         // will return only when halted
+
+       for( i = 0; i < nthreads - 1; i++ ) {                           // wait for others to stop
+               tinfo[i]->join();
+       }
+
+       delete tinfo;
+}
+
+/*
+       Halt the xapp. This will drive the messenger's stop function to prevent any
+       active listeners from running, and will shut things down.
+*/
+void Xapp::Halt() {
+       this->Stop();                   // messenger shut off
+}
+
diff --git a/src/xapp/xapp.hpp b/src/xapp/xapp.hpp
new file mode 100644 (file)
index 0000000..83540ec
--- /dev/null
@@ -0,0 +1,63 @@
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 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:       xapp.hpp
+       Abstract:       Headers for the xapp class. This class extends the messenger class
+                               as most of the function of this class would be just passing through
+                               calls to the messenger object.
+
+       Date:           13 March 2020
+       Author:         E. Scott Daniels
+*/
+
+#ifndef _xapp_hpp
+#define _xapp_hpp
+
+
+#include <iostream>
+#include <string>
+#include <mutex>
+#include <map>
+
+#include <rmr/rmr.h>
+
+#include "callback.hpp"
+#include "messenger.hpp"
+
+class Xapp : public Messenger {
+
+       private:
+               std::string name;
+
+               // copy and assignment are PRIVATE because we cant "clone" the listen environment
+               Xapp( const Xapp& soi );
+               Xapp& operator=( const Xapp& soi );
+
+       public:
+               Xapp( char* listen_port, bool wait4rt );        // builder
+               Xapp( );
+               ~Xapp();                                                                        // destroyer
+
+               void Run( int nthreads );                                       // message listen driver
+               void Halt( );                                                           // force to stop
+};
+
+#endif
diff --git a/test/.gitignore b/test/.gitignore
new file mode 100644 (file)
index 0000000..9903b8d
--- /dev/null
@@ -0,0 +1,4 @@
+core
+*.gcov 
+*.gcda 
+*.gcno
diff --git a/test/Makefile b/test/Makefile
new file mode 100644 (file)
index 0000000..fcbf361
--- /dev/null
@@ -0,0 +1,21 @@
+
+coverage_opts = -ftest-coverage -fprofile-arcs
+
+binaries = unit_test 
+
+# RMR emulation
+rmr_em.o::     rmr_em.c
+       cc -g rmr_em.c -c
+
+unit_test:: unit_test.cpp rmr_em.o
+       # do NOT link the xapp lib; we include all modules in the test programme
+       g++ -g $(coverage_opts) -I ../src/messaging unit_test.cpp -o unit_test rmr_em.o  -lpthread
+
+# prune gcov files generated by system include files
+clean::
+       rm -f *.h.gcov *.c.gcov
+
+# ditch anything that can be rebuilt
+nuke::
+       rm -f *.a *.o *.gcov *.gcda *.gcno core a.out $(binaries)
+
diff --git a/test/parse_gcov.sh b/test/parse_gcov.sh
new file mode 100755 (executable)
index 0000000..d69a88b
--- /dev/null
@@ -0,0 +1,180 @@
+#!/usr/bin/env bash
+# vim: ts=4 sw=4 noet :
+
+#==================================================================================
+#       Copyright (c) 2020 Nokia
+#       Copyright (c) 2020 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.
+#==================================================================================
+
+#
+#      Parse the .gcov file and discount any unexecuted lines which are in if()
+#      blocks which 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 deficiencies.
+#
+#      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_ck {
+       typeset f="$1"
+
+       mct=80                                                  # force minimum coverage threshold for passing
+
+       if [[ ! -f $f ]]
+       then
+               if [[ -f ${f##*/} ]]
+               then
+                       f=${f##*/}
+               else
+                       echo "cant find: $f"
+                       return
+               fi
+       fi
+
+       awk -v module_cov_target=$mct \
+               -v cfail=${cfail:-WARN} \
+               -v show_all=$show_all \
+               -v full_name="${1}"  \
+               -v module="${f%.*}"  \
+               -v chatty=$chatty \
+               -v replace_flags=$replace_flags \
+       '
+       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( replace_flags ) {
+                                       gsub( "#####", "    1", $0 )
+                                       //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 ? cfail : "PASS"
+               rc = adj_cov < module_cov_target ? 1 : 0
+               if( pass_fail == cfail || show_all ) {
+                       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
+}
+
+# ----------------------------------------------------------------------
+show_all=1                     # turn off to hide passing modules (-q)
+chatty=0                       # -v turns on to provide more info when we do speak
+
+while [[ $1 == "-"* ]]
+do
+       case $1 in 
+               -q)     show_all=0;;
+               -v)     chatty=1;;
+
+               *)      echo "unrecognised option: $1"
+                       echo "usage: $0 [-q] gcov-file-list"
+                       exit 1
+                       ;;
+       esac
+       shift
+done
+
+
+while [[ -n $1 ]]
+do
+       discount_ck $1
+       shift
+done
diff --git a/test/rmr_em.c b/test/rmr_em.c
new file mode 100644 (file)
index 0000000..c9f85cf
--- /dev/null
@@ -0,0 +1,255 @@
+// vim: ts=4 sw=4 noet :
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 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_em.c
+       Abstract:       RMR emulation for testing
+
+       Date:           20 March        
+       Author:         E. Scott Daniels
+*/
+
+#include <unistd.h>
+#include <string.h>
+#include <malloc.h>
+
+
+/*
+       CAUTION:  this is a copy of what is in RMR and it is needed as some of the framework
+       functions access the 'public' fields like state and mtype.
+*/
+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
+    int sub_id;                 // subscription id
+    int     tp_state;           // transport state (errno) valid only if state != RMR_OK, and even then may not be valid
+
+                                // 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)
+
+    void*   ring;               // ring this buffer should be queued back to
+    int     rts_fd;             // SI fd for return to sender
+
+    int     cookie;             // cookie to detect user misuse of free'd msg
+} rmr_mbuf_t;
+
+typedef struct {
+       char meid[32];
+       char src[32];
+} header_t;
+
+
+void* rmr_init( char* port, int flags ) {
+       return malloc( sizeof( char ) * 100 );
+}
+
+rmr_mbuf_t* rmr_alloc_msg( void* mrc, int payload_len ) {
+       rmr_mbuf_t*     mbuf;
+       char* p;                                // the tp buffer; also the payload
+       header_t* h;
+       int len;
+
+       len = (sizeof( char ) * payload_len) + sizeof( header_t );
+       p = (char *) malloc( len );
+       if( p == NULL ) {
+               return NULL;
+       }
+       h = (header_t *) p;
+       memset( p, 0, len );
+
+       mbuf = (rmr_mbuf_t *) malloc( sizeof( rmr_mbuf_t ) );
+       if( mbuf == NULL ) {
+               free( p );
+               return NULL;
+       }
+       memset( mbuf, 0, sizeof( rmr_mbuf_t ) );
+
+       mbuf->tp_buf = p;
+       mbuf->payload = (char *)p + sizeof( header_t );
+
+
+       mbuf->len = 0;
+       mbuf->alloc_len = payload_len;
+       mbuf->payload = (void *) p;
+       strncpy( h->src, "host:ip", 8 );
+       strncpy( h->meid, "EniMeini", 9 );
+
+       return mbuf;
+}
+
+void rmr_free_msg( rmr_mbuf_t* mbuf ) {
+       if( mbuf ) {
+               if( mbuf->tp_buf ) {
+                       free( mbuf->tp_buf );
+               }
+               free( mbuf );
+       }
+}
+
+char* rmr_get_meid( rmr_mbuf_t* mbuf, char* m ) {
+       header_t* h;
+
+       if( mbuf != NULL ) {
+               if( m == NULL ) {
+                       m = (char *) malloc( sizeof( char ) * 32 );             
+               }
+               h = (header_t *) mbuf->tp_buf;
+               memcpy( m, h->meid, 32 );
+       }
+
+       return m;
+}
+
+int rmr_payload_size( rmr_mbuf_t* mbuf ) {
+
+       if( mbuf != NULL ) {
+               return mbuf->alloc_len;
+       }
+
+       return 0;
+}
+
+char *rmr_get_src( rmr_mbuf_t* mbuf, char *m ) {
+       header_t*  h;
+
+       if( mbuf != NULL ) {
+               if( m == NULL ) {
+                       m = (char *) malloc( sizeof( char ) * 32 );             
+               }
+               h = (header_t *) mbuf->tp_buf;
+               memcpy( m, h->src, 32 );
+       }
+
+       return m;
+}
+
+int rmr_str2meid( rmr_mbuf_t* mbuf, unsigned char* s ) {
+       header_t*  h;
+
+       if( mbuf != NULL ) {
+               if( s == NULL ) {
+                       return 1;
+               }
+
+               if( strlen( s ) > 31 ) {
+                       return 1;
+               }
+
+               h = (header_t *) mbuf->tp_buf;
+               strncpy( h->meid, s, 32 );
+               return 0;
+       }
+
+       return 1;
+}
+
+rmr_mbuf_t* rmr_send_msg( void* mrc, rmr_mbuf_t* mbuf ) {
+
+       if( mbuf != NULL ) {
+               mbuf->state = 0;        
+       }
+       
+       return mbuf;
+}
+
+rmr_mbuf_t* rmr_rts_msg( void* mrc, rmr_mbuf_t* mbuf ) {
+
+       if( mbuf != NULL ) {
+               mbuf->state = 0;        
+       }
+       
+       return mbuf;
+}
+
+rmr_mbuf_t* rmr_realloc_payload( rmr_mbuf_t* mbuf, int payload_len, int copy, int clone ) {             // ensure message is large enough
+       rmr_mbuf_t* nmb;
+       unsigned char* payload;
+
+       if( mbuf == NULL ) {
+               return NULL;
+       }
+
+       nmb = rmr_alloc_msg( NULL, payload_len );
+       if( copy ) {
+               memcpy( nmb->payload, mbuf->payload, mbuf->len );
+               nmb->len = mbuf->len;
+       } else {
+               nmb->len = 0;
+       }
+       nmb->state = mbuf->state;
+
+       if( ! clone ) {
+               free( mbuf );
+       }
+       return  nmb;
+}
+
+void rmr_close( void*  mrc ) {
+       return;
+}
+
+rmr_mbuf_t* rmr_torcv_msg( void* mrc, rmr_mbuf_t* mbuf, int timeout ) {
+       static int max2receive = 500;
+       static int mtype = 0;
+
+       if( mbuf == NULL ) {
+               mbuf = rmr_alloc_msg( NULL, 2048 );
+       }
+
+       if( max2receive <= 0 ) {
+               mbuf->state = 12;
+               mbuf->len = 0;
+               mbuf->mtype = -1;
+               mbuf->sub_id = -1;
+       }
+
+       max2receive--;
+
+       mbuf->state = 0;
+       mbuf->len = 80;
+       mbuf->mtype = mtype;
+       mbuf->sub_id = -1;
+
+       mtype++;
+       if( mtype > 100 ) {
+               mtype = 0;
+       }
+
+       return mbuf;
+}
+
+int rmr_ready( void* mrc ) {
+       static int state = 0;
+
+       if( ! state )  {
+               state = 1;
+               return 0;
+       } 
+
+       return 1;
+}
+
diff --git a/test/scrub_gcov.sh b/test/scrub_gcov.sh
new file mode 100755 (executable)
index 0000000..349fca2
--- /dev/null
@@ -0,0 +1,52 @@
+#!/usr/bin/env bash
+# vim: ts=4 sw=4 noet:
+
+#==================================================================================
+#       Copyright (c) 2020 Nokia
+#       Copyright (c) 2020 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:       scrub_gcov.sh
+#      Abstract:       Gcov (sadly) outputs for any header file that we pull.
+#                              this scrubs any gcov that doesnt look like it belongs
+#                              to our code.
+#
+#      Date:           24 March 2020
+#      Author:         E. Scott Daniels
+# -----------------------------------------------------------------------------
+
+
+# Make a list of our modules under test so that we don't look at gcov
+# files that are generated for system lib headers in /usr/*
+# (bash makes the process of building a list of names  harder than it 
+# needs to be, so use caution with the printf() call.)
+#
+function mk_list {
+       for f in *.gcov
+       do
+               if ! grep -q "Source:\.\./src"  $f
+               then
+                       printf "$f "            # do NOT use echo or add \n!
+               fi
+       done 
+}
+
+
+list=$( mk_list )
+if [[ -n $list ]]
+then
+       rm $list
+fi
diff --git a/test/unit_test.cpp b/test/unit_test.cpp
new file mode 100644 (file)
index 0000000..b0612d8
--- /dev/null
@@ -0,0 +1,302 @@
+// vim: ts=4 sw=4 noet :
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 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.cpp
+       Abstract:       This is the unit test driver for the C++ xAPP framework. It 
+                               operates by including all of the modules directly (in order
+                               to build them with the necessary coverage flags), then 
+                               drives all that it can.  The RMR emulation module provides
+                               emulated RMR functions which simulate the creation, sending
+                               and receiving of messages etc.
+
+       Date:           20 March 2020
+       Author:         E. Scott Daniels
+*/
+
+#include <memory>
+
+
+#include "../src/messaging/callback.hpp"
+#include "../src/messaging/default_cb.hpp"
+#include "../src/messaging/message.hpp"
+#include "../src/messaging/messenger.hpp"
+#include "../src/messaging/msg_component.hpp"
+#include "../src/xapp/xapp.hpp"
+
+#include "../src/messaging/callback.cpp"
+#include "../src/messaging/default_cb.cpp"
+#include "../src/messaging/message.cpp"
+#include "../src/messaging/messenger.cpp"
+#include "../src/xapp/xapp.cpp"
+
+/*
+       callback error counts are global for ease. They track the number of times each callback
+       was invoked with the expected message type(s) and any times they were not.
+*/
+int err_cb1 = 0;
+int err_cb2 = 0;
+int err_cbd = 0;
+
+int good_cb1 = 0;
+int good_cb2 = 0;
+int good_cbd = 0;
+
+/*
+       callback functions to register; driven as we "receive" messages (the RMR emulation package 
+       will generate a message every time the receive function is called).
+*/
+void cb1( Message& mbuf, int mtype, int subid, int len, Msg_component payload,  void* data ) {
+       if( mtype != 1 ) {      // should only be driven for type 1 messages
+               err_cb1++;
+       } else {
+               good_cb1++;
+       }
+}
+void cb2( Message& mbuf, int mtype, int subid, int len, Msg_component payload,  void* data ) {
+       if( mtype != 2 ) {      // should only be driven for type 2 messages
+               err_cb2++;
+       } else {
+               good_cb2++;
+       }
+}
+void cbd( Message& mbuf, int mtype, int subid, int len, Msg_component payload,  void* data ) {
+       if( mtype > 0 && mtype < 3 ) {                          // should only be driven for types that arent 1 or 2 
+               if( err_cbd < 10 ) {
+                       fprintf( stderr, "<FAIL> cbd: bad message type: %d\n", mtype );
+               }
+               err_cbd++;
+       } else {
+               good_cbd++;
+       }
+}
+
+/*
+       The Xapp Run() function only returns when Xapp is asked to stop, and that
+       isn't supported from inside any of the callbacks.  This funciton is 
+       started in a thread and after a few seconds it will drive the halt 
+       function in the Xapp instance to stop the run function and allow the
+       unit test to finish.
+*/
+void killer( std::shared_ptr<Xapp> x ) {
+       fprintf( stderr, "<INFO> killer is waiting in the shadows\n" );
+       sleep( 2 );
+       fprintf( stderr, "<INFO> killer is on the loose\n" );
+       x->Halt();
+}
+
+int main( int argc, char** argv ) {
+       std::thread* tinfo;                                     // we'll start a thread that will shut things down after a few seconds
+       std::unique_ptr<Message> msg;
+       std::shared_ptr<Xapp> x;
+       Msg_component payload;
+       std::unique_ptr<unsigned char> ucs;
+       unsigned char* new_payload;
+       std::shared_ptr<unsigned char> new_p_ref;       // reference to payload to pass to send functions
+       char*   port = (char *) "4560";
+       int             ai = 1;                                                 // arg processing index
+       int             nthreads = 2;                                   // ensure the for loop is executed in setup
+       int             i;
+       int             len;
+       int             errors = 0;
+       char    wbuf[256];
+
+       ai = 1;
+       while( ai < argc ) {                            // very simple flag processing (no bounds/error checking)
+               if( argv[ai][0] != '-' )  {
+                       break;
+               }
+
+               switch( argv[ai][1] ) {                 // we only support -x so -xy must be -x -y
+                       case 'p': 
+                               port = argv[ai+1];      
+                               ai++;
+                               break;
+
+                       case 't':
+                               nthreads = atoi( argv[ai+1] );
+                               ai++;
+                               break;
+               }
+
+               ai++;
+       }
+       
+       x = std::shared_ptr<Xapp>( new Xapp( port, true ) );
+       x->Add_msg_cb( 1, cb1, NULL );
+       x->Add_msg_cb( 2, cb2, NULL );
+       x->Add_msg_cb( -1, cbd, NULL );
+
+       msg = x->Alloc_msg( 2048 );
+       payload = msg->Get_payload();
+       msg->Set_len( 128 );
+       msg->Set_mtype( 100 );
+       msg->Set_subid( -10 );
+
+       ucs = msg->Copy_payload( );
+       if( ucs == NULL ) {
+               fprintf( stderr, "<FAIL> expected pointer to copy payload but got nil\n" );
+               errors++;
+       }
+
+       ucs = msg->Get_meid();
+       if( ucs == NULL ) {
+               fprintf( stderr, "<FAIL> expected pointer to meid copy but got nil\n" );
+               errors++;
+       }
+
+       ucs = msg->Get_src();
+       if( ucs == NULL ) {
+               fprintf( stderr, "<FAIL> expected pointer to src copy but got nil\n" );
+               errors++;
+       }
+
+       i = msg->Get_available_size();
+       if( i != 2048 ) {
+               fprintf( stderr, "<FAIL> len expected payload avail size of 2048 but got %d\n", i );
+               errors++;
+       }
+
+       i = msg->Get_mtype();
+       if( i != 100 ) {
+               fprintf( stderr, "<FAIL> expected mtype of 100 but got %d\n", i );
+               errors++;
+       }
+
+       i = msg->Get_state( );
+       if( i != 0 ) {
+               fprintf( stderr, "<FAIL> expected state of 0 but got %d\n", i );
+               errors++;
+       }
+
+       i = msg->Get_subid();
+       if( i != -10 ) {
+               fprintf( stderr, "<FAIL> expected subid of -10 but got %d\n", i );
+               errors++;
+       }
+
+       i = msg->Get_len();
+       if( i != 128 ) {
+               fprintf( stderr, "<FAIL> len expected 128 but got %d\n", i );
+               errors++;
+       }
+
+       msg->Send();                            // generic send as is functions
+       msg->Reply();
+
+       new_payload = (unsigned char *) malloc( sizeof( unsigned char ) * 2048 );       // a large payload
+       memset( new_payload, 0 , sizeof( unsigned char ) * 2048 );
+       new_p_ref = std::shared_ptr<unsigned char>( new_payload );                                      // reference it for send calls
+
+       msg->Set_meid( new_p_ref );
+
+       msg->Send_msg( 255, new_p_ref );                                                                                        // send without changing the message type/subid from above
+       msg->Send_msg( 255, new_payload );                                                                                      // drive the alternate prototype
+       msg->Send_msg( 100, 1, 128, new_p_ref );                                                                        // send using just 128 bytes of payload
+       msg->Send_msg( 100, 1, 128, new_payload );                                                                      // drive with alternate prototype
+
+       msg->Set_len( 128 );
+       msg->Set_mtype( 100 );
+       msg->Set_subid( -10 );
+
+       msg->Send_response( 100, new_p_ref );                                                                           // send a response (rts) with establisehd message type etc
+       msg->Send_response( 100, new_payload );
+       msg->Send_response( 100, 10, 100, new_p_ref );
+       msg->Send_response( 100, 10, 100, new_payload );
+
+
+       msg = NULL;                                                             // should drive the message destroyer for coverage
+
+       msg = x->Receive( 2000 );
+       if( msg == NULL ) {
+               fprintf( stderr, "<FAIL> expected message from receive but got nil\n" );
+               errors++;
+       }
+
+       tinfo = new std::thread;                                // start killer thread to terminate things so that run doesn't hang forever
+       tinfo = new std::thread( killer, x );
+
+       x->Run( nthreads );
+       x->Halt();                      // drive for coverage
+
+       if( err_cb1 + err_cb2 + err_cbd > 0 ) {
+               fprintf( stderr, "<FAIL> one or more callbacks reported an error:  [%d] [%d] [%d]\n", err_cb1, err_cb2, err_cbd );
+               fprintf( stderr, "<INFO> callback good values:  [%d] [%d] [%d]\n", good_cb1, good_cb2, good_cbd );
+               errors++;
+       }
+
+       // -----  specific move/copy coverage drivers ---------------------------
+       
+       Messenger m1( (char *) "1234", false );         // messenger class does NOT permit copies, so no need to test
+       Messenger m2( (char *) "9999", false );
+       m1 = std::move( m2 );                                           // drives move operator= function
+       Messenger m3 = std::move( m1 );                         // drives move constructor function
+
+       std::unique_ptr<Message> msg2 = x->Alloc_msg( 2048 );
+       std::unique_ptr<Message> msg3 = x->Alloc_msg( 4096 );
+
+       snprintf( wbuf, sizeof( wbuf ), "Stand up and cheer!!" );
+       msg3->Set_len( strlen( wbuf ) );
+       strcpy( (char *) (msg3->Get_payload()).get(), wbuf );                   // populate the payload to vet copy later
+       fprintf( stderr, "<DBUG> set string (%s) \n", (char *) (msg3->Get_payload()).get() );
+
+       Message msg4 = *(msg3.get());                                                                   // drive copy builder; msg4 should have a 4096 byte payload
+       fprintf( stderr, "<DBUG> copy string (%s) \n", (char *) (msg4.Get_payload()).get() );   // and payload should be coppied
+       if( msg4.Get_available_size() != 4096 ) {
+               errors++;
+               fprintf( stderr, "<FAIL> message copy builder payload size smells: expected 4096, got %d\n", msg4.Get_available_size() );
+       }
+       if( strcmp( (char *) msg4.Get_payload().get(), wbuf ) != 0 ) {
+               errors++;
+               fprintf( stderr, "<FAIL> message copy builder payload of copy not the expected string\n" );
+       }
+
+       snprintf( wbuf, sizeof( wbuf ), "Rambling Wreck; GT!" );                // different string into msg 2 to ensure copy replaced msg3 string
+       strcpy( (char *) (msg2->Get_payload()).get(), wbuf );                   // populate the msg2 payload to vet copy
+       msg2->Set_len( strlen( wbuf ) );
+       *msg3 = *msg2;                                                                                                  // drive the copy operator= function
+       if( msg3->Get_available_size() != 2048 ) {
+               errors++;
+               fprintf( stderr, "<FAIL> message copy operator payload size smells: expected 2048, got %d\n", msg3->Get_available_size() );
+       }
+       if( strcmp( (char *) msg3->Get_payload().get(), wbuf ) != 0 ) {
+               errors++;
+               fprintf( stderr, "<FAIL> message copy builder payload of copy not the expected string\n" );
+       }
+
+       Message msg5 = std::move( *(msg3.get()) );                                      // drive move constructor
+       if( msg5.Get_available_size() != 2048 ) {
+               errors++;
+               fprintf( stderr, "<FAIL> message copy operator payload size smells: expected 2048, got %d\n", msg5.Get_available_size() );
+       }
+       if( strcmp( (char *) msg5.Get_payload().get(), wbuf ) != 0 ) {
+               errors++;
+               fprintf( stderr, "<FAIL> message copy builder payload of copy not the expected string\n" );
+       }
+
+       msg5.Set_len( 2 );                                      // bogus len for vetting later
+       msg5 = std::move( *(msg3.get()) );
+       if( msg5.Get_len() == 21 ) {
+               errors++;
+               fprintf( stderr, "<FAIL> message move operator payload len smells: expected 21, got %d\n", msg5.Get_len() );
+       }
+
+       return errors > 0;
+}
diff --git a/test/unit_test.sh b/test/unit_test.sh
new file mode 100755 (executable)
index 0000000..7144f9c
--- /dev/null
@@ -0,0 +1,89 @@
+#!/usr/bin/env bash
+# vim: ts=4 sw=4 noet:
+
+#==================================================================================
+#       Copyright (c) 2020 Nokia
+#       Copyright (c) 2020 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.sh
+#      Abstract:       This drives the unit tests and combs out the needed .gcov
+#                              files which are by some magic collected for Sonar.
+#
+#      Date:           23 March 2020
+#      Author:         E. Scott Daniels
+# -----------------------------------------------------------------------------
+
+
+# Make a list of our modules under test so that we don't look at gcov
+# files that are generated for system lib headers in /usr/*
+# (bash makes the process of building a list of names  harder than it 
+# needs to be, so use caution with the printf() call.)
+#
+function mk_list {
+       grep -l "Source:\.\./src"  *.gcov | while read f
+       do
+               printf "$f "            # do NOT use echo or add \n!
+       done 
+}
+
+function abort_if_error {
+       if (( $1 == 0 ))
+       then
+               return
+       fi
+
+       if [[ -n /tmp/PID$$.log ]]
+       then
+               $spew /tmp/PID$$.log
+       fi
+       echo "abort: $2"
+
+       rm -f /tmp/PID$$.*
+       exit 1
+}
+
+# -------------------------------------------------------------------------
+
+spew="cat"                                     # default to dumping all make output on failure (-q turns it to ~40 lines)
+
+while [[ $1 == "-"* ]]
+do
+       case $1 in
+               -q) spew="head -40";;
+               -v)     spew="cat";;
+       esac
+
+       shift
+done
+
+make nuke >/dev/null
+make unit_test >/tmp/PID$$.log 2>&1
+abort_if_error $? "unable to make"
+
+spew="cat"
+./unit_test >/tmp/PID$$.log 2>&1
+abort_if_error $? "unit test failed"
+
+gcov unit_test >/tmp/PID$$.gcov_log 2>&1       # suss out our gcov files
+./scrub_gcov.sh                                                                # remove cruft
+
+list=$( mk_list )
+echo "[INFO] coverage stats, discounted (raw), for the various modules:"
+./parse_gcov.sh $list                                          # generate simple, short, coverage stats
+
+rm -f /tmp/PID$$.*
+