Initial repo population 68/1068/2
authorE. Scott Daniels <daniels@research.att.com>
Fri, 4 Oct 2019 13:07:42 +0000 (09:07 -0400)
committerE. Scott Daniels <daniels@research.att.com>
Fri, 4 Oct 2019 19:23:17 +0000 (15:23 -0400)
Signed-off-by: E. Scott Daniels <daniels@research.att.com>
Change-Id: I2652015adb5c77d0f13a75f9651e249cd0d9634d

20 files changed:
Dockerfile [new file with mode: 0644]
LICENSE [new file with mode: 0644]
ci/Dockerfile [new file with mode: 0644]
ci/README [new file with mode: 0644]
ci/stats.ksh [new file with mode: 0644]
container-tag.yaml [new file with mode: 0644]
src/sidecars/listener/.gitignore [new file with mode: 0644]
src/sidecars/listener/Makefile [new file with mode: 0644]
src/sidecars/listener/README [new file with mode: 0644]
src/sidecars/listener/build_dev_env.sh [new file with mode: 0755]
src/sidecars/listener/build_images.sh [new file with mode: 0755]
src/sidecars/listener/mc_listener.c [new file with mode: 0644]
src/sidecars/listener/mcl.c [new file with mode: 0644]
src/sidecars/listener/mcl.h [new file with mode: 0644]
src/sidecars/listener/mcl_dev.df [new file with mode: 0644]
src/sidecars/listener/mcl_runtime.df [new file with mode: 0644]
src/sidecars/listener/pipe_reader.c [new file with mode: 0644]
src/sidecars/listener/sender.c [new file with mode: 0644]
src/sidecars/listener/unit_test.c [new file with mode: 0644]
src/sidecars/listener/verify.sh [new file with mode: 0755]

diff --git a/Dockerfile b/Dockerfile
new file mode 100644 (file)
index 0000000..14f0ba9
--- /dev/null
@@ -0,0 +1,49 @@
+# 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.
+# -------------------------------------------------------------------------------
+
+# CAUTION:   This file eventually should exist in the ci directory, however until
+#                      this can be confirmed, and the .yaml file(s) in the ci project changed
+#                      to indicaate that ci/Dockerfile should be used, this is here with minor
+#                      changes needed to exist at the root.
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+# CI to verify the MC application components
+# Inherits C toolchain from buildpack-deps:stretch then adds cmake and better shell(s).
+
+# It is assumed that this docker file is used with a build command run at the
+# root level of the repo (directory containing the ci directory). E.g.
+#      docker build -f ci/Dockerfile .
+
+FROM buildpack-deps:stretch
+
+RUN apt-get update && apt-get -q -y install cmake ksh
+
+# stuff our repo things into a scratch area
+RUN mkdir /playpen
+ADD . /playpen
+
+
+# add any unit test scripts that need to be driven, e.g.
+# RUN ksh test/mcl_unit_test.ksh
+
+#  This is a final/only script which might print useful things to the log and ALWAYS succeeds.
+RUN ksh /playpen/ci/stats.ksh
+
+# there is no cmd; the build/verification assumes that if the image is created
+# successfully, e.g. none of the previous run commands fail, that the environment
+# is successfully vetted.
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..e7bf3d1
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+
+    Unless otherwise specified, all software contained herein is licensed
+    under the Apache License, Version 2.0 (the "Software License");
+    you may not use this software except in compliance with the Software
+    License. You may obtain a copy of the Software License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the Software License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the Software License for the specific language governing permissions
+    and limitations under the Software License.
+
+
+
+    Unless otherwise specified, all documentation contained herein is licensed
+    under the Creative Commons License, Attribution 4.0 Intl. (the
+    "Documentation License"); you may not use this documentation except in
+    compliance with the Documentation License. You may obtain a copy of the
+    Documentation License at
+
+        https://creativecommons.org/licenses/by/4.0/
+
+    Unless required by applicable law or agreed to in writing, documentation
+    distributed under the Documentation License is distributed on an "AS IS"
+    BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+    implied. See the Documentation License for the specific language governing
+    permissions and limitations under the Documentation License.
diff --git a/ci/Dockerfile b/ci/Dockerfile
new file mode 100644 (file)
index 0000000..57e70a0
--- /dev/null
@@ -0,0 +1,43 @@
+# 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.
+# -------------------------------------------------------------------------------
+
+# CI to verify the MC application components
+# Inherits C toolchain from buildpack-deps:stretch then adds cmake and better shell(s).
+
+# It is assumed that this docker file is used with a build command run at the
+# root level of the repo (directory containing the ci directory). E.g.
+#      docker build -f ci/Dockerfile .
+
+FROM buildpack-deps:stretch
+
+RUN apt-get update && apt-get -q -y install cmake ksh
+
+# stuff our repo things into a scratch area
+RUN mkdir /playpen
+ADD . /playpen
+
+
+# add any unit test scripts that need to be driven, e.g.
+# RUN ksh test/mcl_unit_test.ksh
+
+#  This is a final/only script which might print useful things to the log and ALWAYS succeeds.
+RUN ksh /playpen/ci/stats.ksh
+
+# there is no cmd; the build/verification assumes that if the image is created
+# successfully, e.g. none of the previous run commands fail, that the environment
+# is successfully vetted.
diff --git a/ci/README b/ci/README
new file mode 100644 (file)
index 0000000..abe687b
--- /dev/null
+++ b/ci/README
@@ -0,0 +1,22 @@
+ -------------------------------------------------------------------------------
+    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.
+ -------------------------------------------------------------------------------
+
+These files are needed to support the continuous integration process
+which verifies code on commit and might build images for the application.
+
+The Docker file is used to build an image during CI vetting, and maybe even
+during the building of the overall application container. For vetting,
+the successful build indicates that all vetting of the repo passed.
diff --git a/ci/stats.ksh b/ci/stats.ksh
new file mode 100644 (file)
index 0000000..563ee11
--- /dev/null
@@ -0,0 +1,30 @@
+
+# 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:       stats.ksh
+#      Abstract:       This script is run as the last vetting script during the CI
+#                              process to possibly provide some stats about the verification.
+#                              The script may be run as the only verification script during
+#                              initial development. The script MUST always finish successfully
+#      Date:           24 August 2019  
+# -------------------------------------------------------------------------------
+
+
+echo "$(date) finished verification and/or build"
diff --git a/container-tag.yaml b/container-tag.yaml
new file mode 100644 (file)
index 0000000..56e9ce4
--- /dev/null
@@ -0,0 +1,5 @@
+
+# this provides the jenkins environment with a tag to use when 
+# building verification containers.
+---
+tag: 1.0.0
diff --git a/src/sidecars/listener/.gitignore b/src/sidecars/listener/.gitignore
new file mode 100644 (file)
index 0000000..874c63c
--- /dev/null
@@ -0,0 +1,2 @@
+*.o
+
diff --git a/src/sidecars/listener/Makefile b/src/sidecars/listener/Makefile
new file mode 100644 (file)
index 0000000..36f7113
--- /dev/null
@@ -0,0 +1,60 @@
+# 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.
+#
+#---------------------------------------------------------------------------------
+
+# this make file assuems that both NNG and RMR are installed and that the variables
+# LD_LIBRARY_PATH, LIBRARY_PATH are set correctly.
+
+binaries = mc_listener
+test_progs = sender unit_test pipe_reader
+lib_obj = mcl.o
+lib_h = mcl.h
+
+coverage_opts = -ftest-coverage -fprofile-arcs
+
+# make with no parms should just build 'production' binaries
+all: $(binaries) mc_listener
+
+libmcl.a:      $(lib_obj) $(lib_h)
+       ar -v -r libmcl.a $<
+
+mc_listener: mc_listener.c libmcl.a
+       gcc mc_listener.c -o mc_listener -L. -lmcl  -lrmr_nng -lnng -lm -lpthread
+
+
+# ---- testing stuff -----------------------------------------------------------------
+tests: $(test_progs)
+
+sender : sender.c
+       gcc sender.c -o sender  -lrmr_nng -lnng -lm -lpthread
+
+pipe_reader : pipe_reader.c libmcl.a
+       gcc pipe_reader.c -o pipe_reader  -L. -lmcl -lrmr_nng -lnng -lm -lpthread
+
+unit_test: unit_test.c mcl.c
+       gcc $(coverage_opts) unit_test.c -o unit_test -lrmr_nng -lnng -lm -lpthread
+
+
+# ---- housekeeping stuff -------------------------------------------------------------
+# remove only intermediates
+clean:
+       rm -f *.o *.gcda *.gcno *.gcov
+
+# remove anything that can be rebuilt
+nuke: clean
+       rm -f *mcl.a $(binaries) $(test_progs)
diff --git a/src/sidecars/listener/README b/src/sidecars/listener/README
new file mode 100644 (file)
index 0000000..4b2da47
--- /dev/null
@@ -0,0 +1,77 @@
+
+--------------------------------------------------------------------------------
+
+       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.
+--------------------------------------------------------------------------------
+
+
+
+MC Listener
+
+This directory contains the source for the a simple message listener which 
+writes messages received via RMR into a fifo (named pipe) for an external
+process to consume. 
+
+Fifos are named MT_xxxxxxxx where the Xs are replaced with the message 
+type with up to 7 leading zeros (e.g. MT_00000002).  The data written 
+to a pipe has the form:
+       <8 bytes><n bytes>
+
+Where the first 8 bytes are the ASCII representation of the length of
+the message (n) (the 8th byte is a zero allowing the bytes to be
+treated as a C string if desired.  The next n bytes are the unchanged 
+payload which was received.  No RMR header information (e.g. source, 
+meid, etc.) is communicated.
+
+If the listener is executed with the timestamp extension enabled, then
+the leading 'header' is enhanced such that the time the message was
+received by the listener is added.  Additionally, a leading delimiter
+is added to make synchronisation possible. The timestamp is also an 
+ASCII string of the form 1570110224356 (milliseconds past the epoch).  
+The enhanced header has the format:
+
+       <delimeter><length-bytes><time-bytes>
+
+The <delimeter> is a series of 4 bytes which should always be: '@MCL'.
+It is intended to be used to sequence "frames" in the pipe should there be
+write errors which result in missing data.  If the application reading from
+a pipe does not see this delimeter, then it should read byte by byte from
+the pipe until it does in order to synchronise with the stream.
+
+The <length-bytes> are as descrbed previously: 8 byte ASCII string (nil 
+terminated).
+
+The <time-bytes> is an ASCII string (nil terminated) with a length of 16
+bytes. 
+
+The entire header will require 28 bytes.
+       
+
+There are multiple docker files; *.df.
+       mcl_runime.df -- builds an image with the runtime mc_listener binary
+       mcl_dev.df    -- builds a development image that can be used to
+                                        interactively build and test the library and mc_listener
+                                        application.
+
+Unit testing
+A very small set of unit tests are provided for the library functions in
+mcl.c.  Because of the nature of the fanout function, which blocks waiting
+on RMR messages, it is not possible to unit test that bit of code.
+
+FIFO Reader
+The pipe_reader programme is a simple application which uses the mcl.c 
+library functions to open and read from a single pipe.  If the -e option
+is given it will expect that data in the FIFO has extended headers. Use
+the -? option (or -h) to generate a full usage statement.
diff --git a/src/sidecars/listener/build_dev_env.sh b/src/sidecars/listener/build_dev_env.sh
new file mode 100755 (executable)
index 0000000..d0b241a
--- /dev/null
@@ -0,0 +1,98 @@
+#!/usr/bin/env bash
+# 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:       bld_dev_env.sh
+#      Abstract:       This script is meant to be executed by a RUN command in
+#                              a docker file. It fetches NNG, builds/installs it, and then
+#                              does a wget of the desired RMR version's packages and installs
+#                              them. RMR is fetched from package cloud.
+#
+#      Date:           22 August 2019
+#      Author:         E. Scott Daniels
+# ---------------------------------------------------------------------------
+
+rmr_ver=1.9.0
+nng_ver=v1.1.1
+
+while [[ $1 == -* ]]
+do
+       case $1 in 
+               -n) nng_ver=$2; shift;;
+               -r)     rmr_ver=$2; shift;;
+       esac
+
+       shift
+done
+
+if [[ $nng_ver != "v"* ]]
+then
+       nng_ver="v$nng_ver"
+fi
+
+set -e                                                         # from this point on crash on any error
+mkdir -p /playpen/build/nng
+cd /playpen/build/nng
+git clone https://github.com/nanomsg/nng.git
+cd nng
+git checkout $nng_ver
+mkdir .build
+cd .build
+echo "building nng (messages supressed unless there is an error)"
+
+set +e
+if ! cmake ..  >/tmp/cmake.nng.log 2>&1
+then
+       cat /tmp/cmake.nng.log
+       echo ""
+       echo "### ERROR ### NNG cmake configuration failed"
+       exit 1
+fi
+
+if ! make install >/tmp/build.nng.log 2>&1
+then
+       cat /tmp/build.nng.log
+       echo ""
+       echo "### ERROR ### NNG build failed"
+       exit 1
+fi
+
+set -e
+echo "nng build finished ok"
+cd /playpen
+rm -fr /playpen/build/nng
+
+echo "installing RMR packages version = $rmr_ver"
+mkdir -p /playpen/build/pkgs
+cd /playpen/build/pkgs
+
+base_url=https://packagecloud.io/o-ran-sc/master/packages/debian/stretch/
+base_url=https://packagecloud.io/o-ran-sc/staging/packages/debian/stretch/
+pc_url=${base_url}rmr_${rmr_ver}_amd64.deb/download.deb
+pc_dev_url=${base_url}rmr-dev_${rmr_ver}_amd64.deb/download.deb
+
+wget -q -O rmr.deb $pc_url
+wget -q -O rmr-dev.deb $pc_dev_url
+
+ls -al
+dpkg -i *.deb
+cd /playpen
+rm -fr /playpen/build/pkgs
+echo "RMR package install finished"
diff --git a/src/sidecars/listener/build_images.sh b/src/sidecars/listener/build_images.sh
new file mode 100755 (executable)
index 0000000..4fe1219
--- /dev/null
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+# 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:       build_images.sh
+#      Abstract:       This script will create both the mc_listener runtime and development
+#                              images.
+#      Date:           22 August 2-19
+#      Author:         E. Scott Daniels
+# -----------------------------------------------------------------------------------
+
+skip_dev=1
+if [[ $1 == "all" ]]
+then
+       skip_dev=0
+       shift
+fi
+
+if [[ $1 == "-?" || $1 == "-h" ]]
+then
+       echo "usage: $0 [all] [mcl-version-tag [patch-level]]"
+       echo "   using all as first keyword causes both runtime and dev images to build"
+       exit 0
+fi
+
+
+ver=${1:-1.1}
+patch=${2:-0}
+
+if (( skip_dev == 0 ))
+then
+       echo "building development image"
+       docker build -f mcl_dev.df -t mcl_dev:$ver.$patch .
+fi
+
+echo "building runtime image mc_listener:$ver"
+if docker build -f mcl_runtime.df -t mc_listener:$ver.$patch .
+then
+       echo "build finished"
+       echo ""
+       docker images|grep mc_
+fi
+
diff --git a/src/sidecars/listener/mc_listener.c b/src/sidecars/listener/mc_listener.c
new file mode 100644 (file)
index 0000000..6b8da45
--- /dev/null
@@ -0,0 +1,149 @@
+// 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:       mc_listener.c
+       Abstract:       This application (management campaign listener) will listen for
+                               RMR based messages and write the payloads into FIFOs which
+                               correspond to the message type.
+               
+                               Defaults:
+                                       /var/lib/mc/listener  -- directory for FIFOs
+
+                               Command line options:
+                                       -d <path>   FIFO directory (default is /tmp/mcl/fifos)
+                                       -p <port>       The port to set RMR listener on (default is 4560)
+                                       -r <seconds>  The frequency that count reports are written to
+                                                                       stderr. 0 == 0ff; default is 60.
+
+
+                               RMR based environment variables which might be needed:
+                                       RMR_SEED_RT -- path to the static routing table
+                                       RMR_RTG_SVC -- port to listen for RTG connections
+
+       Date:           22 August 2019
+       Author:         E. Scott Daniels
+*/
+
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <string.h>
+
+
+#include "mcl.h"
+
+//---- support -----------------------------------------------------------------------------
+
+static void bad_arg( char* what ) {
+       fprintf( stderr, "[ERR] option is unrecognised or isn't followed by meaningful data: %s\n", what );
+}
+
+static void usage( char* argv0 ) {
+       fprintf( stderr, "usage: %s [-d fifo-dir] [-e] [-p listen-port] [-q | -r report-freq]\n", argv0 );
+       fprintf( stderr, "  -e  enable extended header in buffers written to FIFOs\n" );
+}
+
+//------------------------------------------------------------------------------------------
+int main( int argc,  char** argv ) {
+       void*   ctx;                                                    // the mc listener library context
+       char*   dname = "/tmp/mcl/fifos";               // default directory where we open fifos
+       char*   port = "4560";                                  // default rmr port
+       char*   siphon_dir = "/tmp/mci/siphon"; // where siphon files are placed
+       int             siphon = 0;                                             // percentage of messages to siphone off
+       int             report_freq = 60;                               // report stats every n seconds
+       int             pidx = 1;                                               // parameter index
+       int             error = 0;
+       int             long_hdrs = 0;                                  // -e sets and causes extended headers to be written
+
+       while( pidx < argc && argv[pidx][0] == '-' ) {                  // simple argument parsing (-x  or -x value)
+               switch( argv[pidx][1] ) {
+                       case 'd':
+                               if( pidx+1 < argc ) {
+                                       dname = strdup( argv[pidx+1] );
+                                       pidx++;
+                               } else {
+                                       bad_arg( argv[pidx] );
+                                       error = 1;
+                               }
+                               break;
+
+                       case 'e':
+                               long_hdrs = 1;
+                               break;
+
+                       case 'p':
+                               if( pidx+1 < argc ) {
+                                       port = strdup( argv[pidx+1] );
+                                       pidx++;
+                               } else {
+                                       bad_arg( argv[pidx] );
+                                       error = 1;
+                               }
+                               break;
+
+                       case 'q':
+                               report_freq = 0;
+                               break;
+
+                       case 'r':
+                               if( pidx+1 < argc ) {
+                                       report_freq = atoi( argv[pidx+1] );
+                                       pidx++;
+                               } else {
+                                       bad_arg( argv[pidx] );
+                                       error = 1;
+                               }
+                               break;
+
+                       case 'h':
+                       case '?':
+                               usage( argv[0] );
+                               exit( 0 );
+                               break;
+
+                       default:
+                               bad_arg( argv[pidx] );
+                               error = 1;
+                               break;
+               }
+
+               pidx++;
+       }
+
+       if( error ) {
+               usage( argv[0] );
+               exit( 1 );
+       }
+
+       ctx = mcl_mk_context( dname );                  // initialise the library context
+       if( ctx == NULL ) {
+               fprintf( stderr, "[FAIL] couldn't initialise the mc listener environment\n" );
+               exit( 1 );
+       }
+       mcl_set_sigh();                                                                 // set signal handler(s)
+
+       mcl_start_listening( ctx,  port, MCL_NOWAIT );          // start the listener, no waiting for rt since we don't send
+       mcl_fifo_fanout( ctx, report_freq, long_hdrs );         // listen and fanout messages to fifo; report to stdout every ~2sec
+
+       fprintf( stderr, "[INFO] mc listener is finished.\n" );
+}
+
diff --git a/src/sidecars/listener/mcl.c b/src/sidecars/listener/mcl.c
new file mode 100644 (file)
index 0000000..06ceb82
--- /dev/null
@@ -0,0 +1,568 @@
+// 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:       mcl.c.
+       Abstract:       The mc listener library content. All external functions
+                               should start with mcl_ and all stderr messages should have
+                               (mcl) as the first token following the severity indicator.
+                               
+       Date:           22 August 2019
+       Author:         E. Scott Daniels
+*/
+
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <string.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/stat.h>
+
+#include <rmr/rmr.h>
+#include <rmr/rmr_symtab.h>
+
+#include "mcl.h"
+
+#define READER 0
+#define WRITER 1
+
+#define TRUE   1
+#define FALSE  0
+
+
+/*
+       Information about one file descriptor. This is pointed to by the hash
+       such that the message type can be used as a key to look up the fifo's
+       file descriptor.
+*/
+typedef struct {
+       int     fd;                                     // open fdes
+       int key;                                // symtab key
+       long long wcount;               // number of writes
+       long long drops;                // number dropped
+
+       long long wcount_rp;    // number of writes during last reporting period
+       long long drops_rp;             // number dropped during last reporting period
+} fifo_t;
+
+/*
+       Our conext.  Pointers to the read and write hash tables (both keyed on the message
+       type), the message router (RMR) context, and other goodies.
+*/
+typedef struct {
+       void*   mrc;                            // the message router's context
+       void*   wr_hash;                        // symtable to look up pipe info based on mt for writing
+       void*   rd_hash;                        // we support reading from pipes, but need a different FD for that
+       char*   fifo_dir;                       // directory where we open fifos
+       
+} mcl_ctx_t;
+
+// -------- private -------------------------------------------------------
+
+
+/*
+       Builds an extended header in the buffer provided, or allocates a new buffer if
+       dest is nil. The header is of the form:
+               <delim><len><timestamp>
+
+       Timestamp is a single unsigned long long in ASCII; ms since epoch.
+       If the current time is 2019/10/03 10:39:51.103 which is 1570113591.103
+       the timestamp generated will be 1570113591103.
+*/
+static char* build_hdr( int len, char* dest, int dest_len ) {
+       struct timespec ts;         // time just before call executed
+
+       if( dest == NULL ) {
+               dest_len = 48;
+               dest = (char *) malloc( sizeof( char ) * dest_len );
+       } else {
+               if( dest_len < 28 ) {           // shouldn't happen, but take no chances
+                       memset( dest, 0, dest_len );
+                       return NULL;
+               }
+       }
+
+       memset( dest, 0, dest_len );
+
+       clock_gettime( CLOCK_REALTIME, &ts );
+       sprintf( dest, "%s%07d", MCL_DELIM, len );
+       sprintf( dest+12, "%ld%03ld", ts.tv_sec, ts.tv_nsec/1000000 );
+
+       return dest;
+}
+
+/*     
+       Build a file name and open. The io_direction is either READER or
+       WRITER.  For a writer we must 'trick' the system into allowing us
+       to open a pipe for writing in non-blocking mode so that we can 
+       report on drops (messages we couldn't write because there was no
+       reader).  The trick is to open a reader on the pipe so that when
+       we open the writer there's a reader and the open won't fail. As
+       soon as we have the writer open, we can close the junk reader.
+
+       If the desired fifo does not exist, it is created.
+*/
+static int open_fifo( mcl_ctx_t* ctx, int mtype, int io_dir ) {
+       char    wbuf[1024];
+       int             fd;                             // real file des
+       int             jfd = -1;                       // junk file des
+       int             state;
+
+       if( ctx == NULL || mtype < 0 ) {
+               return -1;
+       }
+
+       snprintf( wbuf, sizeof( wbuf ), "%s/MT_%09d", ctx->fifo_dir, mtype );
+       
+       state = mkfifo( wbuf, 0660 );           // make the fifo; this will fail if it exists and that's ok
+       if( state != 0 && errno != EEXIST ) {
+               fprintf( stderr, "[ERR] (mcl) unable to create fifo: %s: %s\n", wbuf, strerror( errno ) );
+               return -1;
+       }
+
+       if( io_dir == READER ) {
+               fd = open( wbuf, O_RDONLY  );                   // just open the reader
+               if( fd < 0 ) {
+                       fprintf( stderr, "[ERR] (mcl) fifo open failed (ro): %s: %s\n", wbuf, strerror( errno ) );
+               }
+       } else {
+               jfd = open( wbuf, O_RDWR  | O_NONBLOCK );                       // must have a reader before we can open a non-blocking writer
+               if( jfd < 0 ) {
+                       fprintf( stderr, "[ERR] (mcl) fifo open failed (rw): %s: %s\n", wbuf, strerror( errno ) );
+               }
+       
+               fd = open( wbuf, O_WRONLY  | O_NONBLOCK );                      // this will be our actual writer, in non-blocking mode
+               if( fd < 0 ) {
+                       fprintf( stderr, "[ERR] (mcl) fifo open failed (wo): %s: %s\n", wbuf, strerror( errno ) );
+               }
+
+               close( jfd );                   // should be safe to close this
+       }
+
+
+       return fd;
+}
+
+/*
+       Given a message type, return the file des of the fifo that
+       the payload should be written to.        Returns the file des, or -1
+       on error. When sussing out a read file descriptor this will
+       block until there is a fifo for the message type which is
+       open for reading.
+
+       If fref is not nil, then a pointer to the fifo info block is returned
+       allowing for direct update of counts after the write.
+*/
+static int suss_fifo( mcl_ctx_t* ctx, int mtype, int io_dir, fifo_t** fref ) {
+       fifo_t* fifo;
+       void*   hash;
+
+       if( io_dir == READER ) {                // with an integer key, we nned two hash tables
+               hash = ctx->rd_hash;
+       } else {
+               hash = ctx->wr_hash;
+       }
+
+       if( (fifo = (fifo_t *) rmr_sym_pull( hash, mtype )) == NULL ) {
+               fifo = (fifo_t *) malloc( sizeof( *fifo ) );
+               if( fifo == NULL ) {
+                       return -1;
+               }
+
+               memset( fifo, 0, sizeof( *fifo ) );
+               fifo->key = mtype;
+               fifo->fd = open_fifo( ctx, mtype, io_dir );
+               rmr_sym_map( hash, mtype, fifo );
+       }
+
+       if( fref != NULL ) {
+               *fref = fifo;
+       }
+       return fifo->fd;
+}
+
+/*
+       Make marking counts easier in code
+*/
+static inline void chalk_error( fifo_t* fifo ) {
+       if( fifo != NULL ) {
+               fifo->drops++;
+               fifo->drops_rp++;
+       }
+}
+
+static inline void chalk_ok( fifo_t* fifo ) {
+       if( fifo != NULL ) {
+               fifo->wcount++;
+               fifo->wcount_rp++;
+       }
+}
+
+/*
+       Callback function driven to traverse the symtab and generate the 
+       counts for each fifo.
+*/
+static void wr_stats( void* st, void* entry, char const* name, void* thing, void* data ) {
+       fifo_t* fifo;
+       int             report_period = 60;
+
+       if( data ) {
+               report_period = *((int *) data);
+       }
+
+       if( (fifo = (fifo_t *) thing) != NULL ) {
+               fprintf( stdout, "[STAT] (mcl) mtype=%d total writes=%lld total drops=%lld; during last %ds writes=%lld drops=%lld\n", 
+                       fifo->key, fifo->wcount, fifo->drops, report_period, fifo->wcount_rp, fifo->drops_rp );
+
+               fifo->wcount_rp = 0;            // reset the report counts
+               fifo->drops_rp = 0;
+       }
+}
+
+// ---------- public ------------------------------------------------------
+/*
+       Sets a signal handler for sigpipe so we don't crash if a reader closes the
+       last reading fd on a pipe. We could do this automatically, but if the user
+       programme needs to trap sigpipe too, this gives them the option not to have
+       us interfere.
+*/
+extern int mcl_set_sigh( ) {
+       signal( SIGPIPE, SIG_IGN );
+}
+
+/*
+       "Opens" the interface to RMR such that messages sent to the application will
+       be available via the rmr receive funcitons. This is NOT automatically called
+       by the mk_context function as some applications will be using the mc library
+       for non-RMR, fifo, chores.
+*/
+extern int mcl_start_listening( void* vctx,  char* port, int wait4ready ) {
+       mcl_ctx_t*      ctx;
+       int             announce = 0;
+
+       if( (ctx = (mcl_ctx_t*) vctx) == NULL ) {
+               return 0;
+       }
+
+       ctx->mrc = rmr_init( port, RMR_MAX_RCV_BYTES, RMRFL_NONE );     // start your engines!
+       if( ctx->mrc == NULL ) {
+               fprintf( stderr, "[CRIT]  unable to initialise RMr\n" );
+               return 0;
+       }
+
+       while( wait4ready && ! rmr_ready( ctx->mrc ) ) {                                // only senders need to wait
+               if( announce <= 0 ) {
+                       fprintf( stderr, "[INFO] waiting for RMR to show ready\n" );
+                       announce = 10;
+               } else {
+                       announce--;
+               }
+
+               sleep( 1 );
+       }
+
+       return 1;
+}
+
+/*
+       Blocks until a message arives with a good return code or we timeout. Returns the 
+       rmr message buffer. Timeout value epxected in seconds.
+*/
+extern rmr_mbuf_t* mcl_get_msg( void* vctx, rmr_mbuf_t* msg, int timeout ) {
+       mcl_ctx_t*      ctx;
+
+       if( (ctx = (mcl_ctx_t *) vctx) == NULL ) {
+               return NULL;
+       }
+
+       if( ctx->mrc == NULL ) {
+               fprintf( stderr, "bad context\n" );
+               exit( 1 );
+       }
+
+       do {
+               msg = rmr_torcv_msg( ctx->mrc, msg, timeout * 1000 );                           // wait for next
+       } while( msg == NULL || (msg->state != RMR_OK && msg->state != RMR_ERR_TIMEOUT) );
+
+       return msg;
+}
+
+/*
+       Create the context.
+*/
+extern void* mcl_mk_context( char* dir ) {
+       mcl_ctx_t*      ctx;
+
+       if( (ctx = (mcl_ctx_t *) malloc( sizeof( *ctx ) )) != NULL ) {
+               memset( ctx, 0, sizeof( *ctx ) );
+               ctx->fifo_dir = strdup( dir );
+               ctx->wr_hash = rmr_sym_alloc( 1001 );
+               ctx->rd_hash = rmr_sym_alloc( 1001 );
+
+               if( ctx->wr_hash == NULL  || ctx->rd_hash == NULL ) {
+                       fprintf( stderr, "[ERR] (mcl) unable to allocate hash table for fifo keys\n" );
+                       free( ctx );
+                       return NULL;
+               }
+       }
+
+       return (void *) ctx;
+}
+
+/*
+       Read the header. Best case we read the expected number of bytes, get all
+       of them and find that they start with the delemiter.  Worst case
+       We have to wait for all of the header, or need to synch at the next
+       delimeter. We assume best case most likely and handle it as such.
+*/
+static void read_header( int fd, char* buf ) {
+       int len;
+       int need = MCL_EXHDR_SIZE;              // total needed
+       int dneed;                                              // delimieter needed
+       int     rlen;
+       char*   rp;                             // read position in buf
+
+       len = read( fd, buf, need );
+       if( len == need && strncmp( buf, MCL_DELIM, strlen( MCL_DELIM )) == 0 ) {       // best case, most likely
+               return;
+       }
+       
+       while( TRUE ) {
+               if( len < strlen( MCL_DELIM ) ) {               // must get at least enough bytes to check delim
+                       rp = buf + len;
+                       dneed = strlen( MCL_DELIM ) - len;
+       
+                       while( dneed > 0 ) {
+                               len = read( fd, rp, dneed );
+                               dneed -= len;
+                               rp += len;
+                       }
+               }
+
+               if( strncmp( buf, MCL_DELIM, strlen( MCL_DELIM )) == 0 ) {      // have a good delimiter, just need to wait for bytes
+                       need = MCL_EXHDR_SIZE - strlen( MCL_DELIM );
+                       rp = buf + (MCL_EXHDR_SIZE - need);
+       
+                       while( need > 0 ) {
+                               len = read( fd, rp, need );
+                               need -= len;
+                               rp += len;
+                       }
+
+                       return;
+               }
+
+               while( buf[0] != MCL_DELIM[0] ) {       // wait for a recognised start byte to be read (may cause an additional message drop
+                       len = read( fd, buf, 1 );               // because we ignore start byte that might be in the buffer)
+               }
+
+               need = MCL_EXHDR_SIZE - len;
+       }
+}
+
+
+/*
+       Read one record from the fifo that the message type maps to.
+       Writes at max ublen bytes into the ubuf.
+
+       If long_hdrs is true (!0), then we expect that the stream in the fifo
+       has extended headers (<delim><len><time>), and will write the timestamp
+       from the header into the buffer pointed to by timestamp. The buffer is
+       assumed to be at least MCL_TSTAMP_SIZE bytes in length.
+
+       Further, when extended headers are being used, this function will
+       automatically resynchronise if it detects an issue.
+
+       The function could look for the delimiter and automatically detect whether
+       or not extended headers are in use, but if the stream is out of synch on the
+       first read, this cannot be done, so the funciton requires that the caller
+       know that the FIFO contains extended headers.  
+*/
+static int fifo_read1( void *vctx, int mtype, char* ubuf, int ublen, int long_hdrs, char* timestamp ) {
+       int fd;
+       int len;
+       int     msg_len;
+       int     got = 0;                                                // number of bytes we actually got
+       int need;
+       char wbuf[4096];
+       mcl_ctx_t*      ctx;                                    // our context; mostly for the rmr context reference and symtable
+       fifo_t* fref = NULL;                            // the fifo struct we found
+
+       if( (ctx = (mcl_ctx_t*) vctx) == NULL ) {
+               errno = EINVAL;
+               return 0;
+       }
+
+       if( (fd = suss_fifo( ctx, mtype, READER, NULL ))  >= 0 )  {
+               if( long_hdrs ) {
+                       read_header( fd, wbuf );
+                       msg_len = need = atoi( wbuf + MCL_LEN_OFF );                            // read the length
+                       if( timestamp ) {
+                               strcpy( timestamp, wbuf + MCL_TSTAMP_OFF+1 );
+                       }
+               } else {
+                       if( timestamp != NULL ) {                                               // won't be there, but ensure it's not garbage
+                               *timestamp = 0;
+                       }
+
+                       len = read( fd, wbuf, MCL_LEN_SIZE );                   // we assume we will get all 8 as there isn't a way to sync the old stream
+                       msg_len = need = atoi( wbuf );
+               }
+
+               
+               if( need > ublen ) {
+                       need = ublen;                                           // cannot give them more than they can take
+               }
+               while( need > 0 ) {
+                       len = read( fd, wbuf, need > sizeof( wbuf ) ? sizeof( wbuf ) : need );          
+                       memcpy( ubuf+got, wbuf, len );
+                       got += len;
+                       need -= len;
+               }
+
+               if( msg_len > got ) {                                   // we must ditch rest of this message
+                       need = msg_len = got;
+                       while( need > 0 ) {
+                               len = read( fd, wbuf, need > sizeof( wbuf ) ? sizeof( wbuf ) : need );          
+                               need -= len;
+                       }
+               }
+
+               return got;
+       }
+
+       errno = EBADFD;
+       return 0;
+}
+
+/*
+       Read one record from the fifo that the message type maps to.
+       Writes at max ublen bytes into the ubuf. If extended headers are in use
+       this function will ignore the timestamp.
+
+       If long_hdrs is true (!0), then we expect that the stream in the fifo
+       has extended headers (<delim><len><time>).
+*/
+extern int mcl_fifo_read1( void *vctx, int mtype, char* ubuf, int ublen, int long_hdrs ) {
+       return fifo_read1( vctx, mtype, ubuf, ublen, long_hdrs, NULL );
+}
+
+/*
+       Read a single message from the FIFO returning it in the caller's buffer. If extended
+       headers are being used, and the caller supplied a timestamp buffer, the timestamp
+       which was in the header will be returned in that buffer.  The return value is the number
+       of bytes in the buffer; 0 indicates an error and errno should be set.   
+*/
+extern int mcl_fifo_tsread1( void *vctx, int mtype, char* ubuf, int ublen, int long_hdrs, char *timestamp ) {
+       return fifo_read1( vctx, mtype, ubuf, ublen, long_hdrs, timestamp );
+}
+
+
+/*
+       Will read messages and fan them out based on the message type. This should not
+       return and if it does the caller should assume an error. 
+
+       The output to each fifo is MCL_LEN_SIZE bytes with an ASCII, zero terminated, length
+       string , followed by that number of 'raw' bytes. The raw bytes are the payload 
+       exactly as received.
+
+       The report parameter is the frequency, in seconds, for writing a short
+       status report to stdout. If 0 then it's off.
+
+       If long_hdr is true, then we geneate an extended header with a delimiter and
+       timestamp.
+*/
+extern void mcl_fifo_fanout( void* vctx, int report, int long_hdr  ) {
+       mcl_ctx_t*      ctx;                                    // our context; mostly for the rmr context reference and symtable
+       fifo_t*         fifo;                                   // fifo to chalk counts on
+       rmr_mbuf_t*     mbuf = NULL;                    // received message buffer; recycled on each call
+       char            wbuf[128];                              // buffer to build len string in
+       int                     state;
+       int                     fd;                                             // file des to write to
+       long long       total = 0;                              // total messages received and written
+       long long       total_drops = 0;                // total messages received and written
+       long            count = 0;                              // messages received and written during last reporting period
+       long            errors = 0;                             // unsuccessful payload writes
+       long            drops;                                  // number of drops
+       time_t          next_report = 0;                // we'll report every 2 seconds if report is true
+       time_t          now;
+       int                     hwlen;                                  // write len for header
+
+       if( (ctx = (mcl_ctx_t*) vctx) == NULL ) {
+               fprintf( stderr, "[ERR] (mcl) invalid context given to fanout\n" );
+               errno = EINVAL;
+               return;
+       }
+
+       if( report < 0 ) {
+               report = 0;
+       }
+
+       while( 1 ) {
+               mbuf = mcl_get_msg( ctx, mbuf, report );                        // wait up to report sec for msg (0 == block until message)
+
+               if( mbuf != NULL && mbuf->state == RMR_OK && mbuf->len > 0  ) {
+                       fd = suss_fifo( ctx, mbuf->mtype, WRITER, &fifo );              // map the message type to an open fd
+                       if( fd >= 0 ) {
+                               if( long_hdr ) {
+                                       build_hdr( mbuf->len, wbuf, sizeof( wbuf ) );
+                                       hwlen = MCL_EXHDR_SIZE;
+                               } else {
+                                       snprintf( wbuf, sizeof( wbuf ), "%07d", mbuf->len );                    // size of payload CAUTION: 7d is MCL_LEN_SIZE-1
+                                       hwlen = MCL_LEN_SIZE;
+                               }
+
+                               if( (state = write( fd, wbuf, hwlen )) != hwlen ) {             // write exactly MCL_LEN_SIZE bytes from the buffer
+                                       drops++;
+                                       total_drops++;
+                                       chalk_error( fifo );
+                               } else {
+                                       if( write( fd, mbuf->payload, mbuf->len ) < mbuf->len ) {                       // followed by the payload      
+                                               errors++;
+                                               chalk_error( fifo );
+                                       } else {
+                                               chalk_ok( fifo );
+                                               count++;
+                                               total++;
+                                       }
+                               }
+                       }
+               }
+
+               if( report ) {
+                       if( (now = time( NULL ) ) > next_report ) {
+                       rmr_sym_foreach_class( ctx->wr_hash, 0, wr_stats, &report );        // run endpoints in the active table
+                               fflush( stdout );
+
+                               fprintf( stdout, "[STAT] (mcl) total writes=%lld total drops=%lld; during last %ds writes=%ld drops=%ld errs=%ld errors\n", 
+                                       total, total_drops, report, count, drops, errors );
+                               next_report = now + report;
+                               count = 0;
+                               drops = 0;
+
+                               fflush( stdout );
+                       }
+               }
+       }
+}
+
+
diff --git a/src/sidecars/listener/mcl.h b/src/sidecars/listener/mcl.h
new file mode 100644 (file)
index 0000000..249e851
--- /dev/null
@@ -0,0 +1,57 @@
+// 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:       mcl.h
+       Abstract:       Header file for the mc listener library.
+       Date:           22 August 2019
+       Author:         E. Scott Daniels
+       
+*/
+
+#ifndef mcl_h
+#define mcl_h
+
+#include <rmr/rmr.h>
+#include <rmr/rmr_symtab.h>
+
+// ------- public constants and structs -------------------------------------------------
+
+#define MCL_LEN_SIZE 8         // number of bytes the length has in both short and extended header
+#define MCL_DELIM_SIZE 4       // number of bytes in extended header delimiter
+#define MCL_TSTAMP_SIZE        16      // number of bytes in extended header timestamp
+#define MCL_EXHDR_SIZE (MCL_DELIM_SIZE+MCL_LEN_SIZE+MCL_TSTAMP_SIZE)   // total size of extended header
+
+#define MCL_DELIM              "@MCL"          // delimeter to synch msgs in fifo
+
+#define MCL_TSTAMP_OFF (MCL_DELIM_SIZE+MCL_LEN_SIZE)                                   // offsets in header
+#define MCL_LEN_OFF            MCL_DELIM_SIZE
+
+#define MCL_NOWAIT     0       // do not wait for RMR route table to arrive
+#define MCL_WAIT       1       // block reader start until RMR route table is initialised
+
+//------------ prototypes --------------------------------------------------------------
+extern void mcl_fifo_fanout( void* ctx, int report, int long_hdrs );
+extern rmr_mbuf_t* mcl_get_msg( void* vctx, rmr_mbuf_t* msg, int timeout );
+extern void* mcl_mk_context( char* dir );
+extern int mcl_fifo_read1( void* vctx, int mtype, char* ubuf, int ublen, int long_hdr );
+extern int mcl_fifo_tsread1( void* vctx, int mtype, char* ubuf, int ublen, int long_hdr, char* timestamp );
+extern int mcl_set_sigh( );
+extern int mcl_start_listening( void* vctx, char* port, int wait4ready );
+
+#endif
diff --git a/src/sidecars/listener/mcl_dev.df b/src/sidecars/listener/mcl_dev.df
new file mode 100644 (file)
index 0000000..5ebf244
--- /dev/null
@@ -0,0 +1,51 @@
+# 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:       mcl_dev.df
+#      Abstract:       This is a docker build file which creates an image that can be used
+#                              for interactive development of the MC listener and related applications.
+#                              Building should be as simple as:
+#                                      docker build -f mcl_dev.df -t mcl_dev:latest .
+#
+#                              The image contains the NNG and RMR libraries and headers needed.  The 
+#                              MC listener source is NOT placed into the image. Rather the source should
+#                              be mounted as a volume when the container is created:
+#
+#                                      docker run --rm -it -v $PWD:/playpen/src -u <uid>:<gid> sh
+#                              (assuming that the source is in the current working directory).
+#                              Once the container is running, vi, make and git are all available.
+#      Date:           22 August 2019
+#      Author:         E. Scott Daniels
+#---------------------------------------------------------------------------------------
+
+FROM ubuntu:18.04
+RUN mkdir /playpen
+
+RUN apt-get update && apt-get install -y cmake gcc make vim git
+RUN apt-get install -y cmake g++ wget
+
+RUN mkdir /playpen/bin
+COPY build_dev_env.sh /playpen/bin/
+RUN bash /playpen/bin/build_dev_env.sh
+
+ENV LD_LIBRARY_PATH=/usr/local/lib;/usr/local/lib64
+ENV C_INCLUDE_PATH=/usr/local/include
+
+CMD [ "bash" ]
+
+
diff --git a/src/sidecars/listener/mcl_runtime.df b/src/sidecars/listener/mcl_runtime.df
new file mode 100644 (file)
index 0000000..e547929
--- /dev/null
@@ -0,0 +1,68 @@
+# 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:       mcl_runtime.df
+#      Abstract:       Docker build file which creaes a runtime image for the listener. 
+#                              Building should be as simple as:
+#                                      docker build -f mcl_runtime.f -t mc_listener:xx.xx .
+#
+#                              Running the image in a container:
+#                                      The fifo output directory needs to be mounted to the container
+#                                      e.g.  -v /exports/mcl/fifos:/tmp/mcl/fifos.  The internal
+#                                      directory can be supplied on the command line using -d path, but
+#                                      should not be needed.
+#
+#                                      The RMR listen port must be properly exposed to the container. The
+#                                      internal default is 4560, and can be changed with the -p port option
+#                                      on the command line (needed if running from docker-compose). Typical
+#                                      exposure might be  -p <host-port>:4560  on the docker run command.
+#
+#                                      The MC listener application does NOT need route table updates and thus
+#                                      there is NO need to expose the route table generator port on this
+#                                      container.
+#
+#      Date:           22 August 2019
+#      Author:         E. Scott Daniels
+
+
+FROM ubuntu:18.04 as buildenv
+RUN mkdir /playpen
+
+RUN apt-get update && apt-get install -y cmake gcc make git g++ wget
+
+RUN mkdir /playpen/bin /playpen/src
+COPY build_dev_env.sh /playpen/bin/
+COPY Makefile *.h *.c /playpen/src/
+
+ENV LD_LIBRARY_PATH=/usr/local/lib64:/usr/local/lib
+ENV C_INCLUDE_PATH=/usr/local/include
+RUN bash /playpen/bin/build_dev_env.sh
+RUN cd /playpen/src/; make -B mc_listener sender pipe_reader
+
+# -----  final, smaller image ----------------------------------
+FROM ubuntu:18.04
+
+RUN mkdir -p /playpen/bin
+COPY --from=buildenv /usr/local/lib/* /usr/local/lib/
+COPY --from=buildenv /playpen/src/mc_listener /playpen/src/sender /playpen/src/pipe_reader /playpen/bin/
+COPY verify.sh /playpen/bin
+
+ENV LD_LIBRARY_PATH=/usr/local/lib64:/usr/local/lib
+ENV C_INCLUDE_PATH=/usr/local/include
+
+CMD [ "/playpen/bin/mc_listener" ]
diff --git a/src/sidecars/listener/pipe_reader.c b/src/sidecars/listener/pipe_reader.c
new file mode 100644 (file)
index 0000000..58a11ff
--- /dev/null
@@ -0,0 +1,152 @@
+// 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:       pipe_reader.c
+       Abstract:       Read a single pipe which was associated with a message type.  This
+                               programme is primarily for verification or example of how to use the
+                               read1() function in the mc-listener library.
+
+       Date:           22 August 2019
+       Author:         E. Scott Daniels
+*/
+
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <string.h>
+
+
+#include "mcl.h"
+
+//---- support -----------------------------------------------------------------------------
+
+static void bad_arg( char* what ) {
+       fprintf( stderr, "[ERR] option is unrecognised or isn't followed by meaningful data: %s\n", what );
+}
+
+static void usage( char* argv0 ) {
+       fprintf( stderr, "usage: %s [-d fifo-dir] [-e] [-m msg-type] [-s]\n", argv0 );
+       fprintf( stderr, "   -d  dir (default is /tmp/mcl/fifos)\n" );
+       fprintf( stderr, "   -e  extended headers expected in FIFO data\n" );
+       fprintf( stderr, "   -m  msg-type (default is 0)\n" );
+       fprintf( stderr, "   -s  stats only mode\n" );
+}
+
+//------------------------------------------------------------------------------------------
+int main( int argc,  char** argv ) {
+       void*   ctx;                                                    // the mc listener library context
+       char*   dname = "/tmp/mcl/fifos";               // default directory where we open fifos
+       int             pidx = 1;                                               // parameter index
+       int             error = 0;
+       int             len;
+       int             mtype = 0;
+       char    buf[4096];
+       int             flush_often = 0;
+       int             long_hdrs = 0;                                  // if -e is on command line, we expect long headers
+       int             stats_only = 0;
+       char    timestamp[MCL_TSTAMP_SIZE];             // we'll get the timestamp from this
+       long    count = 0;
+       int             blabber = 0;
+
+       while( pidx < argc && argv[pidx][0] == '-' ) {                  // simple argument parsing (-x  or -x value)
+               switch( argv[pidx][1] ) {
+                       case 'd':
+                               if( pidx+1 < argc ) {
+                                       dname = strdup( argv[pidx+1] );
+                                       pidx++; 
+                               } else {
+                                       bad_arg( argv[pidx] ); 
+                                       error = 1;
+                               }       
+                               break;
+
+                       case 'e':
+                               long_hdrs = 1;
+                               break;
+
+                       case 'f':
+                               flush_often = 1;
+                               break;
+
+                       case 'm':
+                               if( pidx+1 < argc ) {
+                                       mtype = atoi( argv[pidx+1] );
+                                       pidx++; 
+                               } else {
+                                       bad_arg( argv[pidx] ); 
+                                       error = 1;
+                               }       
+                               break;
+
+                       case 's':
+                               stats_only = 1;
+                               break;
+
+                       case 'h':
+                       case '?':
+                               usage( argv[0] );
+                               exit( 0 );
+                               break;
+
+                       default:
+                               bad_arg( argv[pidx] );
+                               error = 1;
+                               break;
+               }
+
+               pidx++;
+       }
+
+       if( error ) {
+               usage( argv[0] );
+               exit( 1 );
+       }
+       
+       ctx = mcl_mk_context( dname );                  // initialise the library context
+       if( ctx == NULL ) {
+               fprintf( stderr, "[FAIL] couldn't initialise the mc listener library" );
+               exit( 1 );
+       }
+
+       while( 1 ) {
+               len = mcl_fifo_tsread1( ctx, mtype, buf, sizeof( buf ) -1, long_hdrs, timestamp );
+               if( len > 0 ) {
+                       if( stats_only ) {
+                               if( time( NULL ) > blabber ) {
+                                       fprintf( stdout, "[%d] %ld messages received\n", mtype, count );
+                                       blabber = time( NULL ) + 2;
+                               }
+                       } else {
+                               buf[len] = 0;
+                               fprintf( stdout, "[%d] ts=%s count=%ld len=%d  msg=%s\n",  mtype, timestamp,  count, len, buf );
+                               if( flush_often ) {
+                                       fflush( stdout );
+                               }
+                       }
+
+                       count++;
+               }
+       }
+
+       fprintf( stderr, "[INFO] mc listener is finished.\n" );
+}
+
diff --git a/src/sidecars/listener/sender.c b/src/sidecars/listener/sender.c
new file mode 100644 (file)
index 0000000..704ae15
--- /dev/null
@@ -0,0 +1,128 @@
+// 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:       sender.c
+       Abstract:       Very simple test sender. Sends messages with a given delay between each.
+                               The sender also uses epoll_wait() to ensure that any received messages
+                               don't clog the queue.
+
+       Parms:          The following positional parameters are recognised on the command line:
+                                       [listen_port [delay [stats-freq] [msg-type]]]]
+
+                                       Defaults:
+                                               listen_port 43086 
+                                               delay (mu-sec) 1000000 (1 sec)
+                                               stats-freq 10
+                                               msg-type 0
+
+       Date:           1 April 2019
+*/
+
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/epoll.h>
+#include <time.h>
+
+#include <rmr/rmr.h>
+
+int main( int argc, char** argv ) {
+       void* mrc;                                                      //msg router context
+       struct epoll_event events[1];                   // list of events to give to epoll
+       struct epoll_event epe;                 // event definition for event to listen to
+       int     ep_fd = -1;                                             // epoll's file des (given to epoll_wait)
+       int rcv_fd;                                                     // file des that NNG tickles -- give this to epoll to listen on
+       int nready;                                                             // number of events ready for receive
+       rmr_mbuf_t*             sbuf;                                   // send buffer
+       rmr_mbuf_t*             rbuf;                                   // received buffer
+       int     count = 0;
+       int     rcvd_count = 0;
+       char*   listen_port = "43086";
+       int             delay = 1000000;                                                // mu-sec delay between messages
+       int             mtype = 0;
+       int             stats_freq = 100;
+
+       if( argc > 1 ) {
+               listen_port = argv[1];
+       }
+       if( argc > 2 ) {
+               delay = atoi( argv[2] );
+       }
+       if( argc > 3 ) {
+               mtype = atoi( argv[3] );
+       }
+
+       fprintf( stderr, "<DEMO> listen port: %s; mtype: %d; delay: %d\n", listen_port, mtype, delay );
+
+       if( (mrc = rmr_init( listen_port, 1400, RMRFL_NONE )) == NULL ) {
+               fprintf( stderr, "<DEMO> unable to initialise RMr\n" );
+               exit( 1 );
+       }
+
+       rcv_fd = rmr_get_rcvfd( mrc );                                  // set up epoll things, start by getting the FD from MRr
+       if( rcv_fd < 0 ) {
+               fprintf( stderr, "<DEMO> unable to set up polling fd\n" );
+               exit( 1 );
+       }
+       if( (ep_fd = epoll_create1( 0 )) < 0 ) {
+               fprintf( stderr, "[FAIL] unable to create epoll fd: %d\n", errno );
+               exit( 1 );
+       }
+       epe.events = EPOLLIN;
+       epe.data.fd = rcv_fd;
+
+       if( epoll_ctl( ep_fd, EPOLL_CTL_ADD, rcv_fd, &epe ) != 0 )  {
+               fprintf( stderr, "[FAIL] epoll_ctl status not 0 : %s\n", strerror( errno ) );
+               exit( 1 );
+       }
+
+       sbuf = rmr_alloc_msg( mrc, 256 );       // alloc first send buffer; subsequent buffers allcoated on send
+       rbuf = NULL;                                            // don't need to alloc receive buffer
+
+       while( ! rmr_ready( mrc ) ) {           // must have a route table before we can send; wait til RMr say it has one
+               sleep( 1 );
+       }
+       fprintf( stderr, "<DEMO> rmr is ready\n" );
+       
+
+       while( 1 ) {                                                                            // send messages until the cows come home
+               snprintf( sbuf->payload, 200, "count=%d received= %d ts=%lld %d stand up and cheer!\n",         // create the payload
+                       count, rcvd_count, (long long) time( NULL ), rand() );
+
+               sbuf->mtype = mtype;                                                    // fill in the message bits
+               sbuf->len =  strlen( sbuf->payload ) + 1;               // our receiver likely wants a nice acsii-z string
+               sbuf->state = 0;
+               sbuf = rmr_send_msg( mrc, sbuf );                               // send it (send returns an empty payload on success, or the original payload on fail/retry)
+               while( sbuf->state == RMR_ERR_RETRY ) {                 // soft failure (device busy?) retry
+                       sbuf = rmr_send_msg( mrc, sbuf );                       // retry send until it's good (simple test; real programmes should do better)
+               }
+               count++;
+               fprintf( stderr, "<SNDR> sent message\n" );
+
+               usleep( delay );
+               mtype++;
+               if( mtype > 6 ) {
+                       mtype = 1;
+               }
+       }
+}
+
diff --git a/src/sidecars/listener/unit_test.c b/src/sidecars/listener/unit_test.c
new file mode 100644 (file)
index 0000000..79bc43f
--- /dev/null
@@ -0,0 +1,77 @@
+// 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:       unit_test.c
+       Abstract:       Basic unit tests for the mc listener.
+       Date:           22 August 2019
+       Author:         E. Scott Daniels
+*/
+
+// this/these are what we are testing; include them directly
+#include "mcl.c"
+
+/*
+       Parms:  [fifo-dir-name]
+*/
+int main( int argc,  char** argv ) {
+       void*   ctx;
+       int             errors;
+       char*   dname = "/tmp/fifos";
+       char*   port = "4560";
+       int             fd;
+       int             fd2;
+
+       if( argc > 1 ) {
+               dname = argv[1];
+       }
+       
+       ctx = mcl_mk_context( dname );
+       if( ctx == NULL ) {
+               fprintf( stderr, "[FAIL] couldn't make context" );
+               exit( 1 );
+       }
+
+       mcl_set_sigh();                 // prevent colobber from broken pipe
+
+       fd = suss_fifo( ctx, 101, 1 );                          // should open the file for writing and return the fdes
+       if( fd < 0 ) {
+               fprintf( stderr, "[FAIL] suss_fifo did not return a valid fd\n" );
+               errors++;
+       }
+
+       fd2= suss_fifo( ctx, 101, 0 );                          // should open the file file for reading and return a different fd
+       if( fd < 0 ) {
+               fprintf( stderr, "[FAIL] suss_fifo did not return a valid fd\n" );
+               errors++;
+       }
+       if( fd == fd2 ) {
+               fprintf( stderr, "[FAIL] reading and writing fifo file descriptors expected to differ; both were %d\n", fd );
+               errors++;
+       }
+
+       mcl_start_listening( ctx, port, 0 );                    // start the listener, no waiting for rt since we don't send
+
+       if( ! errors ) {
+               fprintf( stderr, "[PASS] all tests look peachy\n" );
+       }
+
+       return errors != 0;
+}
+
diff --git a/src/sidecars/listener/verify.sh b/src/sidecars/listener/verify.sh
new file mode 100755 (executable)
index 0000000..3183e8f
--- /dev/null
@@ -0,0 +1,156 @@
+#!/usr/bin/env bash
+# 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:    verify.sh
+# Abstract: Simple script to attempt to verify that the mc_listener is
+#                      capable of running. This will start a listener and a sender
+#                      and then will cat a few lines from one of the FIFOs.
+#                      This script is designed to run using the geneated runtime
+#                      image; in other words, it expects to find the binaries
+#                      in /playpen/bin.
+#
+# Date:                26 August 2019
+# Author:      E. Scott Daniels
+# ----------------------------------------------------------------------
+
+# run sender at a 2 msg/sec rate (500000 musec delay)
+# sender sends msg types 0-6 and the route table in /tmp
+# will direct them to the listener. We also need to switch
+# the RT listen port so there is no collision with the listen
+# preocess.
+#
+function run_sender {
+       echo "starting sender"
+       RMR_SEED_RT=/tmp/local.rt RMR_RTG_SVC=9989 /playpen/bin/sender 43086 10000 >/tmp/sender.log 2>&1 &
+       spid=$!
+       sleep 10
+
+       echo "stopping sender"
+       kill -15 $spid
+}
+
+function run_listener {
+       echo "starting listener"
+       /playpen/bin/mc_listener $ext_hdr -r 1 -d $fifo_dir >/tmp/listen.log 2>&1 &
+       lpid=$!
+
+       sleep 15
+       echo "stopping listener"
+       kill -15 $lpid
+}
+
+# run a pipe reader for one message type
+function run_pr {
+       echo "starting pipe reader $1"
+       /playpen/bin/pipe_reader $ext_hdr -m $1 -d $fifo_dir  >/tmp/pr.$1.log 2>&1 &
+       #/playpen/bin/pipe_reader -m $1 -d $fifo_dir & # >/tmp/pr.$1.log 2>&1 
+       typeset prpid=$!
+       
+       sleep 12
+       echo "stopping pipe reader $1"
+       kill -1 $prpid
+}
+
+# generate a dummy route table that the sender needs
+function gen_rt {
+       cat <<endKat >/tmp/local.rt
+       newrt|start
+       mse | 0 | -1 | localhost:4560   
+       mse | 1 | -1 | localhost:4560   
+       mse | 2 | -1 | localhost:4560   
+       mse | 3 | -1 | localhost:4560   
+       mse | 4 | -1 | localhost:4560   
+       mse | 5 | -1 | localhost:4560   
+       mse | 6 | -1 | localhost:4560   
+       newrt|end
+endKat
+}
+
+# ---- run everything ---------------------------------------------------
+
+ext_hdr="-e"                           # run with extended header enabled
+fifo_dir=/tmp/fifos
+mkdir -p $fifo_dir                     # redirect fifos so we don't depend on mount
+
+gen_rt                                         # generate a dummy route table
+run_listener &
+sleep 4
+
+for p in 0 1 2 3 4 5 6
+do
+       run_pr $p &
+done
+sleep 1
+run_sender &
+
+sleep 20                       # long enough for all functions to finish w/o having to risk a wait hanging
+echo "all functions stopped; looking at logs"
+
+# ---------- validation -------------------------------------------------
+
+errors=0
+
+# logs should be > 0 in size
+echo "----- logs ---------"
+ls -al /tmp/*.log
+
+# pipe reader log files 1-6 should have 'stand up and cheer' messages
+# pipe reader log for MT 0 will likley be empty as sender sends only
+# one of those and buffer not likely flushed. So, we only check 1-6
+#
+for l in 1 2 3 4 5 6
+do
+       if [[ ! -s /tmp/pr.$l.log ]]
+       then
+               echo "[FAIL] log $l was empty"
+               (( errors++ ))
+       else
+               if ! grep -q -i "stand up and cheer" /tmp/pr.$l.log
+               then
+                       echo "[FAIL] pipe reader log did not have any valid messages: /tmp/pr.$l.log"
+                       (( errors++ ))
+               fi
+       fi
+done
+
+if (( ! errors )) 
+then
+       echo "[OK]    All logs seem good"
+fi
+
+nfifos=$( ls /tmp/fifos/MT_* | wc -l )
+if (( nfifos < 7 ))
+then
+       echo "didn't find enough fifos"
+       ls -al /tmp/fifos/*
+       (( errors++ ))
+else
+       echo "[OK]    Found expected fifos"
+fi
+
+if (( errors ))
+then
+       echo "[FAIL] $errors errors noticed"
+else
+       echo "[PASS]"
+fi
+