From e7dfeb8550f4b15689ee87a75d1c9eaafabb4ac4 Mon Sep 17 00:00:00 2001 From: Ron Shacham Date: Fri, 24 Apr 2020 14:46:48 -0400 Subject: [PATCH] Initial commit of TS xapp Signed-off-by: Ron Shacham Change-Id: I1e08ef0cbee769d365f7b4f27a4548161ae4703c --- CMakeLists.txt | 265 +++++++++++++++++++++++++ Dockerfile | 141 ++++++++++++++ README | 22 +++ build_rmr.sh | 23 +++ examples/Makefile | 30 +++ examples/ts_xapp.cpp | 346 +++++++++++++++++++++++++++++++++ examples/ts_xapp.cpp~ | 346 +++++++++++++++++++++++++++++++++ rmr-version.yaml | 3 + routes.txt | 6 + run_xapp.sh | 21 ++ src/messaging/CMakeLists.txt | 47 +++++ src/messaging/README | 47 +++++ src/messaging/callback.cpp | 58 ++++++ src/messaging/callback.hpp | 52 +++++ src/messaging/default_cb.cpp | 54 ++++++ src/messaging/default_cb.hpp | 39 ++++ src/messaging/message.cpp | 419 ++++++++++++++++++++++++++++++++++++++++ src/messaging/message.hpp | 110 +++++++++++ src/messaging/messenger.cpp | 279 ++++++++++++++++++++++++++ src/messaging/messenger.hpp | 82 ++++++++ src/messaging/msg_component.hpp | 57 ++++++ src/xapp/CMakeLists.txt | 40 ++++ src/xapp/README | 27 +++ src/xapp/xapp.cpp | 100 ++++++++++ src/xapp/xapp.hpp | 63 ++++++ test/.gitignore | 4 + test/Makefile | 21 ++ test/parse_gcov.sh | 180 +++++++++++++++++ test/rmr_em.c | 255 ++++++++++++++++++++++++ test/scrub_gcov.sh | 52 +++++ test/unit_test.cpp | 302 +++++++++++++++++++++++++++++ test/unit_test.sh | 89 +++++++++ 32 files changed, 3580 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 Dockerfile create mode 100644 README create mode 100755 build_rmr.sh create mode 100644 examples/Makefile create mode 100644 examples/ts_xapp.cpp create mode 100644 examples/ts_xapp.cpp~ create mode 100644 rmr-version.yaml create mode 100755 routes.txt create mode 100755 run_xapp.sh create mode 100644 src/messaging/CMakeLists.txt create mode 100644 src/messaging/README create mode 100644 src/messaging/callback.cpp create mode 100644 src/messaging/callback.hpp create mode 100644 src/messaging/default_cb.cpp create mode 100644 src/messaging/default_cb.hpp create mode 100644 src/messaging/message.cpp create mode 100644 src/messaging/message.hpp create mode 100644 src/messaging/messenger.cpp create mode 100644 src/messaging/messenger.hpp create mode 100644 src/messaging/msg_component.hpp create mode 100644 src/xapp/CMakeLists.txt create mode 100644 src/xapp/README create mode 100644 src/xapp/xapp.cpp create mode 100644 src/xapp/xapp.hpp create mode 100644 test/.gitignore create mode 100644 test/Makefile create mode 100755 test/parse_gcov.sh create mode 100644 test/rmr_em.c create mode 100755 test/scrub_gcov.sh create mode 100644 test/unit_test.cpp create mode 100755 test/unit_test.sh diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8e65e5f --- /dev/null +++ b/CMakeLists.txt @@ -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= 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 "$;$" ) +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 "$;$" ) + 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 index 0000000..a5e2925 --- /dev/null +++ b/Dockerfile @@ -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 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 index 0000000..905a15d --- /dev/null +++ b/build_rmr.sh @@ -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 index 0000000..07830bc --- /dev/null +++ b/examples/Makefile @@ -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 index 0000000..bd51be9 --- /dev/null +++ b/examples/ts_xapp.cpp @@ -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 +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "ricxfcpp/xapp.hpp" + +using Namespace = std::string; +using Key = std::string; +using Data = std::vector; +using DataMap = std::map; +using Keys = std::set; + + +// ---------------------------------------------------------- + +std::unique_ptr 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 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, " 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, " 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 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 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, " response timeout set to: %d\n", response_to ); + fprintf( stderr, " listening on port: %s\n", port ); + + xfw = std::unique_ptr( 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 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 val_v = Dk[(*si)]; // 4 lines to unpack a string + char val[val_v.size()+1]; // from Data + int i; + for(i=0;ifindKeys(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 val_v = Dk2[(*si)]; // 4 lines to unpack a string + char val[val_v.size()+1]; // from Data + int i; + for(i=0;iRun( 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 index 0000000..9ccd4b5 --- /dev/null +++ b/examples/ts_xapp.cpp~ @@ -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 +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "ricxfcpp/xapp.hpp" + +using Namespace = std::string; +using Key = std::string; +using Data = std::vector; +using DataMap = std::map; +using Keys = std::set; + + +// ---------------------------------------------------------- + +std::unique_ptr 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 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, " 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, " 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 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 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, " response timeout set to: %d\n", response_to ); + fprintf( stderr, " listening on port: %s\n", port ); + + xfw = std::unique_ptr( 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 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 val_v = Dk[(*si)]; // 4 lines to unpack a string + char val[val_v.size()+1]; // from Data + int i; + for(i=0;ifindKeys(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 val_v = Dk2[(*si)]; // 4 lines to unpack a string + char val[val_v.size()+1]; // from Data + int i; + for(i=0;iRun( 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 index 0000000..5dd30dc --- /dev/null +++ b/rmr-version.yaml @@ -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 index 0000000..f0242cf --- /dev/null +++ b/routes.txt @@ -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 index 0000000..f65a4b7 --- /dev/null +++ b/run_xapp.sh @@ -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 index 0000000..507da84 --- /dev/null +++ b/src/messaging/CMakeLists.txt @@ -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 + $ + $ + 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 index 0000000..0683082 --- /dev/null +++ b/src/messaging/README @@ -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 index 0000000..d4d697d --- /dev/null +++ b/src/messaging/callback.cpp @@ -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 + +#include + +#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 index 0000000..b8e9dfa --- /dev/null +++ b/src/messaging/callback.hpp @@ -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 + +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 index 0000000..1083293 --- /dev/null +++ b/src/messaging/default_cb.cpp @@ -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 +#include + +#include +#include + + +#include + +#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 index 0000000..2e96003 --- /dev/null +++ b/src/messaging/default_cb.hpp @@ -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 index 0000000..30a0fbc --- /dev/null +++ b/src/messaging/message.cpp @@ -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 +#include + +#include + +#include + +#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 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( new_payload ); +} + +/* + Makes a copy of the MEID and returns a smart pointer to it. +*/ +std::unique_ptr 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( 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 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( 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( mbuf->payload ); + } + + return NULL; +} + +void Message::Set_meid( std::shared_ptr 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 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 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 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 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 index 0000000..0db0927 --- /dev/null +++ b/src/messaging/message.hpp @@ -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 +#include + +#include + +#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 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 Copy_payload( ); // copy the payload; deletable smart pointer + + std::unique_ptr 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 Get_src(); + int Get_state( ); + int Get_subid(); + + void Set_meid( std::shared_ptr 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 payload ); + bool Send_msg( int mtype, int subid, int payload_len, unsigned char* payload ); + bool Send_msg( int payload_len, std::shared_ptr payload ); + bool Send_msg( int payload_len, unsigned char* payload ); + + bool Send_response( int mtype, int subid, int payload_len, std::shared_ptr response ); + bool Send_response( int mtype, int subid, int payload_len, unsigned char* response ); + bool Send_response( int payload_len, std::shared_ptr 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 index 0000000..24fe164 --- /dev/null +++ b/src/messaging/messenger.cpp @@ -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 +#include + +#include +#include + + +#include +#include +#include +#include +#include + +#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 Messenger::Alloc_msg( int payload_size ) { + return std::unique_ptr( new Message( mrc, payload_size ) ); +} + +void Messenger::Listen( ) { + int count = 0; + rmr_mbuf_t* mbuf = NULL; + std::map::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_ptrm; + + 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( 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, " 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 Messenger::Receive( int timeout ) { + rmr_mbuf_t* mbuf = NULL; + std::unique_ptr 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( 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 index 0000000..0810aeb --- /dev/null +++ b/src/messaging/messenger.hpp @@ -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 +#include +#include +#include +#include + +#include + +#include "message.hpp" + +#ifndef RMR_FALSE + #define RMR_FALSE 0 + #define RMR_TRUE 1 +#endif + +class Messenger { + + private: + std::map 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 Alloc_msg( int payload_size ); // message allocation + void Listen( ); // lisen driver + std::unique_ptr 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 index 0000000..d707257 --- /dev/null +++ b/src/messaging/msg_component.hpp @@ -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 + +// -------------- 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; + +#endif diff --git a/src/xapp/CMakeLists.txt b/src/xapp/CMakeLists.txt new file mode 100644 index 0000000..093ef6f --- /dev/null +++ b/src/xapp/CMakeLists.txt @@ -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 + $ + $ + 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 index 0000000..39391b2 --- /dev/null +++ b/src/xapp/README @@ -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 index 0000000..8feb52e --- /dev/null +++ b/src/xapp/xapp.cpp @@ -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 +#include + +#include +#include + + +#include +#include + +#include +#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 index 0000000..83540ec --- /dev/null +++ b/src/xapp/xapp.hpp @@ -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 +#include +#include +#include + +#include + +#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 index 0000000..9903b8d --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,4 @@ +core +*.gcov +*.gcda +*.gcno diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..fcbf361 --- /dev/null +++ b/test/Makefile @@ -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 index 0000000..d69a88b --- /dev/null +++ b/test/parse_gcov.sh @@ -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 index 0000000..c9f85cf --- /dev/null +++ b/test/rmr_em.c @@ -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 +#include +#include + + +/* + 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 index 0000000..349fca2 --- /dev/null +++ b/test/scrub_gcov.sh @@ -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 index 0000000..b0612d8 --- /dev/null +++ b/test/unit_test.cpp @@ -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 + + +#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, " 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 x ) { + fprintf( stderr, " killer is waiting in the shadows\n" ); + sleep( 2 ); + fprintf( stderr, " 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 msg; + std::shared_ptr x; + Msg_component payload; + std::unique_ptr ucs; + unsigned char* new_payload; + std::shared_ptr 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( 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, " expected pointer to copy payload but got nil\n" ); + errors++; + } + + ucs = msg->Get_meid(); + if( ucs == NULL ) { + fprintf( stderr, " expected pointer to meid copy but got nil\n" ); + errors++; + } + + ucs = msg->Get_src(); + if( ucs == NULL ) { + fprintf( stderr, " expected pointer to src copy but got nil\n" ); + errors++; + } + + i = msg->Get_available_size(); + if( i != 2048 ) { + fprintf( stderr, " len expected payload avail size of 2048 but got %d\n", i ); + errors++; + } + + i = msg->Get_mtype(); + if( i != 100 ) { + fprintf( stderr, " expected mtype of 100 but got %d\n", i ); + errors++; + } + + i = msg->Get_state( ); + if( i != 0 ) { + fprintf( stderr, " expected state of 0 but got %d\n", i ); + errors++; + } + + i = msg->Get_subid(); + if( i != -10 ) { + fprintf( stderr, " expected subid of -10 but got %d\n", i ); + errors++; + } + + i = msg->Get_len(); + if( i != 128 ) { + fprintf( stderr, " 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( 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, " 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, " one or more callbacks reported an error: [%d] [%d] [%d]\n", err_cb1, err_cb2, err_cbd ); + fprintf( stderr, " 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 msg2 = x->Alloc_msg( 2048 ); + std::unique_ptr 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, " 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, " copy string (%s) \n", (char *) (msg4.Get_payload()).get() ); // and payload should be coppied + if( msg4.Get_available_size() != 4096 ) { + errors++; + fprintf( stderr, " 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, " 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, " 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, " 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, " 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, " 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, " 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 index 0000000..7144f9c --- /dev/null +++ b/test/unit_test.sh @@ -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$$.* + -- 2.16.6