3 .. This work is licensed under a Creative Commons Attribution 4.0 International License.
4 .. SPDX-License-Identifier: CC-BY-4.0
6 .. CAUTION: this document is generated from source in doc/src/*
7 .. To make changes edit the source and recompile the document.
8 .. Do NOT make changes directly to .rst or .md files.
11 ============================================================================================
12 RIC xAPP C++ Framework
13 ============================================================================================
14 --------------------------------------------------------------------------------------------
16 --------------------------------------------------------------------------------------------
19 ============================================================================================
21 The C++ framework allows the programmer to create an xApp
22 object instance, and to use that instance as the logic base.
23 The xApp object provides a message level interface to the RIC
24 Message Router (RMR), including the ability to register
25 callback functions which the instance will drive as messages
26 are received; much in the same way that an X-windows
27 application is driven by the window manager for all activity.
28 The xApp may also choose to use its own send/receive loop,
29 and thus is not required to use the callback driver mechanism
30 provided by the framework.
33 ============================================================================================
35 The C++ framework API consists of the creation of the xApp
36 object, and invoking desired functions via the instance of
37 the object. The following paragraphs cover the various steps
38 involved to create an xApp instance, wait for a route table
39 to arrive, send a message, and wait for messages to arrive.
41 Creating the xApp instance
42 --------------------------------------------------------------------------------------------
44 The creation of the xApp instance is as simple as invoking
45 the object's constructor with two required parameters:
51 A C string (pointer to char) which defines the port that RMR will
52 open to listen for connections.
57 A Boolean value which indicates whether or not the
58 initialization process should wait for the arrival of a
59 valid route table before completing. When true is
60 supplied, the initialization will not complete until RMR
61 has received a valid route table (or one is located via
62 the RMR_SEED_RT environment variable).
65 The following code sample illustrates the simplicity of
66 creating the instance of the xApp object.
72 #include <ricxfcpp/xapp.hpp>
74 std::unique_ptr<Xapp> xapp;
75 char* listen_port = (char *) "4560"; //RMR listen port
76 bool wait4table = true; // wait for a route table
77 xapp = std::unique_ptr<Xapp>(
78 new Xapp( listen_port, wait4table ) );
82 Figure 1: Creating an xAPP instance.
84 From a compilation perspective, the following is the simple
85 compiler invocation string needed to compile and link the
86 above program (assuming that the sample code exists in a file
92 g++ man_ex1.cpp -o man_ex1 -lricxfcpp -lrmr_si -lpthread
96 The above program, while complete and capable of being
97 compiled, does nothing useful. When invoked, RMR will be
98 initialized and will begin listening for a route table;
99 blocking the return to the main program until one is
100 received. When a valid route table arrives, initialization
101 will complete and the program will exit as there is no code
102 following the instruction to create the object.
104 Listening For Messages
105 ============================================================================================
107 The program in the previous example can be extended with just
108 a few lines of code to enable it to receive and process
109 messages. The application needs to register a callback
110 function for each message type which it desires to process.
112 Once registered, each time a message is received the
113 registered callback for the message type will be invoked by
117 --------------------------------------------------------------------------------------------
119 As with most callback related systems, a callback must have a
120 well known function signature which generally passes event
121 related information and a "user" data pointer which was
122 registered with the function. The following is the prototype
123 which callback functions must be defined with:
128 void cb_name( Message& m, int mtype, int subid,
129 int payload_len, Msg_component payload,
133 Figure 2: Callback function signature
135 The parameters passed to the callback function are as
136 follows: &multi_space
141 A reference to the Message that was received.
146 The message type (allows for disambiguation if the
147 callback is registered for multiple message types).
152 The subscription ID from the message.
157 The number of bytes which the sender has placed into the
163 A direct reference (smart pointer) to the payload. (The
164 smart pointer is wrapped in a special class in order to
165 provide a custom destruction function without burdening
166 the xApp developer with that knowledge.)
171 A pointer to user data. This is the pointer that was
172 provided when the function was registered.
175 To illustrate the use of a callback function, the previous
176 code example has been extended to add the function, register
177 it for message types 1000 and 1001, and to invoke the Run()
178 function in the framework (explained in the next section).
183 #include <ricxfcpp/xapp.hpp>
184 long m1000_count = 0; // message counters, one for each type
185 long m1001_count = 0;
186 // callback function that will increase the appropriate counter
187 void cbf( Message& mbuf, int mtype, int subid, int len,
188 Msg_component payload, void* data ) {
190 if( (counter = (long *) data) != NULL ) {
195 std::unique_ptr<Xapp> xapp;
196 char* listen_port = (char *) "4560";
197 bool wait4table = false;
198 xapp = std::unique_ptr<Xapp>(
199 new Xapp( listen_port, wait4table ) );
200 // register the same callback function for both msg types
201 xapp->Add_msg_cb( 1000, cbf, (void *) &m1000_count );
202 xapp->Add_msg_cb( 1001, cbf, (void *) &m1001_count );
203 xapp->Run( 1 ); // start the callback driver
207 Figure 3: Callback function example.
209 As before, the program does nothing useful, but now it will
210 execute and receive messages. For this example, the same
211 function can be used to increment the appropriate counter
212 simply by providing a pointer to the counter as the user data
213 when the callback function is registered. In addition, a
214 subtle change from the previous example has been to set the
215 wait for table flag to false.
217 For an xApp that is a receive only application (never sends)
218 it is not necessary to wait for RMR to receive a table from
221 Registering A Default Callback
222 --------------------------------------------------------------------------------------------
224 The xApp may also register a default callback function such
225 that the function will be invoked for any message that does
226 not have a registered callback. If the xAPP does not register
227 a default callback, any message which cannot be mapped to a
228 known callback function is silently dropped. A default
229 callback is registered by providing a *generic* message type
230 of xapp->DEFAULT_CALLBACK on an Add_msg_cb call.
232 The Framework Callback Driver
233 --------------------------------------------------------------------------------------------
235 The Run() function within the Xapp object is invoked to start
236 the callback driver, and the xApp should not expect the
237 function to return under most circumstances. The only
238 parameter that the Run() function expects is the number of
239 threads to start. For each thread requested, the framework
240 will start a listener thread which will allow received
241 messages to be processed in parallel. If supplying a value
242 greater than one, the xApp must ensure that the callback
243 functions are thread safe as it is very likely that the same
244 callback function will be invoked concurrently from multiple
248 ============================================================================================
250 It is very likely that most xApps will need to send messages
251 and will not operate in "receive only" mode. Sending the
252 message is a function of the message object itself and can
253 take one of two forms:
257 $1 Replying to the sender of a received message
259 $1 Sending a message (routed based on the message type and subscription ID)
262 When replying to the sender, the message type and
263 subscription ID are not used to determine the destination of
264 the message; RMR ensures that the message is sent back to the
265 originating xApp. The xApp may still need to change the
266 message type and/or the subscription ID in the message prior
267 to using the reply function.
269 To provide for both situations, two reply functions are
270 supported by the Message object as illustrated with the
271 following prototypes.
276 bool Send_response( int mtype, int subid, int response_len,
277 std:shared_ptr<unsigned char> response );
278 bool Send_response( int response_len, std::shared_ptr<unsigned char> response );
281 Figure 4: Reply function prototypes.
283 In the first prototype the xApp must supply the new message
284 type and subscription ID values, where the second function
285 uses the values which are currently set in the message.
286 Further, the new payload contents, and length, are supplied
287 to both functions; the framework ensures that the message is
288 large enough to accommodate the payload, reallocating it if
289 necessary, and copies the response into the message payload
290 prior to sending. Should the xApp need to change either the
291 message type, or the subscription ID, but not both, the
292 NO_CHANGE constant can be used as illustrated below.
297 msg->Send_response( Message::NO_CHANGE, Message::NO_SUBID,
298 pl_length, (unsigned char *) payload );
301 Figure 5: Send response prototype.
303 In addition to the two function prototypes for
304 Send_response() there are two additional prototypes which
305 allow the new payload to be supplied as a shared smart
306 pointer. The other parameters to these functions are
307 identical to those illustrated above, and thus are not
310 The Send_msg() set of functions supported by the Message
311 object are identical to the Send_response() functions and are
317 bool Send_msg( int mtype, int subid, int payload_len,
318 std::shared_ptr<unsigned char> payload );
319 bool Send_msg( int mtype, int subid, int payload_len,
320 unsigned char* payload );
321 bool Send_msg( int payload_len,
322 std::shared_ptr<unsigned char> payload );
323 bool Send_msg( int payload_len, unsigned char* payload );
326 Figure 6: Send function prototypes.
328 Each send function accepts the message, copies in the payload
329 provided, sets the message type and subscription ID (if
330 provided), and then causes the message to be sent. The only
331 difference between the Send_msg() and Send_response()
332 functions is that the destination of the message is selected
333 based on the mapping of the message type and subscription ID
334 using the current routing table known to RMR.
336 Direct Payload Manipulation
337 --------------------------------------------------------------------------------------------
339 For some applications, it might be more efficient to
340 manipulate the payload portion of an Xapp Message in place,
341 rather than creating it and relying on a buffer copy when the
342 message is finally sent. To achieve this, the xApp must
343 either use the smart pointer to the payload passed to the
344 callback function, or retrieve one from the message using
345 Get_payload() when working with a message outside of a
346 callback function. Once the smart pointer is obtained, the
347 pointer's get() function can be used to directly reference
348 the payload (unsigned char) bytes.
350 When working directly with the payload, the xApp must take
351 care not to write more than the actual payload size which can
352 be extracted from the Message object using the
353 Get_available_size() function.
355 When sending a message where the payload has been directly
356 altered, and no extra buffer copy is needed, a NULL pointer
357 should be passed to the Message send function. The following
358 illustrates how the payload can be directly manipulated and
359 returned to the sender (for simplicity, there is no error
360 handling if the payload size of the received message isn't
361 large enough for the response string, the response is just
367 Msg_component payload; // smart reference
368 int pl_size; // max size of payload
369 payload = msg->Get_payload();
370 pl_size = msg->Get_available_size();
371 if( snprintf( (char *) payload.get(), pl_size,
372 "Msg Received\\n" ) < pl_size ) {
373 msg->Send_response( M_TYPE, SID, strlen( raw_pl ), NULL );
377 Figure 7: Send message without buffer copy.
380 Sending Multiple Responses
381 --------------------------------------------------------------------------------------------
383 It is likely that the xApp will wish to send multiple
384 responses back to the process that sent a message that
385 triggered the callback. The callback function may invoke the
386 Send_response() function multiple times before returning.
388 After each call, the Message retains the necessary
389 information to allow for a subsequent invocation to send more
390 data. It should be noted though, that after the first call to
391 {Send_response() the original payload will be lost; if
392 necessary, the xApp must make a copy of the payload before
393 the first response call is made.
396 --------------------------------------------------------------------------------------------
398 Not all xApps will be "responders," meaning that some xApps
399 will need to send one or more messages before they can expect
400 to receive any messages back. To accomplish this, the xApp
401 must first allocate a message buffer, optionally initialising
402 the payload, and then using the message's Send_msg() function
403 to send a message out. The framework's Alloc_msg() function
404 can be used to create a Message object with a desired payload
407 Framework Provided Callbacks
408 ============================================================================================
410 The framework itself may provide message handling via the
411 driver such that the xApp might not need to implement some
412 message processing functionality. Initially, the C++
413 framework will provide a default callback function to handle
414 the RMR based health check messages. This callback function
415 will assume that if the message was received, and the
416 callback invoked, that all is well and will reply with an OK
417 state. If the xApp should need to override this simplistic
418 response, all it needs to do is to register its own callback
419 function for the health check message type.
422 ============================================================================================
424 The following sections contain several example programmes
425 which are written on top of the C++ framework.
428 --------------------------------------------------------------------------------------------
430 The RMR dump application is an example built on top of the
431 C++ xApp framework to both illustrate the use of the
432 framework, and to provide a useful diagnostic tool when
433 testing and troubleshooting xApps.
435 The RMR dump xApp isn't a traditional xApp inasmuch as its
436 goal is to listen for message types and to dump information
437 about the messages received to the TTY much as tcpdump does
438 for raw packet traffic. The full source code, and Makefile,
439 are in the examples directory of the C++ framework repo.
441 When invoked, the RMR dump program is given one or more
442 message types to listen for. A callback function is
443 registered for each, and the framework Run() function is
444 invoked to drive the process. For each recognised message,
445 and depending on the verbosity level supplied at program
446 start, information about the received message(s) is written
447 to the TTY. If the forwarding option, -f, is given on the
448 command line, and an appropriate route table is provided,
449 each received message is forwarded without change. This
450 allows for the insertion of the RMR dump program into a flow,
451 however if the ultimate receiver of a message needs to reply
452 to that message, the reply will not reach the original
453 sender, so RMR dump is not a complete "middle box"
456 The following is the code for this xAPP. Several functions,
457 which provide logic unrelated to the framework, have been
458 omitted. The full code is in the framework repository.
467 #include "ricxfcpp/xapp.hpp"
469 Information that the callback needs outside
470 of what is given to it via parms on a call
474 int vlevel; // verbosity level
475 bool forward; // if true, message is forwarded
476 int stats_freq; // header/stats after n messages
477 std::atomic<long> pcount; // messages processed
478 std::atomic<long> icount; // messages ignored
479 std::atomic<int> hdr; // number of messages before next header
481 // ----------------------------------------------------------------------
485 void dump( unsigned const char* buf, int len ) {
489 fprintf( stdout, "<RD> 0000 | " );
491 for( i = 0; i < len; i++ ) {
492 cheater[j++] = isprint( buf[i] ) ? buf[i] : '.';
493 fprintf( stdout, "%02x ", buf[i] );
496 fprintf( stdout, " | %s\\n<RD> %04x | ", cheater, i+1 );
502 for( ; i > 0; i-- ) {
503 fprintf( stdout, " " );
506 fprintf( stdout, " | %s\\n", cheater );
510 generate stats when the hdr count reaches 0. Only one active
511 thread will ever see it be exactly 0, so this is thread safe.
513 void stats( cb_info_t& cbi ) {
514 int curv; // current stat trigger value
516 if( curv == 0 ) { // stats when we reach 0
517 fprintf( stdout, "ignored: %ld processed: %ld\\n",
518 cbi.icount.load(), cbi.pcount.load() );
519 if( cbi.vlevel > 0 ) {
520 fprintf( stdout, "\\n %5s %5s %2s %5s\\n",
521 "MTYPE", "SUBID", "ST", "PLLEN" );
523 cbi.hdr = cbi.stats_freq; // reset must be last
526 void cb1( Message& mbuf, int mtype, int subid, int len,
527 Msg_component payload, void* data ) {
530 if( (cbi = (cb_info_t *) data) == NULL ) {
534 stats( *cbi ); // gen stats & header if needed
535 if( cbi->vlevel > 0 ) {
536 fprintf( stdout, "<RD> %-5d %-5d %02d %-5d \\n",
537 mtype, subid, mbuf.Get_state(), len );
538 if( cbi->vlevel > 1 ) {
539 dump( payload.get(), len > 64 ? 64 : len );
543 // forward with no change to len or payload
544 mbuf.Send_msg( Message::NO_CHANGE, NULL );
548 registered as the default callback; it counts the
549 messages that we aren't giving details about.
551 void cbd( Message& mbuf, int mtype, int subid, int len,
552 Msg_component payload, void* data ) {
554 if( (cbi = (cb_info_t *) data) == NULL ) {
560 // forward with no change to len or payload
561 mbuf.Send_msg( Message::NO_CHANGE, NULL );
564 int main( int argc, char** argv ) {
565 std::unique_ptr<Xapp> x;
566 char* port = (char *) "4560";
567 int ai = 1; // arg processing index
569 int ncb = 0; // number of callbacks registered
572 cbi = (cb_info_t *) malloc( sizeof( *cbi ) );
575 cbi->stats_freq = 10;
577 // very simple flag parsing (no error/bounds checking)
579 if( argv[ai][0] != '-' ) { // break on first non-flag
582 // very simple arg parsing; each must be separate -x -y not -xy.
583 switch( argv[ai][1] ) {
584 case 'f': // enable packet forwarding
587 case 'p': // define port
591 case 's': // stats frequency
592 cbi->stats_freq = atoi( argv[ai+1] );
593 if( cbi->stats_freq < 5 ) { // enforce sanity
598 case 't': // thread count
599 nthreads = atoi( argv[ai+1] );
605 case 'v': // simple verbose bump
608 case 'V': // explicit verbose level
609 cbi->vlevel = atoi( argv[ai+1] );
613 fprintf( stderr, "unrecognised option: %s\\n", argv[ai] );
614 fprintf( stderr, "usage: %s [-f] [-p port] "
615 "[-s stats-freq] [-t thread-count] "
616 "[-v | -V n] msg-type1 ... msg-typen\\n",
618 fprintf( stderr, "\\tstats frequency is based on # of messages received\\n" );
619 fprintf( stderr, "\\tverbose levels (-V) 0 counts only, "
620 "1 message info 2 payload dump\\n" );
625 cbi->hdr = cbi->stats_freq;
626 fprintf( stderr, "<RD> listening on port: %s\\n", port );
627 // create xapp, wait for route table if forwarding
628 x = std::unique_ptr<Xapp>( new Xapp( port, cbi->forward ) );
629 // register callback for each type on the command line
631 mtype = atoi( argv[ai] );
633 fprintf( stderr, "<RD> capturing messages for type %d\\n", mtype );
634 x->Add_msg_cb( mtype, cb1, cbi );
638 fprintf( stderr, "<RD> no message types specified on the command line\\n" );
641 x->Add_msg_cb( x->DEFAULT_CALLBACK, cbd, cbi ); // register default cb
642 fprintf( stderr, "<RD> starting driver\\n" );
644 // return from run() is not expected, but some compilers might
645 // compilain if there isn't a return value here.
650 Figure 8: Simple callback application.
653 --------------------------------------------------------------------------------------------
655 This sample programme implements a simple message listener
656 which registers three callback functions to process two
657 specific message types and a default callback to handle
658 unrecognised messages.
660 When a message of type 1 is received, it will send two
661 response messages back to the sender. Two messages are sent
662 in order to illustrate that it is possible to send multiple
663 responses using the same received message.
665 The programme illustrates how multiple listening threads can
666 be used, but the programme is **not** thread safe; to keep
667 this example as simple as possible, the counters are not
668 locked when incremented.
674 #include "ricxfcpp/message.hpp"
675 #include "ricxfcpp/msg_component.hpp"
676 #include "ricxfcpp/xapp.hpp"
677 // counts; not thread safe
683 // respond with 2 messages for each type 1 received
684 void cb1( Message& mbuf, int mtype, int subid, int len,
685 Msg_component payload, void* data ) {
688 // illustrate that we can use the same buffer for 2 rts calls
689 mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK1\\n" );
690 mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK2\\n" );
693 // just count messages
694 void cb2( Message& mbuf, int mtype, int subid, int len,
695 Msg_component payload, void* data ) {
698 // default to count all unrecognised messages
699 void cbd( Message& mbuf, int mtype, int subid, int len,
700 Msg_component payload, void* data ) {
703 int main( int argc, char** argv ) {
705 char* port = (char *) "4560";
706 int ai = 1; // arg processing index
708 // very simple flag processing (no bounds/error checking)
710 if( argv[ai][0] != '-' ) {
713 switch( argv[ai][1] ) { // we only support -x so -xy must be -x -y
719 nthreads = atoi( argv[ai+1] );
725 fprintf( stderr, "<XAPP> listening on port: %s\\n", port );
726 fprintf( stderr, "<XAPP> starting %d threads\\n", nthreads );
727 x = new Xapp( port, true );
728 x->Add_msg_cb( 1, cb1, NULL ); // register callbacks
729 x->Add_msg_cb( 2, cb2, NULL );
730 x->Add_msg_cb( x->DEFAULT_CALLBACK, cbd, NULL );
731 x->Run( nthreads ); // let framework drive
732 // control should not return
736 Figure 9: Simple callback application.
740 --------------------------------------------------------------------------------------------
742 This is another very simple application which demonstrates
743 how an application can control its own listen loop while
744 sending messages. As with the other examples, some error
745 checking is skipped, and short cuts have been made in order
746 to keep the example small and to the point.
756 #include "ricxfcpp/xapp.hpp"
757 extern int main( int argc, char** argv ) {
758 std::unique_ptr<Xapp> xfw;
759 std::unique_ptr<Message> msg;
760 Msg_component payload; // special type of unique pointer to the payload
765 int response_to = 0; // max timeout wating for a response
766 char* port = (char *) "4555";
768 int rmtype; // received message type
769 int delay = 1000000; // mu-sec delay; default 1s
770 // very simple flag processing (no bounds/error checking)
772 if( argv[ai][0] != '-' ) {
775 // we only support -x so -xy must be -x -y
776 switch( argv[ai][1] ) {
777 // delay between messages (mu-sec)
779 delay = atoi( argv[ai+1] );
786 // timeout in seconds; we need to convert to ms for rmr calls
788 response_to = atoi( argv[ai+1] ) * 1000;
794 fprintf( stderr, "<XAPP> response timeout set to: %d\\n", response_to );
795 fprintf( stderr, "<XAPP> listening on port: %s\\n", port );
796 // get an instance and wait for a route table to be loaded
797 xfw = std::unique_ptr<Xapp>( new Xapp( port, true ) );
798 msg = xfw->Alloc_msg( 2048 );
799 for( i = 0; i < 100; i++ ) {
804 // we'll reuse a received message; get max size
805 sz = msg->Get_available_size();
806 // direct access to payload; add something silly
807 payload = msg->Get_payload();
808 len = snprintf( (char *) payload.get(), sz, "This is message %d\\n", i );
809 // payload updated in place, prevent copy by passing nil
810 if ( ! msg->Send_msg( mtype, Message::NO_SUBID, len, NULL )) {
811 fprintf( stderr, "<SNDR> send failed: %d\\n", i );
813 // receive anything that might come back
814 msg = xfw->Receive( response_to );
816 rmtype = msg->Get_mtype();
817 payload = msg->Get_payload();
818 fprintf( stderr, "got: mtype=%d payload=(%s)\\n",
819 rmtype, (char *) payload.get() );
821 msg = xfw->Alloc_msg( 2048 );
830 Figure 10: Simple looping sender application.