Initial repo population

Signed-off-by: E. Scott Daniels <daniels@research.att.com>
Change-Id: I2652015adb5c77d0f13a75f9651e249cd0d9634d
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..14f0ba9
--- /dev/null
+++ b/Dockerfile
@@ -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
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
index 0000000..57e70a0
--- /dev/null
+++ b/ci/Dockerfile
@@ -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
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
index 0000000..563ee11
--- /dev/null
+++ b/ci/stats.ksh
@@ -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
index 0000000..56e9ce4
--- /dev/null
+++ b/container-tag.yaml
@@ -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
index 0000000..874c63c
--- /dev/null
+++ b/src/sidecars/listener/.gitignore
@@ -0,0 +1,2 @@
+*.o
+
diff --git a/src/sidecars/listener/Makefile b/src/sidecars/listener/Makefile
new file mode 100644
index 0000000..36f7113
--- /dev/null
+++ b/src/sidecars/listener/Makefile
@@ -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
index 0000000..4b2da47
--- /dev/null
+++ b/src/sidecars/listener/README
@@ -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
index 0000000..d0b241a
--- /dev/null
+++ b/src/sidecars/listener/build_dev_env.sh
@@ -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
index 0000000..4fe1219
--- /dev/null
+++ b/src/sidecars/listener/build_images.sh
@@ -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
index 0000000..6b8da45
--- /dev/null
+++ b/src/sidecars/listener/mc_listener.c
@@ -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
index 0000000..06ceb82
--- /dev/null
+++ b/src/sidecars/listener/mcl.c
@@ -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
index 0000000..249e851
--- /dev/null
+++ b/src/sidecars/listener/mcl.h
@@ -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
index 0000000..5ebf244
--- /dev/null
+++ b/src/sidecars/listener/mcl_dev.df
@@ -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
index 0000000..e547929
--- /dev/null
+++ b/src/sidecars/listener/mcl_runtime.df
@@ -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
index 0000000..58a11ff
--- /dev/null
+++ b/src/sidecars/listener/pipe_reader.c
@@ -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
index 0000000..704ae15
--- /dev/null
+++ b/src/sidecars/listener/sender.c
@@ -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
index 0000000..79bc43f
--- /dev/null
+++ b/src/sidecars/listener/unit_test.c
@@ -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
index 0000000..3183e8f
--- /dev/null
+++ b/src/sidecars/listener/verify.sh
@@ -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
+