Add support for config file parsing and watching
[ric-plt/xapp-frame-cpp.git] / docs / user-guide.rst
index a4ab5e9..d9137a3 100644 (file)
-     
-.. This work is licensed under a Creative Commons Attribution 4.0 International License. 
-.. SPDX-License-Identifier: CC-BY-4.0 
-.. 
-.. CAUTION: this document is generated from source in doc/src/* 
-.. To make changes edit the source and recompile the document. 
-.. Do NOT make changes directly to .rst or .md files. 
-============================================================================================ 
-RIC xAPP C++ Framework 
-============================================================================================ 
--------------------------------------------------------------------------------------------- 
-User's Guide 
--------------------------------------------------------------------------------------------- 
-Introduction 
-============================================================================================ 
-The C++ framework allows the programmer to create an xApp 
-object instance, and to use that instance as the logic base. 
-The xApp object provides a message level interface to the RIC 
-Message Router (RMR), including the ability to register 
-callback functions which the instance will drive as messages 
-are received; much in the same way that an X-windows 
-application is driven by the window manager for all activity. 
-The xApp may also choose to use its own send/receive loop, 
-and thus is not required to use the callback driver mechanism 
-provided by the framework. 
-The Framework API 
-============================================================================================ 
-The C++ framework API consists of the creation of the xApp 
-object, and invoking desired functions via the instance of 
-the object. The following paragraphs cover the various steps 
-involved to create an xApp instance, wait for a route table 
-to arrive, send a message, and wait for messages to arrive. 
-Creating the xApp instance 
--------------------------------------------------------------------------------------------- 
-The creation of the xApp instance is as simple as invoking 
-the object's constructor with two required parameters: 
-       
-      port 
-          
-         A C string (pointer to char) which defines the port that RMR will 
-         open to listen for connections. 
-          
-       
-      wait 
-          
-         A Boolean value which indicates whether or not the 
-         initialization process should wait for the arrival of a 
-         valid route table before completing. When true is 
-         supplied, the initialization will not complete until RMR 
-         has received a valid route table (or one is located via 
-         the RMR_SEED_RT environment variable). 
-       
-       
-      The following code sample illustrates the simplicity of 
-      creating the instance of the xApp object. 
-       
-       
-      :: 
-         
-            #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. 
-       
-       
-      :: 
-         
-           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: 
-       
-       
-      :: 
-         
-            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). 
-       
-      :: 
-         
-            #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. 
-       
-       
-      :: 
-         
-           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. 
-       
-       
-      :: 
-         
-            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. 
-       
-       
-      :: 
-         
-            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). 
-       
-       
-      :: 
-         
-            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. 
-       
-       
-       
-      :: 
-         
-        #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
-            by the framework.
-        */
-        typedef struct {
-            int        vlevel;             // verbosity level
-            bool    forward;            // if true, message is forwarded
-            int        stats_freq;         // header/stats after n messages
-            std::atomic<long>    pcount; // messages processed
-            std::atomic<long>    icount; // messages ignored
-            std::atomic<int>    hdr;    // number of messages before next header
-        } cb_info_t;
-        // ----------------------------------------------------------------------
-        /*
-            Dump bytes to tty.
-        */
-        void dump( unsigned const char* buf, int len ) {
-            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, "   " );
-                }
-                cheater[j] = 0;
-                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() );
-                if( cbi.vlevel > 0 ) {
-                    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 ai = 1;                    // arg processing index
-            cb_info_t*    cbi;
-            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
-                            cbi->stats_freq = 5;
-                        }
-                        ai++;
-                        break;
-                    case 't':                        // thread count
-                        nthreads = atoi( argv[ai+1] );
-                        if( nthreads < 1 ) {
-                            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] "
-                                        "[-s stats-freq]  [-t thread-count] "
-                                        "[-v | -V n] msg-type1 ... msg-typen\\n",
-                                        argv[0] );
-                        fprintf( stderr, "\\tstats frequency is based on # of messages received\\n" );
-                        fprintf( stderr, "\\tverbose levels (-V) 0 counts only, "
-                                        "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] );
-                ai++;
-                fprintf( stderr, "<RD> capturing messages for type %d\\n", mtype );
-                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. 
-       
-       
-      :: 
-         
-        #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, 
-                    Msg_component payload,  void* data ) {
-            cb2_count++;
-        }
-        // default to count all unrecognised messages
-        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. 
-       
-       
-      :: 
-         
-        #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 ai;
-            int response_to = 0;                // max timeout wating for a response
-            char*    port = (char *) "4555";
-            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)
-                    case 'd':
-                        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++;
-                        break;
-                }
-                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 ) {
-                    rmtype = msg->Get_mtype();
-                    payload = msg->Get_payload();
-                    fprintf( stderr, "got: mtype=%d payload=(%s)\\n",
-                        rmtype, (char *) payload.get() );
-                } else {
-                    msg = xfw->Alloc_msg( 2048 );
-                }
-                if( delay > 0 ) {
-                    usleep( delay );
-                }
-            }
-        }
-       
-       
-      Figure 10: Simple looping sender application. 
-       
+============
+USER'S GUIDE
+============
+.. 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.
+
+
+
+
+INTRODUCTION
+============
+
+The C++ framework allows the programmer to create an instance
+of the ``Xapp`` object which then can be used as a foundation
+for the application. 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.
+
+
+Termonology
+-----------
+
+To avoid confusion the term **xAPP** is used in this document
+to refer to the user's application code which is creating
+``Xapp,`` and related objects provided by the *framework.*
+The use of *framework* should be taken to mean any of the
+classes and/or support functions which are provided by the
+``ricxfcpp`` library.
+
+
+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.
+
+
+The Namespace
+-------------
+
+Starting with version 2.0.0 the framwork introduces a
+*namespace* of ``xapp`` for the following classes and types:
+
+
+   * Alarm
+   * Jhash
+   * Message
+   * Msg_component
+
+
+This is a breaking change and as such the major version was
+bumpped from 1 to 2.
+
+
+Creating the xApp instance
+--------------------------
+
+The creation of the xApp instance is as simple as invoking
+the object's constructor with two required parameters:
+
+
+       .. list-table::
+         :widths: auto
+         :header-rows: 0
+         :class: borderless
+
+
+         * - **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``.
+
+
+::
+
+     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:
+
+
+::
+
+      void cb_name( xapp::Message& m, int mtype, int subid,
+            int payload_len, xapp::Msg_component payload,
+            void* usr_data );
+
+Figure 2: Callback function signature
+
+The parameters passed to the callback function are as
+follows:
+
+
+       .. list-table::
+         :widths: auto
+         :header-rows: 0
+         :class: borderless
+
+
+         * - **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( xapp::Message& mbuf, int mtype, int subid, int len,
+                  xapp::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:
+
+
+   * 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.
+
+
+::
+
+      msg->Send_response( xapp::Message::NO_CHANGE, xapp::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.
+
+
+::
+
+      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).
+
+
+::
+
+      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.
+
+
+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.
+
+
+Creating The Jhash Object
+-------------------------
+
+The Jhash object is created simply by passing a json string
+to the constructor.
+
+::
+
+      #include <ricxfcpp/Jhash.hpp>
+
+      std::string jstring = "{ \\"tag\\": \\"Hello World\\" }";
+      Jhash*  jh;
+
+      jh =  new Jhash( jstring.c_str() );
+
+Figure 8: The creation of the Jhash object.
+
+Once the Jhash object has been created any of the methods
+described in the following paragraphs can be used to retrieve
+the data:
+
+
+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 *blob,* limits the scope of visible fields.
+
+As an example, the json contained in figure 9, contains a
+"root" blob and two *sub-blobs* (address and lease_info).
+
+
+::
+
+      {
+          "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"
+          }
+      }
+
+Figure 9: Sample json with a root and two blobs.
+
+Upon creation of the Jhash object, the *root* fields,
+``lodge_name,`` ``member_count,`` and ``grand_poobah`` are
+immediately available. The fields in the *sub-blobs* are
+available only when the correct blob is selected. The code
+sample in figure 10 illustrates how a *sub-blob* is selected.
+
+::
+
+      jh->Set_blob( (char *) "address" );     // select address
+      jh->Unset_blob();                       // return to root
+      jh->Set_blob( (char *) "lease_info" );  // select the lease blob
+
+Figure 10: Blob selection example.
+
+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 ``Set_blob`` function will attempt to
+select the named blob from the current blob, and not the
+root.
+
+
+Simple Value Extraction
+-----------------------
+
+Simple values are the expected data types *string, value,*
+and *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, *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:
+
+
+::
+
+      std::string String( const char* name );
+      float Value( const char* name );
+      bool Bool( const char* name );
+
+
+Each of these functions returns the value associated with the
+field with the given *name.* If the value is missing, the
+following default values are returned:
+
+
+       .. list-table::
+         :widths: 15,80
+         :header-rows: 0
+         :class: borderless
+
+
+         * - **String**
+
+           -
+
+             An empty string (.e.g "").
+
+
+
+             |
+
+
+
+         * - **Value**
+
+           -
+
+             Zero (e.g 0.0)
+
+
+
+             |
+
+
+
+         * - **bool**
+
+           -
+
+             false
+
+
+
+If the user needs to disambiguate between a missing value and
+the default value either the ``Missing`` or ``Exists``
+function should be used first.
+
+
+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 *blob,* therefore it is important to ensure
+that the correct blob is selected before using either of
+these functions. The prototypes for the ``Exists`` and
+``Missing`` functions are below:
+
+::
+
+      bool Exists( const char* name );
+      bool Is_missing( const char* name );
+
+The ``Exists`` function returns *true* if the field name
+exists in the json and *false* otherwise. Conversely, the
+``Missing`` function returns *true* when the field name does
+not exist in the json.
+
+
+Testing Field Type
+------------------
+
+The ``Exists`` and ``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:
+
+::
+
+      bool Is_bool( const char* name );
+      bool Is_null( const char* name );
+      bool Is_string( const char* name );
+      bool Is_value( const char* name );
+
+
+Each of these functions return *true* if the field with the
+given name is of the type being tested for.
+
+
+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
+*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:
+
+::
+
+      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 );
+
+
+For each of these functions the ``eidx`` is the zero based
+element index which is to be tested or selected.
+
+
+Arrays of Blobs
+---------------
+
+An array containing blobs, rather than simple field value
+pairs, the blob must be selected prior to using it, just as a
+sub-blob needed to be selected. The ``Set_blob_ele`` function
+is used to do this and has the following prototype:
+
+::
+
+      bool Set_blob_ele( const char* name, int eidx );
+
+
+As with selecting a sub-blob, an unset must be performed
+before selecting the next blob. Figure 11 illustrates how
+these functions can be used to read and print values from the
+json in figure 12.
+
+::
+
+      "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 }
+      ]
+
+Figure 11: Json array containing blobs.
+
+
+::
+
+      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
+      }
+
+Figure 12: Code to process the array of blobs.
+
+
+
+ALARM MANAGER INTERFACE
+=======================
+
+The C++ framework provides an API which allows the xAPP to
+easily construct and generate alarm messages. Alarm messages
+are a special class of RMR message, allocated in a similar
+fashion as an RMR message through the framework's
+``Alloc_alarm()`` function.
+
+The API consists of the following function types:
+
+
+       .. list-table::
+         :widths: auto
+         :header-rows: 0
+         :class: borderless
+
+
+         * - **Raise**
+
+           -
+
+             Cause the alarm to be assigned a severity and and sent via
+
+             RMR message to the alarm collector process.
+
+
+
+
+
+             |
+
+
+
+         * - **Clear**
+
+           -
+
+             Cause a clear message to be sent to the alarm collector.
+
+
+
+
+
+             |
+
+
+
+         * - **Raise Again**
+
+           -
+
+             Cause a clear followed by a raise message to be sent to
+
+             the alarm collector.
+
+
+
+
+
+Allocating Alarms
+-----------------
+
+The ``xapp`` function provided by the framework is used to
+create an alarm object. Once the xAPP has an alarm object it
+can be used to send one, or more, alarm messages to the
+collector.
+
+The allocation function has three prototypes which allow the
+xAPP to create an alarm with an initial set of information as
+is appropriate. The following are the prototypes for the
+allocate functions:
+
+
+::
+
+    std::unique_ptr<xapp::Alarm> Alloc_alarm( );
+    std::unique_ptr<xapp::Alarm> Alloc_alarm( std::string meid );
+    std::unique_ptr<xapp::Alarm> Alloc_alarm( int prob_id, std::string meid );
+
+Figure 13: Alarm allocation prototypes.
+
+Each of the allocation functions returns a unique pointer to
+the alarm. In the simplest form (1) the alarm is initialised
+with an empty meid (managed element ID) string, and unset
+problem ID (-1). The second prototype allows the xAPP to
+supply the meid, and in the third form both the problem ID
+and the meid are used to initialise the alarm.
+
+
+Raising An Alarm
+----------------
+
+Once an alarm has been allocated, its ``Raise()`` function
+can be used to cause the alarm to be sent to the collector.
+The raise process allows the xAPP to perform the following
+modifications to the alarm before sending the message:
+
+
+   * Set the alarm severity
+
+   * Set the problem ID value
+
+   * Set the alarm information string
+
+   * Set the additional information string
+
+
+The following are the prototypes for the ``Raise()``
+functions of an alarm object: ..... In its simplest form (1)
+the ``Raise()`` function will send the alarm without making
+any changes to the data. The final two forms allow the xAPP
+to supply additional data which is added to the alarm before
+sending the message. Each of the raise functions returns
+``true`` on success and ``false`` if the alarm message could
+not be sent.
+
+
+Severity
+--------
+
+The severity is one of the ``SEV_`` constants listed below.
+These map to alarm collector strings and insulate the xAPP
+from any future alarm collector changes. The specific meaning
+of these severity types are defined by the alarm collector
+and thus no attempt is made to guess what their actual
+meaning is. These constants are available by including
+``alarm.hpp.``
+
+
+   ::
+
+         SEV_MAJOR
+         SEV_MINOR
+         SEV_WARN
+         SEV_DEFAULT
+
+Figure 14: Severity constants available in alarm.hpp.
+
+
+The Problem ID
+--------------
+
+The problem ID is an integer which is assigned by the xAPP.
+The framework makes no attempt to verify that it has been se,
+nor does it attempt to validate the value. If the xAPP does
+not set the value, ``-1`` is used.
+
+
+Information Strings
+-------------------
+
+The two information strings are also xAPP defined and provide
+the information that the xAPP deems necessary and related to
+the alarm. What the collector expects, and how these strings
+are used, is beyond the scope of the framework to describe or
+validate. If not supplied, empty strings are sent in the
+alarm message.
+
+
+Clearing An Alarm
+-----------------
+
+The ``Clear()`` function of an alarm may be used to send a
+clear message. In a manner similar to the ``Raise()``
+functions, the ``Clear()`` functions allow the existing alarm
+data to be sent without change, or for the xAPP to modify the
+data before the message is sent to the collector. The
+following are the prototype for these functions.
+
+::
+
+     bool Clear( );
+     bool Clear( int severity, int problem, std::string info );
+     bool Clear( int severity, int problem, std::string info, std::string addional_info );
+     bool Clear_all( );
+
+
+Figure 15: Clear function prototypes.
+
+Each of the clear functions returns ``true`` on success and
+``false`` if the alarm message could not be sent.
+
+The ``Clear_all()`` function sends a special action code to
+the collector which is assumed to clear all alarms. However,
+it is unknown whether that implies **all** alarms, or all
+alarms matching the ``problem_id,`` or some other
+interpretation. Please consult the alarm collector
+documentation for these specifics.
+
+
+Adjusting Alarm Contents
+------------------------
+
+It might be necessary for the xAPP to adjust the alarm
+contents outside of the scope of the ``Raise()`` function, or
+to adjust data that cannot be manipulated by ``Raise().`` The
+following are the (self explanatory) prototypes for the
+*setter* functions which are available to the xAPP.
+
+
+::
+
+    void Set_additional( std::string new_info );
+    void Set_appid( std::string new_id );
+    void Set_info( std::string new_info );
+    void Set_meid( std::string new_meid );
+    void Set_problem( int new_id );
+    void Set_severity( int new_sev );
+
+Figure 16: Alarm Setters
+
+
+
+METRICS SUPPORT
+===============
+
+The C++ xAPP framework provides a lightweight interface to
+the metrics gateway allowing the xAPP to create and send
+metrics updates without needing to understand the underlying
+message format. From the xAPP's perspective, the metrics
+object is created with one or more key/value measurement
+pairs and then is sent to the process responsible for
+forwarding them to the various collection processes. The
+following sections describe the Metrics object and the API
+associated with it.
+
+
+Creating The Metrics Object
+---------------------------
+
+The ``xapp`` object can be created directly, or via the xapp
+framework. When creating directly the xAPP must supply an RMR
+message for the object to use; when the framework is used to
+create the object, the message is created as as part of the
+process. The framework provides three constructors for the
+metrics instance allowing the xAPP to supply the measurement
+source, the source and reporter, or to default to using the
+xAPP name as both the source and reporter (see section
+*Source and Reporter* for a more detailed description of
+these). The framework constructors are illustrated in figure
+17.
+
+
+::
+
+    std::unique_ptr<xapp::Metrics> Alloc_metrics( );
+    std::unique_ptr<xapp::Metrics> Alloc_metrics( std::string source );
+    std::unique_ptr<xapp::Metrics> Alloc_metrics( std::string reporter, std::string source );
+
+Figure 17: The framework constructors for creating an
+instance of the metrics object.
+
+
+::
+
+
+      #include <ricxfcpp/Metrics>
+
+      char* port = (char *) "4560";
+
+      auto x = std::unique_ptr<Xapp>( new Xapp( port ) );
+      auto reading = std::shared_ptr<xapp::Metrics>( x->Alloc_metric( ) );
+
+Figure 18: Metrics instance creation using the framework.
+
+Figures 18 illustrates how the framework constructor can be
+used to create a metrics instance. While it is unlikely that
+an xAPP will create a metrics instance directly, there are
+three similar constructors available. These are prototypes
+are shown in figure 19 and their use is illustrated in figure
+20.
+
+::
+
+     Metrics( std::shared_ptr<xapp::Message> msg );
+     Metrics( std::shared_ptr<xapp::Message> msg, std::string msource );
+     Metrics( std::shared_ptr<xapp::Message> msg, std::string reporter, std::string msource );
+
+Figure 19: Metrics object constructors.
+
+
+::
+
+      #include <ricxfcpp/Metrics>
+
+      char* port = (char *) "4560";
+
+      auto x = std::unique_ptr<Xapp>( new Xapp( port ) );
+      auto msg = std::shared_ptr<xapp::Message>( x->Alloc_msg( 4096 ) );
+      auto reading = std::shared_ptr<xapp::Metrics>( new Metrics( msg ) );
+
+Figure 20: Direct creation of a metrics instance.
+
+
+
+Adding Values
+-------------
+
+Once an instance of the metrics object is created, the xAPP
+may push values in preparation to sending the measurement(s)
+to the collector. The ``Push_data()`` function is used to
+push each key/value pair and is illustrated in figure 21.
+
+::
+
+          reading->Push_data( "normal_count", (double) norm_count );
+          reading->Push_data( "high_count", (double) hi_count );
+          reading->Push_data( "excessive_count", (double) ex_count );
+
+Figure 21: Pushing key/value pairs into a metrics instance.
+
+
+
+Sending A Measurement Set
+-------------------------
+
+After all of the measurement key/value pairs have been added
+to the instance, the ``Send()`` function can be invoked to
+create the necessary RMR message and send that to the
+collection application. Following the send, the key/value
+pairs are cleared from the instance and the xAPP is free to
+start pushing values into the instance again. The send
+function has the following prototype and returns ``true`` on
+success and ``false`` if the measurements could not be sent.
+
+
+Source and Reporter
+-------------------
+
+The alarm collector has the understanding that a measurement
+might be *sourced* from one piece of equipment, or software
+component, but reported by another. For auditing purposes it
+makes sense to distinguish these, and as such the metrics
+object allows the xAPP to identify the case when the source
+and reporter are something other than the xAPP which is
+generating the metrics message(s).
+
+The *source* is the component to which the measurement
+applies. This could be a network interface card counting
+packets, a temperature sensor, or the xAPP itself reporting
+xAPP related metrics. The *reporter* is the application that
+is reporting the measurement(s) to the collector.
+
+By default, both reporter and source are assumed to be the
+xAPP, and the name is automatically determined using the
+run-time supplied programme name. Should the xAPP need to
+report measurements for more than one source it has the
+option to create an instance for every reporter source
+combination, or to set the reporter and/or source with the
+generation of each measurement set. To facilitate the ability
+to change the source and/or the reporter without the need to
+create a new metrics instance, two *setter* functions are
+provided. The prototypes for these are shown in figure 22.
+
+
+::
+
+      void Set_source( std::string new_source );
+      void Set_reporter( std::string new_reporter );
+
+Figure 22: Setter functions allowing the reporter and/or
+source to be set after construction.
+
+
+
+CONFIGURATION SUPPORT
+=====================
+
+The C++ xAPP framework provides the xAPP with an interface to
+load, parse and receive update notifications on the
+configuration. The configuration, also known as the xAPP
+descriptor, is assumed to be a file containing json with a
+well known structure, with some fields or *objects* used by
+an xAPP for configuration purposes. The following paragraphs
+describe the support that the framework provides to the xAPP
+with respect to the configuration aspects of the descriptor.
+
+
+The Config Object
+-----------------
+
+The xAPP must create an instance of the ``config`` object in
+order to take advantage of the support. This is accomplished
+by using one of two constructors illustrated with code
+samples in figure 23.
+
+
+::
+
+      #include <ricxfcpp/config.hpp>
+
+      auto cfg = new xapp::Config( );
+      auto cfg = new xapp::Config( "/var/myapp/config.json"  );
+
+Figure 23: Creating a configuration instance.
+
+The creation of the object causes the file to be found,
+loaded, after which the xAPP can use the instance functions
+to access the information it needs.
+
+
+Available Functions
+-------------------
+
+Once a configuration has been created the following
+capabilities are available:
+
+
+   * Get a control value (numeric, string, or boolean)
+
+   * Get the RMR port for the container with the supplied
+     name
+
+   * Set a notification callback function
+
+   * Get the raw contents of the file
+
+
+
+Control Values
+--------------
+
+The ``controls`` section of the xAPP descriptor is generally
+used to supply a *flat* namespace of key/value pairs which
+the xAPP can use for configuration. These pairs are supplied
+by the xAPP author as a part of development, and thus are
+specific to each xAPP. The framework provides a general set
+of functions which allows a key to be searched for in this
+section and returned to the caller. Data is assumed to be one
+of three types: numeric (double), string, or boolean.
+
+Two methods for each return type are supported with the more
+specific form allowing the xAPP to supply a default value to
+be used should the file not contain the requested field. The
+function prototypes for these are provided in figure 24.
+
+::
+
+    bool Get_control_bool( std::string name, bool defval );
+    bool Get_control_bool( std::string name );
+
+    std::string Get_control_str( std::string name, std::string defval );
+    std::string Get_control_str( std::string name );
+
+    double Get_control_value( std::string name, double defval );
+    double Get_control_value( std::string name );
+
+Figure 24: The various controls section get functions.
+
+If the more generic form of these functions is used, without
+a default value, the return values are false, "", and 0.0 in
+the respective order of the prototypes in figure 24.
+
+
+The RMR Port
+------------
+
+The ``messaging`` section of the xAPP descriptor provides the
+ability to define one or more RMR *listen ports* that apply
+to the xAPP(s) started in a given container. The xAPP may
+read a port value (as a string) using the defined port name
+via the ``Get_port`` function whose prototype is illustrated
+in figure 25 below.
+
+
+::
+
+    std::string Get_port( std::string name );
+
+Figure 25: The get port prototype.
+
+
+
+Raw File Contents
+-----------------
+
+While it is not anticipated to be necessary, the xAPP might
+need direct access to the raw contents of the configuration
+file. As a convenience the framework provides the
+``Get_contents()`` function which reads the entire file into
+a standard library string and returns that to the calling
+function. Parsing and interpreting the raw contents is then
+up to the xAPP.
+
+
+Notification Of Changes
+-----------------------
+
+When desired, the xAPP may register a notification callback
+function with the framework. This callback will be driven any
+time a change to the descriptor is detected. When a change is
+detected, the revised descriptor is read into the existing
+object (overlaying any previous information), before invoking
+the callback. The callback may then retrieve the updated
+values and make any adjustments which are necessary. The
+prototype for the xAPP callback function is described in
+figure 26.
+
+
+::
+
+     void cb_name( xapp::Config& c, void* data )
+
+Figure 26: The prototype which the xAPP configuration notify
+callback must use.
+
+
+
+Enabling The Notifications
+--------------------------
+
+Notifications are enabled by invoking the
+``Set_callback()`` function. Once enabled, the framework will
+monitor the configuration source and invoke the callback upon
+change. This occurs in a separate thread than the main xAPP
+thread; it is up to the xAPP to guard against any potential
+data collisions when evaluating configuration changes. If the
+xAPP does not register a notification function the framework
+will not monitor the configuration for changes and the object
+will have static data. Figure 27 illustrates how the xAPP can
+define and register a notification callback.
+
+
+::
+
+
+    //  notification callback; allows verbose level to change on the fly
+    void config_chg( xapp::Config& c, void* vdata ) {
+      app_ctx* ctx;      // application context
+
+     ctx = (app_ctx *) vdata;
+     ctx->vlevel = c->Get_value( "verbose_level", ctx->vlevel );
+    }
+
+Figure 27: Small notification callback function allowing on
+the fly verbose level change.
+
+
+The xAPP would register the ``config_chg()`` function as the
+notification callback using the call illustrated in figure
+28.
+
+::
+
+
+     auto conf = new xapp::Config();
+     conf->Set_callback( config_chg );
+
+Figure 28: Setting the notification callback and and
+activating notifications.
+
+
+
+
+xAPP Descriptor Notes
+---------------------
+
+While it is beyond the scope of this document to describe the
+complete contents of an xAPP descriptor file, it is prudent
+to mention several items which are related to the information
+used from the descriptor file. The following paragraphs
+discuss things which the xAPP developer should be aware of,
+and keep in mind when using the configuration class.
+
+
+The RMR Section
+---------------
+
+There is a deprecated section within the xAPP descriptor
+which has the title *rmr.* The *messaging* section provides
+more flexibility, and additional information and has been a
+replacement for the *rmr* section for all applications. The
+information in the *rmr* section should be kept consistent
+with the duplicated information in the *messaging* section as
+long as there are container management and/or platform
+applications (e.g. Route Manager) which are back level and do
+not recognise the *messaging* section. The configuration
+parsing and support provided by the framework will ignore the
+*rmr* section.
+
+
+EXAMPLE PROGRAMMES
+==================
+
+The following sections contain several example programmes
+which are written on top of the C++ framework. All of these
+examples are available in the code repository RIC xAPP C++
+framework available via the following URL:
+
+.. class:: center
+   ``https://gerrit.o-ran-sc.org/r/admin/repos/ric-plt/xapp-frame-cpp``
+
+
+
+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
+         by the framework.
+     */
+     typedef struct {
+         int        vlevel;             // verbosity level
+         bool    forward;            // if true, message is forwarded
+         int        stats_freq;         // header/stats after n messages
+         std::atomic<long>    pcount; // messages processed
+         std::atomic<long>    icount; // messages ignored
+         std::atomic<int>    hdr;    // number of messages before next header
+     } cb_info_t;
+
+     // ----------------------------------------------------------------------
+
+     /*
+         Dump bytes to tty.
+     */
+     void dump( unsigned const char* buf, int len ) {
+         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, "   " );
+             }
+             cheater[j] = 0;
+             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() );
+             if( cbi.vlevel > 0 ) {
+                 fprintf( stdout, "\\n     %5s %5s %2s %5s\\n",
+                     "MTYPE", "SUBID", "ST", "PLLEN" );
+             }
+
+             cbi.hdr = cbi.stats_freq;        // reset must be last
+         }
+     }
+
+     void cb1( xapp::Message& mbuf, int mtype, int subid, int len,
+                     xapp::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( xapp::Message::NO_CHANGE, NULL );
+         }
+     }
+
+     /*
+         registered as the default callback; it counts the
+         messages that we aren't giving details about.
+     */
+     void cbd( xapp::Message& mbuf, int mtype, int subid, int len,
+                     xapp::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( xapp::Message::NO_CHANGE, NULL );
+         }
+     }
+
+     int main( int argc, char** argv ) {
+         std::unique_ptr<Xapp> x;
+         char*    port = (char *) "4560";
+         int ai = 1;                    // arg processing index
+         cb_info_t*    cbi;
+         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
+                         cbi->stats_freq = 5;
+                     }
+                     ai++;
+                     break;
+
+                 case 't':                        // thread count
+                     nthreads = atoi( argv[ai+1] );
+                     if( nthreads < 1 ) {
+                         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] "
+                                     "[-s stats-freq]  [-t thread-count] "
+                                     "[-v | -V n] msg-type1 ... msg-typen\\n",
+                                     argv[0] );
+                     fprintf( stderr, "\\tstats frequency is based on # of messages received\\n" );
+                     fprintf( stderr, "\\tverbose levels (-V) 0 counts only, "
+                                     "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] );
+             ai++;
+             fprintf( stderr, "<RD> capturing messages for type %d\\n", mtype );
+             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 29: 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.
+
+
+Metrics Generation
+------------------
+
+The example also illustrates how a metrics object instance
+can be created and used to send appliction metrics to the
+collector. In this example the primary callback function will
+genereate metrics with the receipt of each 1000th message.
+
+
+   ::
+
+     #include <stdio.h>
+
+     #include "ricxfcpp/message.hpp"
+     #include "ricxfcpp/msg_component.hpp"
+     #include <ricxfcpp/metrics.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
+         Send metrics every 1000 messages.
+     */
+     void cb1( xapp::Message& mbuf, int mtype, int subid, int len,
+                 xapp::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++;
+
+         if( cb1_count % 1000 == 0 && data != NULL ) {   // send metrics every 1000 messages
+             auto x = (Xapp *) data;
+             auto msgm = std::shared_ptr<xapp::Message>( x->Alloc_msg( 4096 ) );
+
+             auto m = std::unique_ptr<xapp::Metrics>( new xapp::Metrics( msgm ) );
+             m->Push_data( "tst_cb1", (double) cb1_count );
+             m->Push_data( "tst_cb2", (double) cb2_count );
+             m->Send();
+         }
+     }
+
+     // just count messages
+     void cb2( xapp::Message& mbuf, int mtype, int subid, int len,
+                 xapp::Msg_component payload,  void* data ) {
+         cb2_count++;
+     }
+
+     // default to count all unrecognised messages
+     void cbd( xapp::Message& mbuf, int mtype, int subid, int len,
+                 xapp::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, x );        // 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 30: 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<xapp::Message> msg;
+         xapp::Msg_component payload;                // special type of unique pointer to the payload
+
+         int    sz;
+         int len;
+         int i;
+         int ai;
+         int response_to = 0;                // max timeout wating for a response
+         char*    port = (char *) "4555";
+         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)
+                 case 'd':
+                     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++;
+                     break;
+             }
+             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, xapp::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 ) {
+                 rmtype = msg->Get_mtype();
+                 payload = msg->Get_payload();
+                 fprintf( stderr, "got: mtype=%d payload=(%s)\\n",
+                     rmtype, (char *) payload.get() );
+             } else {
+                 msg = xfw->Alloc_msg( 2048 );
+             }
+
+             if( delay > 0 ) {
+                 usleep( delay );
+             }
+         }
+     }
+
+   Figure 31: Simple looping sender application.
+
+
+
+Alarm Generation
+----------------
+
+This is an extension of a previous example which sends an
+alarm during initialisation and clears the alarm as soon as
+messages are being received. It is unknown if this is the
+type of alarm that is expected at the collector, but
+illustrates how an alarm is allocated, raised and cleared.
+
+
+   ::
+
+
+     #include <stdio.h>
+     #include <string.h>
+     #include <unistd.h>
+
+     #include <iostream>
+     #include <memory>
+
+     #include "ricxfcpp/xapp.hpp"
+     #include "ricxfcpp/alarm.hpp"
+
+     extern int main( int argc, char** argv ) {
+         std::unique_ptr<Xapp> xfw;
+         std::unique_ptr<xapp::Message> msg;
+         xapp::Msg_component payload;                // special type of unique pointer to the payload
+         std::unique_ptr<xapp::Alarm>    alarm;
+
+         bool received = false;                // false until we've received a message
+         int    sz;
+         int len;
+         int i;
+         int ai = 1;
+         int response_to = 0;                // max timeout wating for a response
+         char*    port = (char *) "4555";
+         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)
+                 case 'd':
+                     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++;
+                     break;
+             }
+             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 );
+
+
+         // raise an unavilable alarm which we'll clear on the first recevied message
+         alarm =  xfw->Alloc_alarm( "meid-1234"  );
+         alarm->Raise( xapp::Alarm::SEV_MINOR, 13, "unavailable", "no data recevied" );
+
+         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, xapp::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 ) {
+                 if( ! received ) {
+                     // clear the alarm on first received message
+                     alarm->Clear( xapp::Alarm::SEV_MINOR, 13, "messages flowing", "" );
+                     received = true;
+                 }
+
+                 rmtype = msg->Get_mtype();
+                 payload = msg->Get_payload();
+                 fprintf( stderr, "got: mtype=%d payload=(%s)\\n",
+                     rmtype, (char *) payload.get() );
+             } else {
+                 msg = xfw->Alloc_msg( 2048 );
+             }
+
+             if( delay > 0 ) {
+                 usleep( delay );
+             }
+         }
+     }
+
+   Figure 32: Simple looping sender application with alarm
+   generation.
+
+
+
+Configuration Interface
+-----------------------
+
+This example is a simple illustration of how the
+configuration file support (xAPP descriptor) can be used to
+suss out configuration parameters before creating the Xapp
+object. The example also illustrates how a notification
+callback can be used to react to changes in the
+configuration.
+
+
+   ::
+
+     #include <stdio.h>
+
+     #include "ricxfcpp/config.hpp"
+     #include "ricxfcpp/message.hpp"
+     #include "ricxfcpp/msg_component.hpp"
+     #include <ricxfcpp/metrics.hpp>
+     #include "ricxfcpp/xapp.hpp"
+
+     int vlevel = 0;                    // verbose mode set via config
+
+     /*
+         Just print something to the tty when we receive a message
+         and are in verbose mode.
+     */
+     void cb1( xapp::Message& mbuf, int mtype, int subid, int len,
+                 xapp::Msg_component payload,  void* data ) {
+         if( vlevel > 0 ) {
+             fprintf( stdout, "message received is %d bytes long\\n", len );
+         }
+     }
+
+     /*
+         Driven when the configuration changes. We snarf the verbose
+         level from the new config and update it. If it changed to
+         >0, incoming messages should be recorded with a tty message.
+         If set to 0, then tty output will be disabled.
+     */
+     void config_cb( xapp::Config& c, void* data ) {
+         int* vp;
+
+         if( (vp = (int *) data) != NULL ) {
+             *vp = c.Get_control_value( "verbose_level", *vp );
+         }
+     }
+
+     int main( int argc, char** argv ) {
+         Xapp*    x;
+         int        nthreads = 1;
+         std::unique_ptr<xapp::Config> cfg;
+
+         // only parameter recognised is the config file name
+         if( argc > 1 ) {
+             cfg = std::unique_ptr<xapp::Config>( new xapp::Config( std::string( argv[1] ) ) );
+         } else {
+             cfg = std::unique_ptr<xapp::Config>( new xapp::Config( ) );
+         }
+
+         // must get a port from the config; no default
+         auto port = cfg->Get_port( "rmr-data" );
+         if( port.empty() ) {
+             fprintf( stderr, "<FAIL> no port in config file\\n" );
+             exit( 1 );
+         }
+
+         // dig other data from the config
+         vlevel = cfg->Get_control_value( "verbose_level", 0 );
+         nthreads = cfg->Get_control_value( "thread_count", 1 );
+         // very simple flag processing (no bounds/error checking)
+
+         if( vlevel > 0 ) {
+             fprintf( stderr, "<XAPP> listening on port: %s\\n", port.c_str() );
+             fprintf( stderr, "<XAPP> starting %d threads\\n", nthreads );
+         }
+
+         // register the config change notification callback
+         cfg->Set_callback( config_cb, (void *) &vlevel );
+
+         x = new Xapp( port.c_str(), true );
+         x->Add_msg_cb( 1, cb1, x );        // register message callback
+
+         x->Run( nthreads );                // let framework drive
+         // control should not return
+     }
+
+   Figure 33: Simple application making use of the
+   configuration object.
+