Add json support 79/4279/7
authorE. Scott Daniels <daniels@research.att.com>
Mon, 29 Jun 2020 19:39:57 +0000 (15:39 -0400)
committerE. Scott Daniels <daniels@research.att.com>
Tue, 30 Jun 2020 18:58:31 +0000 (14:58 -0400)
The munchkin xAPP needs lightweight json, as might other xAPPs,
so it makes sense to do the work onece and add it to the framework.

Issue-ID: RIC-420

Signed-off-by: E. Scott Daniels <daniels@research.att.com>
Change-Id: I20ea999ba77566d1404f0d69be0f63669943236e

27 files changed:
.gitmodules [new file with mode: 0644]
CHANGES
CMakeLists.txt
doc/src/lib/rst.im
doc/src/lib/setup.im
doc/src/rtd/rel-notes.rst
doc/src/rtd/rel-notes.xfm
doc/src/user/.gitignore
doc/src/user/Makefile
doc/src/user/cpp_frame.im
doc/src/user/jhash.im [new file with mode: 0644]
doc/src/user/user_guide.xfm
docs/rel-notes.rst
docs/user-guide.rst
ext/jsmn [new submodule]
src/json/CMakeLists.txt [new file with mode: 0644]
src/json/README [new file with mode: 0644]
src/json/jhash.cpp [new file with mode: 0644]
src/json/jhash.hpp [new file with mode: 0644]
src/json/jwrapper.c [new file with mode: 0644]
src/json/jwrapper.h [new file with mode: 0644]
test/Makefile
test/jhash_test.cpp [new file with mode: 0644]
test/scrub_gcov.sh
test/test.json [new file with mode: 0644]
test/unit_test.sh
test/ut_support.cpp [new file with mode: 0644]

diff --git a/.gitmodules b/.gitmodules
new file mode 100644 (file)
index 0000000..a88d7ab
--- /dev/null
@@ -0,0 +1,3 @@
+[submodule "ext/jsmn"]
+       path = ext/jsmn
+       url = https://github.com/zserge/jsmn
diff --git a/CHANGES b/CHANGES
index 74ab3b7..d7bd724 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,9 +1,12 @@
 # This file contains a brief summary of each version bump.
 
+2020 June 29; version 1.2.0
+       Add support for json parsing
+
 2020 June 26; version 1.1.0
        Change the port type in constructors to indicate "const"
 
-       Version bump to 1.1.0 to allow patches to bronze code to 
+       Version bump to 1.1.0 to allow patches to bronze code to
        continue to be done on the 1.0.* level.
 
 2020 April 28; version 1.0.0
index eaeef37..9ae759e 100644 (file)
@@ -29,7 +29,7 @@ project( ricxfcpp )
 cmake_minimum_required( VERSION 3.5 )
 
 set( major_version "1" )               # should be automatically populated from git tag later, but until CI process sets a tag we use this
-set( minor_version "1" )
+set( minor_version "2" )
 set( patch_level "0" )
 
 set( install_root "${CMAKE_INSTALL_PREFIX}" )
@@ -153,8 +153,7 @@ endif()
 # but Cmake insists on having these exist when we add them to include directories to
 # enable code to find them after we build them.
 #
-include_directories( "${CMAKE_CURRENT_SOURCE_DIR}/src/messaging" )
-
+include_directories( "${CMAKE_CURRENT_SOURCE_DIR}/src/messaging;${CMAKE_CURRENT_SOURCE_DIR}/src/json;${CMAKE_CURRENT_SOURCE_DIR}/ext/jsmn" )
 
 # Compiler flags
 #
@@ -170,31 +169,43 @@ else()
 endif()
 unset( GPROF CACHE )                                   # we don't want this to persist
 
+
+# --------------------- external building --------------------------------------------------------
+execute_process( COMMAND  git submodule update --init -- ext/jsmn
+                WORKING_DIRECTORY  ${CMAKE_CURRENT_SOURCE_DIR}
+        )
+
+
+# --------------------- framework building --------------------------------------------------------
 # Include modules
+add_subdirectory( src/json )
 add_subdirectory( src/messaging )
 add_subdirectory( src/xapp )
 #add_subdirectory( doc )                               # this will auto skip if {X}fm is not available
 
-
 # shared and static libraries are built from the same object files.
 #
-add_library( ricxfcpp_shared SHARED "$<TARGET_OBJECTS:message_objects>;$<TARGET_OBJECTS:xapp_objects>" )
+add_library( ricxfcpp_shared SHARED
+       "$<TARGET_OBJECTS:message_objects>;$<TARGET_OBJECTS:json_objects>;$<TARGET_OBJECTS:xapp_objects>"
+)
 set_target_properties( ricxfcpp_shared
        PROPERTIES
        OUTPUT_NAME "ricxfcpp"
        SOVERSION ${major_version}
-       VERSION ${major_version}.${minor_version}.${patch_level} 
+       VERSION ${major_version}.${minor_version}.${patch_level}
 )
 target_include_directories( ricxfcpp_shared PUBLIC "src/messenger" "src/xapp" )
 
 # we only build/export the static archive (.a) if generating a dev package
 if( DEV_PKG )
-       add_library( ricxfcpp_static STATIC "$<TARGET_OBJECTS:message_objects>;$<TARGET_OBJECTS:xapp_objects>" )
+       add_library( ricxfcpp_static STATIC
+               "$<TARGET_OBJECTS:message_objects>;$<TARGET_OBJECTS:json_objects>;$<TARGET_OBJECTS:xapp_objects>"
+       )
        set_target_properties( ricxfcpp_static
                PROPERTIES
                OUTPUT_NAME "ricxfcpp"
                SOVERSION ${major_version}
-               VERSION ${major_version}.${minor_version}.${patch_level} 
+               VERSION ${major_version}.${minor_version}.${patch_level}
        )
        target_include_directories( ricxfcpp_static PUBLIC "src/messenger" "src/xapp" )
 endif()
index e63a901..5d6da09 100644 (file)
        .dv item .br &lic
        .dv li .br &lic
 
-       .dv ex_start .sp 1 ^:^: .br .ll -2 .in +2 .nf
+       .dv ex_start .sp 1 ^:^: .sp 1 .ll -2 .in +2 .nf
        .dv ex_end .fo on .in -2 .ll +2 .sp 1
 
        .dv proto_start .sp 1 .cc .5i .st 9 .sf Courier-bold .nf
index a99d6d7..07d4f7c 100644 (file)
        .gv e OUTPUT_TYPE ot
        .im  &{lib}/&{ot}.im
 
+       .gv e XFM_PASS pass
+       .dv pass &{pass!1}
+
+
+       .**  define a 'subroutine' to add a variable and place it into the forward reference file
+       .**  fref_sub.ca contains code to execute by the set_fref macor and fref.ca contains the
+       .**  forward references to include during pass2. The macro set_ref will define the variable
+       .**  passed as $1 (name) with the value passed as $2 and will add it to the fref.ca file.
+       .**
+.ca shift start fref_sub.ca
+       .ca expand extend fref.ca
+       .dv &vname &vval
+       .ca end
+.ca end
+       .dv set_fref .dv vname $1 ^: .dv vval $2 ^: .dv $1 $2 ^: .im fref_sub.ca
+
+
+       .if &pass 2 =
+               .im fref.ca
+       .ei
+               .** initialise the forward ref file
+               .ca start fref.ca
+                       .** forward reference variables; auto generated
+.ca end
+       .fi
+
 
        .** set up for an index when using pfm and snare file is defined
        .if pfm
index 7c5615b..0be1017 100644 (file)
@@ -18,6 +18,13 @@ The following is a list of release highlights for the C++
 xAPP Framework.
 
 
+2020 June 29; version 1.2.0
+---------------------------
+
+Add support for json parsing
+
+
+
 2020 June 26; version 1.1.0
 ---------------------------
 
index 8049fb6..370cda7 100644 (file)
 &h1(C++ Framework Release Notes)
 The following is a list of release highlights for the C++ xAPP Framework.
 
+&h2(2020 June 29; version 1.2.0)
+       Add support for json parsing
+&space
+
 &h2(2020 June 26; version 1.1.0)
        Change the port type in constructors to indicate "const"
 &space
 
-       Version bump to 1.1.0 to allow patches to bronze code to 
+       Version bump to 1.1.0 to allow patches to bronze code to
        continue to be done on the 1.0.* level.
 &space
 
index aa1acd1..8ef4aae 100644 (file)
@@ -3,3 +3,5 @@
 *.txt
 *.html
 *.sp
+*.ca
+*.toc
index 729b7fb..ca3c252 100644 (file)
@@ -19,7 +19,7 @@
 
 docs = user_guide
 src = user_guide.xfm
-imbed_src = cpp_frame.im example1.im example2.im example3.im
+imbed_src = cpp_frame.im example1.im example2.im example3.im jhash.im
 desired_out = rst ps
 
 # use care: the output type is used to source the macros based on the type
@@ -37,7 +37,7 @@ desired_out = rst ps
 
 %.rst: %.xfm
        OUTPUT_TYPE=rst XFM_PASS=1 tfm $< /dev/null
-       GEN_TITLE=1 OUTPUT_TYPE=rst XFM_PASS=2 tfm $< | sed 's/^ //' >$@
+       GEN_TITLE=1 OUTPUT_TYPE=rst XFM_PASS=2 tfm $< | sed 's/^ //; s/ *$$//' >$@
 
 %.txt: %.xfm
        OUTPUT_TYPE=txt XFM_PASS=1 tfm $< /dev/null
@@ -55,7 +55,7 @@ user_guide.ps: user_guide.xfm $(imbed_src)
 $(docs:%=%.rst): always
 
 publish: user_guide.rst
-       cp *.rst ../../../docs/
+       cp user_guide.rst ../../../docs/user-guide.rst
 
 # intermeidate junk that might straggle
 clean:
index aa4b166..9481b5a 100644 (file)
@@ -41,7 +41,7 @@ parameters:
 &half_space
 &indent
 &beg_dlist(.5i:&ditemtext)
-    &ditem(port) A C string (char *) which defines the port that RMR will open to listen for connections.
+    &ditem(port) A C string (pointer to char) which defines the port that RMR will open to listen for connections.
     &half_space
 
     &ditem(wait) A Boolean value which indicates whether or not the initialization process should wait for
@@ -208,7 +208,7 @@ Sending the message is a function of the message object itself and can take one
 
 &half_space
 &indent
-&beg_list &lic1
+&beg_list(&lic1)
     &item Replying to the sender of a received message
     &half_space
 
diff --git a/doc/src/user/jhash.im b/doc/src/user/jhash.im
new file mode 100644 (file)
index 0000000..d381542
--- /dev/null
@@ -0,0 +1,255 @@
+.** vim: sw=4 ts=4 et :
+.if false
+==================================================================================
+    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.
+==================================================================================
+.fi
+
+
+.if false
+       This imbed file contains the portion of the document that describes the
+       json support that is provided by the framework.
+.fi
+
+
+&h1(Json Support)
+The C++ xAPP framework provides a  very lightweight json parser and data
+hash facility.
+Briefly, a json hash (Jhash) can be established by creating an instance of
+the Jhash object with a string of valid json.
+The resulting object's functions can then be used to read values from the
+resulting hash.
+
+
+&h2(Creating The Jhash Object)
+The Jhash object is created simply by passing a json string to the constructor.
+
+.cc 10l
+&ex_start
+    #include <ricxfcpp/Jhash>
+
+    std::string jstring = "{ \"tag\": \"Hello World\" }";
+    Jhash*  jh;
+
+    jh =  new Jhash( jstring.c_str() );
+&ex_end
+&figure(The creation of the Jhash object.)
+&space
+
+
+Once the Jhash object has been created any of the methods described in the following
+paragraphs can be used to retrieve the data:
+
+&h2(Json Blobs)
+Json objects can be nested, and the nesting is supported by this  representation.
+The approach taken by Jhash is a "directory view" approach, where the "current directory,"
+or current &ital(blob,) limits the scope of visible fields.
+
+&space
+As an example, the json contained in figure jblob_fig, contains a "root" blob and
+two &ital(sub-blobs) (address and lease_info).
+
+&ex_start
+    {
+        "lodge_name": "Water Buffalo Lodge 714",
+        "member_count": 41,
+        "grand_poobah": "Larry K. Slate",
+        "attendance":   [ 23, 14, 41, 38, 24 ],
+        "address": {
+            "street":    "16801 Stonway Lane",
+            "suite":     null,
+            "city":      "Bedrock",
+            "post_code": "45701"
+        },
+        "lease_info": {
+            "owner":    "Stonegate Properties",
+            "amount":   216.49,
+            "due":      "monthly",
+            "contact:"  "Kyle Limestone"
+        }
+    }
+&ex_end
+.gv fig
+.sv _fig
+&set_fref(jblob_fig:&_fig)
+&figure(Sample json with a root and too blobs.)
+
+&space
+Upon creation of the Jhash object, the &ital(root) fields, &cw(lodge_name,) &cw(member_count,) and
+&cw(grand_poobah) are immediately available.
+The fields in the &ital(sub-blobs) are avalable only when the correct blob is selected.
+The code sample in figure &fig_blob_sample illustrates how  a &ital(sub-blob) is selected.
+
+&ex_start
+    jh->Set_blob( (char *) "address" );     // select address
+    jh->Unset_blob();                       // return to root
+    jh->Set_blob( (char *) "lease_info" );  // slect the lease blob
+&ex_end
+.gv fig
+.sv _fig
+&set_fref(fig_blob_sample:&_fig)
+&figure(Blob selection example.)
+&space
+
+Currently, the selected blob must be unset in order to select a blob at the root
+level; unset always sets the root blob.
+Attempting to use the &cw(Set_blob)  function will attempt to select the named blob
+from the current blob, and not the root.
+
+&h2(Simple Value Extraction)
+Simple values are the expected data types &ital(string, value,) and &ital(boolean.)
+This lightweight json parser treats all values as floating point numbers and does not
+attempt to maintain a separate integer type.
+A fourth type, &ital(null,) is supported to allow the user to expressly check for
+a field which is defined but has no value; as opposed to a field that was completely
+missing from the data.
+The following are the prototypes for the functions which allow values to be extracted:
+
+&half_space
+&ex_start
+    std::string String( const char* name );
+    float Value( const char* name );
+    bool Bool( const char* name );
+&ex_end
+&space
+
+Each of these funcitons returns the value associated with the field with the given &ital(name.)
+If the value is missing, the following default values are returned:
+
+&half_space
+&indent
+&beg_dlist( 1i Helvetica-bold : : 15,80 )
+    &di(String:) An empty string (.e.g "").
+    &di(Value:) Zero (e.g 0.0)
+    &di(bool:) false
+&end_dlist
+&uindent
+&space
+
+If the user needs to disambiguate between a missing value and the default value either the
+&cw(Missing) or &cw(Exists) function should be used first.
+
+&h2(Testing For Existing and Missing Fields)
+Two functions allow the developer to determine whether or not a field is included in the
+json.
+Both of these functions work on the current &ital(blob,) therefore it is important to ensure
+that the correct blob is selected before using either of these funcitons.
+The prototpyes for the &cw(Exists) and &cw(Missing) functions are below:
+
+&ex_start
+    bool Exists( const char* name );
+    bool Is_missing( const char* name );
+&ex_end
+
+The &cw(Exists) function returns &ital(true) if the field name exists in the json and &ital(false) otherwise.
+Conversly, the &cw(Missing) funciton returns &ital(true) when the field name does not exist in the json.
+
+
+&h2(Testing Field Type)
+The &cw(Exists) and &cw(Missing) functions might not be enough for the user code to validate
+the data that it has.
+To assist with this, several functions allow direct type testing on a field in the current
+blob.
+The following are the prototypes for these functions:
+
+&ex_start
+    bool Is_bool( const char* name );
+    bool Is_null( const char* name );
+    bool Is_string( const char* name );
+    bool Is_value( const char* name );
+&ex_end
+
+&space
+Each of these funcitons return &ital(true) if the field with the given name is of the type
+being tested for.
+
+
+&h2(Arrays)
+Arrays are supported in the same manner as simple field values with the addition of the need
+to supply an array index when fetching values from the object.
+In addition, there is a &ital(length) function which can be used to determine the number
+of elements in the named array.
+The prototypes for the array based functions are below:
+
+&ex_start
+    int Array_len( const char* name );
+
+    bool Is_bool_ele( const char* name, int eidx );
+    bool Is_null_ele( const char* name, int eidx );
+    bool Is_string_ele( const char* name, int eidx );
+    bool Is_value_ele( const char* name, int eidx );
+
+    bool Bool_ele( const char* name, int eidx );
+    std::string String_ele( const char* name, int eidx );
+    float Value_ele( const char* name, int eidx );
+&ex_end
+&space
+
+For each of these functions the &cw(eidx) is the zero based element index which is to
+be tested or selected.
+
+&h3(Arrays of Blobs)
+An array containing blobs, rather than simiple field value pairs, the blob must
+be selected prior to using it, just as a sub-blob needed to be selected.
+The &cw(Set_blob_ele) function is used to do this and has the following prototype:
+
+&ex_start
+    bool Set_blob_ele( const char* name, int eidx );
+&ex_end
+&space
+
+As with selecting a sub-blob, an unset must be preformed before selecting the next blob.
+Figure &array_blob_code_fig illustrates how these functions can be used to read and print
+values from the json in figure &array_blob_json_fig.
+
+
+.cc 8l
+&ex_start
+    "members": [
+        { "name": "Fred Flinstone", "member_num": 42 },
+        { "name": "Barney Rubble", "member_num": 48 },
+        { "name": "Larry K Slate", "member_num": 22 },
+        { "name": "Kyle Limestone", "member_num": 49 }
+    ]
+&ex_end
+.gv fig
+&set_fref(array_blob_code_fig:&_fig)
+&figure(Json array containing blobs.)
+&space
+
+
+.cc 18l
+&ex_start
+    std::string mname;
+    float mnum;
+    int len;
+
+    len = jh->Array_len( (char *) "members" );
+    for( i = 0; i < len; i++ ) {
+        jh->Set_blob_ele( (char *) "members", i );  // select blob
+
+        mname = jh->String( (char *) "name" );      // read values
+        mnum = jh->Value( (char *) "member_num" );
+        fprintf( stdout, "%s is member %d\n", mname.c_str(), (int) mnum );
+
+        jh->Unset_blob();                           // back to root
+    }
+&ex_end
+.gv fig
+&set_fref(array_blob_json_fig:&_fig)
+&figure(Code to process the array of blobs.)
+&space
+
index c34538f..6df8248 100644 (file)
 
 .dv col_width 6.5i
 .dv fname_base user_guide
-.dv doc_title RIC xAPP C++ Framework
-.dv doc_subtitle User's Guide
+.if pfm
+       .dv doc_title RIC xAPP C++ Framework
+       .dv doc_subtitle User's Guide
+.ei
+       .dv doc_title User's Guide
+.fi
 .dv orig_date 21 April 2020
 
 .dv textfont Helvetica
 .ll 6i
 .hn off
 
-.im ../lib/setup.im 
-
-.im &{lib}/front_junk.im 
+.im ../lib/setup.im
+.im &{lib}/front_junk.im
 
+.**  ------ major sections --------
 .im cpp_frame.im
+.im hash.im
 
 &h1(Example Programmes)
 The following sections contain several example programmes which are written on
index 7c5615b..0be1017 100644 (file)
@@ -18,6 +18,13 @@ The following is a list of release highlights for the C++
 xAPP Framework.
 
 
+2020 June 29; version 1.2.0
+---------------------------
+
+Add support for json parsing
+
+
+
 2020 June 26; version 1.1.0
 ---------------------------
 
index a4ab5e9..58ccd58 100644 (file)
-     
-.. This work is licensed under a Creative Commons Attribution 4.0 International License. 
-.. SPDX-License-Identifier: CC-BY-4.0 
-.. 
-.. CAUTION: this document is generated from source in doc/src/* 
-.. To make changes edit the source and recompile the document. 
-.. Do NOT make changes directly to .rst or .md files. 
-============================================================================================ 
-RIC xAPP C++ Framework 
-============================================================================================ 
--------------------------------------------------------------------------------------------- 
-User's Guide 
--------------------------------------------------------------------------------------------- 
-Introduction 
-============================================================================================ 
-The C++ framework allows the programmer to create an xApp 
-object instance, and to use that instance as the logic base. 
-The xApp object provides a message level interface to the RIC 
-Message Router (RMR), including the ability to register 
-callback functions which the instance will drive as messages 
-are received; much in the same way that an X-windows 
-application is driven by the window manager for all activity. 
-The xApp may also choose to use its own send/receive loop, 
-and thus is not required to use the callback driver mechanism 
-provided by the framework. 
-The Framework API 
-============================================================================================ 
-The C++ framework API consists of the creation of the xApp 
-object, and invoking desired functions via the instance of 
-the object. The following paragraphs cover the various steps 
-involved to create an xApp instance, wait for a route table 
-to arrive, send a message, and wait for messages to arrive. 
-Creating the xApp instance 
--------------------------------------------------------------------------------------------- 
-The creation of the xApp instance is as simple as invoking 
-the object's constructor with two required parameters: 
-       
-      port 
-          
-         A C string (pointer to char) which defines the port that RMR will 
-         open to listen for connections. 
-          
-       
-      wait 
-          
-         A Boolean value which indicates whether or not the 
-         initialization process should wait for the arrival of a 
-         valid route table before completing. When true is 
-         supplied, the initialization will not complete until RMR 
-         has received a valid route table (or one is located via 
-         the RMR_SEED_RT environment variable). 
-       
-       
-      The following code sample illustrates the simplicity of 
-      creating the instance of the xApp object. 
-       
-       
-      :: 
-         
+.. This work is licensed under a Creative Commons Attribution 4.0 International License.
+.. SPDX-License-Identifier: CC-BY-4.0
+..
+.. CAUTION: this document is generated from source in doc/src/*
+.. To make changes edit the source and recompile the document.
+.. Do NOT make changes directly to .rst or .md files.
+
+
+============================================================================================
+User's Guide
+============================================================================================
+
+
+INTRODUCTION
+============
+
+The C++ framework allows the programmer to create an xApp
+object instance, and to use that instance as the logic base.
+The xApp object provides a message level interface to the RIC
+Message Router (RMR), including the ability to register
+callback functions which the instance will drive as messages
+are received; much in the same way that an X-windows
+application is driven by the window manager for all activity.
+The xApp may also choose to use its own send/receive loop,
+and thus is not required to use the callback driver mechanism
+provided by the framework.
+
+
+THE FRAMEWORK API
+=================
+
+The C++ framework API consists of the creation of the xApp
+object, and invoking desired functions via the instance of
+the object. The following paragraphs cover the various steps
+involved to create an xApp instance, wait for a route table
+to arrive, send a message, and wait for messages to arrive.
+
+
+Creating the xApp instance
+--------------------------
+
+The creation of the xApp instance is as simple as invoking
+the object's constructor with two required parameters:
+
+
+      port
+         A C string (pointer to char) which defines the port that
+         RMR will open to listen for connections.
+
+      wait
+         A Boolean value which indicates whether or not the
+         initialization process should wait for the arrival of a
+         valid route table before completing. When true is
+         supplied, the initialization will not complete until RMR
+         has received a valid route table (or one is located via
+         the RMR_SEED_RT environment variable).
+
+      The following code sample illustrates the simplicity of
+      creating the instance of the xApp object.
+
+
+      ::
+
             #include <memory>
             #include <ricxfcpp/xapp.hpp>
             int main( ) {
                 std::unique_ptr<Xapp> xapp;
                 char* listen_port = (char *) "4560";    //RMR listen port
                 bool  wait4table = true;            // wait for a route table
+
                 xapp = std::unique_ptr<Xapp>(
                       new Xapp( listen_port, wait4table ) );
             }
-       
-       
-      Figure 1: Creating an xAPP instance. 
-       
-      From a compilation perspective, the following is the simple 
-      compiler invocation string needed to compile and link the 
-      above program (assuming that the sample code exists in a file 
-      called man_ex1.cpp. 
-       
-       
-      :: 
-         
+
+      Figure 1: Creating an xAPP instance.
+
+      From a compilation perspective, the following is the simple
+      compiler invocation string needed to compile and link the
+      above program (assuming that the sample code exists in a file
+      called man_ex1.cpp.
+
+
+      ::
+
            g++ man_ex1.cpp -o man_ex1 -lricxfcpp -lrmr_si -lpthread
-       
-       
-       
-      The above program, while complete and capable of being 
-      compiled, does nothing useful. When invoked, RMR will be 
-      initialized and will begin listening for a route table; 
-      blocking the return to the main program until one is 
-      received. When a valid route table arrives, initialization 
-      will complete and the program will exit as there is no code 
-      following the instruction to create the object. 
-       
-Listening For Messages 
-============================================================================================ 
-       
-      The program in the previous example can be extended with just 
-      a few lines of code to enable it to receive and process 
-      messages. The application needs to register a callback 
-      function for each message type which it desires to process. 
-       
-      Once registered, each time a message is received the 
-      registered callback for the message type will be invoked by 
-      the framework. 
-       
-Callback Signature 
--------------------------------------------------------------------------------------------- 
-       
-      As with most callback related systems, a callback must have a 
-      well known function signature which generally passes event 
-      related information and a "user" data pointer which was 
-      registered with the function. The following is the prototype 
-      which callback functions must be defined with: 
-       
-       
-      :: 
-         
+
+
+      The above program, while complete and capable of being
+      compiled, does nothing useful. When invoked, RMR will be
+      initialized and will begin listening for a route table;
+      blocking the return to the main program until one is
+      received. When a valid route table arrives, initialization
+      will complete and the program will exit as there is no code
+      following the instruction to create the object.
+
+
+LISTENING FOR MESSAGES
+======================
+
+      The program in the previous example can be extended with just
+      a few lines of code to enable it to receive and process
+      messages. The application needs to register a callback
+      function for each message type which it desires to process.
+
+      Once registered, each time a message is received the
+      registered callback for the message type will be invoked by
+      the framework.
+
+
+Callback Signature
+------------------
+
+      As with most callback related systems, a callback must have a
+      well known function signature which generally passes event
+      related information and a "user" data pointer which was
+      registered with the function. The following is the prototype
+      which callback functions must be defined with:
+
+
+      ::
+
             void cb_name( Message& m, int mtype, int subid,
                   int payload_len, Msg_component payload,
                   void* usr_data );
-       
-       
-      Figure 2: Callback function signature 
-       
-      The parameters passed to the callback function are as 
-      follows: &multi_space 
-       
-       
-      m 
-          
-         A reference to the Message that was received. 
-          
-       
-      mtype 
-          
-         The message type (allows for disambiguation if the 
-         callback is registered for multiple message types). 
-          
-       
-      subid 
-          
-         The subscription ID from the message. 
-          
-       
-      payload len 
-          
-         The number of bytes which the sender has placed into the 
-         payload. 
-          
-       
-      payload 
-          
-         A direct reference (smart pointer) to the payload. (The 
-         smart pointer is wrapped in a special class in order to 
-         provide a custom destruction function without burdening 
-         the xApp developer with that knowledge.) 
-          
-       
-      user data 
-          
-         A pointer to user data. This is the pointer that was 
-         provided when the function was registered. 
-       
-       
-      To illustrate the use of a callback function, the previous 
-      code example has been extended to add the function, register 
-      it for message types 1000 and 1001, and to invoke the Run() 
-      function in the framework (explained in the next section). 
-       
-      :: 
-         
+
+      Figure 2: Callback function signature
+
+      The parameters passed to the callback function are as
+      follows: &multi_space
+
+      m
+         A reference to the Message that was received.
+
+      mtype
+         The message type (allows for disambiguation if the
+         callback is registered for multiple message types).
+
+      subid
+         The subscription ID from the message.
+
+      payload len
+         The number of bytes which the sender has placed into the
+         payload.
+
+      payload
+         A direct reference (smart pointer) to the payload. (The
+         smart pointer is wrapped in a special class in order to
+         provide a custom destruction function without burdening
+         the xApp developer with that knowledge.)
+
+      user data
+         A pointer to user data. This is the pointer that was
+         provided when the function was registered.
+
+      To illustrate the use of a callback function, the previous
+      code example has been extended to add the function, register
+      it for message types 1000 and 1001, and to invoke the Run()
+      function in the framework (explained in the next section).
+
+      ::
+
             #include <memory>
             #include <ricxfcpp/xapp.hpp>
             long m1000_count = 0;    // message counters, one for each type
             long m1001_count = 0;
+
             // callback function that will increase the appropriate counter
             void cbf( Message& mbuf, int mtype, int subid, int len,
                         Msg_component payload,  void* data ) {
                 long* counter;
+
                 if( (counter = (long *) data) != NULL ) {
                     (*counter)++;
                 }
             }
+
             int main( ) {
                 std::unique_ptr<Xapp> xapp;
                 char* listen_port = (char *) "4560";
                 bool  wait4table = false;
+
                 xapp = std::unique_ptr<Xapp>(
                       new Xapp( listen_port, wait4table ) );
+
                 // register the same callback function for both msg types
                 xapp->Add_msg_cb( 1000, cbf, (void *) &m1000_count );
                 xapp->Add_msg_cb( 1001, cbf, (void *) &m1001_count );
+
                 xapp->Run( 1 );        // start the callback driver
             }
-       
-       
-      Figure 3: Callback function example. 
-       
-      As before, the program does nothing useful, but now it will 
-      execute and receive messages. For this example, the same 
-      function can be used to increment the appropriate counter 
-      simply by providing a pointer to the counter as the user data 
-      when the callback function is registered. In addition, a 
-      subtle change from the previous example has been to set the 
-      wait for table flag to false. 
-       
-      For an xApp that is a receive only application (never sends) 
-      it is not necessary to wait for RMR to receive a table from 
-      the Route Manager. 
-       
-Registering A Default Callback 
--------------------------------------------------------------------------------------------- 
-       
-      The xApp may also register a default callback function such 
-      that the function will be invoked for any message that does 
-      not have a registered callback. If the xAPP does not register 
-      a default callback, any message which cannot be mapped to a 
-      known callback function is silently dropped. A default 
-      callback is registered by providing a *generic* message type 
-      of xapp->DEFAULT_CALLBACK on an Add_msg_cb call. 
-       
-The Framework Callback Driver 
--------------------------------------------------------------------------------------------- 
-       
-      The Run() function within the Xapp object is invoked to start 
-      the callback driver, and the xApp should not expect the 
-      function to return under most circumstances. The only 
-      parameter that the Run() function expects is the number of 
-      threads to start. For each thread requested, the framework 
-      will start a listener thread which will allow received 
-      messages to be processed in parallel. If supplying a value 
-      greater than one, the xApp must ensure that the callback 
-      functions are thread safe as it is very likely that the same 
-      callback function will be invoked concurrently from multiple 
-      threads. 
-       
-Sending Messages 
-============================================================================================ 
-       
-      It is very likely that most xApps will need to send messages 
-      and will not operate in "receive only" mode. Sending the 
-      message is a function of the message object itself and can 
-      take one of two forms: 
-       
-       
-       
-      $1 Replying to the sender of a received message 
-       
-      $1 Sending a message (routed based on the message type and subscription ID) 
-       
-       
-      When replying to the sender, the message type and 
-      subscription ID are not used to determine the destination of 
-      the message; RMR ensures that the message is sent back to the 
-      originating xApp. The xApp may still need to change the 
-      message type and/or the subscription ID in the message prior 
-      to using the reply function. 
-       
-      To provide for both situations, two reply functions are 
-      supported by the Message object as illustrated with the 
-      following prototypes. 
-       
-       
-      :: 
-         
+
+      Figure 3: Callback function example.
+
+      As before, the program does nothing useful, but now it will
+      execute and receive messages. For this example, the same
+      function can be used to increment the appropriate counter
+      simply by providing a pointer to the counter as the user data
+      when the callback function is registered. In addition, a
+      subtle change from the previous example has been to set the
+      wait for table flag to false.
+
+      For an xApp that is a receive only application (never sends)
+      it is not necessary to wait for RMR to receive a table from
+      the Route Manager.
+
+
+Registering A Default Callback
+------------------------------
+
+      The xApp may also register a default callback function such
+      that the function will be invoked for any message that does
+      not have a registered callback. If the xAPP does not register
+      a default callback, any message which cannot be mapped to a
+      known callback function is silently dropped. A default
+      callback is registered by providing a *generic* message type
+      of xapp->DEFAULT_CALLBACK on an Add_msg_cb call.
+
+
+The Framework Callback Driver
+-----------------------------
+
+      The Run() function within the Xapp object is invoked to start
+      the callback driver, and the xApp should not expect the
+      function to return under most circumstances. The only
+      parameter that the Run() function expects is the number of
+      threads to start. For each thread requested, the framework
+      will start a listener thread which will allow received
+      messages to be processed in parallel. If supplying a value
+      greater than one, the xApp must ensure that the callback
+      functions are thread safe as it is very likely that the same
+      callback function will be invoked concurrently from multiple
+      threads.
+
+
+SENDING MESSAGES
+================
+
+      It is very likely that most xApps will need to send messages
+      and will not operate in "receive only" mode. Sending the
+      message is a function of the message object itself and can
+      take one of two forms:
+
+
+      + Replying to the sender of a received message
+
+      + Sending a message (routed based on the message type and subscription ID)
+
+
+      When replying to the sender, the message type and
+      subscription ID are not used to determine the destination of
+      the message; RMR ensures that the message is sent back to the
+      originating xApp. The xApp may still need to change the
+      message type and/or the subscription ID in the message prior
+      to using the reply function.
+
+      To provide for both situations, two reply functions are
+      supported by the Message object as illustrated with the
+      following prototypes.
+
+
+      ::
+
            bool Send_response(  int mtype, int subid, int response_len,
                 std:shared_ptr<unsigned char> response );
+
            bool Send_response(  int response_len, std::shared_ptr<unsigned char> response );
-       
-       
-      Figure 4: Reply function prototypes. 
-       
-      In the first prototype the xApp must supply the new message 
-      type and subscription ID values, where the second function 
-      uses the values which are currently set in the message. 
-      Further, the new payload contents, and length, are supplied 
-      to both functions; the framework ensures that the message is 
-      large enough to accommodate the payload, reallocating it if 
-      necessary, and copies the response into the message payload 
-      prior to sending. Should the xApp need to change either the 
-      message type, or the subscription ID, but not both, the 
-      NO_CHANGE constant can be used as illustrated below. 
-       
-       
-      :: 
-         
+
+      Figure 4: Reply function prototypes.
+
+      In the first prototype the xApp must supply the new message
+      type and subscription ID values, where the second function
+      uses the values which are currently set in the message.
+      Further, the new payload contents, and length, are supplied
+      to both functions; the framework ensures that the message is
+      large enough to accommodate the payload, reallocating it if
+      necessary, and copies the response into the message payload
+      prior to sending. Should the xApp need to change either the
+      message type, or the subscription ID, but not both, the
+      NO_CHANGE constant can be used as illustrated below.
+
+
+      ::
+
             msg->Send_response( Message::NO_CHANGE, Message::NO_SUBID,
                 pl_length, (unsigned char *) payload );
-       
-       
-      Figure 5: Send response prototype. 
-       
-      In addition to the two function prototypes for 
-      Send_response() there are two additional prototypes which 
-      allow the new payload to be supplied as a shared smart 
-      pointer. The other parameters to these functions are 
-      identical to those illustrated above, and thus are not 
-      presented here. 
-       
-      The Send_msg() set of functions supported by the Message 
-      object are identical to the Send_response() functions and are 
-      shown below. 
-       
-       
-      :: 
-         
+
+      Figure 5: Send response prototype.
+
+      In addition to the two function prototypes for
+      Send_response() there are two additional prototypes which
+      allow the new payload to be supplied as a shared smart
+      pointer. The other parameters to these functions are
+      identical to those illustrated above, and thus are not
+      presented here.
+
+      The Send_msg() set of functions supported by the Message
+      object are identical to the Send_response() functions and are
+      shown below.
+
+
+      ::
+
             bool Send_msg( int mtype, int subid, int payload_len,
                 std::shared_ptr<unsigned char> payload );
+
             bool Send_msg( int mtype, int subid, int payload_len,
                 unsigned char* payload );
+
             bool Send_msg( int payload_len,
                 std::shared_ptr<unsigned char> payload );
+
             bool Send_msg( int payload_len, unsigned char* payload );
-       
-       
-      Figure 6: Send function prototypes. 
-       
-      Each send function accepts the message, copies in the payload 
-      provided, sets the message type and subscription ID (if 
-      provided), and then causes the message to be sent. The only 
-      difference between the Send_msg() and Send_response() 
-      functions is that the destination of the message is selected 
-      based on the mapping of the message type and subscription ID 
-      using the current routing table known to RMR. 
-       
-Direct Payload Manipulation 
--------------------------------------------------------------------------------------------- 
-       
-      For some applications, it might be more efficient to 
-      manipulate the payload portion of an Xapp Message in place, 
-      rather than creating it and relying on a buffer copy when the 
-      message is finally sent. To achieve this, the xApp must 
-      either use the smart pointer to the payload passed to the 
-      callback function, or retrieve one from the message using 
-      Get_payload() when working with a message outside of a 
-      callback function. Once the smart pointer is obtained, the 
-      pointer's get() function can be used to directly reference 
-      the payload (unsigned char) bytes. 
-       
-      When working directly with the payload, the xApp must take 
-      care not to write more than the actual payload size which can 
-      be extracted from the Message object using the 
-      Get_available_size() function. 
-       
-      When sending a message where the payload has been directly 
-      altered, and no extra buffer copy is needed, a NULL pointer 
-      should be passed to the Message send function. The following 
-      illustrates how the payload can be directly manipulated and 
-      returned to the sender (for simplicity, there is no error 
-      handling if the payload size of the received message isn't 
-      large enough for the response string, the response is just 
-      not sent). 
-       
-       
-      :: 
-         
+
+      Figure 6: Send function prototypes.
+
+      Each send function accepts the message, copies in the payload
+      provided, sets the message type and subscription ID (if
+      provided), and then causes the message to be sent. The only
+      difference between the Send_msg() and Send_response()
+      functions is that the destination of the message is selected
+      based on the mapping of the message type and subscription ID
+      using the current routing table known to RMR.
+
+
+Direct Payload Manipulation
+---------------------------
+
+      For some applications, it might be more efficient to
+      manipulate the payload portion of an Xapp Message in place,
+      rather than creating it and relying on a buffer copy when the
+      message is finally sent. To achieve this, the xApp must
+      either use the smart pointer to the payload passed to the
+      callback function, or retrieve one from the message using
+      Get_payload() when working with a message outside of a
+      callback function. Once the smart pointer is obtained, the
+      pointer's get() function can be used to directly reference
+      the payload (unsigned char) bytes.
+
+      When working directly with the payload, the xApp must take
+      care not to write more than the actual payload size which can
+      be extracted from the Message object using the
+      Get_available_size() function.
+
+      When sending a message where the payload has been directly
+      altered, and no extra buffer copy is needed, a NULL pointer
+      should be passed to the Message send function. The following
+      illustrates how the payload can be directly manipulated and
+      returned to the sender (for simplicity, there is no error
+      handling if the payload size of the received message isn't
+      large enough for the response string, the response is just
+      not sent).
+
+
+      ::
+
             Msg_component payload;  // smart reference
             int pl_size;            // max size of payload
+
             payload = msg->Get_payload();
             pl_size = msg->Get_available_size();
             if( snprintf( (char *) payload.get(), pl_size,
                 "Msg Received\\n" ) < pl_size ) {
               msg->Send_response( M_TYPE, SID, strlen( raw_pl ), NULL );
             }
-       
-       
-      Figure 7: Send message without buffer copy. 
-       
-       
-Sending Multiple Responses 
--------------------------------------------------------------------------------------------- 
-       
-      It is likely that the xApp will wish to send multiple 
-      responses back to the process that sent a message that 
-      triggered the callback. The callback function may invoke the 
-      Send_response() function multiple times before returning. 
-       
-      After each call, the Message retains the necessary 
-      information to allow for a subsequent invocation to send more 
-      data. It should be noted though, that after the first call to 
-      {Send_response() the original payload will be lost; if 
-      necessary, the xApp must make a copy of the payload before 
-      the first response call is made. 
-       
-Message Allocation 
--------------------------------------------------------------------------------------------- 
-       
-      Not all xApps will be "responders," meaning that some xApps 
-      will need to send one or more messages before they can expect 
-      to receive any messages back. To accomplish this, the xApp 
-      must first allocate a message buffer, optionally initialising 
-      the payload, and then using the message's Send_msg() function 
-      to send a message out. The framework's Alloc_msg() function 
-      can be used to create a Message object with a desired payload 
-      size. 
-       
-Framework Provided Callbacks 
-============================================================================================ 
-       
-      The framework itself may provide message handling via the 
-      driver such that the xApp might not need to implement some 
-      message processing functionality. Initially, the C++ 
-      framework will provide a default callback function to handle 
-      the RMR based health check messages. This callback function 
-      will assume that if the message was received, and the 
-      callback invoked, that all is well and will reply with an OK 
-      state. If the xApp should need to override this simplistic 
-      response, all it needs to do is to register its own callback 
-      function for the health check message type. 
-       
-Example Programmes 
-============================================================================================ 
-       
-      The following sections contain several example programmes 
-      which are written on top of the C++ framework. 
-       
-RMR Dump xAPP 
--------------------------------------------------------------------------------------------- 
-       
-      The RMR dump application is an example built on top of the 
-      C++ xApp framework to both illustrate the use of the 
-      framework, and to provide a useful diagnostic tool when 
-      testing and troubleshooting xApps. 
-       
-      The RMR dump xApp isn't a traditional xApp inasmuch as its 
-      goal is to listen for message types and to dump information 
-      about the messages received to the TTY much as tcpdump does 
-      for raw packet traffic. The full source code, and Makefile, 
-      are in the examples directory of the C++ framework repo. 
-       
-      When invoked, the RMR dump program is given one or more 
-      message types to listen for. A callback function is 
-      registered for each, and the framework Run() function is 
-      invoked to drive the process. For each recognised message, 
-      and depending on the verbosity level supplied at program 
-      start, information about the received message(s) is written 
-      to the TTY. If the forwarding option, -f, is given on the 
-      command line, and an appropriate route table is provided, 
-      each received message is forwarded without change. This 
-      allows for the insertion of the RMR dump program into a flow, 
-      however if the ultimate receiver of a message needs to reply 
-      to that message, the reply will not reach the original 
-      sender, so RMR dump is not a complete "middle box" 
-      application. 
-       
-      The following is the code for this xAPP. Several functions, 
-      which provide logic unrelated to the framework, have been 
-      omitted. The full code is in the framework repository. 
-       
-       
-       
-      :: 
-         
+
+      Figure 7: Send message without buffer copy.
+
+
+
+Sending Multiple Responses
+--------------------------
+
+      It is likely that the xApp will wish to send multiple
+      responses back to the process that sent a message that
+      triggered the callback. The callback function may invoke the
+      Send_response() function multiple times before returning.
+
+      After each call, the Message retains the necessary
+      information to allow for a subsequent invocation to send more
+      data. It should be noted though, that after the first call to
+      {Send_response() the original payload will be lost; if
+      necessary, the xApp must make a copy of the payload before
+      the first response call is made.
+
+
+Message Allocation
+------------------
+
+      Not all xApps will be "responders," meaning that some xApps
+      will need to send one or more messages before they can expect
+      to receive any messages back. To accomplish this, the xApp
+      must first allocate a message buffer, optionally initialising
+      the payload, and then using the message's Send_msg() function
+      to send a message out. The framework's Alloc_msg() function
+      can be used to create a Message object with a desired payload
+      size.
+
+
+FRAMEWORK PROVIDED CALLBACKS
+============================
+
+      The framework itself may provide message handling via the
+      driver such that the xApp might not need to implement some
+      message processing functionality. Initially, the C++
+      framework will provide a default callback function to handle
+      the RMR based health check messages. This callback function
+      will assume that if the message was received, and the
+      callback invoked, that all is well and will reply with an OK
+      state. If the xApp should need to override this simplistic
+      response, all it needs to do is to register its own callback
+      function for the health check message type.
+
+
+EXAMPLE PROGRAMMES
+==================
+
+      The following sections contain several example programmes
+      which are written on top of the C++ framework.
+
+
+RMR Dump xAPP
+-------------
+
+      The RMR dump application is an example built on top of the
+      C++ xApp framework to both illustrate the use of the
+      framework, and to provide a useful diagnostic tool when
+      testing and troubleshooting xApps.
+
+      The RMR dump xApp isn't a traditional xApp inasmuch as its
+      goal is to listen for message types and to dump information
+      about the messages received to the TTY much as tcpdump does
+      for raw packet traffic. The full source code, and Makefile,
+      are in the examples directory of the C++ framework repo.
+
+      When invoked, the RMR dump program is given one or more
+      message types to listen for. A callback function is
+      registered for each, and the framework Run() function is
+      invoked to drive the process. For each recognised message,
+      and depending on the verbosity level supplied at program
+      start, information about the received message(s) is written
+      to the TTY. If the forwarding option, -f, is given on the
+      command line, and an appropriate route table is provided,
+      each received message is forwarded without change. This
+      allows for the insertion of the RMR dump program into a flow,
+      however if the ultimate receiver of a message needs to reply
+      to that message, the reply will not reach the original
+      sender, so RMR dump is not a complete "middle box"
+      application.
+
+      The following is the code for this xAPP. Several functions,
+      which provide logic unrelated to the framework, have been
+      omitted. The full code is in the framework repository.
+
+
+
+      ::
+
         #include <stdio.h>
         #include <unistd.h>
         #include <atomic>
+
         #include "ricxfcpp/xapp.hpp"
+
         /*
             Information that the callback needs outside
             of what is given to it via parms on a call
@@ -478,7 +474,9 @@ RMR Dump xAPP
             std::atomic<long>    icount; // messages ignored
             std::atomic<int>    hdr;    // number of messages before next header
         } cb_info_t;
+
         // ----------------------------------------------------------------------
+
         /*
             Dump bytes to tty.
         */
@@ -486,17 +484,20 @@ RMR Dump xAPP
             int        i;
             int        j;
             char    cheater[17];
+
             fprintf( stdout, "<RD> 0000 | " );
             j = 0;
             for( i = 0; i < len; i++ ) {
                 cheater[j++] =  isprint( buf[i] ) ? buf[i] : '.';
                 fprintf( stdout, "%02x ", buf[i] );
+
                 if( j == 16 ) {
                     cheater[j] = 0;
                     fprintf( stdout, " | %s\\n<RD> %04x | ", cheater, i+1 );
                     j = 0;
                 }
             }
+
             if( j ) {
                 i = 16 - (i % 16);
                 for( ; i > 0; i-- ) {
@@ -506,13 +507,16 @@ RMR Dump xAPP
                 fprintf( stdout, " | %s\\n", cheater );
             }
         }
+
         /*
             generate stats when the hdr count reaches 0. Only one active
             thread will ever see it be exactly 0, so this is thread safe.
         */
         void stats( cb_info_t& cbi ) {
             int curv;                    // current stat trigger value
+
             curv = cbi.hdr--;
+
             if( curv == 0 ) {                    // stats when we reach 0
                 fprintf( stdout, "ignored: %ld  processed: %ld\\n",
                     cbi.icount.load(), cbi.pcount.load() );
@@ -520,30 +524,38 @@ RMR Dump xAPP
                     fprintf( stdout, "\\n     %5s %5s %2s %5s\\n",
                         "MTYPE", "SUBID", "ST", "PLLEN" );
                 }
+
                 cbi.hdr = cbi.stats_freq;        // reset must be last
             }
         }
+
         void cb1( Message& mbuf, int mtype, int subid, int len,
                         Msg_component payload,  void* data ) {
             cb_info_t*    cbi;
             long total_count;
+
             if( (cbi = (cb_info_t *) data) == NULL ) {
                 return;
             }
+
             cbi->pcount++;
             stats( *cbi );            // gen stats & header if needed
+
             if( cbi->vlevel > 0 ) {
                 fprintf( stdout, "<RD> %-5d %-5d %02d %-5d \\n",
                         mtype, subid, mbuf.Get_state(), len );
+
                 if( cbi->vlevel > 1 ) {
                     dump(  payload.get(), len > 64 ? 64 : len );
                 }
             }
+
             if( cbi->forward ) {
                 // forward with no change to len or payload
                 mbuf.Send_msg( Message::NO_CHANGE, NULL );
             }
         }
+
         /*
             registered as the default callback; it counts the
             messages that we aren't giving details about.
@@ -551,16 +563,20 @@ RMR Dump xAPP
         void cbd( Message& mbuf, int mtype, int subid, int len,
                         Msg_component payload,  void* data ) {
             cb_info_t*    cbi;
+
             if( (cbi = (cb_info_t *) data) == NULL ) {
                 return;
             }
+
             cbi->icount++;
             stats( *cbi );
+
             if( cbi->forward ) {
                 // forward with no change to len or payload
                 mbuf.Send_msg( Message::NO_CHANGE, NULL );
             }
         }
+
         int main( int argc, char** argv ) {
             std::unique_ptr<Xapp> x;
             char*    port = (char *) "4560";
@@ -569,25 +585,30 @@ RMR Dump xAPP
             int        ncb = 0;            // number of callbacks registered
             int        mtype;
             int        nthreads = 1;
+
             cbi = (cb_info_t *) malloc( sizeof( *cbi ) );
             cbi->pcount = 0;
             cbi->icount = 0;
             cbi->stats_freq = 10;
+
             ai = 1;
             // very simple flag parsing (no error/bounds checking)
             while( ai < argc ) {
                 if( argv[ai][0] != '-' )  {        // break on first non-flag
                     break;
                 }
+
                 // very simple arg parsing; each must be separate -x -y not -xy.
                 switch( argv[ai][1] ) {
                     case 'f':                    // enable packet forwarding
                         cbi->forward = true;
                         break;
+
                     case 'p':                     // define port
                         port = argv[ai+1];
                         ai++;
                         break;
+
                     case 's':                        // stats frequency
                         cbi->stats_freq = atoi( argv[ai+1] );
                         if( cbi->stats_freq < 5 ) {    // enforce sanity
@@ -595,6 +616,7 @@ RMR Dump xAPP
                         }
                         ai++;
                         break;
+
                     case 't':                        // thread count
                         nthreads = atoi( argv[ai+1] );
                         if( nthreads < 1 ) {
@@ -602,13 +624,16 @@ RMR Dump xAPP
                         }
                         ai++;
                         break;
+
                     case 'v':            // simple verbose bump
                         cbi->vlevel++;
                         break;
+
                     case 'V':            // explicit verbose level
                         cbi->vlevel = atoi( argv[ai+1] );
                         ai++;
                         break;
+
                     default:
                         fprintf( stderr, "unrecognised option: %s\\n", argv[ai] );
                         fprintf( stderr, "usage: %s [-f] [-p port] "
@@ -620,12 +645,16 @@ RMR Dump xAPP
                                         "1 message info 2 payload dump\\n" );
                         exit( 1 );
                 }
+
                 ai++;
             }
+
             cbi->hdr = cbi->stats_freq;
             fprintf( stderr, "<RD> listening on port: %s\\n", port );
+
             // create xapp, wait for route table if forwarding
             x = std::unique_ptr<Xapp>( new Xapp( port, cbi->forward ) );
+
             // register callback for each type on the command line
             while( ai < argc ) {
                 mtype = atoi( argv[ai] );
@@ -634,130 +663,155 @@ RMR Dump xAPP
                 x->Add_msg_cb( mtype, cb1, cbi );
                 ncb++;
             }
+
             if( ncb < 1 ) {
                 fprintf( stderr, "<RD> no message types specified on the command line\\n" );
                 exit( 1 );
             }
+
             x->Add_msg_cb( x->DEFAULT_CALLBACK, cbd, cbi );        // register default cb
+
             fprintf( stderr, "<RD> starting driver\\n" );
             x->Run( nthreads );
+
             // return from run() is not expected, but some compilers might
             // compilain if there isn't a return value here.
             return 0;
         }
-       
-       
-      Figure 8: Simple callback application. 
-       
-Callback Receiver 
--------------------------------------------------------------------------------------------- 
-       
-      This sample programme implements a simple message listener 
-      which registers three callback functions to process two 
-      specific message types and a default callback to handle 
-      unrecognised messages. 
-       
-      When a message of type 1 is received, it will send two 
-      response messages back to the sender. Two messages are sent 
-      in order to illustrate that it is possible to send multiple 
-      responses using the same received message. 
-       
-      The programme illustrates how multiple listening threads can 
-      be used, but the programme is **not** thread safe; to keep 
-      this example as simple as possible, the counters are not 
-      locked when incremented. 
-       
-       
-      :: 
-         
+
+      Figure 8: Simple callback application.
+
+
+Callback Receiver
+-----------------
+
+      This sample programme implements a simple message listener
+      which registers three callback functions to process two
+      specific message types and a default callback to handle
+      unrecognised messages.
+
+      When a message of type 1 is received, it will send two
+      response messages back to the sender. Two messages are sent
+      in order to illustrate that it is possible to send multiple
+      responses using the same received message.
+
+      The programme illustrates how multiple listening threads can
+      be used, but the programme is **not** thread safe; to keep
+      this example as simple as possible, the counters are not
+      locked when incremented.
+
+
+      ::
+
         #include <stdio.h>
+
         #include "ricxfcpp/message.hpp"
         #include "ricxfcpp/msg_component.hpp"
         #include "ricxfcpp/xapp.hpp"
+
         // counts; not thread safe
         long cb1_count = 0;
         long cb2_count = 0;
         long cbd_count = 0;
+
         long cb1_lastts = 0;
         long cb1_lastc = 0;
+
         // respond with 2 messages for each type 1 received
         void cb1( Message& mbuf, int mtype, int subid, int len,
                     Msg_component payload,  void* data ) {
             long now;
             long total_count;
+
             // illustrate that we can use the same buffer for 2 rts calls
             mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK1\\n" );
             mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK2\\n" );
+
             cb1_count++;
         }
+
         // just count messages
-        void cb2( Message& mbuf, int mtype, int subid, int len, 
+        void cb2( Message& mbuf, int mtype, int subid, int len,
                     Msg_component payload,  void* data ) {
             cb2_count++;
         }
+
         // default to count all unrecognised messages
-        void cbd( Message& mbuf, int mtype, int subid, int len, 
+        void cbd( Message& mbuf, int mtype, int subid, int len,
                     Msg_component payload,  void* data ) {
             cbd_count++;
         }
+
         int main( int argc, char** argv ) {
             Xapp* x;
             char*    port = (char *) "4560";
             int ai = 1;                            // arg processing index
             int nthreads = 1;
+
             // very simple flag processing (no bounds/error checking)
             while( ai < argc ) {
                 if( argv[ai][0] != '-' )  {
                     break;
                 }
+
                 switch( argv[ai][1] ) {            // we only support -x so -xy must be -x -y
                     case 'p':
                         port = argv[ai+1];
                         ai++;
                         break;
+
                     case 't':
                         nthreads = atoi( argv[ai+1] );
                         ai++;
                         break;
                 }
+
                 ai++;
             }
+
             fprintf( stderr, "<XAPP> listening on port: %s\\n", port );
             fprintf( stderr, "<XAPP> starting %d threads\\n", nthreads );
+
             x = new Xapp( port, true );
             x->Add_msg_cb( 1, cb1, NULL );                // register callbacks
             x->Add_msg_cb( 2, cb2, NULL );
             x->Add_msg_cb( x->DEFAULT_CALLBACK, cbd, NULL );
+
             x->Run( nthreads );                // let framework drive
             // control should not return
         }
-       
-       
-      Figure 9: Simple callback application. 
-       
-       
-Looping Sender 
--------------------------------------------------------------------------------------------- 
-       
-      This is another very simple application which demonstrates 
-      how an application can control its own listen loop while 
-      sending messages. As with the other examples, some error 
-      checking is skipped, and short cuts have been made in order 
-      to keep the example small and to the point. 
-       
-       
-      :: 
-         
+
+      Figure 9: Simple callback application.
+
+
+
+Looping Sender
+--------------
+
+      This is another very simple application which demonstrates
+      how an application can control its own listen loop while
+      sending messages. As with the other examples, some error
+      checking is skipped, and short cuts have been made in order
+      to keep the example small and to the point.
+
+
+      ::
+
+
         #include <stdio.h>
         #include <string.h>
         #include <unistd.h>
+
         #include <iostream>
         #include <memory>
+
         #include "ricxfcpp/xapp.hpp"
+
         extern int main( int argc, char** argv ) {
             std::unique_ptr<Xapp> xfw;
             std::unique_ptr<Message> msg;
             Msg_component payload;                // special type of unique pointer to the payload
+
             int    sz;
             int len;
             int i;
@@ -767,11 +821,14 @@ Looping Sender
             int    mtype = 0;
             int rmtype;                            // received message type
             int delay = 1000000;                // mu-sec delay; default 1s
+
+
             // very simple flag processing (no bounds/error checking)
             while( ai < argc ) {
                 if( argv[ai][0] != '-' )  {
                     break;
                 }
+
                 // we only support -x so -xy must be -x -y
                 switch( argv[ai][1] ) {
                     // delay between messages (mu-sec)
@@ -779,10 +836,12 @@ Looping Sender
                         delay = atoi( argv[ai+1] );
                         ai++;
                         break;
+
                     case 'p':
                         port = argv[ai+1];
                         ai++;
                         break;
+
                     // timeout in seconds; we need to convert to ms for rmr calls
                     case 't':
                         response_to = atoi( argv[ai+1] ) * 1000;
@@ -791,25 +850,32 @@ Looping Sender
                 }
                 ai++;
             }
+
             fprintf( stderr, "<XAPP> response timeout set to: %d\\n", response_to );
             fprintf( stderr, "<XAPP> listening on port: %s\\n", port );
+
             // get an instance and wait for a route table to be loaded
             xfw = std::unique_ptr<Xapp>( new Xapp( port, true ) );
             msg = xfw->Alloc_msg( 2048 );
+
             for( i = 0; i < 100; i++ ) {
                 mtype++;
                 if( mtype > 10 ) {
                     mtype = 0;
                 }
+
                 // we'll reuse a received message; get max size
                 sz = msg->Get_available_size();
+
                 // direct access to payload; add something silly
                 payload = msg->Get_payload();
                 len = snprintf( (char *) payload.get(), sz, "This is message %d\\n", i );
+
                 // payload updated in place, prevent copy by passing nil
                 if ( ! msg->Send_msg( mtype, Message::NO_SUBID,  len, NULL )) {
                     fprintf( stderr, "<SNDR> send failed: %d\\n", i );
                 }
+
                 // receive anything that might come back
                 msg = xfw->Receive( response_to );
                 if( msg != NULL ) {
@@ -820,12 +886,12 @@ Looping Sender
                 } else {
                     msg = xfw->Alloc_msg( 2048 );
                 }
+
                 if( delay > 0 ) {
                     usleep( delay );
                 }
             }
         }
-       
-       
-      Figure 10: Simple looping sender application. 
-       
+
+      Figure 10: Simple looping sender application.
+
diff --git a/ext/jsmn b/ext/jsmn
new file mode 160000 (submodule)
index 0000000..7b6858a
--- /dev/null
+++ b/ext/jsmn
@@ -0,0 +1 @@
+Subproject commit 7b6858a5855299d173c5ab2b46e611bf9961cbef
diff --git a/src/json/CMakeLists.txt b/src/json/CMakeLists.txt
new file mode 100644 (file)
index 0000000..ac6ff25
--- /dev/null
@@ -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( json_objects OBJECT
+       jwrapper.c
+       jhash.cpp
+)
+
+target_include_directories ( json_objects PUBLIC
+       $<BUILD_INTERFACE:>
+
+       $<INSTALL_INTERFACE:include>
+)
+
+# header files should go into .../include/xfcpp/
+if( DEV_PKG )
+       install( FILES
+               jhash.hpp
+               DESTINATION ${install_inc}
+       )
+endif()
+
diff --git a/src/json/README b/src/json/README
new file mode 100644 (file)
index 0000000..6f775a3
--- /dev/null
@@ -0,0 +1,11 @@
+
+This directory contains the code to allow the framework to
+provide a light-weight json parser. The parser, based on
+a the third party Jasmn code (link below) parses a json
+"blob" into a symbol table (rmr_symtab). The xcpp_json
+class provides the API to then access the parsed data.
+
+
+The jsmn package is included at the root as an submodule
+and is used only at build time.
+
diff --git a/src/json/jhash.cpp b/src/json/jhash.cpp
new file mode 100644 (file)
index 0000000..14a0486
--- /dev/null
@@ -0,0 +1,283 @@
+// 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:       jhash.cpp
+       Abstract:       This class provides the ability to parse a json string into
+                               a hashtable, and exposes various functions that can be used
+                               to read the data from the hash.
+
+       Date:           26 June 2020
+       Author:         E. Scott Daniels
+*/
+
+
+#include <string>
+
+#include "jwrapper.h"
+#include "jhash.hpp"
+
+// ------------------------------------------------------------------------
+
+
+
+// ----------- construction/destruction housekeeping things -------------------
+/*
+       Accept a string that contains valid json. Causes it to be parsed
+       after which the functions provided by the class can be used to
+       suss out the values.
+*/
+Jhash::Jhash( const char* jbuf ) :
+       st( jw_new( jbuf ) ),
+       master_st( NULL )
+{ /* empty body */ }
+
+//Jhash::Jhash( Jhash&& soi );                                         // mover
+//Jhash::Jhash& operator=( Jhash&& soi );                      // move operator
+
+/*
+       Blow it away.
+*/
+Jhash::~Jhash() {
+       if( master_st != NULL ) {               // revert blob set if needed
+               st = master_st;
+       }
+
+       jw_nuke( st );
+       st = NULL;
+       master_st = NULL;
+}
+
+
+// ------------ public API ---------------------------------------------------
+
+// IMPORTANT: all underlying jwrapper functions check for nil st and name pointers
+//                             so that is NOT needed in this code.
+
+
+// --- root control ----------------------------------------------------------
+/*
+       Sets the "root" to the named blob. Until unset, all subsequent
+       calls to Jhash functions (e.g. Is_missing()) will use this for the
+       look up. Returns true if name exists and was indeed a blob.
+
+       It is legit to call set multiple times without unsetting; set
+       overlays with the named root; unset needs only to be called to
+       return to the top level.
+*/
+bool Jhash::Set_blob( const char* name ) {
+       void*   bst;                                            // blob symbol table
+
+       if( master_st == NULL ) {                       // must capture master
+               master_st == st;
+       }
+
+       if( (bst = jw_blob( st, name )) != NULL ) {
+               st = bst;
+               return true;
+       }
+
+       return false;
+}
+
+/*
+       Return the suss root (blob root) to the root of the symtab.
+*/
+void Jhash::Unset_blob( ) {
+       if( master_st != NULL ) {
+               st = master_st;
+       }
+/*
+ else {
+fprintf( stderr, "bad unset pointer%p  %p\n", master_st, st );
+}
+*/
+}
+
+// ---------------- debugging and sanity checks ---------------------------------------
+/*
+       Returns true if there were parse errors which resulted in an empty
+       or non-existant hash.
+
+       Right now we don't have much to work with other than checking for a
+       nil table.
+*/
+bool Jhash::Parse_errors( ) {
+       return st == NULL;
+}
+
+/*
+       Dump the selected blob as much as we can.
+*/
+void Jhash::Dump() {
+       jw_dump( st );
+}
+
+// ---------------- type testing -----------------------------------------
+
+/*
+       These funcitons return true if the named object in the current blob
+       is the indicated type
+*/
+bool Jhash::Is_value( const char* name ) {
+       return jw_is_value( st, name ) == 1;
+}
+
+bool Jhash::Is_bool( const char* name ) {
+       return jw_is_bool( st, name ) == 1;
+}
+
+bool Jhash::Is_null( const char* name ) {
+       return jw_is_null( st, name ) == 1;
+}
+
+bool Jhash::Is_string( const char* name ) {
+       return jw_is_string( st, name ) == 1;
+}
+
+/*
+       These functions return true if the indicated element in the array
+       <name> is the indicated type.
+*/
+bool Jhash::Is_string_ele( const char* name, int eidx ) {
+       return jw_is_string_ele( st, name, eidx ) == 1;
+}
+
+bool Jhash::Is_value_ele( const char* name, int eidx ) {
+       return jw_is_value_ele( st, name, eidx ) == 1;
+}
+
+bool Jhash::Is_bool_ele( const char* name, int eidx ) {
+       return jw_is_bool_ele( st, name, eidx ) == 1;
+}
+
+bool Jhash::Is_null_ele( const char* name, int eidx ) {
+       return jw_is_null_ele( st, name, eidx ) == 1;
+}
+
+
+// ---------------- presence ------------------------------------------------
+/*
+       Returns true if the named element is in the hash.
+*/
+bool Jhash::Exists( const char* name ) {
+       return jw_exists( st, name ) == 1;
+}
+
+/*
+       Returns true if the named element is not in the hash.
+*/
+bool Jhash::Is_missing( const char* name ) {
+       return jw_missing( st, name ) == 1;
+}
+
+// ---------------- value sussing ----------------------------------------
+
+/*
+       Returns the boolean value for the object if it is a bool; false otherwise.
+
+       Symtab saves bool values as 1 for true and doesn't provide a bool fetch
+       function. So, fetch the value and return true if it is 1.
+*/
+bool Jhash::Bool( const char* name ) {
+       int v;
+       v = (int) jw_value( st, name );
+       return v == 1;
+}
+
+/*
+       Returns a C++ string to the named object; If the element is not
+       in the hash an empty string is returned.
+*/
+std::string Jhash::String( const char* name ) {
+       std::string rv = "";
+       char*   hashv;
+
+       if( (hashv = jw_string( st, name )) != NULL ) {
+               rv = std::string( hashv );
+       }
+
+       return rv;
+}
+
+
+/*
+       Returns the value assocated with the named object; If the element is not
+       in the hash 0 is returned.
+*/
+double Jhash::Value( const char* name ) {
+       return jw_value( st, name );
+}
+
+// ------ array related things --------------------------------------------
+
+/*
+       Return the length of the named array, or -1 if it doesn't exist.
+*/
+int Jhash::Array_len( const char* name ) {
+       return jw_array_len( st, name );
+}
+
+
+/*
+       Sets the blob in the array <name>[eidx] to the current reference blob.
+*/
+bool Jhash::Set_blob_ele( const char* name, int eidx ) {
+       void*   bst;
+
+       if( (bst = jw_obj_ele( st, name, eidx )) != NULL ) {
+               if( master_st == NULL ) {                       // must capture master
+                       master_st = st;
+               }
+
+               st = bst;
+               return true;
+       }
+
+       return false;
+}
+/*
+       Return the string at index eidx in the array <name>.
+*/
+std::string Jhash::String_ele( const char* name, int eidx ) {
+       std::string rv = "";
+       char*   hashv;
+
+       if( (hashv = jw_string_ele( st, name, eidx )) != NULL ) {
+               rv = std::string( hashv );
+       }
+
+       return rv;
+}
+
+/*
+       Return the value at index eidx in the array <name>.
+*/
+double Jhash::Value_ele( const char* name, int eidx ) {
+       return jw_value_ele( st, name, eidx );
+}
+
+/*
+       Return the bool value at index eidx in the array <name>.
+*/
+bool Jhash::Bool_ele( const char* name, int eidx ) {
+       return jw_bool_ele( st, name, eidx ) == 1;
+}
+
diff --git a/src/json/jhash.hpp b/src/json/jhash.hpp
new file mode 100644 (file)
index 0000000..3ff6498
--- /dev/null
@@ -0,0 +1,86 @@
+// 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:       jhash.hpp
+       Abstract:       This class provides the ability to parse a json string into
+                               a hashtable, and exposes various functions that can be used
+                               to read the data from the hash.
+
+       Date:           26 June 2020
+       Author:         E. Scott Daniels
+*/
+
+#ifndef _JHASH_HPP
+#define _JHASH_HPP
+
+
+#include <string>
+
+// ------------------------------------------------------------------------
+
+class Jhash {
+       private:
+               void*   st;                                                     // the resulting symbol table generated by parse
+               void*   master_st;                                      // if user switches to a sub-blob; this tracks the original root st
+
+               Jhash& operator=( const Jhash& soi );   // jhashes cannot be copied because of underlying symbol table goo
+               Jhash( const Jhash& soi );
+
+       public:
+
+               Jhash( const char* jblob );                                     // builder
+               //Jhash( Message&& soi );                                               // mover
+               //Jhash& operator=( Message&& soi );                    // move operator
+               ~Jhash();                                                                       // destruction
+
+               bool Set_blob( const char* name );                                      // blob/root selection
+               void Unset_blob( );
+               bool Set_blob_ele( const char* name, int eidx );        // set from an array element
+
+               bool Parse_errors( );
+               void Dump();
+
+               std::string String( const char* name );                         // value fetching
+               double Value( const char* name );
+               bool Bool( const char* name );
+
+               bool Exists( const char* name );                                        // presence checking
+               bool Is_missing( const char* name );
+
+               bool Is_bool( const char* name );                                       // type checking functions
+               bool Is_null( const char* name );
+               bool Is_string( const char* name );
+               bool Is_value( const char* name );
+
+               int Array_len( const char* name );
+
+               bool Is_bool_ele( const char* name, int eidx );         // type of array element checks
+               bool Is_null_ele( const char* name, int eidx );
+               bool Is_string_ele( const char* name, int eidx );
+               bool Is_value_ele( const char* name, int eidx );
+
+               bool Bool_ele( const char* name, int eidx );                     // array oriented sussing functions
+               std::string String_ele( const char* name, int eidx );
+               double Value_ele( const char* name, int eidx );
+};
+
+
+#endif
diff --git a/src/json/jwrapper.c b/src/json/jwrapper.c
new file mode 100644 (file)
index 0000000..f6c32c3
--- /dev/null
@@ -0,0 +1,807 @@
+
+// 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:       jwrapper.c
+       Abstract:       A wrapper interface to the jsmn library which makes it a bit easier
+                               to use.  Parses a json string capturing the contents in a symtab.
+
+                               This code is based on the AT&T VFd open source library available
+                               on github.com/att/vfd.  The changes are mostly to port to the
+                               RMR version of symtab from VFd's version.
+
+       Author:         E. Scott Daniels
+       Date:           26 June 2020
+
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+
+#ifndef DEBUG
+       #define DEBUG 0
+#endif
+
+#define JSMN_STATIC 1          // jsmn no longer builds into a library; this pulls as static functions
+#include <jsmn.h>
+//#include <../../ext/jsmn/jsmn.h>
+
+#include <rmr/rmr_symtab.h>
+
+#define JSON_SYM_NAME  "_jw_json_string"
+#define MAX_THINGS             1024 * 4        // max objects/elements
+
+#define PT_UNKNOWN             0                       // primative types; unknown for non prim
+#define PT_VALUE               1
+#define PT_BOOL                        2
+#define PT_NULL                        3
+#define PT_STRING              4
+
+#define OBJ_SPACE              1                       // space in the symbol table where json bits are stashed
+
+extern void jw_nuke( void* st );
+
+// ---------------------------------------------------------------------------------------
+
+/*
+       This is what we will manage in the symtab. Right now we store all values (primatives)
+       as double, but we could be smarter about it and look for a decimal. Unsigned and
+       differences between long, long long etc are tough.
+*/
+typedef struct jthing {
+       int jsmn_type;                          // propigated type from jsmn (jsmn constants)
+       int prim_type;                          // finer grained primative type (bool, null, value)
+       int     nele;                                   // number of elements if applies
+       union {
+               double fv;
+               void *pv;
+       } v;
+} jthing_t;
+
+
+/*
+       Given the json token, 'extract' the element by marking the end with a
+       nil character, and returning a pointer to the start.  We do this so that
+       we don't create a bunch of small buffers that must be found and freed; we
+       can just release the json string and we'll be done (read won't leak).
+*/
+static char* extract( char* buf, jsmntok_t *jtoken ) {
+       buf[jtoken->end] = 0;
+       return &buf[jtoken->start];
+}
+
+/*
+       create a new jthing and add a reference to it in the symbol table st.
+       sets the number of elements to 1 by default.
+*/
+static jthing_t *mk_thing( void *st, char *name, int jsmn_type ) {
+       jthing_t        *jtp = NULL;
+
+       if( st != NULL &&
+               name != NULL &&
+               (jtp = (jthing_t *) malloc( sizeof( *jtp ) )) != NULL ) {
+
+               if( DEBUG ) {
+                       fprintf( stderr, "<DBUG> jwrapper adding: %s type=%d\n", name, jsmn_type );
+               }
+
+               jtp->jsmn_type = jsmn_type;
+               jtp->prim_type = PT_UNKNOWN;                    // caller must set this
+               jtp->nele = 1;
+               jtp->v.fv = 0;
+
+               rmr_sym_put( st, name, OBJ_SPACE, jtp );
+       } else {
+               fprintf( stderr, "[WARN] jwrapper: unable to create '%s' type=%d\n", name, jsmn_type );
+       }
+
+       return jtp;
+}
+
+
+/*
+       Find the named array. Returns a pointer to the jthing that represents
+       the array (type, size and pointer to actual array of jthings).
+       Returns nil pointer if the named thing isn't there or isn't an array.
+*/
+static jthing_t* suss_array( void* st, const char* name ) {
+       jthing_t* jtp = NULL;                                                   // thing that is referenced by the symtab
+
+       if( st != NULL &&
+               name != NULL &&
+               (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
+
+               jtp =  jtp->jsmn_type == JSMN_ARRAY  ? jtp : NULL;
+       }
+
+       return jtp;
+}
+
+/*
+       Suss an array from the hash and return the ith element.
+*/
+static jthing_t* suss_element( void* st, const char* name, int idx ) {
+       jthing_t* jtp;                                                                  // thing that is referenced by the symtab
+       jthing_t* jarray;
+       jthing_t* rv = NULL;
+
+       if( (jtp = suss_array( st, name )) != NULL &&           // have pointer
+               idx >= 0 &&                                                                             // and in range
+               idx < jtp->nele ) {
+
+               if( (jarray = jtp->v.pv)  != NULL ) {
+                       rv = &jarray[idx];
+               }
+       }
+
+       return rv;
+}
+
+
+/*
+       Invoked for each thing in the symtab; we free the things that actually point to
+       allocated data (e.g. arrays) and recurse to handle objects.
+*/
+static void nix_things( void* st, void* se, const char* name,  void* ele, void *data ) {
+       jthing_t*       j;
+       jthing_t*       jarray;
+       int i;
+
+       j = (jthing_t *) ele;
+       if( j ) {
+               switch( j->jsmn_type ) {
+                       case JSMN_ARRAY:
+                               if( (jarray = (jthing_t *) j->v.pv)  != NULL ) {
+                                       for( i = 0; i < j->nele; i++ ) {                                        // must look for embedded objects
+                                               if( jarray[i].jsmn_type == JSMN_OBJECT ) {
+                                                       jw_nuke( jarray[i].v.pv );
+                                                       jarray[i].jsmn_type = JSMN_UNDEFINED;                   // prevent accidents
+                                               }
+                                       }
+
+                                       free( j->v.pv );                        // must free the array (arrays aren't nested, so all things in the array don't reference allocated mem)
+                               }
+                               break;
+
+                       case JSMN_OBJECT:
+                               jw_nuke( j->v.pv );
+                               j->jsmn_type = JSMN_UNDEFINED;                  // prevent a double free
+                               break;
+               }
+       }
+}
+
+/*
+       Invoked for each thing and prints what we can to stderr.
+*/
+static void dump_things( void* st, void* se, const char* name,  void* ele, void *data ) {
+       jthing_t*       j;
+       jthing_t*       jarray;
+       int i;
+
+       j = (jthing_t *) ele;
+       if( j ) {
+               fprintf( stderr, "<DBUG> jwrapper: element '%s' has ptype %d, jsmn type %d\n", name, j->prim_type, j->jsmn_type );
+       } else {
+               fprintf( stderr, "<DBUG> jwrapper: element has no data: '%s'\n", name );
+       }
+}
+
+/*
+       Real work for parsing an object ({...}) from the json.   Called by jw_new() and
+       recurses to deal with sub-objects.
+*/
+void* parse_jobject( void* st, char *json, char* prefix ) {
+       jthing_t        *jtp;                   // json thing that we just created
+       int             i;
+       int             n;
+       char    *name;                          // name in the json
+       char    *data;                          // data string from the json
+       jthing_t*       jarray;                 // array of jthings we'll coonstruct
+       int             size;
+       int             osize;
+       int             njtokens;                       // tokens actually sussed out
+       jsmn_parser jp;                         // 'parser' object
+       jsmntok_t *jtokens;                     // pointer to tokens returned by the parser
+       char    pname[1024];            // name with prefix
+       char    wbuf[256];                      // temp buf to build a working name in
+       char*   dstr;                           // dup'd string
+
+       jsmn_init( &jp );                       // does this have a failure mode?
+
+       jtokens = (jsmntok_t *) malloc( sizeof( *jtokens ) * MAX_THINGS );
+       if( jtokens == NULL ) {
+               fprintf( stderr, "[CRI] jwrapper: cannot allocate tokens array\n" );
+               return NULL;
+       }
+
+       njtokens = jsmn_parse( &jp, json, strlen( json ), jtokens, MAX_THINGS );
+
+       if( jtokens[0].type != JSMN_OBJECT ) {                          // if it's not an object then we can't parse it.
+               fprintf( stderr, "[WARN] jwrapper: badly formed json; initial opening bracket ({) not detected\n" );
+               return NULL;
+       }
+
+       if( DEBUG ) {
+               for( i = 1; i < njtokens-1; i++ ) {
+                       fprintf( stderr, "<DBUG> %4d: size=%d start=%d end=%d %s\n", i, jtokens[i].size, jtokens[i].start, jtokens[i].end, extract( json, &jtokens[i] ) );
+               }
+       }
+
+       for( i = 1; i < njtokens-1; i++ ) {                                     // we'll silently skip the last token if it's "name" without a value
+               if( jtokens[i].type != JSMN_STRING ) {
+                       fprintf( stderr, "[WARN] jwrapper: badly formed json [%d]; expected name (string) found type=%d %s\n", i, jtokens[i].type, extract( json, &jtokens[i] ) );
+                       rmr_sym_free( st );
+                       return NULL;
+               }
+               name = extract( json, &jtokens[i] );
+               if( *prefix != 0 ) {
+                       snprintf( pname, sizeof( pname ), "%s.%s", prefix, name );
+                       name = pname;
+               }
+
+               size = jtokens[i].size;
+
+               i++;                                                                            // at the data token now
+               switch( jtokens[i].type ) {
+                       case JSMN_OBJECT:                               // save object in two ways: as an object 'blob' and in the current symtab using name as a base (original)
+                               dstr = strdup( extract( json, &jtokens[i] ) );
+                               snprintf( wbuf, sizeof( wbuf ), "%s_json", name );      // must stash the json string in the symtab for clean up during nuke
+                               rmr_sym_put( st, wbuf, OBJ_SPACE, dstr );
+
+                               parse_jobject( st, dstr, name );                                        // recurse to add the object as objectname.xxxx elements
+
+                               if( (jtp = mk_thing( st, name, jtokens[i].type )) != NULL &&            // create thing and reference it in current symtab
+                                       (jtp->v.pv = (void *) rmr_sym_alloc( 255 ) ) != NULL ) {                // object is just a blob
+
+                                       dstr = strdup( extract( json, &jtokens[i] ) );
+                                       rmr_sym_put( jtp->v.pv, JSON_SYM_NAME, OBJ_SPACE, dstr );               // must stash json so it is freed during nuke()
+                                       parse_jobject( jtp->v.pv,  dstr, "" );                                                  // recurse acorss the string and build a new symtab
+
+                                       size = jtokens[i].end;                                                                                  // done with them, we need to skip them
+                                       i++;
+                                       while( i < njtokens-1  &&  jtokens[i].end < size ) {
+                                               if( DEBUG ){
+                                                       fprintf( stderr, "\tskip: [%d] object element start=%d end=%d (%s)\n",
+                                                               i, jtokens[i].start, jtokens[i].end, extract( json, &jtokens[i])  );
+                                               }
+                                               i++;
+                                       }
+
+                                       i--;                                            // must allow loop to bump past the last
+                               }
+                               break;
+
+                       case JSMN_ARRAY:
+                               size = jtokens[i].size;         // size is burried here, and not with the name
+                               jtp = mk_thing( st, name, jtokens[i].type );
+
+                               i++;                    // skip first ele; it is the whole array string which I don't grock the need for, but it's their code...
+                               if( jtp == NULL ) {
+                                       fprintf( stderr, "[WARN] jwrapper: memory alloc error processing element [%d] in json\n", i );
+                                       rmr_sym_free( st );
+                                       return NULL;
+                               }
+                               jarray = jtp->v.pv = (jsmntok_t *) malloc( sizeof( *jarray ) * size );          // allocate the array
+                               memset( jarray, 0, sizeof( *jarray ) * size );
+                               jtp->nele = size;
+
+                               for( n = 0; n < size; n++ ) {                                                           // for each array element
+                                       jarray[n].prim_type      = PT_UNKNOWN;                                          // assume not primative type
+                                       switch( jtokens[i+n].type ) {
+
+                                               case JSMN_OBJECT:
+                                                       jarray[n].v.pv = (void *) rmr_sym_alloc( 255 );
+                                                       if( jarray[n].v.pv != NULL ) {
+                                                               jarray[n].jsmn_type = JSMN_OBJECT;
+                                                               parse_jobject( jarray[n].v.pv,  extract( json, &jtokens[i+n]  ), "" );          // recurse acorss the string and build a new symtab
+                                                               osize = jtokens[i+n].end;                                                                       // done with them, we need to skip them
+                                                               i++;
+                                                               while( i+n < njtokens-1  &&  jtokens[n+i].end < osize ) {
+                                                                       i++;
+                                                               }
+                                                               i--;                                    // allow incr at loop end
+                                                       }
+                                                       break;
+
+                                               case JSMN_ARRAY:
+                                                       fprintf( stderr, "[WARN] jwrapper: [%d] array element %d is not valid type (array) is not string or primative\n", i, n );
+                                                       n += jtokens[i+n].size;                 // this should skip the nested array
+                                                       break;
+
+                                               case JSMN_STRING:
+                                                       data = extract( json, &jtokens[i+n] );
+                                                       jarray[n].v.pv = (void *) data;
+                                                       jarray[n].prim_type = PT_STRING;
+                                                       jarray[n].jsmn_type = JSMN_STRING;
+                                                       break;
+
+                                               case JSMN_PRIMITIVE:
+                                                       data = extract( json, &jtokens[i+n] );
+                                                       switch( *data ) {
+                                                               case 'T':
+                                                               case 't':
+                                                                       jarray[n].v.fv = 1;
+                                                                       jarray[n].prim_type      = PT_BOOL;
+                                                                       break;
+
+                                                               case 'F':
+                                                               case 'f':
+                                                                       jarray[n].prim_type      = PT_BOOL;
+                                                                       jarray[n].v.fv = 0;
+                                                                       break;
+
+                                                               case 'N':                                                                               // assume null, nil, or some variant
+                                                               case 'n':
+                                                                       jarray[n].prim_type      = PT_NULL;
+                                                                       jarray[n].v.fv = 0;
+                                                                       break;
+
+                                                               default:
+                                                                       jarray[n].prim_type      = PT_VALUE;
+                                                                       jarray[n].v.fv = strtod( data, NULL );          // store all numerics as double
+                                                                       break;
+                                                       }
+
+                                                       jarray[n].jsmn_type = JSMN_PRIMITIVE;
+                                                       break;
+
+                                               case JSMN_UNDEFINED:
+                                                       // fallthrough
+                                               default:
+                                                       fprintf( stderr, "[WARN] jwrapper: [%d] array element %d is not valid type (unknown=%d) is not string or primative\n", i, n, jtokens[i].type  );
+                                                       rmr_sym_free( st );
+                                                       return NULL;
+                                                       break;
+                                       }
+                               }
+
+                               i += size - 1;          // must allow loop to push to next
+                               break;
+
+                       case JSMN_STRING:
+                               data = extract( json, &jtokens[i] );
+                               if( (jtp = mk_thing( st, name, jtokens[i].type )) != NULL ) {
+                                       jtp->prim_type = PT_STRING;
+                                       jtp->v.pv =  (void *) data;                                             // just point into the large json string
+                               }
+                               break;
+
+                       case JSMN_PRIMITIVE:
+                               data = extract( json, &jtokens[i] );
+                               if( (jtp = mk_thing( st, name, jtokens[i].type )) != NULL ) {
+                                       switch( *data ) {                                                               // assume T|t is true and F|f is false
+                                               case 'T':
+                                               case 't':
+                                                       jtp->prim_type = PT_BOOL;
+                                                       jtp->v.fv = 1;
+                                                       break;
+
+                                               case 'F':
+                                               case 'f':
+                                                       jtp->prim_type = PT_BOOL;
+                                                       jtp->v.fv = 0;
+                                                       break;
+
+                                               case 'N':                                                                       // Null or some form of that
+                                               case 'n':
+                                                       jtp->prim_type = PT_NULL;
+                                                       jtp->v.fv = 0;
+                                                       break;
+
+                                               default:
+                                                       jtp->prim_type = PT_VALUE;
+                                                       jtp->v.fv = strtod( data, NULL );               // store all numerics as double
+                                                       break;
+                                       }
+                               }
+                               break;
+
+                       default:
+                               fprintf( stderr, "[WARN] jwrapper: element [%d] is undefined or of unknown type\n", i );
+                               break;
+               }
+       }
+
+       free( jtokens );
+       return st;
+}
+
+// --------------- public functions -----------------------------------------------------------------
+
+/*
+       Destroy all operating structures assocaited with the symtab pointer passed in.
+*/
+extern void jw_nuke( void* st ) {
+       char*   buf;                                    // pointer to the original json to free
+
+       if( st != NULL ) {
+               rmr_sym_foreach_class( st, OBJ_SPACE, nix_things, NULL );       // free anything that the symtab references
+               rmr_sym_free( st );                                                                                     // free the symtab itself
+       }
+}
+
+/*
+       Scan the given st and write some useful (?) info to stderr.
+*/
+extern void jw_dump( void* st ) {
+       if( st != NULL ) {
+               rmr_sym_foreach_class( st, OBJ_SPACE, dump_things, NULL );
+       } else {
+               fprintf( stderr, "<DBUG> jwrapper: dump: no table\n" );
+       }
+}
+
+/*
+       Given a json string, parse it, and put the things into a symtab.
+       return the symtab pointer to the caller. They pass the symtab
+       pointer back to the various get functions.
+
+       This is the entry point. It sets up the symbol table and invokes the parse object
+       funtion to start at the first level. Parse object will recurse for nested objects
+       if present.
+*/
+extern void* jw_new( const char* json ) {
+       void    *st;                            // symbol table
+       char*   djson;                          // dup so we can save it
+       void*   rp = NULL;                      // return value
+
+       if( json != NULL && (st = rmr_sym_alloc( MAX_THINGS )) != NULL ) {
+               djson = strdup( json );                                                                                                 // allows user to free/overlay their buffer as needed
+               rmr_sym_put( st, (unsigned char *) JSON_SYM_NAME, OBJ_SPACE, djson );   // must have a reference to the string until symtab is trashed
+
+               rp =  parse_jobject( st,  djson, "" );                                                                  // empty prefix for the root object
+       }
+
+       return rp;
+}
+
+/*
+       Returns true (1) if the named field is missing.
+*/
+extern int jw_missing( void* st, const char* name ) {
+       int rv = 0;
+
+       if( st != NULL && name != NULL ) {
+               rv = rmr_sym_get( st, name, OBJ_SPACE ) == NULL;
+       }
+
+       return rv;
+}
+
+/*
+       Returns true (1) if the named field is in the blob;
+*/
+extern int jw_exists( void* st, const char* name ) {
+       int rv = 0;
+
+       if( st != NULL && name != NULL ) {
+               rv =  rmr_sym_get( st, name, OBJ_SPACE ) != NULL;
+       }
+
+       return rv;
+}
+
+/*
+       Returns true (1) if the primative type is value (double).
+*/
+extern int jw_is_value( void* st, const char* name ) {
+       jthing_t* jtp;                                                                  // thing that is referenced by the symtab
+       int rv = 0;
+
+       if( st != NULL &&
+               name != NULL &&
+               (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
+
+               rv = jtp->prim_type == PT_VALUE;
+       }
+
+       return rv;
+}
+/*
+       Returns true (1) if the primative type is string.
+*/
+extern int jw_is_string( void* st, const char* name ) {
+       jthing_t* jtp;                                                                  // thing that is referenced by the symtab
+       int rv = 0;
+
+       if( st != NULL &&
+               name != NULL &&
+               (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
+
+               rv = jtp->prim_type == PT_STRING;
+       }
+
+       return rv;
+}
+
+/*
+       Returns true (1) if the primative type is boolean.
+*/
+extern int jw_is_bool( void* st, const char* name ) {
+       jthing_t* jtp;                                                                  // thing that is referenced by the symtab
+       int             rv = 0;
+
+       if( st != NULL &&
+               name != NULL &&
+               (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
+
+               rv =  jtp->prim_type == PT_BOOL;
+       }
+
+       return rv;
+}
+
+/*
+       Returns true (1) if the primative type was a 'null' type.
+*/
+extern int jw_is_null( void* st, const char* name ) {
+       jthing_t* jtp;                                                                  // thing that is referenced by the symtab
+       int             rv = 0;
+
+       if( st != NULL &&
+               name != NULL &&
+               (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
+
+               rv = jtp->prim_type == PT_NULL;
+       }
+
+       return rv;
+}
+
+/*
+       Look up the name in the symtab and return the string (data).
+*/
+extern char* jw_string( void* st, const char* name ) {
+       jthing_t* jtp;                                                                  // thing that is referenced by the symtab
+       char*           rv = NULL;
+
+       if( st != NULL &&
+               name != NULL &&
+               (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
+
+               if( jtp->jsmn_type == JSMN_STRING ) {
+                       rv = (char *) jtp->v.pv;
+               }
+       }
+
+       return rv;
+}
+
+/*
+       Look up name and return the value.
+*/
+extern double jw_value( void* st, const char* name ) {
+       jthing_t* jtp;                                                                  // thing that is referenced by the symtab
+       double  rv = 0.0;
+
+       if( st != NULL &&
+               name != NULL &&
+               (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
+
+               if( jtp->jsmn_type == JSMN_PRIMITIVE ) {
+                       rv = jtp->v.fv;
+               }
+       }
+
+       return rv;
+}
+
+/*
+       Look up name and return the blob (symtab).
+*/
+extern void* jw_blob( void* st, const char* name ) {
+       jthing_t* jtp;                                                                  // thing that is referenced by the symtab
+       void*   rv = NULL;
+
+       if( st != NULL &&
+               name != NULL &&
+               (jtp = (jthing_t *) rmr_sym_get( st, name, OBJ_SPACE )) != NULL ) {
+
+               if( jtp->jsmn_type == JSMN_OBJECT ) {
+                       rv = (void *) jtp->v.pv;
+               }
+       }
+
+       return rv;
+}
+
+/*
+       Look up the element and return boolean state; This takes the C approach and
+       returns true/false based on the value.
+*/
+extern int jw_bool_ele( void* st, const char* name, int idx ) {
+       jthing_t* jtp;                                                                  // thing that is referenced by the symtab entry
+       int             rv = 0;
+
+       if( st != NULL &&
+               name != NULL &&
+               (jtp = suss_element( st, name, idx )) != NULL ) {
+
+                       rv = !! ((int) jtp->v.fv);
+       }
+
+       return rv;
+}
+/*
+       Look up array element as a string. Returns NULL if:
+               name is not an array
+               name is not in the hash
+               index is out of range
+               element is not a string
+*/
+extern char* jw_string_ele( void* st, const char* name, int idx ) {
+       jthing_t* jtp;                                                                  // thing that is referenced by the symtab entry
+       char*           rv = NULL;
+
+       if( st != NULL &&
+               name != NULL &&
+               (jtp = suss_element( st, name, idx )) != NULL ) {
+
+               if( jtp->jsmn_type == JSMN_STRING ) {
+                       rv = (char *) jtp->v.pv;
+               }
+       }
+
+       return rv;
+}
+
+/*
+       Look up array element as a value. Returns 0 if:
+               name is not an array
+               name is not in the hash
+               index is out of range
+               element is not a value
+*/
+extern double jw_value_ele( void* st, const char* name, int idx ) {
+       jthing_t*       jtp;                                                    // thing that is referenced by the symtab entry
+       double          rv = 0.0;
+
+       if( st != NULL &&
+               name != NULL &&
+               (jtp = suss_element( st, name, idx )) != NULL ) {
+
+               if( jtp->prim_type == PT_VALUE ) {
+                       rv = jtp->v.fv;
+               }
+       }
+
+       return rv;
+}
+/*
+       Look up the element and check to see if it is a string.
+       Return true (1) if it is.
+*/
+extern int jw_is_string_ele( void* st, const char* name, int idx ) {
+       jthing_t* jtp;                                                                  // thing that is referenced by the symtab entry
+       int             rv = 0;
+
+       if( st != NULL &&
+               name != NULL &&
+               (jtp = suss_element( st, name, idx )) != NULL ) {
+
+                       rv = jtp->prim_type == PT_STRING;
+       }
+
+       return rv;
+}
+
+/*
+       Look up the element and check to see if it is a value primative.
+       Return true (1) if it is.
+*/
+extern int jw_is_value_ele( void* st, const char* name, int idx ) {
+       jthing_t* jtp;                                                                  // thing that is referenced by the symtab entry
+       int             rv = 0;
+
+       if( st != NULL &&
+               name != NULL &&
+               (jtp = suss_element( st, name, idx )) != NULL ) {
+
+                       rv = jtp->prim_type == PT_VALUE;
+       }
+
+       return rv;
+}
+
+/*
+       Look up the element and check to see if it is a boolean primative.
+       Return true (1) if it is.
+*/
+extern int jw_is_bool_ele( void* st, const char* name, int idx ) {
+       jthing_t* jtp;                                                                  // thing that is referenced by the symtab entry
+       int             rv = 0;
+
+       if( st != NULL &&
+               name != NULL &&
+               (jtp = suss_element( st, name, idx )) != NULL ) {
+
+                       rv = jtp->prim_type == PT_BOOL;
+       }
+
+       return rv;
+}
+
+/*
+       Look up the element and check to see if it is a null primative.
+       Return true (1) if it is.
+*/
+extern int jw_is_null_ele( void* st, const char* name, int idx ) {
+       jthing_t* jtp;                                                                  // thing that is referenced by the symtab entry
+       int             rv = 0;
+
+       if( st != NULL &&
+               name != NULL &&
+               (jtp = suss_element( st, name, idx )) != NULL ) {
+
+                       rv =  jtp->prim_type == PT_NULL;
+       }
+
+       return rv;
+}
+
+/*
+       Look up array element as an object. Returns NULL if:
+               name is not an array
+               name is not in the hash
+               index is out of range
+               element is not an object
+
+       An object in an array is a standalone symbol table. Thus the object
+       is treated differently than a nested object whose members are a
+       part of the parent namespace.  An object in an array has its own
+       namespace.
+*/
+extern void* jw_obj_ele( void* st, const char* name, int idx ) {
+       jthing_t* jtp;                                                                  // thing that is referenced by the symtab entry
+       void*           rv = NULL;
+
+       if( st != NULL &&
+               name != NULL &&
+               (jtp = suss_element( st, name, idx )) != NULL ) {
+
+               if( jtp->jsmn_type == JSMN_OBJECT ) {
+                       rv = (void *) jtp->v.pv;
+               }
+       }
+
+       return rv;
+}
+
+/*
+       Return the size of the array named. Returns -1 if the thing isn't an array,
+       and returns the number of elements otherwise.
+*/
+extern int jw_array_len( void* st, const char* name ) {
+       jthing_t* jtp;                                                                  // thing that is referenced by the symtab entry
+       int             rv = -1;
+
+       if( st != NULL &&
+               name != NULL &&
+               (jtp = suss_array( st, name )) != NULL ) {
+
+               rv = jtp->nele;
+       }
+
+       return rv;
+}
diff --git a/src/json/jwrapper.h b/src/json/jwrapper.h
new file mode 100644 (file)
index 0000000..c560dbc
--- /dev/null
@@ -0,0 +1,76 @@
+// 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:       jwrapper.h
+       Abstract:       Header file for jwrapper; mostly prototypes.
+
+       Date:           26 June 2020
+       Author:         E. Scott Daniels
+*/
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef _JWRAPPER_H
+#define _JWRAPPER_H
+
+
+//---------------- jwrapper -------------------------------------------------------------------------------
+extern void jw_nuke( void* st );
+extern void* jw_new( const char* json );
+
+extern int jw_bool_ele( void* st, const char* name, int idx );
+extern void jw_dump( void* st );
+extern int jw_missing( void* st, const char* name );
+extern int jw_exists( void* st, const char* name );
+extern char* jw_string( void* st, const char* name );
+extern double jw_value( void* st, const char* name );
+extern void* jw_blob( void* st, const char* name );
+extern char* jw_string_ele( void* st, const char* name, int idx );
+extern double jw_value_ele( void* st, const char* name, int idx );
+extern void* jw_obj_ele( void* st, const char* name, int idx );
+extern int jw_array_len( void* st, const char* name );
+
+extern int jw_is_string( void* st, const char* name );
+extern int jw_is_value( void* st, const char* name );
+extern int jw_is_bool( void* st, const char* name );
+extern int jw_is_null( void* st, const char* name );
+
+extern int jw_is_string_ele( void* st, const char* name, int idx );
+extern int jw_is_value_ele( void* st, const char* name, int idx );
+extern int jw_is_bool_ele( void* st, const char* name, int idx );
+extern int jw_is_null_ele( void* st, const char* name, int idx );
+
+// ---------------- jw_xapi ---------------------------------------------------------------------------------
+extern int jwx_get_bool( void* jblob, char const* field_name, int def_value );
+extern double jwx_get_value( void* jblob, char const* field_name, double def_value );
+extern int jwx_get_ivalue( void* jblob, char const* field_name, int def_value );
+extern char* jwx_get_value_as_str( void* jblob, char const* field_name, char const* def_value, int  fmt );
+extern char* jwx_get_str( void* jblob, char const* field_name, char const* def_value );
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
index fcbf361..0abcc17 100644 (file)
@@ -1,7 +1,9 @@
 
 coverage_opts = -ftest-coverage -fprofile-arcs
 
-binaries = unit_test 
+binaries = unit_test
+
+tests::        unit_test jhash_test
 
 # RMR emulation
 rmr_em.o::     rmr_em.c
@@ -11,6 +13,14 @@ unit_test:: unit_test.cpp rmr_em.o
        # do NOT link the xapp lib; we include all modules in the test programme
        g++ -g $(coverage_opts) -I ../src/messaging unit_test.cpp -o unit_test rmr_em.o  -lpthread
 
+# build a special jwrapper object with coverage settings
+jwrapper_test.o:: ../src/json/jwrapper.c ../src/json/jwrapper.h
+       cc $(coverage_opts)  -I  ../src/json -I ../ext/jsmn  ../src/json/jwrapper.c -c -o jwrapper_test.o
+
+jhash_test:: jwrapper_test.o jhash_test.cpp
+       # do NOT link the xapp lib; we include all modules in the test programme
+       g++ -g $(coverage_opts) -I ../src/json -I ../ext/jsmn jhash_test.cpp -o jhash_test jwrapper_test.o -lrmr_si -lpthread
+
 # prune gcov files generated by system include files
 clean::
        rm -f *.h.gcov *.c.gcov
diff --git a/test/jhash_test.cpp b/test/jhash_test.cpp
new file mode 100644 (file)
index 0000000..be4171c
--- /dev/null
@@ -0,0 +1,268 @@
+// vim: ts=4 sw=4 noet :
+/*
+==================================================================================
+       Copyright (c) 2020 Nokia
+       Copyright (c) 2020 AT&T Intellectual Property.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================================
+*/
+
+/*
+       Mnemonic:       json_test.cpp
+       Abstract:       Unit test for the json module. This expects that a static json
+                               file exist in the current directory with a known set of fields,
+                               arrays and objects that can be sussed out after parsing. The
+                               expected file is test.json.
+
+       Date:           26 June 2020
+       Author:         E. Scott Daniels
+*/
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include <string>
+#include <memory>
+
+
+/*
+       Very simple file reader. Reads up to 8k into a single buffer and
+       returns the buffer as char*.  Easier to put json test things in
+       a file than strings.
+*/
+static char* read_jstring( char* fname ) {
+       char*   rbuf;
+       int     fd;
+       int len;
+
+       rbuf = (char *) malloc( sizeof( char ) * 8192 );
+       fd = open( fname, O_RDONLY, 0 );
+       if( fd < 0  ) {
+               fprintf( stderr, "<ABORT> can't open test file: %s: %s\n", fname, strerror( errno ) );
+               exit( 1 );
+       }
+
+       len = read( fd, rbuf, 8190 );
+       if( len < 0  ) {
+               close( fd );
+               fprintf( stderr, "<ABORT> read from file failed: %s: %s\n", fname, strerror( errno ) );
+               exit( 1 );
+       }
+
+       rbuf[len] = 0;
+       close( fd );
+
+       return rbuf;
+}
+
+// this also tests jwrapper.c but that is built as a special object to link in
+// rather than including here.
+//
+#include "../src/json/jhash.hpp"
+#include "../src/json/jhash.cpp"
+
+#include "ut_support.cpp"
+
+int main( int argc, char** argv ) {
+       int             errors = 0;
+       Jhash*  jh;
+       char*   jstr;
+       std::string     sval;
+       double  val;
+       bool    state;
+       int             i;
+       int             len;
+       int             true_count = 0;
+
+       set_test_name( "jhash_test" );
+       jstr = read_jstring( (char *) "test.json" );                    // read and parse the json
+
+       fprintf( stderr, "read: (%s)\n", jstr );
+
+       jh = new Jhash( jstr );
+
+       if( jh == NULL ) {
+               fprintf( stderr, "<FAIL> could not parse json string from: test.json\n" );
+               exit( 1 );
+       }
+
+
+       sval = jh->String( (char *) "meeting_day" );
+       fprintf( stderr, "<INFO> sval=(%s)\n", sval.c_str() );
+       errors += fail_if( sval.compare( "" ) == 0, "did not get meeting day string" );
+       errors += fail_if( sval.compare( "Tuesday" ) != 0, "meeting day was not expected string" );
+
+       sval = jh->String( (char *) "meeting_place" );
+       fprintf( stderr, "<INFO> sval=(%s)\n", sval.c_str() );
+       errors += fail_if( sval.compare( "" ) == 0, "did not get meeting place" );
+       errors += fail_if( sval.compare( "16801 East Green Drive" ) != 0, "meeting place stirng was not correct" );
+
+       state = jh->Exists( (char *) "meeting_place" );
+       errors += fail_if( !state, "test for meeting place exists did not return true" );
+
+       state = jh->Exists( (char *) "no-name" );
+       errors += fail_if( state, "test for non-existant thing returned true" );
+
+       state = jh->Is_missing( (char *) "no-name" );
+       errors += fail_if( !state, "missing test for non-existant thing returned false" );
+
+       state = jh->Is_missing( (char *) "meeting_place" );
+       errors += fail_if( state, "missing test for existing thing returned true" );
+
+       val = jh->Value( (char *) "lodge_number" );
+       errors += fail_if( val != 41.0, "lodge number value was not correct" );
+
+       val = jh->Value( (char *) "monthly_dues" );
+       fprintf( stderr, "<INFO> got dues: %.2f\n", val );
+       errors += fail_if( val != (double) 43.5, "lodge dues value was not correct" );
+
+       len = jh->Array_len( (char *) "members" );
+       fprintf( stderr, "<INFO> got %d members\n", len );
+       errors += fail_if( len != 4, "array length was not correct" );
+       if( len > 0 ) {
+               for( i = 0; i < len; i++ ) {
+                       if( ! jh->Set_blob_ele( (char *) "members", i ) ) {
+                               errors++;
+                               fprintf( stderr, (char *) "couldn't set blob for element %d\n", i );
+                       } else {
+                               fprintf( stderr, (char *) "<INFO> testing element %d of %d\n", i, len );
+
+                               state = jh->Is_value( (char *) "age" );
+                               errors += fail_if( !state, "is value test for age returned false" );
+                               state = jh->Is_value( (char *) "married" );
+                               errors += fail_if( state, "is value test for married returned true" );
+
+                               state = jh->Is_string( (char *) "occupation" );
+                               errors += fail_if( !state, "is string test for spouse returned false" );
+                               state = jh->Is_string( (char *) "married" );
+                               errors += fail_if( state, "is string test for married returned true" );
+
+                               state = jh->Is_bool( (char *) "married" );
+                               errors += fail_if( !state, "is bool test for married returned false" );
+                               state = jh->Is_bool( (char *) "occupation" );
+                               errors += fail_if( state, "is bool test for spouse returned true" );
+
+                               val = jh->Value( (char *) "age" );
+                               fprintf( stderr, "<INFO> got age: %.2f\n", (double) val );
+                               errors += fail_if( val < 0,  "age value wasn't positive" );
+
+                               sval = jh->String( (char *) "name" );
+                               fprintf( stderr, "<INFO> sval=(%s)\n", sval.c_str() );
+                               errors += fail_if( sval.compare( "" ) == 0, "no name found in element" );
+
+                               if( jh->Bool( (char *) "married" ) ) {
+                                       true_count++;
+                               }
+                       }
+
+                       jh->Unset_blob();               // must return to root
+               }
+
+               fprintf( stderr, "<INFO> true count = %d\n", true_count );
+               errors += fail_if( true_count != 3, "married == true count was not right" );
+       }
+
+       state = jh->Set_blob( (char *) "no-such-thing" );
+       errors += fail_if( state, "setting blob to non-existant blob returned true" );
+
+       state = jh->Set_blob( (char *) "grand_poobah" );
+       errors += fail_if( !state, "setting blob to existing blob failed" );
+       if( state ) {
+               sval  = jh->String( (char *) "elected" );
+               fprintf( stderr, "<INFO> sval=(%s)\n", sval.c_str() );
+               errors += fail_if( sval != "February 2019", "blob 'elected' didn't return the expected string" );
+
+               state = jh->Exists( (char *) "monthly_dues" );
+               errors += fail_if( state, "blob that shouldn't have a field reports it does" );
+
+               jh->Unset_blob( );                                                      // ensure that this is found once we unset to root
+               state = jh->Exists( (char *) "monthly_dues" );
+               errors += fail_if( !state, "after rest, root blob, that should have a field, reports it does not" );
+       }
+
+
+       // ---- test array element value type checks -------------------------------------------------
+       state = jh->Is_string_ele( (char *) "sponser", 1 );
+       errors += fail_if( !state, "string element check on sponser failed" );
+       state = jh->Is_string_ele( (char *) "current_on_dues", 1 );
+       errors += fail_if( state, "string element check on non-stirng element returned true" );
+
+       state = jh->Is_value_ele( (char *) "dues_assistance", 1 );
+       errors += fail_if( !state, "string element check on sponser failed" );
+       state = jh->Is_value_ele( (char *) "current_on_dues", 1 );
+       errors += fail_if( state, "string element check on non-stirng element returned true" );
+
+       state = jh->Is_bool_ele( (char *) "current_on_dues", 1 );
+       errors += fail_if( !state, "string element check on sponser failed" );
+       state = jh->Is_bool_ele( (char *) "sponser", 1 );
+       errors += fail_if( state, "string element check on non-stirng element returned true" );
+
+       state = jh->Is_null( (char *) "nvt" );
+       errors += fail_if( !state, "test for nil value returned false" );
+       state = jh->Is_null( (char *) "lodge_number" );
+       errors += fail_if( state, "nil test for non-nil value returned true" );
+
+       state = jh->Is_null_ele( (char *) "nvat", 0 );
+       errors += fail_if( !state, "test for nil array element value returned false" );
+
+
+       // ---- test sussing of elements from arrays -------------------------------------------------
+       sval = jh->String_ele( (char *) "sponser", 1 );
+       errors += fail_if( sval.compare( "" ) == 0, "get string element failed for sponser" );
+
+       val = jh->Value_ele( (char *) "dues_assistance", 1 );
+       errors += fail_if( val == 0.0, "get value element for dues_assistance was zero" );
+
+       state = jh->Bool_ele( (char *) "current_on_dues", 1 );
+       errors += fail_if( state, "bool ele test returned true for a false value" );
+       state = jh->Bool_ele( (char *) "current_on_dues", 0 );
+       errors += fail_if( !state, "bool ele test returned false for a true value" );
+
+
+       val = jh->Value( (char *) "timestamp" );
+       fprintf( stderr, "<INFO> timestamp: %.10f\n", val );
+
+
+
+
+
+       delete jh;
+
+       fprintf( stderr, "<INFO> testing for failures; jwrapper error and warning messages expected\n" );
+       // ---- these shouild all fail to parse, generate warnings to stderr, and drive error handling coverage ----
+    jh = new Jhash( (char *) "{ \"bad\": [ [ 1, 2, 3 ], [ 3, 4, 5]] }" );              // drive the exception process for bad json
+       delete jh;
+
+    jh = new Jhash( (char *) " \"bad\":  5 }" );                       // no opening brace
+       state = jh->Parse_errors();
+       errors += fail_if( !state, "parse errors check returned false when known errors exist" );
+       delete jh;
+
+    jh = new Jhash( (char *) "{ \"bad\":  fred }" );           // no quotes
+       delete jh;
+
+    jh = new Jhash( (char *) "{ \"bad:  456, \"good\": 100 }" );                       // missing quote; impossible to detect error
+       jh->Dump();                                                                                                                             // but dump should provide details
+       fprintf( stderr, "<INFO> good value=%d\n", (int) val );
+       delete jh;
+
+
+       // ---------------------------- end housekeeping ---------------------------
+       announce_results( errors );
+       return !!errors;
+}
index 349fca2..51f0815 100755 (executable)
@@ -44,7 +44,6 @@ function mk_list {
        done 
 }
 
-
 list=$( mk_list )
 if [[ -n $list ]]
 then
diff --git a/test/test.json b/test/test.json
new file mode 100644 (file)
index 0000000..6fd3ad1
--- /dev/null
@@ -0,0 +1,50 @@
+{
+       "lodge_number":         41,
+       "meeting_day":          "Tuesday",
+       "meeting_place":        "16801 East Green Drive",
+       "monthly_dues":         43.50,
+
+       "grand_poobah": {
+               "name": "Larry K. Slate",
+               "elected":      "February 2019",
+               "term":         "2 years"
+       },
+
+       "members": [
+               {
+                       "name" : "Fred Flintstone",
+                       "occupation" : "operator",
+                       "age" : 45,
+                       "married" : true,
+                       "spouse" : "Wilma Flintsone"
+               },
+               {
+                       "name" : "Barney Rubble",
+                       "occupation" : "sidekick",
+                       "age" : 43,
+                       "married" : true,
+                       "spouse" : "Betty Rubble"
+               },
+               {
+                       "name" : "Larry K. Slate",
+                       "occupation" : "manager",
+                       "age" : 52,
+                       "married" : true,
+                       "spouse" : "Linda Slate"
+               },
+               {
+                       "name" : "Benjamin Gravel",
+                       "occupation" : "sculpter",
+                       "age" : 25,
+                       "married" : false
+               }
+       ],
+
+       "timestamp": 1593541361.450337059,
+       "comment": "these are to drive array building in jwrapper",
+       "current_on_dues": [ true, false, true, true ],
+       "dues_assistance": [ 0.2, 0.1, 0.25, 0.2 ],
+       "sponser": [ "slate", "slate", "stonewall", "brick" ],
+       "nvat": [  null, null ],
+       "nvt": null
+}
index 7144f9c..2f30686 100755 (executable)
 
 # Make a list of our modules under test so that we don't look at gcov
 # files that are generated for system lib headers in /usr/*
-# (bash makes the process of building a list of names  harder than it 
+# (bash makes the process of building a list of names  harder than it
 # needs to be, so use caution with the printf() call.)
 #
 function mk_list {
        grep -l "Source:\.\./src"  *.gcov | while read f
        do
                printf "$f "            # do NOT use echo or add \n!
-       done 
+       done
 }
 
 function abort_if_error {
@@ -70,18 +70,30 @@ do
        shift
 done
 
+export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
+export LIBRARY_PATH=/usr/local/lib:$LIBRARY_PATH
+
 make nuke >/dev/null
-make unit_test >/tmp/PID$$.log 2>&1
+make tests >/tmp/PID$$.log 2>&1
 abort_if_error $? "unable to make"
+echo "tests successfully built" >&2
 
 spew="cat"
-./unit_test >/tmp/PID$$.log 2>&1
-abort_if_error $? "unit test failed"
 
-gcov unit_test >/tmp/PID$$.gcov_log 2>&1       # suss out our gcov files
+for x in unit_test jhash_test
+do
+       ./$x >/tmp/PID$$.log 2>&1
+       abort_if_error $? "test failed: $x"
+       gcov $x.c >/dev/null 2>&1
+done
+
+# wrapper_test is driven by jhash_test, but must be covered explicitly
+gcov jwrapper_test.c >/dev/null 2>&1
+
 ./scrub_gcov.sh                                                                # remove cruft
 
 list=$( mk_list )
+echo ""
 echo "[INFO] coverage stats, discounted (raw), for the various modules:"
 ./parse_gcov.sh $list                                          # generate simple, short, coverage stats
 
diff --git a/test/ut_support.cpp b/test/ut_support.cpp
new file mode 100644 (file)
index 0000000..8e6be90
--- /dev/null
@@ -0,0 +1,69 @@
+// vi: ts=4 sw=4 noet:
+/*
+==================================================================================
+    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:  ut_support.c
+    Abstract:  Tools to make unit testing easier.
+
+    Date:       16 June 2020
+    Author:     E. Scott Daniels
+*/
+
+#include <string>
+
+static std::string test_name = "unknown";
+
+/*
+       Set the name of the current tester
+*/
+extern void set_test_name( std::string name ) {
+       test_name = name;
+}
+
+/*
+       Returns 1 if the condition is true (not zero)
+*/
+extern int fail_if( int cond, std::string reason ) {
+       if( cond ) {
+               fprintf( stderr, "<FAIL> %s: %s\n", test_name.c_str(), reason.c_str() );
+               return 1;
+       }
+
+       return 0;
+}
+
+/*
+       Returns 1 if the condition is false.
+*/
+extern int fail_if_false( int cond, std::string reason ) {
+       if( !cond ) {
+               fprintf( stderr, "<FAIL> %s: %s\n", test_name.c_str(), reason.c_str() );
+               return 1;
+       }
+
+       return 0;
+}
+
+extern void announce_results( int errors ) {
+       if( errors > 0 ) {
+               fprintf( stderr, "<FAIL> %s: failed with %d errors\n", test_name.c_str(), errors );
+       } else {
+               fprintf( stderr, "<PASS> %s: passed with\n", test_name.c_str() );
+       }
+}