From: E. Scott Daniels Date: Thu, 9 Jul 2020 15:56:10 +0000 (-0400) Subject: Actually put the JSON doc into the user manual X-Git-Tag: 2.0.0~2 X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=5a9d7c67d1ec1e4410995eae4b50f929396935f6;p=ric-plt%2Fxapp-frame-cpp.git Actually put the JSON doc into the user manual The user manual include statment wasn't pulling the json section. This change fixes that. This change also closes some memory leaks in the json wrapper. Signed-off-by: E. Scott Daniels Change-Id: I54849c67960fd945a85327a3ff308d7d7651b673 --- diff --git a/doc/src/lib/front_junk.im b/doc/src/lib/front_junk.im index 718cc6a..1109537 100644 --- a/doc/src/lib/front_junk.im +++ b/doc/src/lib/front_junk.im @@ -75,16 +75,18 @@ .** ensure these happen after toc is rendered .if pfm .pn on 0 noline center f=%d - .cd 1 6.0i + .cd 1 180i .fi .ei .if "&ot" "rst" = .dv raw_comment_sym ^.. - .cd 1 26i + .cd 1 18i m=0i .ll 18i .im &{lib}/raw_license.im + .cd 1 6.5i m=0i .ll 6i +.if false .if doc_title &many_equals &doc_title @@ -95,6 +97,7 @@ &doc_subtitle &many_dashes .fi +.fi .fi .dv _front_junk 1 diff --git a/doc/src/lib/rst.im b/doc/src/lib/rst.im index 5d6da09..437ec8a 100644 --- a/doc/src/lib/rst.im +++ b/doc/src/lib/rst.im @@ -67,7 +67,7 @@ .fi .dv h4 **$1** - .** bloody rst won't allow breaks in a bullet list so we have to allow the column to go wide. + .** initially need a wide column to allow copyright to not break .dv cd 1 180i m=0i diff --git a/doc/src/lib/setup.im b/doc/src/lib/setup.im index 07d4f7c..c04a8ca 100644 --- a/doc/src/lib/setup.im +++ b/doc/src/lib/setup.im @@ -40,7 +40,10 @@ .** imbed output type specific macro file .gv e OUTPUT_TYPE ot - .im &{lib}/&{ot}.im + .dv output_type &{ot!txt} + + .im cmd_master.im + .** .im &{lib}/&{ot}.im .gv e XFM_PASS pass .dv pass &{pass!1} diff --git a/doc/src/user/Makefile b/doc/src/user/Makefile index ca3c252..70ab002 100644 --- a/doc/src/user/Makefile +++ b/doc/src/user/Makefile @@ -17,6 +17,8 @@ # this uses {X}fm which can be cloned from: https://gitlab.com/rouxware/xfm +XPATH=XFM_PATH=.:/usr/local/share/xfm TFM_PATH=.:/usr/local/share/xfm + docs = user_guide src = user_guide.xfm imbed_src = cpp_frame.im example1.im example2.im example3.im jhash.im @@ -28,20 +30,20 @@ desired_out = rst ps # references etc. # %.ps: %.xfm - OUTPUT_TYPE=generic_ps XFM_PASS=1 pfm $< /dev/null - OUTPUT_TYPE=generic_ps XFM_PASS=2 pfm $< $@ + $(XPATH) OUTPUT_TYPE=generic_ps XFM_PASS=1 pfm $< /dev/null + $(XPATH) OUTPUT_TYPE=generic_ps XFM_PASS=2 pfm $< $@ %.md: %.xfm - OUTPUT_TYPE=markdown XFM_PASS=1 tfm $< /dev/null - OUTPUT_TYPE=markdown XFM_PASS=2 tfm $< | sed 's/^ //' >$@ + $(XPATH) OUTPUT_TYPE=markdown XFM_PASS=1 tfm $< /dev/null + $(XPATH) OUTPUT_TYPE=markdown XFM_PASS=2 tfm $< | sed 's/^ //' >$@ %.rst: %.xfm - OUTPUT_TYPE=rst XFM_PASS=1 tfm $< /dev/null - GEN_TITLE=1 OUTPUT_TYPE=rst XFM_PASS=2 tfm $< | sed 's/^ //; s/ *$$//' >$@ + $(XPATH) OUTPUT_TYPE=rst XFM_PASS=1 tfm $< /dev/null + $(XPATH) GEN_TITLE=1 OUTPUT_TYPE=rst XFM_PASS=2 tfm $< | sed 's/^ //; s/ *$$//' >$@ %.txt: %.xfm - OUTPUT_TYPE=txt XFM_PASS=1 tfm $< /dev/null - OUTPUT_TYPE=txt XFM_PASS=2 tfm $< $@ + $(XPATH) OUTPUT_TYPE=txt XFM_PASS=1 tfm $< /dev/null + $(XPATH) OUTPUT_TYPE=txt XFM_PASS=2 tfm $< $@ # ----------------------------------------------------------------------------------- all: $(desired_out:%=user_guide.%) diff --git a/doc/src/user/cpp_frame.im b/doc/src/user/cpp_frame.im index 9481b5a..0c7a560 100644 --- a/doc/src/user/cpp_frame.im +++ b/doc/src/user/cpp_frame.im @@ -41,10 +41,10 @@ parameters: &half_space &indent &beg_dlist(.5i:&ditemtext) - &ditem(port) A C string (pointer to char) which defines the port that RMR will open to listen for connections. + &di(port) A C string (pointer to char) which defines the port that RMR will open to listen for connections. &half_space - &ditem(wait) A Boolean value which indicates whether or not the initialization process should wait for + &di(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 &cw(RMR_SEED_RT) environment variable). @@ -67,7 +67,7 @@ The following code sample illustrates the simplicity of creating the instance of new Xapp( listen_port, wait4table ) ); } &ex_end -&figure( Creating an xAPP instance.) +&fig( Creating an xAPP instance.) &space From a compilation perspective, the following is the simple compiler invocation string needed to compile @@ -106,31 +106,31 @@ The following is the prototype which callback functions must be defined with: int payload_len, Msg_component payload, void* usr_data ); &ex_end -&figure( Callback function signature) +&fig( Callback function signature) &space The parameters passed to the callback function are as follows: -&multi_space( .1 ) +&mult_space( .1 ) &indent &beg_dlist(1i:&ditemtext) - &ditem(m) A reference to the Message that was received. + &di(m) A reference to the Message that was received. &half_space - &ditem(mtype) The message type (allows for disambiguation if the callback is registered for multiple message types). + &di(mtype) The message type (allows for disambiguation if the callback is registered for multiple message types). &half_space - &ditem(subid) The subscription ID from the message. + &di(subid) The subscription ID from the message. &half_space - &ditem(payload len) The number of bytes which the sender has placed into the payload. + &di(payload len) The number of bytes which the sender has placed into the payload. &half_space - &ditem(payload) A direct reference (smart pointer) to the payload. (The smart pointer is wrapped in a + &di(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.) &half_space - &ditem(user data) A pointer to user data. This is the pointer that was provided when the function was registered. + &di(user data) A pointer to user data. This is the pointer that was provided when the function was registered. &end_dlist &uindent &space @@ -170,7 +170,7 @@ framework (explained in the next section). xapp->Run( 1 ); // start the callback driver } &ex_end -&figure( Callback function example.) +&fig( Callback function example.) &space As before, the program does nothing useful, but now it will execute and receive messages. @@ -209,10 +209,10 @@ Sending the message is a function of the message object itself and can take one &half_space &indent &beg_list(&lic1) - &item Replying to the sender of a received message + &li Replying to the sender of a received message &half_space - &item Sending a message (routed based on the message type and subscription ID) + &li Sending a message (routed based on the message type and subscription ID) &end_list &uindent &space @@ -232,7 +232,7 @@ with the following prototypes. bool Send_response( int response_len, std::shared_ptr response ); &ex_end -&figure( Reply function prototypes. ) +&fig( Reply function prototypes. ) &space In the first prototype the xApp must supply the new message type and subscription ID values, where the @@ -248,7 +248,7 @@ constant can be used as illustrated below. msg->Send_response( Message::NO_CHANGE, Message::NO_SUBID, pl_length, (unsigned char *) payload ); &ex_end -&figure( Send response prototype. ) +&fig( Send response prototype. ) &space In addition to the two function prototypes for &cw(Send_response()) there are two additional prototypes @@ -273,7 +273,7 @@ functions and are shown below. bool Send_msg( int payload_len, unsigned char* payload ); &ex_end -&figure( Send function prototypes. ) +&fig( Send function prototypes. ) &space Each send function accepts the message, copies in the payload provided, sets the message type and subscription @@ -314,7 +314,7 @@ for the response string, the response is just not sent). msg->Send_response( M_TYPE, SID, strlen( raw_pl ), NULL ); } &ex_end -&figure( Send message without buffer copy. ) +&fig( Send message without buffer copy. ) &space &h2(Sending Multiple Responses) diff --git a/doc/src/user/example1.im b/doc/src/user/example1.im index f54af6d..8ca90b6 100644 --- a/doc/src/user/example1.im +++ b/doc/src/user/example1.im @@ -52,6 +52,6 @@ the framework, have been omitted. The full code is in the framework repository. &ex_start .im j=start-example ../../../examples/rmr_dump.cpp &ex_end -&figure( Simple callback application. ) +&fig( Simple callback application. ) &uindent diff --git a/doc/src/user/example2.im b/doc/src/user/example2.im index 90a7f51..bc1a9fc 100644 --- a/doc/src/user/example2.im +++ b/doc/src/user/example2.im @@ -40,6 +40,6 @@ are not locked when incremented. &ex_start .im j=start-example ../../../examples/xapp_t1.cpp &ex_end -&figure( Simple callback application. ) +&fig( Simple callback application. ) &uindent &space diff --git a/doc/src/user/example3.im b/doc/src/user/example3.im index e25f03e..09d2117 100644 --- a/doc/src/user/example3.im +++ b/doc/src/user/example3.im @@ -30,6 +30,6 @@ been made in order to keep the example small and to the point. &ex_start .im j=start-example ../../../examples/xapp_t2.cpp &ex_end -&figure( Simple looping sender application. ) +&fig( Simple looping sender application. ) &space diff --git a/doc/src/user/jhash.im b/doc/src/user/jhash.im index d381542..ec30b0c 100644 --- a/doc/src/user/jhash.im +++ b/doc/src/user/jhash.im @@ -46,7 +46,7 @@ The Jhash object is created simply by passing a json string to the constructor. jh = new Jhash( jstring.c_str() ); &ex_end -&figure(The creation of the Jhash object.) +&fig(The creation of the Jhash object.) &space @@ -85,7 +85,7 @@ two &ital(sub-blobs) (address and lease_info). .gv fig .sv _fig &set_fref(jblob_fig:&_fig) -&figure(Sample json with a root and too blobs.) +&fig(Sample json with a root and too blobs.) &space Upon creation of the Jhash object, the &ital(root) fields, &cw(lodge_name,) &cw(member_count,) and @@ -101,7 +101,7 @@ The code sample in figure &fig_blob_sample illustrates how a &ital(sub-blob) is .gv fig .sv _fig &set_fref(fig_blob_sample:&_fig) -&figure(Blob selection example.) +&fig(Blob selection example.) &space Currently, the selected blob must be unset in order to select a blob at the root @@ -227,7 +227,7 @@ values from the json in figure &array_blob_json_fig. &ex_end .gv fig &set_fref(array_blob_code_fig:&_fig) -&figure(Json array containing blobs.) +&fig(Json array containing blobs.) &space @@ -250,6 +250,6 @@ values from the json in figure &array_blob_json_fig. &ex_end .gv fig &set_fref(array_blob_json_fig:&_fig) -&figure(Code to process the array of blobs.) +&fig(Code to process the array of blobs.) &space diff --git a/doc/src/user/user_guide.xfm b/doc/src/user/user_guide.xfm index 6df8248..4a469f7 100644 --- a/doc/src/user/user_guide.xfm +++ b/doc/src/user/user_guide.xfm @@ -54,7 +54,7 @@ .** ------ major sections -------- .im cpp_frame.im -.im hash.im +.im jhash.im &h1(Example Programmes) The following sections contain several example programmes which are written on diff --git a/docs/user-guide.rst b/docs/user-guide.rst index 58ccd58..e0ac2e1 100644 --- a/docs/user-guide.rst +++ b/docs/user-guide.rst @@ -1,3 +1,6 @@ +============ +USER'S GUIDE +============ .. This work is licensed under a Creative Commons Attribution 4.0 International License. .. SPDX-License-Identifier: CC-BY-4.0 .. @@ -6,9 +9,6 @@ .. Do NOT make changes directly to .rst or .md files. -============================================================================================ -User's Guide -============================================================================================ INTRODUCTION @@ -43,855 +43,1164 @@ 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. + .. list-table:: + :widths: auto + :header-rows: 0 + :class: borderless - 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). + * - **port** + - + A C string (pointer to char) which defines the port that + RMR will open to listen for connections. - The following code sample illustrates the simplicity of - creating the instance of the xApp object. + | - :: + * - **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). - #include - #include - int main( ) { - std::unique_ptr xapp; - char* listen_port = (char *) "4560"; //RMR listen port - bool wait4table = true; // wait for a route table - xapp = std::unique_ptr( - new Xapp( listen_port, wait4table ) ); - } - Figure 1: Creating an xAPP instance. +The following code sample illustrates the simplicity of +creating the instance of the xApp object. + + +:: + + #include + #include + int main( ) { + std::unique_ptr xapp; + char* listen_port = (char *) "4560"; //RMR listen port + bool wait4table = true; // wait for a route table - 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. + xapp = std::unique_ptr( + 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. + 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. +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. +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: +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 ); + void cb_name( Message& m, int mtype, int subid, + int payload_len, Msg_component payload, + void* usr_data ); - Figure 2: Callback function signature +Figure 2: Callback function signature - The parameters passed to the callback function are as - follows: &multi_space +The parameters passed to the callback function are as +follows: - 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). + .. list-table:: + :widths: auto + :header-rows: 0 + :class: borderless - subid - The subscription ID from the message. + * - **m** + - + A reference to the Message that was received. - 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. + * - **mtype** + - + The message type (allows for disambiguation if the + callback is registered for multiple message types). - 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 - #include - long m1000_count = 0; // message counters, one for each type - long m1001_count = 0; + * - **subid** + - + The subscription ID from the message. - // 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; - char* listen_port = (char *) "4560"; - bool wait4table = false; + * - **payload len** + - + The number of bytes which the sender has placed into the + payload. - xapp = std::unique_ptr( - 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 - } + * - **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 + #include + 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; + char* listen_port = (char *) "4560"; + bool wait4table = false; + + xapp = std::unique_ptr( + new Xapp( listen_port, wait4table ) ); - Figure 3: Callback function example. + // 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 ); - 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. + xapp->Run( 1 ); // start the callback driver + } - 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. +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 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. +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: +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 + * Replying to the sender of a received message - + Sending a message (routed based on the message type and subscription ID) + * 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. +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. +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 response ); + bool Send_response( int mtype, int subid, int response_len, + std:shared_ptr response ); - bool Send_response( int response_len, std::shared_ptr response ); + bool Send_response( int response_len, std::shared_ptr response ); - Figure 4: Reply function prototypes. +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. +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 ); + msg->Send_response( Message::NO_CHANGE, Message::NO_SUBID, + pl_length, (unsigned char *) payload ); - Figure 5: Send response prototype. +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. +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. +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 payload ); + bool Send_msg( int mtype, int subid, int payload_len, + std::shared_ptr payload ); - bool Send_msg( int mtype, int subid, int payload_len, - 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 payload ); + bool Send_msg( int payload_len, + std::shared_ptr payload ); - bool Send_msg( int payload_len, unsigned char* payload ); + bool Send_msg( int payload_len, unsigned char* payload ); - Figure 6: Send function prototypes. +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. +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 +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). - 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. +:: + + 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. +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. +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. +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. +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 -================== +JSON SUPPORT +============ - The following sections contain several example programmes - which are written on top of the C++ framework. +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. -RMR Dump xAPP -------------- +Creating The Jhash Object +------------------------- - 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 - #include - #include - - #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 pcount; // messages processed - std::atomic icount; // messages ignored - std::atomic 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, " 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 %04x | ", cheater, i+1 ); - j = 0; - } - } +The Jhash object is created simply by passing a json string +to the constructor. - 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 + #include - curv = cbi.hdr--; + std::string jstring = "{ \\"tag\\": \\"Hello World\\" }"; + Jhash* jh; - 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" ); - } + jh = new Jhash( jstring.c_str() ); - cbi.hdr = cbi.stats_freq; // reset must be last - } - } +Figure 8: The creation of the Jhash object. - void cb1( Message& mbuf, int mtype, int subid, int len, - Msg_component payload, void* data ) { - cb_info_t* cbi; - long total_count; +Once the Jhash object has been created any of the methods +described in the following paragraphs can be used to retrieve +the data: - if( (cbi = (cb_info_t *) data) == NULL ) { - return; - } - cbi->pcount++; - stats( *cbi ); // gen stats & header if needed +Json Blobs +---------- - if( cbi->vlevel > 0 ) { - fprintf( stdout, " %-5d %-5d %02d %-5d \\n", - mtype, subid, mbuf.Get_state(), len ); +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. - if( cbi->vlevel > 1 ) { - dump( payload.get(), len > 64 ? 64 : len ); - } - } +As an example, the json contained in figure jblob_fig, +contains a "root" blob and two *sub-blobs* (address and +lease_info). - 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; + { + "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" + } + } - if( (cbi = (cb_info_t *) data) == NULL ) { - return; - } +Figure 9: Sample json with a root and too blobs. - cbi->icount++; - stats( *cbi ); +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 +avalable only when the correct blob is selected. The code +sample in figure 10 illustrates how a *sub-blob* is selected. - 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 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++; - } + jh->Set_blob( (char *) "address" ); // select address + jh->Unset_blob(); // return to root + jh->Set_blob( (char *) "lease_info" ); // slect the lease blob - cbi->hdr = cbi->stats_freq; - fprintf( stderr, " listening on port: %s\\n", port ); +Figure 10: Blob selection example. - // create xapp, wait for route table if forwarding - x = std::unique_ptr( new Xapp( port, cbi->forward ) ); +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. - // register callback for each type on the command line - while( ai < argc ) { - mtype = atoi( argv[ai] ); - ai++; - fprintf( stderr, " capturing messages for type %d\\n", mtype ); - x->Add_msg_cb( mtype, cb1, cbi ); - ncb++; - } - if( ncb < 1 ) { - fprintf( stderr, " no message types specified on the command line\\n" ); - exit( 1 ); - } +Simple Value Extraction +----------------------- - x->Add_msg_cb( x->DEFAULT_CALLBACK, cbd, cbi ); // register default cb +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: - fprintf( stderr, " 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. + std::string String( const char* name ); + float Value( const char* name ); + bool Bool( const char* name ); -Callback Receiver ------------------ +Each of these funcitons returns the value associated with the +field with the given *name.* If the value is missing, the +following default values are returned: - 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. + .. list-table:: + :widths: 15,80 + :header-rows: 0 + :class: borderless - 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. + * - **String** + - + An empty string (.e.g ""). + | - :: + * - **Value** + - + Zero (e.g 0.0) - #include + | - #include "ricxfcpp/message.hpp" - #include "ricxfcpp/msg_component.hpp" - #include "ricxfcpp/xapp.hpp" + * - **bool** + - + false - // 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; +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 funcitons. The prototpyes 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. Conversly, the +``Missing`` funciton 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 funcitons 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 simiple 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 ); + - // 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" ); +As with selecting a sub-blob, an unset must be preformed +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. - cb1_count++; +:: + + "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. + + + +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 + #include + #include + + #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 pcount; // messages processed + std::atomic icount; // messages ignored + std::atomic 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, " 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 %04x | ", cheater, i+1 ); + j = 0; + } } - // just count messages - void cb2( Message& mbuf, int mtype, int subid, int len, + 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 ) { - cb2_count++; + cb_info_t* cbi; + long total_count; + + if( (cbi = (cb_info_t *) data) == NULL ) { + return; } - // default to count all unrecognised messages - void cbd( Message& mbuf, int mtype, int subid, int len, + cbi->pcount++; + stats( *cbi ); // gen stats & header if needed + + if( cbi->vlevel > 0 ) { + fprintf( stdout, " %-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 ) { - cbd_count++; + 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 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; - int main( int argc, char** argv ) { - Xapp* x; - char* port = (char *) "4560"; - int ai = 1; // arg processing index - int nthreads = 1; + 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; - // very simple flag processing (no bounds/error checking) - while( ai < argc ) { - if( argv[ai][0] != '-' ) { + case 't': // thread count + nthreads = atoi( argv[ai+1] ); + if( nthreads < 1 ) { + nthreads = 1; + } + ai++; break; - } - switch( argv[ai][1] ) { // we only support -x so -xy must be -x -y - case 'p': - port = argv[ai+1]; - ai++; - break; + case 'v': // simple verbose bump + cbi->vlevel++; + break; - case 't': - nthreads = atoi( argv[ai+1] ); - ai++; - break; - } + case 'V': // explicit verbose level + cbi->vlevel = atoi( argv[ai+1] ); + ai++; + break; - ai++; + 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 ); } - fprintf( stderr, " listening on port: %s\\n", port ); - fprintf( stderr, " starting %d threads\\n", nthreads ); + ai++; + } - 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 ); + cbi->hdr = cbi->stats_freq; + fprintf( stderr, " listening on port: %s\\n", port ); - x->Run( nthreads ); // let framework drive - // control should not return + // create xapp, wait for route table if forwarding + x = std::unique_ptr( new Xapp( port, cbi->forward ) ); + + // register callback for each type on the command line + while( ai < argc ) { + mtype = atoi( argv[ai] ); + ai++; + fprintf( stderr, " capturing messages for type %d\\n", mtype ); + x->Add_msg_cb( mtype, cb1, cbi ); + ncb++; + } + + if( ncb < 1 ) { + fprintf( stderr, " no message types specified on the command line\\n" ); + exit( 1 ); } - Figure 9: Simple callback application. + x->Add_msg_cb( x->DEFAULT_CALLBACK, cbd, cbi ); // register default cb + + fprintf( stderr, " 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 13: 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 + + #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, " listening on port: %s\\n", port ); + fprintf( stderr, " 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 14: 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. +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 + #include + #include - #include - #include - #include + #include + #include - #include - #include + #include "ricxfcpp/xapp.hpp" - #include "ricxfcpp/xapp.hpp" + extern int main( int argc, char** argv ) { + std::unique_ptr xfw; + std::unique_ptr msg; + Msg_component payload; // special type of unique pointer to the payload - extern int main( int argc, char** argv ) { - std::unique_ptr xfw; - std::unique_ptr 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 - 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; - // very simple flag processing (no bounds/error checking) - while( ai < argc ) { - if( argv[ai][0] != '-' ) { + 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; - } - - // 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++; } + ai++; + } + + fprintf( stderr, " response timeout set to: %d\\n", response_to ); + fprintf( stderr, " listening on port: %s\\n", port ); + + // get an instance and wait for a route table to be loaded + xfw = std::unique_ptr( new Xapp( port, true ) ); + msg = xfw->Alloc_msg( 2048 ); - fprintf( stderr, " response timeout set to: %d\\n", response_to ); - fprintf( stderr, " listening on port: %s\\n", port ); + for( i = 0; i < 100; i++ ) { + mtype++; + if( mtype > 10 ) { + mtype = 0; + } - // get an instance and wait for a route table to be loaded - xfw = std::unique_ptr( new Xapp( port, true ) ); - msg = xfw->Alloc_msg( 2048 ); + // we'll reuse a received message; get max size + sz = msg->Get_available_size(); - for( i = 0; i < 100; i++ ) { - mtype++; - if( mtype > 10 ) { - mtype = 0; - } + // direct access to payload; add something silly + payload = msg->Get_payload(); + len = snprintf( (char *) payload.get(), sz, "This is message %d\\n", i ); - // we'll reuse a received message; get max size - sz = msg->Get_available_size(); + // payload updated in place, prevent copy by passing nil + if ( ! msg->Send_msg( mtype, Message::NO_SUBID, len, NULL )) { + fprintf( stderr, " send failed: %d\\n", i ); + } - // direct access to payload; add something silly + // receive anything that might come back + msg = xfw->Receive( response_to ); + if( msg != NULL ) { + rmtype = msg->Get_mtype(); 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, " 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 ); - } + 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. + Figure 15: Simple looping sender application. diff --git a/src/json/jhash.cpp b/src/json/jhash.cpp index 14a0486..7f4a446 100644 --- a/src/json/jhash.cpp +++ b/src/json/jhash.cpp @@ -45,8 +45,8 @@ suss out the values. */ Jhash::Jhash( const char* jbuf ) : - st( jw_new( jbuf ) ), - master_st( NULL ) + master_st( NULL ), + st( jw_new( jbuf ) ) { /* empty body */ } //Jhash::Jhash( Jhash&& soi ); // mover diff --git a/src/json/jwrapper.c b/src/json/jwrapper.c index f6c32c3..3f85a59 100644 --- a/src/json/jwrapper.c +++ b/src/json/jwrapper.c @@ -58,6 +58,7 @@ #define PT_STRING 4 #define OBJ_SPACE 1 // space in the symbol table where json bits are stashed +#define MGT_SPACE 2 // non-json objects in the hash (management things) extern void jw_nuke( void* st ); @@ -180,17 +181,31 @@ static void nix_things( void* st, void* se, const char* name, void* ele, void * } free( j->v.pv ); // must free the array (arrays aren't nested, so all things in the array don't reference allocated mem) + free( j ); } break; - case JSMN_OBJECT: + case JSMN_OBJECT: // delete the sub symtab jw_nuke( j->v.pv ); j->jsmn_type = JSMN_UNDEFINED; // prevent a double free + free( j ); + break; + + case JSMN_STRING: + case JSMN_PRIMITIVE: + free( j ); break; } } } +/* + Nix non-json things that are also in the hash. +*/ +static void nix_mgt( void* st, void* se, const char* name, void* ele, void *data ) { + free( ele ); +} + /* Invoked for each thing and prints what we can to stderr. */ @@ -239,6 +254,8 @@ void* parse_jobject( void* st, char *json, char* prefix ) { if( jtokens[0].type != JSMN_OBJECT ) { // if it's not an object then we can't parse it. fprintf( stderr, "[WARN] jwrapper: badly formed json; initial opening bracket ({) not detected\n" ); + rmr_sym_free( st ); + free( jtokens ); return NULL; } @@ -252,6 +269,7 @@ void* parse_jobject( void* st, char *json, char* prefix ) { if( jtokens[i].type != JSMN_STRING ) { fprintf( stderr, "[WARN] jwrapper: badly formed json [%d]; expected name (string) found type=%d %s\n", i, jtokens[i].type, extract( json, &jtokens[i] ) ); rmr_sym_free( st ); + free( jtokens ); return NULL; } name = extract( json, &jtokens[i] ); @@ -267,7 +285,7 @@ void* parse_jobject( void* st, char *json, char* prefix ) { case JSMN_OBJECT: // save object in two ways: as an object 'blob' and in the current symtab using name as a base (original) dstr = strdup( extract( json, &jtokens[i] ) ); snprintf( wbuf, sizeof( wbuf ), "%s_json", name ); // must stash the json string in the symtab for clean up during nuke - rmr_sym_put( st, wbuf, OBJ_SPACE, dstr ); + rmr_sym_put( st, wbuf, MGT_SPACE, dstr ); parse_jobject( st, dstr, name ); // recurse to add the object as objectname.xxxx elements @@ -275,7 +293,7 @@ void* parse_jobject( void* st, char *json, char* prefix ) { (jtp->v.pv = (void *) rmr_sym_alloc( 255 ) ) != NULL ) { // object is just a blob dstr = strdup( extract( json, &jtokens[i] ) ); - rmr_sym_put( jtp->v.pv, JSON_SYM_NAME, OBJ_SPACE, dstr ); // must stash json so it is freed during nuke() + rmr_sym_put( jtp->v.pv, JSON_SYM_NAME, MGT_SPACE, dstr ); // must stash json so it is freed during nuke() parse_jobject( jtp->v.pv, dstr, "" ); // recurse acorss the string and build a new symtab size = jtokens[i].end; // done with them, we need to skip them @@ -300,6 +318,7 @@ void* parse_jobject( void* st, char *json, char* prefix ) { if( jtp == NULL ) { fprintf( stderr, "[WARN] jwrapper: memory alloc error processing element [%d] in json\n", i ); rmr_sym_free( st ); + free( jtokens ); return NULL; } jarray = jtp->v.pv = (jsmntok_t *) malloc( sizeof( *jarray ) * size ); // allocate the array @@ -327,6 +346,9 @@ void* parse_jobject( void* st, char *json, char* prefix ) { case JSMN_ARRAY: fprintf( stderr, "[WARN] jwrapper: [%d] array element %d is not valid type (array) is not string or primative\n", i, n ); n += jtokens[i+n].size; // this should skip the nested array + free( jtp ); + free( jarray ); + jarray = NULL; break; case JSMN_STRING: @@ -371,6 +393,7 @@ void* parse_jobject( void* st, char *json, char* prefix ) { default: fprintf( stderr, "[WARN] jwrapper: [%d] array element %d is not valid type (unknown=%d) is not string or primative\n", i, n, jtokens[i].type ); rmr_sym_free( st ); + free( jtokens ); return NULL; break; } @@ -436,7 +459,8 @@ extern void jw_nuke( void* st ) { char* buf; // pointer to the original json to free if( st != NULL ) { - rmr_sym_foreach_class( st, OBJ_SPACE, nix_things, NULL ); // free anything that the symtab references + rmr_sym_foreach_class( st, OBJ_SPACE, nix_things, NULL ); // free any json thing that the symtab references + rmr_sym_foreach_class( st, MGT_SPACE, nix_mgt, NULL ); // free management things rmr_sym_free( st ); // free the symtab itself } } @@ -462,15 +486,18 @@ extern void jw_dump( void* st ) { if present. */ extern void* jw_new( const char* json ) { - void *st; // symbol table + void *st = NULL; // symbol table char* djson; // dup so we can save it void* rp = NULL; // return value if( json != NULL && (st = rmr_sym_alloc( MAX_THINGS )) != NULL ) { djson = strdup( json ); // allows user to free/overlay their buffer as needed - rmr_sym_put( st, (unsigned char *) JSON_SYM_NAME, OBJ_SPACE, djson ); // must have a reference to the string until symtab is trashed - - rp = parse_jobject( st, djson, "" ); // empty prefix for the root object + rp = parse_jobject( st, djson, "" ); // empty prefix for the root object; parse_jo will clean up and free st + if( rp == NULL ) { + free( djson ); + } else { + rmr_sym_put( st, (unsigned char *) JSON_SYM_NAME, MGT_SPACE, djson ); // must have a reference to the string until symtab is trashed + } } return rp; diff --git a/test/Makefile b/test/Makefile index 0abcc17..4245d3b 100644 --- a/test/Makefile +++ b/test/Makefile @@ -15,7 +15,7 @@ unit_test:: unit_test.cpp rmr_em.o # build a special jwrapper object with coverage settings jwrapper_test.o:: ../src/json/jwrapper.c ../src/json/jwrapper.h - cc $(coverage_opts) -I ../src/json -I ../ext/jsmn ../src/json/jwrapper.c -c -o jwrapper_test.o + cc $(coverage_opts) -g -I ../src/json -I ../ext/jsmn ../src/json/jwrapper.c -c -o jwrapper_test.o jhash_test:: jwrapper_test.o jhash_test.cpp # do NOT link the xapp lib; we include all modules in the test programme diff --git a/test/jhash_test.cpp b/test/jhash_test.cpp index be4171c..58d283f 100644 --- a/test/jhash_test.cpp +++ b/test/jhash_test.cpp @@ -95,6 +95,7 @@ int main( int argc, char** argv ) { fprintf( stderr, "read: (%s)\n", jstr ); jh = new Jhash( jstr ); + free( jstr ); if( jh == NULL ) { fprintf( stderr, " could not parse json string from: test.json\n" );