1 .. This work is licensed under a Creative Commons Attribution 4.0 International License.
2 .. SPDX-License-Identifier: CC-BY-4.0
4 .. CAUTION: this document is generated from source in doc/src/*
5 .. To make changes edit the source and recompile the document.
6 .. Do NOT make changes directly to .rst or .md files.
9 ============================================================================================
11 ============================================================================================
17 The C++ framework allows the programmer to create an xApp
18 object instance, and to use that instance as the logic base.
19 The xApp object provides a message level interface to the RIC
20 Message Router (RMR), including the ability to register
21 callback functions which the instance will drive as messages
22 are received; much in the same way that an X-windows
23 application is driven by the window manager for all activity.
24 The xApp may also choose to use its own send/receive loop,
25 and thus is not required to use the callback driver mechanism
26 provided by the framework.
32 The C++ framework API consists of the creation of the xApp
33 object, and invoking desired functions via the instance of
34 the object. The following paragraphs cover the various steps
35 involved to create an xApp instance, wait for a route table
36 to arrive, send a message, and wait for messages to arrive.
39 Creating the xApp instance
40 --------------------------
42 The creation of the xApp instance is as simple as invoking
43 the object's constructor with two required parameters:
47 A C string (pointer to char) which defines the port that
48 RMR will open to listen for connections.
51 A Boolean value which indicates whether or not the
52 initialization process should wait for the arrival of a
53 valid route table before completing. When true is
54 supplied, the initialization will not complete until RMR
55 has received a valid route table (or one is located via
56 the RMR_SEED_RT environment variable).
58 The following code sample illustrates the simplicity of
59 creating the instance of the xApp object.
65 #include <ricxfcpp/xapp.hpp>
67 std::unique_ptr<Xapp> xapp;
68 char* listen_port = (char *) "4560"; //RMR listen port
69 bool wait4table = true; // wait for a route table
71 xapp = std::unique_ptr<Xapp>(
72 new Xapp( listen_port, wait4table ) );
75 Figure 1: Creating an xAPP instance.
77 From a compilation perspective, the following is the simple
78 compiler invocation string needed to compile and link the
79 above program (assuming that the sample code exists in a file
85 g++ man_ex1.cpp -o man_ex1 -lricxfcpp -lrmr_si -lpthread
88 The above program, while complete and capable of being
89 compiled, does nothing useful. When invoked, RMR will be
90 initialized and will begin listening for a route table;
91 blocking the return to the main program until one is
92 received. When a valid route table arrives, initialization
93 will complete and the program will exit as there is no code
94 following the instruction to create the object.
97 LISTENING FOR MESSAGES
98 ======================
100 The program in the previous example can be extended with just
101 a few lines of code to enable it to receive and process
102 messages. The application needs to register a callback
103 function for each message type which it desires to process.
105 Once registered, each time a message is received the
106 registered callback for the message type will be invoked by
113 As with most callback related systems, a callback must have a
114 well known function signature which generally passes event
115 related information and a "user" data pointer which was
116 registered with the function. The following is the prototype
117 which callback functions must be defined with:
122 void cb_name( Message& m, int mtype, int subid,
123 int payload_len, Msg_component payload,
126 Figure 2: Callback function signature
128 The parameters passed to the callback function are as
129 follows: &multi_space
132 A reference to the Message that was received.
135 The message type (allows for disambiguation if the
136 callback is registered for multiple message types).
139 The subscription ID from the message.
142 The number of bytes which the sender has placed into the
146 A direct reference (smart pointer) to the payload. (The
147 smart pointer is wrapped in a special class in order to
148 provide a custom destruction function without burdening
149 the xApp developer with that knowledge.)
152 A pointer to user data. This is the pointer that was
153 provided when the function was registered.
155 To illustrate the use of a callback function, the previous
156 code example has been extended to add the function, register
157 it for message types 1000 and 1001, and to invoke the Run()
158 function in the framework (explained in the next section).
163 #include <ricxfcpp/xapp.hpp>
164 long m1000_count = 0; // message counters, one for each type
165 long m1001_count = 0;
167 // callback function that will increase the appropriate counter
168 void cbf( Message& mbuf, int mtype, int subid, int len,
169 Msg_component payload, void* data ) {
172 if( (counter = (long *) data) != NULL ) {
178 std::unique_ptr<Xapp> xapp;
179 char* listen_port = (char *) "4560";
180 bool wait4table = false;
182 xapp = std::unique_ptr<Xapp>(
183 new Xapp( listen_port, wait4table ) );
185 // register the same callback function for both msg types
186 xapp->Add_msg_cb( 1000, cbf, (void *) &m1000_count );
187 xapp->Add_msg_cb( 1001, cbf, (void *) &m1001_count );
189 xapp->Run( 1 ); // start the callback driver
192 Figure 3: Callback function example.
194 As before, the program does nothing useful, but now it will
195 execute and receive messages. For this example, the same
196 function can be used to increment the appropriate counter
197 simply by providing a pointer to the counter as the user data
198 when the callback function is registered. In addition, a
199 subtle change from the previous example has been to set the
200 wait for table flag to false.
202 For an xApp that is a receive only application (never sends)
203 it is not necessary to wait for RMR to receive a table from
207 Registering A Default Callback
208 ------------------------------
210 The xApp may also register a default callback function such
211 that the function will be invoked for any message that does
212 not have a registered callback. If the xAPP does not register
213 a default callback, any message which cannot be mapped to a
214 known callback function is silently dropped. A default
215 callback is registered by providing a *generic* message type
216 of xapp->DEFAULT_CALLBACK on an Add_msg_cb call.
219 The Framework Callback Driver
220 -----------------------------
222 The Run() function within the Xapp object is invoked to start
223 the callback driver, and the xApp should not expect the
224 function to return under most circumstances. The only
225 parameter that the Run() function expects is the number of
226 threads to start. For each thread requested, the framework
227 will start a listener thread which will allow received
228 messages to be processed in parallel. If supplying a value
229 greater than one, the xApp must ensure that the callback
230 functions are thread safe as it is very likely that the same
231 callback function will be invoked concurrently from multiple
238 It is very likely that most xApps will need to send messages
239 and will not operate in "receive only" mode. Sending the
240 message is a function of the message object itself and can
241 take one of two forms:
244 + Replying to the sender of a received message
246 + Sending a message (routed based on the message type and subscription ID)
249 When replying to the sender, the message type and
250 subscription ID are not used to determine the destination of
251 the message; RMR ensures that the message is sent back to the
252 originating xApp. The xApp may still need to change the
253 message type and/or the subscription ID in the message prior
254 to using the reply function.
256 To provide for both situations, two reply functions are
257 supported by the Message object as illustrated with the
258 following prototypes.
263 bool Send_response( int mtype, int subid, int response_len,
264 std:shared_ptr<unsigned char> response );
266 bool Send_response( int response_len, std::shared_ptr<unsigned char> response );
268 Figure 4: Reply function prototypes.
270 In the first prototype the xApp must supply the new message
271 type and subscription ID values, where the second function
272 uses the values which are currently set in the message.
273 Further, the new payload contents, and length, are supplied
274 to both functions; the framework ensures that the message is
275 large enough to accommodate the payload, reallocating it if
276 necessary, and copies the response into the message payload
277 prior to sending. Should the xApp need to change either the
278 message type, or the subscription ID, but not both, the
279 NO_CHANGE constant can be used as illustrated below.
284 msg->Send_response( Message::NO_CHANGE, Message::NO_SUBID,
285 pl_length, (unsigned char *) payload );
287 Figure 5: Send response prototype.
289 In addition to the two function prototypes for
290 Send_response() there are two additional prototypes which
291 allow the new payload to be supplied as a shared smart
292 pointer. The other parameters to these functions are
293 identical to those illustrated above, and thus are not
296 The Send_msg() set of functions supported by the Message
297 object are identical to the Send_response() functions and are
303 bool Send_msg( int mtype, int subid, int payload_len,
304 std::shared_ptr<unsigned char> payload );
306 bool Send_msg( int mtype, int subid, int payload_len,
307 unsigned char* payload );
309 bool Send_msg( int payload_len,
310 std::shared_ptr<unsigned char> payload );
312 bool Send_msg( int payload_len, unsigned char* payload );
314 Figure 6: Send function prototypes.
316 Each send function accepts the message, copies in the payload
317 provided, sets the message type and subscription ID (if
318 provided), and then causes the message to be sent. The only
319 difference between the Send_msg() and Send_response()
320 functions is that the destination of the message is selected
321 based on the mapping of the message type and subscription ID
322 using the current routing table known to RMR.
325 Direct Payload Manipulation
326 ---------------------------
328 For some applications, it might be more efficient to
329 manipulate the payload portion of an Xapp Message in place,
330 rather than creating it and relying on a buffer copy when the
331 message is finally sent. To achieve this, the xApp must
332 either use the smart pointer to the payload passed to the
333 callback function, or retrieve one from the message using
334 Get_payload() when working with a message outside of a
335 callback function. Once the smart pointer is obtained, the
336 pointer's get() function can be used to directly reference
337 the payload (unsigned char) bytes.
339 When working directly with the payload, the xApp must take
340 care not to write more than the actual payload size which can
341 be extracted from the Message object using the
342 Get_available_size() function.
344 When sending a message where the payload has been directly
345 altered, and no extra buffer copy is needed, a NULL pointer
346 should be passed to the Message send function. The following
347 illustrates how the payload can be directly manipulated and
348 returned to the sender (for simplicity, there is no error
349 handling if the payload size of the received message isn't
350 large enough for the response string, the response is just
356 Msg_component payload; // smart reference
357 int pl_size; // max size of payload
359 payload = msg->Get_payload();
360 pl_size = msg->Get_available_size();
361 if( snprintf( (char *) payload.get(), pl_size,
362 "Msg Received\\n" ) < pl_size ) {
363 msg->Send_response( M_TYPE, SID, strlen( raw_pl ), NULL );
366 Figure 7: Send message without buffer copy.
370 Sending Multiple Responses
371 --------------------------
373 It is likely that the xApp will wish to send multiple
374 responses back to the process that sent a message that
375 triggered the callback. The callback function may invoke the
376 Send_response() function multiple times before returning.
378 After each call, the Message retains the necessary
379 information to allow for a subsequent invocation to send more
380 data. It should be noted though, that after the first call to
381 {Send_response() the original payload will be lost; if
382 necessary, the xApp must make a copy of the payload before
383 the first response call is made.
389 Not all xApps will be "responders," meaning that some xApps
390 will need to send one or more messages before they can expect
391 to receive any messages back. To accomplish this, the xApp
392 must first allocate a message buffer, optionally initialising
393 the payload, and then using the message's Send_msg() function
394 to send a message out. The framework's Alloc_msg() function
395 can be used to create a Message object with a desired payload
399 FRAMEWORK PROVIDED CALLBACKS
400 ============================
402 The framework itself may provide message handling via the
403 driver such that the xApp might not need to implement some
404 message processing functionality. Initially, the C++
405 framework will provide a default callback function to handle
406 the RMR based health check messages. This callback function
407 will assume that if the message was received, and the
408 callback invoked, that all is well and will reply with an OK
409 state. If the xApp should need to override this simplistic
410 response, all it needs to do is to register its own callback
411 function for the health check message type.
417 The following sections contain several example programmes
418 which are written on top of the C++ framework.
424 The RMR dump application is an example built on top of the
425 C++ xApp framework to both illustrate the use of the
426 framework, and to provide a useful diagnostic tool when
427 testing and troubleshooting xApps.
429 The RMR dump xApp isn't a traditional xApp inasmuch as its
430 goal is to listen for message types and to dump information
431 about the messages received to the TTY much as tcpdump does
432 for raw packet traffic. The full source code, and Makefile,
433 are in the examples directory of the C++ framework repo.
435 When invoked, the RMR dump program is given one or more
436 message types to listen for. A callback function is
437 registered for each, and the framework Run() function is
438 invoked to drive the process. For each recognised message,
439 and depending on the verbosity level supplied at program
440 start, information about the received message(s) is written
441 to the TTY. If the forwarding option, -f, is given on the
442 command line, and an appropriate route table is provided,
443 each received message is forwarded without change. This
444 allows for the insertion of the RMR dump program into a flow,
445 however if the ultimate receiver of a message needs to reply
446 to that message, the reply will not reach the original
447 sender, so RMR dump is not a complete "middle box"
450 The following is the code for this xAPP. Several functions,
451 which provide logic unrelated to the framework, have been
452 omitted. The full code is in the framework repository.
462 #include "ricxfcpp/xapp.hpp"
465 Information that the callback needs outside
466 of what is given to it via parms on a call
470 int vlevel; // verbosity level
471 bool forward; // if true, message is forwarded
472 int stats_freq; // header/stats after n messages
473 std::atomic<long> pcount; // messages processed
474 std::atomic<long> icount; // messages ignored
475 std::atomic<int> hdr; // number of messages before next header
478 // ----------------------------------------------------------------------
483 void dump( unsigned const char* buf, int len ) {
488 fprintf( stdout, "<RD> 0000 | " );
490 for( i = 0; i < len; i++ ) {
491 cheater[j++] = isprint( buf[i] ) ? buf[i] : '.';
492 fprintf( stdout, "%02x ", buf[i] );
496 fprintf( stdout, " | %s\\n<RD> %04x | ", cheater, i+1 );
503 for( ; i > 0; i-- ) {
504 fprintf( stdout, " " );
507 fprintf( stdout, " | %s\\n", cheater );
512 generate stats when the hdr count reaches 0. Only one active
513 thread will ever see it be exactly 0, so this is thread safe.
515 void stats( cb_info_t& cbi ) {
516 int curv; // current stat trigger value
520 if( curv == 0 ) { // stats when we reach 0
521 fprintf( stdout, "ignored: %ld processed: %ld\\n",
522 cbi.icount.load(), cbi.pcount.load() );
523 if( cbi.vlevel > 0 ) {
524 fprintf( stdout, "\\n %5s %5s %2s %5s\\n",
525 "MTYPE", "SUBID", "ST", "PLLEN" );
528 cbi.hdr = cbi.stats_freq; // reset must be last
532 void cb1( Message& mbuf, int mtype, int subid, int len,
533 Msg_component payload, void* data ) {
537 if( (cbi = (cb_info_t *) data) == NULL ) {
542 stats( *cbi ); // gen stats & header if needed
544 if( cbi->vlevel > 0 ) {
545 fprintf( stdout, "<RD> %-5d %-5d %02d %-5d \\n",
546 mtype, subid, mbuf.Get_state(), len );
548 if( cbi->vlevel > 1 ) {
549 dump( payload.get(), len > 64 ? 64 : len );
554 // forward with no change to len or payload
555 mbuf.Send_msg( Message::NO_CHANGE, NULL );
560 registered as the default callback; it counts the
561 messages that we aren't giving details about.
563 void cbd( Message& mbuf, int mtype, int subid, int len,
564 Msg_component payload, void* data ) {
567 if( (cbi = (cb_info_t *) data) == NULL ) {
575 // forward with no change to len or payload
576 mbuf.Send_msg( Message::NO_CHANGE, NULL );
580 int main( int argc, char** argv ) {
581 std::unique_ptr<Xapp> x;
582 char* port = (char *) "4560";
583 int ai = 1; // arg processing index
585 int ncb = 0; // number of callbacks registered
589 cbi = (cb_info_t *) malloc( sizeof( *cbi ) );
592 cbi->stats_freq = 10;
595 // very simple flag parsing (no error/bounds checking)
597 if( argv[ai][0] != '-' ) { // break on first non-flag
601 // very simple arg parsing; each must be separate -x -y not -xy.
602 switch( argv[ai][1] ) {
603 case 'f': // enable packet forwarding
607 case 'p': // define port
612 case 's': // stats frequency
613 cbi->stats_freq = atoi( argv[ai+1] );
614 if( cbi->stats_freq < 5 ) { // enforce sanity
620 case 't': // thread count
621 nthreads = atoi( argv[ai+1] );
628 case 'v': // simple verbose bump
632 case 'V': // explicit verbose level
633 cbi->vlevel = atoi( argv[ai+1] );
638 fprintf( stderr, "unrecognised option: %s\\n", argv[ai] );
639 fprintf( stderr, "usage: %s [-f] [-p port] "
640 "[-s stats-freq] [-t thread-count] "
641 "[-v | -V n] msg-type1 ... msg-typen\\n",
643 fprintf( stderr, "\\tstats frequency is based on # of messages received\\n" );
644 fprintf( stderr, "\\tverbose levels (-V) 0 counts only, "
645 "1 message info 2 payload dump\\n" );
652 cbi->hdr = cbi->stats_freq;
653 fprintf( stderr, "<RD> listening on port: %s\\n", port );
655 // create xapp, wait for route table if forwarding
656 x = std::unique_ptr<Xapp>( new Xapp( port, cbi->forward ) );
658 // register callback for each type on the command line
660 mtype = atoi( argv[ai] );
662 fprintf( stderr, "<RD> capturing messages for type %d\\n", mtype );
663 x->Add_msg_cb( mtype, cb1, cbi );
668 fprintf( stderr, "<RD> no message types specified on the command line\\n" );
672 x->Add_msg_cb( x->DEFAULT_CALLBACK, cbd, cbi ); // register default cb
674 fprintf( stderr, "<RD> starting driver\\n" );
677 // return from run() is not expected, but some compilers might
678 // compilain if there isn't a return value here.
682 Figure 8: Simple callback application.
688 This sample programme implements a simple message listener
689 which registers three callback functions to process two
690 specific message types and a default callback to handle
691 unrecognised messages.
693 When a message of type 1 is received, it will send two
694 response messages back to the sender. Two messages are sent
695 in order to illustrate that it is possible to send multiple
696 responses using the same received message.
698 The programme illustrates how multiple listening threads can
699 be used, but the programme is **not** thread safe; to keep
700 this example as simple as possible, the counters are not
701 locked when incremented.
708 #include "ricxfcpp/message.hpp"
709 #include "ricxfcpp/msg_component.hpp"
710 #include "ricxfcpp/xapp.hpp"
712 // counts; not thread safe
720 // respond with 2 messages for each type 1 received
721 void cb1( Message& mbuf, int mtype, int subid, int len,
722 Msg_component payload, void* data ) {
726 // illustrate that we can use the same buffer for 2 rts calls
727 mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK1\\n" );
728 mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK2\\n" );
733 // just count messages
734 void cb2( Message& mbuf, int mtype, int subid, int len,
735 Msg_component payload, void* data ) {
739 // default to count all unrecognised messages
740 void cbd( Message& mbuf, int mtype, int subid, int len,
741 Msg_component payload, void* data ) {
745 int main( int argc, char** argv ) {
747 char* port = (char *) "4560";
748 int ai = 1; // arg processing index
751 // very simple flag processing (no bounds/error checking)
753 if( argv[ai][0] != '-' ) {
757 switch( argv[ai][1] ) { // we only support -x so -xy must be -x -y
764 nthreads = atoi( argv[ai+1] );
772 fprintf( stderr, "<XAPP> listening on port: %s\\n", port );
773 fprintf( stderr, "<XAPP> starting %d threads\\n", nthreads );
775 x = new Xapp( port, true );
776 x->Add_msg_cb( 1, cb1, NULL ); // register callbacks
777 x->Add_msg_cb( 2, cb2, NULL );
778 x->Add_msg_cb( x->DEFAULT_CALLBACK, cbd, NULL );
780 x->Run( nthreads ); // let framework drive
781 // control should not return
784 Figure 9: Simple callback application.
791 This is another very simple application which demonstrates
792 how an application can control its own listen loop while
793 sending messages. As with the other examples, some error
794 checking is skipped, and short cuts have been made in order
795 to keep the example small and to the point.
808 #include "ricxfcpp/xapp.hpp"
810 extern int main( int argc, char** argv ) {
811 std::unique_ptr<Xapp> xfw;
812 std::unique_ptr<Message> msg;
813 Msg_component payload; // special type of unique pointer to the payload
819 int response_to = 0; // max timeout wating for a response
820 char* port = (char *) "4555";
822 int rmtype; // received message type
823 int delay = 1000000; // mu-sec delay; default 1s
826 // very simple flag processing (no bounds/error checking)
828 if( argv[ai][0] != '-' ) {
832 // we only support -x so -xy must be -x -y
833 switch( argv[ai][1] ) {
834 // delay between messages (mu-sec)
836 delay = atoi( argv[ai+1] );
845 // timeout in seconds; we need to convert to ms for rmr calls
847 response_to = atoi( argv[ai+1] ) * 1000;
854 fprintf( stderr, "<XAPP> response timeout set to: %d\\n", response_to );
855 fprintf( stderr, "<XAPP> listening on port: %s\\n", port );
857 // get an instance and wait for a route table to be loaded
858 xfw = std::unique_ptr<Xapp>( new Xapp( port, true ) );
859 msg = xfw->Alloc_msg( 2048 );
861 for( i = 0; i < 100; i++ ) {
867 // we'll reuse a received message; get max size
868 sz = msg->Get_available_size();
870 // direct access to payload; add something silly
871 payload = msg->Get_payload();
872 len = snprintf( (char *) payload.get(), sz, "This is message %d\\n", i );
874 // payload updated in place, prevent copy by passing nil
875 if ( ! msg->Send_msg( mtype, Message::NO_SUBID, len, NULL )) {
876 fprintf( stderr, "<SNDR> send failed: %d\\n", i );
879 // receive anything that might come back
880 msg = xfw->Receive( response_to );
882 rmtype = msg->Get_mtype();
883 payload = msg->Get_payload();
884 fprintf( stderr, "got: mtype=%d payload=(%s)\\n",
885 rmtype, (char *) payload.get() );
887 msg = xfw->Alloc_msg( 2048 );
896 Figure 10: Simple looping sender application.