--- /dev/null
+[submodule "ext/jsmn"]
+ path = ext/jsmn
+ url = https://github.com/zserge/jsmn
# 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
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}" )
# 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
#
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()
.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
.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
xAPP Framework.
+2020 June 29; version 1.2.0
+---------------------------
+
+Add support for json parsing
+
+
+
2020 June 26; version 1.1.0
---------------------------
&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
*.txt
*.html
*.sp
+*.ca
+*.toc
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
%.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
$(docs:%=%.rst): always
publish: user_guide.rst
- cp *.rst ../../../docs/
+ cp user_guide.rst ../../../docs/user-guide.rst
# intermeidate junk that might straggle
clean:
&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
&half_space
&indent
-&beg_list &lic1
+&beg_list(&lic1)
&item Replying to the sender of a received message
&half_space
--- /dev/null
+.** 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
+
.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
xAPP Framework.
+2020 June 29; version 1.2.0
+---------------------------
+
+Add support for json parsing
+
+
+
2020 June 26; version 1.1.0
---------------------------
-
-
-.. 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
std::atomic<long> icount; // messages ignored
std::atomic<int> hdr; // number of messages before next header
} cb_info_t;
+
// ----------------------------------------------------------------------
+
/*
Dump bytes to tty.
*/
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-- ) {
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() );
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.
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";
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
}
ai++;
break;
+
case 't': // thread count
nthreads = atoi( argv[ai+1] );
if( nthreads < 1 ) {
}
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] "
"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] );
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;
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)
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;
}
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 ) {
} else {
msg = xfw->Alloc_msg( 2048 );
}
+
if( delay > 0 ) {
usleep( delay );
}
}
}
-
-
- Figure 10: Simple looping sender application.
-
+
+ Figure 10: Simple looping sender application.
+
--- /dev/null
+Subproject commit 7b6858a5855299d173c5ab2b46e611bf9961cbef
--- /dev/null
+# 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()
+
--- /dev/null
+
+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.
+
--- /dev/null
+// 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;
+}
+
--- /dev/null
+// 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
--- /dev/null
+
+// 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;
+}
--- /dev/null
+// 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
+
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
# 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
--- /dev/null
+// 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;
+}
done
}
-
list=$( mk_list )
if [[ -n $list ]]
then
--- /dev/null
+{
+ "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
+}
# 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 {
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
--- /dev/null
+// 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() );
+ }
+}