Add support for config file parsing and watching

This change introduces the ability to load and parse
the json xapp descriptor (config) file. It also provides
the ability for the xAPP to register a callback function
which is executed when the descriptor is changed.

Issue-ID: RIC-428

Signed-off-by: E. Scott Daniels <daniels@research.att.com>
Change-Id: I7a1147aa8055599ef4b36ab17960e32ccb5b741f
diff --git a/src/config/CMakeLists.txt b/src/config/CMakeLists.txt
new file mode 100644
index 0000000..d56b24d
--- /dev/null
+++ b/src/config/CMakeLists.txt
@@ -0,0 +1,42 @@
+# vim: sw=4 ts=4 noet:
+#
+#==================================================================================
+#   Copyright (c) 2020 Nokia
+#   Copyright (c) 2020 AT&T Intellectual Property.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+#==================================================================================
+#
+
+
+# For clarity: this generates object, not a lib as the CM command implies.
+#
+add_library( config_objects OBJECT
+	config.cpp
+	config_cb.cpp
+)
+
+target_include_directories (config_objects PUBLIC
+	$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
+	$<INSTALL_INTERFACE:include>
+	PRIVATE src)
+
+# header files should go into .../include/xfcpp/
+if( DEV_PKG )
+	install( FILES
+		config.hpp
+		config_cb.hpp
+		DESTINATION ${install_inc}
+	)
+endif()
+
diff --git a/src/config/config.cpp b/src/config/config.cpp
new file mode 100644
index 0000000..16eb421
--- /dev/null
+++ b/src/config/config.cpp
@@ -0,0 +1,371 @@
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+    Copyright (c) 2020 AT&T Intellectual Property.
+    Copyright (c) 2020 Nokia
+
+   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:	config.cpp
+    Abstract:	Support for reading config json.
+
+				The config structure allows simplified parsing of the json and
+				easy access to the things we belive all xAPPs will use (port
+				digging form the named "interface" and control settings). This
+				also supports the watching on the file and driving the user
+				callback when the config file appears to have changed (write
+				close on the file).
+
+				Accessing information from the json is serialised (mutex) as
+				with the random nature of file updates, we must ensure that
+				we don't change the json as we're trying to read from it. Locking
+				should be transparent to the user xAPP.
+
+    Date:       27 July 2020
+    Author:     E. Scott Daniels
+*/
+
+#include <errno.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <sys/inotify.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <iostream>
+#include <sstream>
+#include <fstream>
+#include <thread>
+#include <memory>
+
+#include "jhash.hpp"
+#include "config.hpp"
+#include "config_cb.hpp"
+
+namespace xapp {
+
+
+// ----- private things --------
+/*
+	Notification listener. This function is started in a thread and listens for
+	changes to the config file. When it sees an interesting change (write close)
+	to the file, then it will read the new set of json, parse it, and drive the
+	user callback.
+
+	We must watch the directory containing the file as if the file is edited and
+	replaced it's likely saved with a different referencing inode. We'd see the
+	first change, but not any subsequent changes as the inotify is based on inodes
+	and not directory entries.
+*/
+void xapp::Config::Listener( ) {
+	struct inotify_event*	ie;		// event that popped
+	int ifd;						// the inotify file des
+	int	wfd;						// the watched file des
+	int	n;
+	char	rbuf[4096];				// large read buffer as the event is var len
+	char*	dname;					// directory name
+	char*	bname;					// basename
+	char*	tok;
+
+	ifd = inotify_init1( 0 );		// initialise watcher setting blocking read (no option)
+	if( ifd < 0 ) {
+		fprintf( stderr, "<XFCPP> ### ERR ### unable to initialise file watch %s\n", strerror( errno ) );
+		return;
+	}
+
+	dname = strdup( fname.c_str() );					// defrock the file name into dir and basename
+	if( (tok = strrchr( dname, '/' )) != NULL ) {
+		*tok = 0;
+		bname = strdup( tok+1 );
+	} else {
+		free( dname );
+		dname = strdup( "." );
+		bname = strdup( fname.c_str() );
+	}
+
+	wfd = inotify_add_watch( ifd, (char *) dname, IN_MOVED_TO | IN_CLOSE_WRITE );		// we only care about close write changes
+
+	if( wfd < 0 ) {
+		fprintf( stderr, "<XFCPP> ### ERR ### unable to add watch on config file %s: %s\n", fname.c_str(), strerror( errno ) );
+		return;
+	}
+
+	while( true ) {
+		n = read( ifd, rbuf, sizeof( rbuf ) );				// read the event
+		if( n < 0  ) {
+			if( errno == EAGAIN ) {
+				continue;
+			} else {
+				fprintf( stderr, "<XFCPP ### CRIT ### config listener read err: %s\n", strerror( errno ) );
+				return;
+			}
+		}
+
+		ie = (inotify_event *) rbuf;
+		if( ie->len > 0 && strcmp( bname, ie->name ) == 0  ) {
+			// TODO: lock
+			auto njh = jparse( fname );							// reparse the file
+			// TODO: unlock
+
+			if( njh != NULL && cb != NULL ) {				// good parse, save and drive user callback
+				jh = njh;
+				cb->Drive_cb( *this, user_cb_data );
+			}
+		}
+	}
+}
+
+
+/*
+	Read a file containing json and parse into a framework Jhash.
+
+	Using C i/o will speed this up, but I can't imagine that we need
+	speed reading the config file once in a while.
+	The file read comes from a stack overflow example:
+		stackoverflow.com/questions/2912520/read-file-contents-into-a-string-in-c
+*/
+std::shared_ptr<xapp::Jhash> xapp::Config::jparse( std::string ufname ) {
+	fname = ufname;
+
+	std::ifstream ifs( fname );
+	std::string st( (std::istreambuf_iterator<char>( ifs ) ), (std::istreambuf_iterator<char>() ) );
+
+	auto new_jh = std::shared_ptr<xapp::Jhash>( new xapp::Jhash( st.c_str() ) );
+	return  new_jh->Parse_errors() ? NULL : new_jh;
+}
+
+/*
+	Read the configuration file from what we find as the filename in the environment (assumed
+	to be referenced by $XAPP_DESCRIPTOR_PATH/config-file.json. If the env var is not
+	defined we assume ./.  The data is then parsed with the assumption that it's json.
+
+	The actual meaning of the environment variable is confusing. The name is "path" which
+	should mean that this is the directory in which the config file lives, but the examples
+	elsewhere suggest that this is a filename (either fully qualified or relative). For now
+	we will assume that it's a file name, though we could add some intelligence to determine
+	if it's a directory name or not if it comes to it.
+*/
+std::shared_ptr<xapp::Jhash> xapp::Config::jparse( ) {
+	char*	data;
+
+	if( (data = getenv( (char *) "XAPP_DESCRIPTOR_PATH" )) == NULL ) {
+		data =  (char *) "./config-file.json";
+	}
+
+	return jparse( std::string( data ) );
+}
+
+// --------------------- construction, destruction -------------------------------------------
+
+/*
+	By default expect to find XAPP_DESCRIPTOR_PATH in the environment and assume that it
+	is the directory name where we can find config-file.json. The build process will
+	read and parse the json allowing the user xAPP to invoke the supplied  "obvious"
+	functions to retrieve data.  If there is something in an xAPP's config that isn't
+	standard, it can get the raw Jhash object and go at it directly. The idea is that
+	the common things should be fairly painless to extract from the json goop.
+*/
+xapp::Config::Config() :
+	jh( jparse() ),
+	listener( NULL )
+{ /* empty body */ }
+
+/*
+	Similar, except that it allows the xAPP to supply the filename (testing?)
+*/
+xapp::Config::Config( std::string fname) :
+	jh( jparse( fname ) ),
+	listener( NULL )
+{ /* empty body */ }
+
+
+/*
+	Read and return the raw file blob as a single string. User can parse, or do
+	whatever they need (allows non-json things if necessary).
+*/
+std::string xapp::Config::Get_contents( ) {
+	std::string rv = "";
+
+	if( ! fname.empty() ) {
+		std::ifstream ifs( fname );
+		std::string st( (std::istreambuf_iterator<char>( ifs ) ), (std::istreambuf_iterator<char>() ) );
+		rv = st;
+	}
+
+	return rv;
+}
+
+// ----- convience function for things we think an xAPP will likely need to pull from the config
+
+
+/*
+	Suss out the port for the named "interface". The interface is likely the application
+	name.
+*/
+std::string xapp::Config::Get_port( std::string name ) {
+	int i;
+	int	nele = 0;
+	double value;
+	std::string rv = "";		// result value
+	std::string pname;			// element port name in the json
+
+	if( jh == NULL ) {
+		return rv;
+	}
+
+	jh->Unset_blob();
+	if( jh->Set_blob( (char *) "messaging" ) ) {
+		nele = jh->Array_len( (char *) "ports" );
+		for( i = 0; i < nele; i++ ) {
+			if( jh->Set_blob_ele( (char *) "ports", i ) ) {
+				pname = jh->String( (char *) "name" );
+				if( pname.compare( name ) == 0 ) {				// this element matches the name passed in
+					value = jh->Value( (char *) "port" );
+					rv = std::to_string( (int) value );
+					jh->Unset_blob( );							// leave hash in a known state
+					return rv;
+				}
+			}
+
+			jh->Unset_blob( );								// Jhash requires bump to root, and array reselct to move to next ele
+			jh->Set_blob( (char *) "messaging" );
+		}
+	}
+
+	jh->Unset_blob();
+	return rv;
+}
+
+/*
+	Suss out the named string from the controls object. If the resulting value is
+	missing or "", then the default is returned.
+*/
+std::string xapp::Config::Get_control_str( std::string name, std::string defval ) {
+	std::string value;
+	std::string rv;				// result value
+
+	rv = defval;
+	if( jh == NULL ) {
+		return rv;
+	}
+
+	jh->Unset_blob();
+	if( jh->Set_blob( (char *) "controls" ) ) {
+		if( jh->Exists( name.c_str() ) )  {
+			value = jh->String( name.c_str() );
+			if( value.compare( "" ) != 0 ) {
+				rv = value;
+			}
+		}
+	}
+
+	jh->Unset_blob();
+	return rv;
+}
+
+/*
+	Convenience funciton without default. "" returned if not found.
+	No default value; returns "" if not set.
+*/
+std::string xapp::Config::Get_control_str( std::string name ) {
+	return Get_control_str( name, "" );
+}
+
+/*
+	Suss out the named field from the controls object with the assumption that it is a boolean.
+	If the resulting value is missing then the defval is used.
+*/
+bool xapp::Config::Get_control_bool( std::string name, bool defval ) {
+	bool value;
+	bool rv;				// result value
+
+	rv = defval;
+	if( jh == NULL ) {
+		return rv;
+	}
+
+	jh->Unset_blob();
+	if( jh->Set_blob( (char *) "controls" ) ) {
+		if( jh->Exists( name.c_str() ) )  {
+			rv = jh->Bool( name.c_str() );
+		}
+	}
+
+	jh->Unset_blob();
+	return rv;
+}
+
+
+/*
+	Convenience function without default.
+*/
+bool xapp::Config::Get_control_bool( std::string name ) {
+	return Get_control_bool( name, false );
+}
+
+
+/*
+	Suss out the named field from the controls object with the assumption that it is a value (float/int).
+	If the resulting value is missing then the defval is used.
+*/
+double xapp::Config::Get_control_value( std::string name, double defval ) {
+	double value;
+
+	auto rv = defval;				// return value; set to default
+	if( jh == NULL ) {
+		return rv;
+	}
+
+	jh->Unset_blob();
+	if( jh->Set_blob( (char *) "controls" ) ) {
+		if( jh->Exists( name.c_str() ) )  {
+			rv = jh->Value( name.c_str() );
+		}
+	}
+
+	jh->Unset_blob();
+	return rv;
+}
+
+
+/*
+	Convenience function. If value is undefined, then 0 is returned.
+*/
+double xapp::Config::Get_control_value( std::string name ) {
+	return Get_control_value( name, 0.0 );
+}
+
+
+// ---- notification support ---------------------------------------------------------------
+
+
+/*
+	Accept the user's notification function, and data that it needs (pointer to
+	something unknown), and stash that as a callback.
+
+	The fact that the user xAPP registers a callback also triggers the creation
+	of a thread to listen for changes on the config file.
+*/
+void xapp::Config::Set_callback( notify_callback usr_func, void* usr_data ) {
+	cb = std::unique_ptr<Config_cb>( new Config_cb( usr_func, usr_data ) );
+	user_cb_data = usr_data;
+
+	if( listener == NULL ) {				// start thread if needed
+		listener = new std::thread( &xapp::Config::Listener, this );
+	}
+}
+
+} // namespace
diff --git a/src/config/config.hpp b/src/config/config.hpp
new file mode 100644
index 0000000..5dac6a3
--- /dev/null
+++ b/src/config/config.hpp
@@ -0,0 +1,83 @@
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+    Copyright (c) 2020 AT&T Intellectual Property.
+    Copyright (c) 2020 Nokia
+
+   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:	config.
+    Abstract:	Header for the config reader/watcher class.
+
+    Date:       27 July 2020
+    Author:     E. Scott Daniels
+*/
+
+#ifndef XAPP_CONFIG_HPP
+#define XAPP_CONFIG_HPP
+
+
+#include <iostream>
+#include <thread>
+
+#include "jhash.hpp"
+#include "config.hpp"
+#include "config_cb.hpp"
+
+namespace xapp {
+
+#define MAX_PFNAME	(4096 + 256)		// max path name and max filname + nil for buffer allocation
+
+class Config {
+	std::string	fname;				// the file name that we'll listen to
+	std::thread* listener;			// listener thread info
+
+	std::shared_ptr<Jhash>	jh;		// the currently parsed json from the config
+	std::unique_ptr<Config_cb> cb;	// info needed to drive user code when config change noticed
+	void*	user_cb_data;			// data that the caller wants passed on notification callback
+
+	// -----------------------------------------------------------------------
+	private:
+		std::shared_ptr<xapp::Jhash>  jparse( std::string fname );
+		std::shared_ptr<xapp::Jhash>  jparse( );
+		void Listener( );
+
+	public:
+		Config();						// builders
+		Config( std::string fname);
+
+		bool Get_control_bool( std::string name, bool defval );
+		bool Get_control_bool( std::string name );
+
+		std::string Get_contents( );
+
+		std::string Get_control_str( std::string name, std::string defval );
+		std::string Get_control_str( std::string name );
+
+		double Get_control_value( std::string name, double defval );
+		double Get_control_value( std::string name );
+
+		std::string Get_port( std::string name );
+
+		void Set_callback( notify_callback usr_func, void* usr_data );
+};
+
+
+} // namespace
+
+
+
+#endif
diff --git a/src/config/config_cb.cpp b/src/config/config_cb.cpp
new file mode 100644
index 0000000..309ddba
--- /dev/null
+++ b/src/config/config_cb.cpp
@@ -0,0 +1,62 @@
+
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+	Copyright (c) 2020 Nokia
+	Copyright (c) 2020 AT&T Intellectual Property.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+/*
+	Mnemonic:	notify_cb.cpp
+	Abstract:	Notification drivers.
+
+	Date:		27 July 2020
+	Author:		E. Scott Daniels
+*/
+
+//#include <cstdlib>
+
+#include "config.hpp"
+#include "config_cb.hpp"
+
+namespace xapp {
+
+/*
+	Builder.
+*/
+Config_cb::Config_cb( notify_callback ufun, void* udata ) :
+		user_fun( ufun ),
+		udata( udata )
+{ /* empty body */  }
+
+
+/*
+	there is nothing to be done from a destruction perspective, so no
+	destruction function needed at the moment.
+*/
+
+/*
+	Drive_cb will invoke the callback and pass along the stuff passed here.
+*/
+void xapp::Config_cb::Drive_cb( xapp::Config& c, void* udata ) {
+	if( user_fun != NULL ) {
+		user_fun( c, udata );
+	}
+}
+
+
+
+} // namespace
diff --git a/src/config/config_cb.hpp b/src/config/config_cb.hpp
new file mode 100644
index 0000000..f94d129
--- /dev/null
+++ b/src/config/config_cb.hpp
@@ -0,0 +1,64 @@
+
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+	Copyright (c) 2020 Nokia
+	Copyright (c) 2020 AT&T Intellectual Property.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+/*
+	Mnemonic:	callback.hpp
+	Abstract:	Manages config notification callback data and such.
+				This is a bit of over kill because, unlike the message
+				receipt callbacks, there is only one potential callback
+				for the config. We could manage this inside of the conf
+				class, but if there is ever the need to have multiple
+				callbacks, the base is set.
+
+	Date:		27 July 2020
+	Author:		E. Scott Daniels
+*/
+
+
+#ifndef _XAPP_CONF_CB_HPP
+#define _XAPP_CONF_CB_HPP
+
+#include <memory>
+
+namespace xapp {
+
+class Config;
+
+/*
+	Describes the user function that we will invoke
+*/
+typedef void(*notify_callback)( xapp::Config& c, void* user_data );
+
+class Config_cb {
+
+	private:
+		notify_callback user_fun;
+		void*	udata;									// user data
+
+	public:
+		Config_cb( notify_callback cbfun, void* user_data );		// builder
+		void Drive_cb( xapp::Config& c, void* udata );				// invoker
+};
+
+} // namespace
+
+#endif
+