4 .. This work is licensed under a Creative Commons Attribution 4.0 International License.
5 .. SPDX-License-Identifier: CC-BY-4.0
7 .. CAUTION: this document is generated from source in doc/src/*
8 .. To make changes edit the source and recompile the document.
9 .. Do NOT make changes directly to .rst or .md files.
17 The C++ framework allows the programmer to create an instance
18 of the ``Xapp`` object which then can be used as a foundation
19 for the application. The ``Xapp`` object provides a message
20 level interface to the RIC Message Router (RMR), including
21 the ability to register callback functions which the instance
22 will drive as messages are received; much in the same way
23 that an X-windows application is driven by the window manager
24 for all activity. The xApp may also choose to use its own
25 send/receive loop, and thus is not required to use the
26 callback driver mechanism provided by the framework.
32 To avoid confusion the term **xAPP** is used in this document
33 to refer to the user's application code which is creating
34 ``Xapp,`` and related objects provided by the *framework.*
35 The use of *framework* should be taken to mean any of the
36 classes and/or support functions which are provided by the
43 The C++ framework API consists of the creation of the xApp
44 object, and invoking desired functions via the instance of
45 the object. The following paragraphs cover the various steps
46 involved to create an xApp instance, wait for a route table
47 to arrive, send a message, and wait for messages to arrive.
53 Starting with version 2.0.0 the framwork introduces a
54 *namespace* of ``xapp`` for the following classes and types:
63 This is a breaking change and as such the major version was
67 Creating the xApp instance
68 --------------------------
70 The creation of the xApp instance is as simple as invoking
71 the object's constructor with two required parameters:
84 A C string (pointer to char) which defines the port that
86 RMR will open to listen for connections.
100 A Boolean value which indicates whether or not the
102 initialization process should wait for the arrival of a
104 valid route table before completing. When true is
106 supplied, the initialization will not complete until RMR
108 has received a valid route table (or one is located via
110 the ``RMR_SEED_RT`` environment variable).
114 The following code sample illustrates the simplicity of
115 creating the instance of the xApp object.
121 #include <ricxfcpp/xapp.hpp>
123 std::unique_ptr<Xapp> xapp;
124 char* listen_port = (char *) "4560"; //RMR listen port
125 bool wait4table = true; // wait for a route table
127 xapp = std::unique_ptr<Xapp>(
128 new Xapp( listen_port, wait4table ) );
131 Figure 1: Creating an xAPP instance.
133 From a compilation perspective, the following is the simple
134 compiler invocation string needed to compile and link the
135 above program (assuming that the sample code exists in a file
136 called ``man_ex1.cpp``.
141 g++ man_ex1.cpp -o man_ex1 -lricxfcpp -lrmr_si -lpthread
144 The above program, while complete and capable of being
145 compiled, does nothing useful. When invoked, RMR will be
146 initialized and will begin listening for a route table;
147 blocking the return to the main program until one is
148 received. When a valid route table arrives, initialization
149 will complete and the program will exit as there is no code
150 following the instruction to create the object.
153 LISTENING FOR MESSAGES
154 ======================
156 The program in the previous example can be extended with just
157 a few lines of code to enable it to receive and process
158 messages. The application needs to register a callback
159 function for each message type which it desires to process.
161 Once registered, each time a message is received the
162 registered callback for the message type will be invoked by
169 As with most callback related systems, a callback must have a
170 well known function signature which generally passes event
171 related information and a "user" data pointer which was
172 registered with the function. The following is the prototype
173 which callback functions must be defined with:
178 void cb_name( xapp::Message& m, int mtype, int subid,
179 int payload_len, xapp::Msg_component payload,
182 Figure 2: Callback function signature
184 The parameters passed to the callback function are as
198 A reference to the Message that was received.
212 The message type (allows for disambiguation if the
214 callback is registered for multiple message types).
228 The subscription ID from the message.
242 The number of bytes which the sender has placed into the
258 A direct reference (smart pointer) to the payload. (The
260 smart pointer is wrapped in a special class in order to
262 provide a custom destruction function without burdening
264 the xApp developer with that knowledge.)
278 A pointer to user data. This is the pointer that was
280 provided when the function was registered.
284 To illustrate the use of a callback function, the previous
285 code example has been extended to add the function, register
286 it for message types 1000 and 1001, and to invoke the
287 ``Run()`` function in the framework (explained in the next
293 #include <ricxfcpp/xapp.hpp>
294 long m1000_count = 0; // message counters, one for each type
295 long m1001_count = 0;
297 // callback function that will increase the appropriate counter
298 void cbf( xapp::Message& mbuf, int mtype, int subid, int len,
299 xapp::Msg_component payload, void* data ) {
302 if( (counter = (long *) data) != NULL ) {
308 std::unique_ptr<Xapp> xapp;
309 char* listen_port = (char *) "4560";
310 bool wait4table = false;
312 xapp = std::unique_ptr<Xapp>(
313 new Xapp( listen_port, wait4table ) );
315 // register the same callback function for both msg types
316 xapp->Add_msg_cb( 1000, cbf, (void *) &m1000_count );
317 xapp->Add_msg_cb( 1001, cbf, (void *) &m1001_count );
319 xapp->Run( 1 ); // start the callback driver
322 Figure 3: Callback function example.
324 As before, the program does nothing useful, but now it will
325 execute and receive messages. For this example, the same
326 function can be used to increment the appropriate counter
327 simply by providing a pointer to the counter as the user data
328 when the callback function is registered. In addition, a
329 subtle change from the previous example has been to set the
330 wait for table flag to ``false.``
332 For an xApp that is a receive only application (never sends)
333 it is not necessary to wait for RMR to receive a table from
337 Registering A Default Callback
338 ------------------------------
340 The xApp may also register a default callback function such
341 that the function will be invoked for any message that does
342 not have a registered callback. If the xAPP does not register
343 a default callback, any message which cannot be mapped to a
344 known callback function is silently dropped. A default
345 callback is registered by providing a *generic* message type
346 of ``xapp->DEFAULT_CALLBACK`` on an ``Add_msg_cb`` call.
349 The Framework Callback Driver
350 -----------------------------
352 The ``Run()`` function within the Xapp object is invoked to
353 start the callback driver, and the xApp should not expect the
354 function to return under most circumstances. The only
355 parameter that the ``Run()`` function expects is the number
356 of threads to start. For each thread requested, the framework
357 will start a listener thread which will allow received
358 messages to be processed in parallel. If supplying a value
359 greater than one, the xApp must ensure that the callback
360 functions are thread safe as it is very likely that the same
361 callback function will be invoked concurrently from multiple
368 It is very likely that most xApps will need to send messages
369 and will not operate in "receive only" mode. Sending the
370 message is a function of the message object itself and can
371 take one of two forms:
374 * Replying to the sender of a received message
376 * Sending a message (routed based on the message type and
380 When replying to the sender, the message type and
381 subscription ID are not used to determine the destination of
382 the message; RMR ensures that the message is sent back to the
383 originating xApp. The xApp may still need to change the
384 message type and/or the subscription ID in the message prior
385 to using the reply function.
387 To provide for both situations, two reply functions are
388 supported by the Message object as illustrated with the
389 following prototypes.
394 bool Send_response( int mtype, int subid, int response_len,
395 std:shared_ptr<unsigned char> response );
397 bool Send_response( int response_len, std::shared_ptr<unsigned char> response );
399 Figure 4: Reply function prototypes.
401 In the first prototype the xApp must supply the new message
402 type and subscription ID values, where the second function
403 uses the values which are currently set in the message.
404 Further, the new payload contents, and length, are supplied
405 to both functions; the framework ensures that the message is
406 large enough to accommodate the payload, reallocating it if
407 necessary, and copies the response into the message payload
408 prior to sending. Should the xApp need to change either the
409 message type, or the subscription ID, but not both, the
410 ``NO_CHANGE`` constant can be used as illustrated below.
415 msg->Send_response( xapp::Message::NO_CHANGE, xapp::Message::NO_SUBID,
416 pl_length, (unsigned char *) payload );
418 Figure 5: Send response prototype.
420 In addition to the two function prototypes for
421 ``Send_response()`` there are two additional prototypes which
422 allow the new payload to be supplied as a shared smart
423 pointer. The other parameters to these functions are
424 identical to those illustrated above, and thus are not
427 The ``Send_msg()`` set of functions supported by the Message
428 object are identical to the ``Send_response()`` functions and
434 bool Send_msg( int mtype, int subid, int payload_len,
435 std::shared_ptr<unsigned char> payload );
437 bool Send_msg( int mtype, int subid, int payload_len,
438 unsigned char* payload );
440 bool Send_msg( int payload_len,
441 std::shared_ptr<unsigned char> payload );
443 bool Send_msg( int payload_len, unsigned char* payload );
445 Figure 6: Send function prototypes.
447 Each send function accepts the message, copies in the payload
448 provided, sets the message type and subscription ID (if
449 provided), and then causes the message to be sent. The only
450 difference between the ``Send_msg()`` and
451 ``Send_response()`` functions is that the destination of the
452 message is selected based on the mapping of the message type
453 and subscription ID using the current routing table known to
457 Direct Payload Manipulation
458 ---------------------------
460 For some applications, it might be more efficient to
461 manipulate the payload portion of an Xapp Message in place,
462 rather than creating it and relying on a buffer copy when the
463 message is finally sent. To achieve this, the xApp must
464 either use the smart pointer to the payload passed to the
465 callback function, or retrieve one from the message using
466 ``Get_payload()`` when working with a message outside of a
467 callback function. Once the smart pointer is obtained, the
468 pointer's get() function can be used to directly reference
469 the payload (unsigned char) bytes.
471 When working directly with the payload, the xApp must take
472 care not to write more than the actual payload size which can
473 be extracted from the Message object using the
474 ``Get_available_size()`` function.
476 When sending a message where the payload has been directly
477 altered, and no extra buffer copy is needed, a NULL pointer
478 should be passed to the Message send function. The following
479 illustrates how the payload can be directly manipulated and
480 returned to the sender (for simplicity, there is no error
481 handling if the payload size of the received message isn't
482 large enough for the response string, the response is just
488 Msg_component payload; // smart reference
489 int pl_size; // max size of payload
491 payload = msg->Get_payload();
492 pl_size = msg->Get_available_size();
493 if( snprintf( (char *) payload.get(), pl_size,
494 "Msg Received\\n" ) < pl_size ) {
495 msg->Send_response( M_TYPE, SID, strlen( raw_pl ), NULL );
498 Figure 7: Send message without buffer copy.
502 Sending Multiple Responses
503 --------------------------
505 It is likely that the xApp will wish to send multiple
506 responses back to the process that sent a message that
507 triggered the callback. The callback function may invoke the
508 ``Send_response()`` function multiple times before returning.
510 After each call, the Message retains the necessary
511 information to allow for a subsequent invocation to send more
512 data. It should be noted though, that after the first call to
513 ``{Send_response()`` the original payload will be lost; if
514 necessary, the xApp must make a copy of the payload before
515 the first response call is made.
521 Not all xApps will be "responders," meaning that some xApps
522 will need to send one or more messages before they can expect
523 to receive any messages back. To accomplish this, the xApp
524 must first allocate a message buffer, optionally initialising
525 the payload, and then using the message's ``Send_msg()``
526 function to send a message out. The framework's
527 ``Alloc_msg()`` function can be used to create a Message
528 object with a desired payload size.
531 FRAMEWORK PROVIDED CALLBACKS
532 ============================
534 The framework itself may provide message handling via the
535 driver such that the xApp might not need to implement some
536 message processing functionality. Initially, the C++
537 framework will provide a default callback function to handle
538 the RMR based health check messages. This callback function
539 will assume that if the message was received, and the
540 callback invoked, that all is well and will reply with an OK
541 state. If the xApp should need to override this simplistic
542 response, all it needs to do is to register its own callback
543 function for the health check message type.
549 The C++ xAPP framework provides a very lightweight json
550 parser and data hash facility. Briefly, a json hash (Jhash)
551 can be established by creating an instance of the Jhash
552 object with a string of valid json. The resulting object's
553 functions can then be used to read values from the resulting
557 Creating The Jhash Object
558 -------------------------
560 The Jhash object is created simply by passing a json string
565 #include <ricxfcpp/Jhash.hpp>
567 std::string jstring = "{ \\"tag\\": \\"Hello World\\" }";
570 jh = new Jhash( jstring.c_str() );
572 Figure 8: The creation of the Jhash object.
574 Once the Jhash object has been created any of the methods
575 described in the following paragraphs can be used to retrieve
582 Json objects can be nested, and the nesting is supported by
583 this representation. The approach taken by Jhash is a
584 "directory view" approach, where the "current directory," or
585 current *blob,* limits the scope of visible fields.
587 As an example, the json contained in figure 9, contains a
588 "root" blob and two *sub-blobs* (address and lease_info).
594 "lodge_name": "Water Buffalo Lodge 714",
596 "grand_poobah": "Larry K. Slate",
597 "attendance": [ 23, 14, 41, 38, 24 ],
599 "street": "16801 Stonway Lane",
605 "owner": "Stonegate Properties",
608 "contact:" "Kyle Limestone"
612 Figure 9: Sample json with a root and two blobs.
614 Upon creation of the Jhash object, the *root* fields,
615 ``lodge_name,`` ``member_count,`` and ``grand_poobah`` are
616 immediately available. The fields in the *sub-blobs* are
617 available only when the correct blob is selected. The code
618 sample in figure 10 illustrates how a *sub-blob* is selected.
622 jh->Set_blob( (char *) "address" ); // select address
623 jh->Unset_blob(); // return to root
624 jh->Set_blob( (char *) "lease_info" ); // select the lease blob
626 Figure 10: Blob selection example.
628 Currently, the selected blob must be unset in order to select
629 a blob at the root level; unset always sets the root blob.
630 Attempting to use the ``Set_blob`` function will attempt to
631 select the named blob from the current blob, and not the
635 Simple Value Extraction
636 -----------------------
638 Simple values are the expected data types *string, value,*
639 and *boolean.* This lightweight json parser treats all values
640 as floating point numbers and does not attempt to maintain a
641 separate integer type. A fourth type, *null,* is supported to
642 allow the user to expressly check for a field which is
643 defined but has no value; as opposed to a field that was
644 completely missing from the data. The following are the
645 prototypes for the functions which allow values to be
651 std::string String( const char* name );
652 float Value( const char* name );
653 bool Bool( const char* name );
656 Each of these functions returns the value associated with the
657 field with the given *name.* If the value is missing, the
658 following default values are returned:
671 An empty string (.e.g "").
699 If the user needs to disambiguate between a missing value and
700 the default value either the ``Missing`` or ``Exists``
701 function should be used first.
704 Testing For Existing and Missing Fields
705 ---------------------------------------
707 Two functions allow the developer to determine whether or not
708 a field is included in the json. Both of these functions work
709 on the current *blob,* therefore it is important to ensure
710 that the correct blob is selected before using either of
711 these functions. The prototypes for the ``Exists`` and
712 ``Missing`` functions are below:
716 bool Exists( const char* name );
717 bool Is_missing( const char* name );
719 The ``Exists`` function returns *true* if the field name
720 exists in the json and *false* otherwise. Conversely, the
721 ``Missing`` function returns *true* when the field name does
722 not exist in the json.
728 The ``Exists`` and ``Missing`` functions might not be enough
729 for the user code to validate the data that it has. To assist
730 with this, several functions allow direct type testing on a
731 field in the current blob. The following are the prototypes
736 bool Is_bool( const char* name );
737 bool Is_null( const char* name );
738 bool Is_string( const char* name );
739 bool Is_value( const char* name );
742 Each of these functions return *true* if the field with the
743 given name is of the type being tested for.
749 Arrays are supported in the same manner as simple field
750 values with the addition of the need to supply an array index
751 when fetching values from the object. In addition, there is a
752 *length* function which can be used to determine the number
753 of elements in the named array. The prototypes for the array
754 based functions are below:
758 int Array_len( const char* name );
760 bool Is_bool_ele( const char* name, int eidx );
761 bool Is_null_ele( const char* name, int eidx );
762 bool Is_string_ele( const char* name, int eidx );
763 bool Is_value_ele( const char* name, int eidx );
765 bool Bool_ele( const char* name, int eidx );
766 std::string String_ele( const char* name, int eidx );
767 float Value_ele( const char* name, int eidx );
770 For each of these functions the ``eidx`` is the zero based
771 element index which is to be tested or selected.
777 An array containing blobs, rather than simple field value
778 pairs, the blob must be selected prior to using it, just as a
779 sub-blob needed to be selected. The ``Set_blob_ele`` function
780 is used to do this and has the following prototype:
784 bool Set_blob_ele( const char* name, int eidx );
787 As with selecting a sub-blob, an unset must be performed
788 before selecting the next blob. Figure 11 illustrates how
789 these functions can be used to read and print values from the
795 { "name": "Fred Flinstone", "member_num": 42 },
796 { "name": "Barney Rubble", "member_num": 48 },
797 { "name": "Larry K Slate", "member_num": 22 },
798 { "name": "Kyle Limestone", "member_num": 49 }
801 Figure 11: Json array containing blobs.
810 len = jh->Array_len( (char *) "members" );
811 for( i = 0; i < len; i++ ) {
812 jh->Set_blob_ele( (char *) "members", i ); // select blob
814 mname = jh->String( (char *) "name" ); // read values
815 mnum = jh->Value( (char *) "member_num" );
816 fprintf( stdout, "%s is member %d\\n", mname.c_str(), (int) mnum );
818 jh->Unset_blob(); // back to root
821 Figure 12: Code to process the array of blobs.
825 ALARM MANAGER INTERFACE
826 =======================
828 The C++ framework provides an API which allows the xAPP to
829 easily construct and generate alarm messages. Alarm messages
830 are a special class of RMR message, allocated in a similar
831 fashion as an RMR message through the framework's
832 ``Alloc_alarm()`` function.
834 The API consists of the following function types:
847 Cause the alarm to be assigned a severity and and sent via
849 RMR message to the alarm collector process.
863 Cause a clear message to be sent to the alarm collector.
877 Cause a clear followed by a raise message to be sent to
888 The ``xapp`` function provided by the framework is used to
889 create an alarm object. Once the xAPP has an alarm object it
890 can be used to send one, or more, alarm messages to the
893 The allocation function has three prototypes which allow the
894 xAPP to create an alarm with an initial set of information as
895 is appropriate. The following are the prototypes for the
901 std::unique_ptr<xapp::Alarm> Alloc_alarm( );
902 std::unique_ptr<xapp::Alarm> Alloc_alarm( std::string meid );
903 std::unique_ptr<xapp::Alarm> Alloc_alarm( int prob_id, std::string meid );
905 Figure 13: Alarm allocation prototypes.
907 Each of the allocation functions returns a unique pointer to
908 the alarm. In the simplest form (1) the alarm is initialised
909 with an empty meid (managed element ID) string, and unset
910 problem ID (-1). The second prototype allows the xAPP to
911 supply the meid, and in the third form both the problem ID
912 and the meid are used to initialise the alarm.
918 Once an alarm has been allocated, its ``Raise()`` function
919 can be used to cause the alarm to be sent to the collector.
920 The raise process allows the xAPP to perform the following
921 modifications to the alarm before sending the message:
924 * Set the alarm severity
926 * Set the problem ID value
928 * Set the alarm information string
930 * Set the additional information string
933 The following are the prototypes for the ``Raise()``
934 functions of an alarm object: ..... In its simplest form (1)
935 the ``Raise()`` function will send the alarm without making
936 any changes to the data. The final two forms allow the xAPP
937 to supply additional data which is added to the alarm before
938 sending the message. Each of the raise functions returns
939 ``true`` on success and ``false`` if the alarm message could
946 The severity is one of the ``SEV_`` constants listed below.
947 These map to alarm collector strings and insulate the xAPP
948 from any future alarm collector changes. The specific meaning
949 of these severity types are defined by the alarm collector
950 and thus no attempt is made to guess what their actual
951 meaning is. These constants are available by including
962 Figure 14: Severity constants available in alarm.hpp.
968 The problem ID is an integer which is assigned by the xAPP.
969 The framework makes no attempt to verify that it has been se,
970 nor does it attempt to validate the value. If the xAPP does
971 not set the value, ``-1`` is used.
977 The two information strings are also xAPP defined and provide
978 the information that the xAPP deems necessary and related to
979 the alarm. What the collector expects, and how these strings
980 are used, is beyond the scope of the framework to describe or
981 validate. If not supplied, empty strings are sent in the
988 The ``Clear()`` function of an alarm may be used to send a
989 clear message. In a manner similar to the ``Raise()``
990 functions, the ``Clear()`` functions allow the existing alarm
991 data to be sent without change, or for the xAPP to modify the
992 data before the message is sent to the collector. The
993 following are the prototype for these functions.
998 bool Clear( int severity, int problem, std::string info );
999 bool Clear( int severity, int problem, std::string info, std::string addional_info );
1003 Figure 15: Clear function prototypes.
1005 Each of the clear functions returns ``true`` on success and
1006 ``false`` if the alarm message could not be sent.
1008 The ``Clear_all()`` function sends a special action code to
1009 the collector which is assumed to clear all alarms. However,
1010 it is unknown whether that implies **all** alarms, or all
1011 alarms matching the ``problem_id,`` or some other
1012 interpretation. Please consult the alarm collector
1013 documentation for these specifics.
1016 Adjusting Alarm Contents
1017 ------------------------
1019 It might be necessary for the xAPP to adjust the alarm
1020 contents outside of the scope of the ``Raise()`` function, or
1021 to adjust data that cannot be manipulated by ``Raise().`` The
1022 following are the (self explanatory) prototypes for the
1023 *setter* functions which are available to the xAPP.
1028 void Set_additional( std::string new_info );
1029 void Set_appid( std::string new_id );
1030 void Set_info( std::string new_info );
1031 void Set_meid( std::string new_meid );
1032 void Set_problem( int new_id );
1033 void Set_severity( int new_sev );
1035 Figure 16: Alarm Setters
1042 The C++ xAPP framework provides a lightweight interface to
1043 the metrics gateway allowing the xAPP to create and send
1044 metrics updates without needing to understand the underlying
1045 message format. From the xAPP's perspective, the metrics
1046 object is created with one or more key/value measurement
1047 pairs and then is sent to the process responsible for
1048 forwarding them to the various collection processes. The
1049 following sections describe the Metrics object and the API
1053 Creating The Metrics Object
1054 ---------------------------
1056 The ``xapp`` object can be created directly, or via the xapp
1057 framework. When creating directly the xAPP must supply an RMR
1058 message for the object to use; when the framework is used to
1059 create the object, the message is created as as part of the
1060 process. The framework provides three constructors for the
1061 metrics instance allowing the xAPP to supply the measurement
1062 source, the source and reporter, or to default to using the
1063 xAPP name as both the source and reporter (see section
1064 *Source and Reporter* for a more detailed description of
1065 these). The framework constructors are illustrated in figure
1071 std::unique_ptr<xapp::Metrics> Alloc_metrics( );
1072 std::unique_ptr<xapp::Metrics> Alloc_metrics( std::string source );
1073 std::unique_ptr<xapp::Metrics> Alloc_metrics( std::string reporter, std::string source );
1075 Figure 17: The framework constructors for creating an
1076 instance of the metrics object.
1082 #include <ricxfcpp/Metrics>
1084 char* port = (char *) "4560";
1086 auto x = std::unique_ptr<Xapp>( new Xapp( port ) );
1087 auto reading = std::shared_ptr<xapp::Metrics>( x->Alloc_metric( ) );
1089 Figure 18: Metrics instance creation using the framework.
1091 Figures 18 illustrates how the framework constructor can be
1092 used to create a metrics instance. While it is unlikely that
1093 an xAPP will create a metrics instance directly, there are
1094 three similar constructors available. These are prototypes
1095 are shown in figure 19 and their use is illustrated in figure
1100 Metrics( std::shared_ptr<xapp::Message> msg );
1101 Metrics( std::shared_ptr<xapp::Message> msg, std::string msource );
1102 Metrics( std::shared_ptr<xapp::Message> msg, std::string reporter, std::string msource );
1104 Figure 19: Metrics object constructors.
1109 #include <ricxfcpp/Metrics>
1111 char* port = (char *) "4560";
1113 auto x = std::unique_ptr<Xapp>( new Xapp( port ) );
1114 auto msg = std::shared_ptr<xapp::Message>( x->Alloc_msg( 4096 ) );
1115 auto reading = std::shared_ptr<xapp::Metrics>( new Metrics( msg ) );
1117 Figure 20: Direct creation of a metrics instance.
1124 Once an instance of the metrics object is created, the xAPP
1125 may push values in preparation to sending the measurement(s)
1126 to the collector. The ``Push_data()`` function is used to
1127 push each key/value pair and is illustrated in figure 21.
1131 reading->Push_data( "normal_count", (double) norm_count );
1132 reading->Push_data( "high_count", (double) hi_count );
1133 reading->Push_data( "excessive_count", (double) ex_count );
1135 Figure 21: Pushing key/value pairs into a metrics instance.
1139 Sending A Measurement Set
1140 -------------------------
1142 After all of the measurement key/value pairs have been added
1143 to the instance, the ``Send()`` function can be invoked to
1144 create the necessary RMR message and send that to the
1145 collection application. Following the send, the key/value
1146 pairs are cleared from the instance and the xAPP is free to
1147 start pushing values into the instance again. The send
1148 function has the following prototype and returns ``true`` on
1149 success and ``false`` if the measurements could not be sent.
1155 The alarm collector has the understanding that a measurement
1156 might be *sourced* from one piece of equipment, or software
1157 component, but reported by another. For auditing purposes it
1158 makes sense to distinguish these, and as such the metrics
1159 object allows the xAPP to identify the case when the source
1160 and reporter are something other than the xAPP which is
1161 generating the metrics message(s).
1163 The *source* is the component to which the measurement
1164 applies. This could be a network interface card counting
1165 packets, a temperature sensor, or the xAPP itself reporting
1166 xAPP related metrics. The *reporter* is the application that
1167 is reporting the measurement(s) to the collector.
1169 By default, both reporter and source are assumed to be the
1170 xAPP, and the name is automatically determined using the
1171 run-time supplied programme name. Should the xAPP need to
1172 report measurements for more than one source it has the
1173 option to create an instance for every reporter source
1174 combination, or to set the reporter and/or source with the
1175 generation of each measurement set. To facilitate the ability
1176 to change the source and/or the reporter without the need to
1177 create a new metrics instance, two *setter* functions are
1178 provided. The prototypes for these are shown in figure 22.
1183 void Set_source( std::string new_source );
1184 void Set_reporter( std::string new_reporter );
1186 Figure 22: Setter functions allowing the reporter and/or
1187 source to be set after construction.
1191 CONFIGURATION SUPPORT
1192 =====================
1194 The C++ xAPP framework provides the xAPP with an interface to
1195 load, parse and receive update notifications on the
1196 configuration. The configuration, also known as the xAPP
1197 descriptor, is assumed to be a file containing json with a
1198 well known structure, with some fields or *objects* used by
1199 an xAPP for configuration purposes. The following paragraphs
1200 describe the support that the framework provides to the xAPP
1201 with respect to the configuration aspects of the descriptor.
1207 The xAPP must create an instance of the ``config`` object in
1208 order to take advantage of the support. This is accomplished
1209 by using one of two constructors illustrated with code
1210 samples in figure 23.
1215 #include <ricxfcpp/config.hpp>
1217 auto cfg = new xapp::Config( );
1218 auto cfg = new xapp::Config( "/var/myapp/config.json" );
1220 Figure 23: Creating a configuration instance.
1222 The creation of the object causes the file to be found,
1223 loaded, after which the xAPP can use the instance functions
1224 to access the information it needs.
1230 Once a configuration has been created the following
1231 capabilities are available:
1234 * Get a control value (numeric, string, or boolean)
1236 * Get the RMR port for the container with the supplied
1239 * Set a notification callback function
1241 * Get the raw contents of the file
1248 The ``controls`` section of the xAPP descriptor is generally
1249 used to supply a *flat* namespace of key/value pairs which
1250 the xAPP can use for configuration. These pairs are supplied
1251 by the xAPP author as a part of development, and thus are
1252 specific to each xAPP. The framework provides a general set
1253 of functions which allows a key to be searched for in this
1254 section and returned to the caller. Data is assumed to be one
1255 of three types: numeric (double), string, or boolean.
1257 Two methods for each return type are supported with the more
1258 specific form allowing the xAPP to supply a default value to
1259 be used should the file not contain the requested field. The
1260 function prototypes for these are provided in figure 24.
1264 bool Get_control_bool( std::string name, bool defval );
1265 bool Get_control_bool( std::string name );
1267 std::string Get_control_str( std::string name, std::string defval );
1268 std::string Get_control_str( std::string name );
1270 double Get_control_value( std::string name, double defval );
1271 double Get_control_value( std::string name );
1273 Figure 24: The various controls section get functions.
1275 If the more generic form of these functions is used, without
1276 a default value, the return values are false, "", and 0.0 in
1277 the respective order of the prototypes in figure 24.
1283 The ``messaging`` section of the xAPP descriptor provides the
1284 ability to define one or more RMR *listen ports* that apply
1285 to the xAPP(s) started in a given container. The xAPP may
1286 read a port value (as a string) using the defined port name
1287 via the ``Get_port`` function whose prototype is illustrated
1293 std::string Get_port( std::string name );
1295 Figure 25: The get port prototype.
1302 While it is not anticipated to be necessary, the xAPP might
1303 need direct access to the raw contents of the configuration
1304 file. As a convenience the framework provides the
1305 ``Get_contents()`` function which reads the entire file into
1306 a standard library string and returns that to the calling
1307 function. Parsing and interpreting the raw contents is then
1311 Notification Of Changes
1312 -----------------------
1314 When desired, the xAPP may register a notification callback
1315 function with the framework. This callback will be driven any
1316 time a change to the descriptor is detected. When a change is
1317 detected, the revised descriptor is read into the existing
1318 object (overlaying any previous information), before invoking
1319 the callback. The callback may then retrieve the updated
1320 values and make any adjustments which are necessary. The
1321 prototype for the xAPP callback function is described in
1327 void cb_name( xapp::Config& c, void* data )
1329 Figure 26: The prototype which the xAPP configuration notify
1334 Enabling The Notifications
1335 --------------------------
1337 Notifications are enabled by invoking the
1338 ``Set_callback()`` function. Once enabled, the framework will
1339 monitor the configuration source and invoke the callback upon
1340 change. This occurs in a separate thread than the main xAPP
1341 thread; it is up to the xAPP to guard against any potential
1342 data collisions when evaluating configuration changes. If the
1343 xAPP does not register a notification function the framework
1344 will not monitor the configuration for changes and the object
1345 will have static data. Figure 27 illustrates how the xAPP can
1346 define and register a notification callback.
1352 // notification callback; allows verbose level to change on the fly
1353 void config_chg( xapp::Config& c, void* vdata ) {
1354 app_ctx* ctx; // application context
1356 ctx = (app_ctx *) vdata;
1357 ctx->vlevel = c->Get_value( "verbose_level", ctx->vlevel );
1360 Figure 27: Small notification callback function allowing on
1361 the fly verbose level change.
1364 The xAPP would register the ``config_chg()`` function as the
1365 notification callback using the call illustrated in figure
1371 auto conf = new xapp::Config();
1372 conf->Set_callback( config_chg );
1374 Figure 28: Setting the notification callback and and
1375 activating notifications.
1380 xAPP Descriptor Notes
1381 ---------------------
1383 While it is beyond the scope of this document to describe the
1384 complete contents of an xAPP descriptor file, it is prudent
1385 to mention several items which are related to the information
1386 used from the descriptor file. The following paragraphs
1387 discuss things which the xAPP developer should be aware of,
1388 and keep in mind when using the configuration class.
1394 There is a deprecated section within the xAPP descriptor
1395 which has the title *rmr.* The *messaging* section provides
1396 more flexibility, and additional information and has been a
1397 replacement for the *rmr* section for all applications. The
1398 information in the *rmr* section should be kept consistent
1399 with the duplicated information in the *messaging* section as
1400 long as there are container management and/or platform
1401 applications (e.g. Route Manager) which are back level and do
1402 not recognise the *messaging* section. The configuration
1403 parsing and support provided by the framework will ignore the
1410 The following sections contain several example programmes
1411 which are written on top of the C++ framework. All of these
1412 examples are available in the code repository RIC xAPP C++
1413 framework available via the following URL:
1416 ``https://gerrit.o-ran-sc.org/r/admin/repos/ric-plt/xapp-frame-cpp``
1423 The RMR dump application is an example built on top of the
1424 C++ xApp framework to both illustrate the use of the
1425 framework, and to provide a useful diagnostic tool when
1426 testing and troubleshooting xApps.
1428 The RMR dump xApp isn't a traditional xApp inasmuch as its
1429 goal is to listen for message types and to dump information
1430 about the messages received to the TTY much as
1431 ``tcpdump`` does for raw packet traffic. The full source
1432 code, and Makefile, are in the ``examples`` directory of the
1435 When invoked, the RMR dump program is given one or more
1436 message types to listen for. A callback function is
1437 registered for each, and the framework ``Run()`` function is
1438 invoked to drive the process. For each recognised message,
1439 and depending on the verbosity level supplied at program
1440 start, information about the received message(s) is written
1441 to the TTY. If the forwarding option, -f, is given on the
1442 command line, and an appropriate route table is provided,
1443 each received message is forwarded without change. This
1444 allows for the insertion of the RMR dump program into a flow,
1445 however if the ultimate receiver of a message needs to reply
1446 to that message, the reply will not reach the original
1447 sender, so RMR dump is not a complete "middle box"
1450 The following is the code for this xAPP. Several functions,
1451 which provide logic unrelated to the framework, have been
1452 omitted. The full code is in the framework repository.
1462 #include "ricxfcpp/xapp.hpp"
1465 Information that the callback needs outside
1466 of what is given to it via parms on a call
1470 int vlevel; // verbosity level
1471 bool forward; // if true, message is forwarded
1472 int stats_freq; // header/stats after n messages
1473 std::atomic<long> pcount; // messages processed
1474 std::atomic<long> icount; // messages ignored
1475 std::atomic<int> hdr; // number of messages before next header
1478 // ----------------------------------------------------------------------
1483 void dump( unsigned const char* buf, int len ) {
1488 fprintf( stdout, "<RD> 0000 | " );
1490 for( i = 0; i < len; i++ ) {
1491 cheater[j++] = isprint( buf[i] ) ? buf[i] : '.';
1492 fprintf( stdout, "%02x ", buf[i] );
1496 fprintf( stdout, " | %s\\n<RD> %04x | ", cheater, i+1 );
1503 for( ; i > 0; i-- ) {
1504 fprintf( stdout, " " );
1507 fprintf( stdout, " | %s\\n", cheater );
1512 generate stats when the hdr count reaches 0. Only one active
1513 thread will ever see it be exactly 0, so this is thread safe.
1515 void stats( cb_info_t& cbi ) {
1516 int curv; // current stat trigger value
1520 if( curv == 0 ) { // stats when we reach 0
1521 fprintf( stdout, "ignored: %ld processed: %ld\\n",
1522 cbi.icount.load(), cbi.pcount.load() );
1523 if( cbi.vlevel > 0 ) {
1524 fprintf( stdout, "\\n %5s %5s %2s %5s\\n",
1525 "MTYPE", "SUBID", "ST", "PLLEN" );
1528 cbi.hdr = cbi.stats_freq; // reset must be last
1532 void cb1( xapp::Message& mbuf, int mtype, int subid, int len,
1533 xapp::Msg_component payload, void* data ) {
1537 if( (cbi = (cb_info_t *) data) == NULL ) {
1542 stats( *cbi ); // gen stats & header if needed
1544 if( cbi->vlevel > 0 ) {
1545 fprintf( stdout, "<RD> %-5d %-5d %02d %-5d \\n",
1546 mtype, subid, mbuf.Get_state(), len );
1548 if( cbi->vlevel > 1 ) {
1549 dump( payload.get(), len > 64 ? 64 : len );
1553 if( cbi->forward ) {
1554 // forward with no change to len or payload
1555 mbuf.Send_msg( xapp::Message::NO_CHANGE, NULL );
1560 registered as the default callback; it counts the
1561 messages that we aren't giving details about.
1563 void cbd( xapp::Message& mbuf, int mtype, int subid, int len,
1564 xapp::Msg_component payload, void* data ) {
1567 if( (cbi = (cb_info_t *) data) == NULL ) {
1574 if( cbi->forward ) {
1575 // forward with no change to len or payload
1576 mbuf.Send_msg( xapp::Message::NO_CHANGE, NULL );
1580 int main( int argc, char** argv ) {
1581 std::unique_ptr<Xapp> x;
1582 char* port = (char *) "4560";
1583 int ai = 1; // arg processing index
1585 int ncb = 0; // number of callbacks registered
1589 cbi = (cb_info_t *) malloc( sizeof( *cbi ) );
1592 cbi->stats_freq = 10;
1595 // very simple flag parsing (no error/bounds checking)
1596 while( ai < argc ) {
1597 if( argv[ai][0] != '-' ) { // break on first non-flag
1601 // very simple arg parsing; each must be separate -x -y not -xy.
1602 switch( argv[ai][1] ) {
1603 case 'f': // enable packet forwarding
1604 cbi->forward = true;
1607 case 'p': // define port
1612 case 's': // stats frequency
1613 cbi->stats_freq = atoi( argv[ai+1] );
1614 if( cbi->stats_freq < 5 ) { // enforce sanity
1615 cbi->stats_freq = 5;
1620 case 't': // thread count
1621 nthreads = atoi( argv[ai+1] );
1622 if( nthreads < 1 ) {
1628 case 'v': // simple verbose bump
1632 case 'V': // explicit verbose level
1633 cbi->vlevel = atoi( argv[ai+1] );
1638 fprintf( stderr, "unrecognised option: %s\\n", argv[ai] );
1639 fprintf( stderr, "usage: %s [-f] [-p port] "
1640 "[-s stats-freq] [-t thread-count] "
1641 "[-v | -V n] msg-type1 ... msg-typen\\n",
1643 fprintf( stderr, "\\tstats frequency is based on # of messages received\\n" );
1644 fprintf( stderr, "\\tverbose levels (-V) 0 counts only, "
1645 "1 message info 2 payload dump\\n" );
1652 cbi->hdr = cbi->stats_freq;
1653 fprintf( stderr, "<RD> listening on port: %s\\n", port );
1655 // create xapp, wait for route table if forwarding
1656 x = std::unique_ptr<Xapp>( new Xapp( port, cbi->forward ) );
1658 // register callback for each type on the command line
1659 while( ai < argc ) {
1660 mtype = atoi( argv[ai] );
1662 fprintf( stderr, "<RD> capturing messages for type %d\\n", mtype );
1663 x->Add_msg_cb( mtype, cb1, cbi );
1668 fprintf( stderr, "<RD> no message types specified on the command line\\n" );
1672 x->Add_msg_cb( x->DEFAULT_CALLBACK, cbd, cbi ); // register default cb
1674 fprintf( stderr, "<RD> starting driver\\n" );
1677 // return from run() is not expected, but some compilers might
1678 // compilain if there isn't a return value here.
1682 Figure 29: Simple callback application.
1688 This sample programme implements a simple message listener
1689 which registers three callback functions to process two
1690 specific message types and a default callback to handle
1691 unrecognised messages.
1693 When a message of type 1 is received, it will send two
1694 response messages back to the sender. Two messages are sent
1695 in order to illustrate that it is possible to send multiple
1696 responses using the same received message.
1698 The programme illustrates how multiple listening threads can
1699 be used, but the programme is **not** thread safe; to keep
1700 this example as simple as possible, the counters are not
1701 locked when incremented.
1707 The example also illustrates how a metrics object instance
1708 can be created and used to send appliction metrics to the
1709 collector. In this example the primary callback function will
1710 genereate metrics with the receipt of each 1000th message.
1717 #include "ricxfcpp/message.hpp"
1718 #include "ricxfcpp/msg_component.hpp"
1719 #include <ricxfcpp/metrics.hpp>
1720 #include "ricxfcpp/xapp.hpp"
1722 // counts; not thread safe
1727 long cb1_lastts = 0;
1731 Respond with 2 messages for each type 1 received
1732 Send metrics every 1000 messages.
1734 void cb1( xapp::Message& mbuf, int mtype, int subid, int len,
1735 xapp::Msg_component payload, void* data ) {
1739 // illustrate that we can use the same buffer for 2 rts calls
1740 mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK1\\n" );
1741 mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK2\\n" );
1745 if( cb1_count % 1000 == 0 && data != NULL ) { // send metrics every 1000 messages
1746 auto x = (Xapp *) data;
1747 auto msgm = std::shared_ptr<xapp::Message>( x->Alloc_msg( 4096 ) );
1749 auto m = std::unique_ptr<xapp::Metrics>( new xapp::Metrics( msgm ) );
1750 m->Push_data( "tst_cb1", (double) cb1_count );
1751 m->Push_data( "tst_cb2", (double) cb2_count );
1756 // just count messages
1757 void cb2( xapp::Message& mbuf, int mtype, int subid, int len,
1758 xapp::Msg_component payload, void* data ) {
1762 // default to count all unrecognised messages
1763 void cbd( xapp::Message& mbuf, int mtype, int subid, int len,
1764 xapp::Msg_component payload, void* data ) {
1768 int main( int argc, char** argv ) {
1770 char* port = (char *) "4560";
1771 int ai = 1; // arg processing index
1774 // very simple flag processing (no bounds/error checking)
1775 while( ai < argc ) {
1776 if( argv[ai][0] != '-' ) {
1780 switch( argv[ai][1] ) { // we only support -x so -xy must be -x -y
1787 nthreads = atoi( argv[ai+1] );
1795 fprintf( stderr, "<XAPP> listening on port: %s\\n", port );
1796 fprintf( stderr, "<XAPP> starting %d threads\\n", nthreads );
1798 x = new Xapp( port, true );
1799 x->Add_msg_cb( 1, cb1, x ); // register callbacks
1800 x->Add_msg_cb( 2, cb2, NULL );
1801 x->Add_msg_cb( x->DEFAULT_CALLBACK, cbd, NULL );
1803 x->Run( nthreads ); // let framework drive
1804 // control should not return
1807 Figure 30: Simple callback application.
1814 This is another very simple application which demonstrates
1815 how an application can control its own listen loop while
1816 sending messages. As with the other examples, some error
1817 checking is skipped, and short cuts have been made in order
1818 to keep the example small and to the point.
1831 #include "ricxfcpp/xapp.hpp"
1833 extern int main( int argc, char** argv ) {
1834 std::unique_ptr<Xapp> xfw;
1835 std::unique_ptr<xapp::Message> msg;
1836 xapp::Msg_component payload; // special type of unique pointer to the payload
1842 int response_to = 0; // max timeout wating for a response
1843 char* port = (char *) "4555";
1845 int rmtype; // received message type
1846 int delay = 1000000; // mu-sec delay; default 1s
1849 // very simple flag processing (no bounds/error checking)
1850 while( ai < argc ) {
1851 if( argv[ai][0] != '-' ) {
1855 // we only support -x so -xy must be -x -y
1856 switch( argv[ai][1] ) {
1857 // delay between messages (mu-sec)
1859 delay = atoi( argv[ai+1] );
1868 // timeout in seconds; we need to convert to ms for rmr calls
1870 response_to = atoi( argv[ai+1] ) * 1000;
1877 fprintf( stderr, "<XAPP> response timeout set to: %d\\n", response_to );
1878 fprintf( stderr, "<XAPP> listening on port: %s\\n", port );
1880 // get an instance and wait for a route table to be loaded
1881 xfw = std::unique_ptr<Xapp>( new Xapp( port, true ) );
1882 msg = xfw->Alloc_msg( 2048 );
1884 for( i = 0; i < 100; i++ ) {
1890 // we'll reuse a received message; get max size
1891 sz = msg->Get_available_size();
1893 // direct access to payload; add something silly
1894 payload = msg->Get_payload();
1895 len = snprintf( (char *) payload.get(), sz, "This is message %d\\n", i );
1897 // payload updated in place, prevent copy by passing nil
1898 if ( ! msg->Send_msg( mtype, xapp::Message::NO_SUBID, len, NULL )) {
1899 fprintf( stderr, "<SNDR> send failed: %d\\n", i );
1902 // receive anything that might come back
1903 msg = xfw->Receive( response_to );
1905 rmtype = msg->Get_mtype();
1906 payload = msg->Get_payload();
1907 fprintf( stderr, "got: mtype=%d payload=(%s)\\n",
1908 rmtype, (char *) payload.get() );
1910 msg = xfw->Alloc_msg( 2048 );
1919 Figure 31: Simple looping sender application.
1926 This is an extension of a previous example which sends an
1927 alarm during initialisation and clears the alarm as soon as
1928 messages are being received. It is unknown if this is the
1929 type of alarm that is expected at the collector, but
1930 illustrates how an alarm is allocated, raised and cleared.
1943 #include "ricxfcpp/xapp.hpp"
1944 #include "ricxfcpp/alarm.hpp"
1946 extern int main( int argc, char** argv ) {
1947 std::unique_ptr<Xapp> xfw;
1948 std::unique_ptr<xapp::Message> msg;
1949 xapp::Msg_component payload; // special type of unique pointer to the payload
1950 std::unique_ptr<xapp::Alarm> alarm;
1952 bool received = false; // false until we've received a message
1957 int response_to = 0; // max timeout wating for a response
1958 char* port = (char *) "4555";
1960 int rmtype; // received message type
1961 int delay = 1000000; // mu-sec delay; default 1s
1964 // very simple flag processing (no bounds/error checking)
1965 while( ai < argc ) {
1966 if( argv[ai][0] != '-' ) {
1970 // we only support -x so -xy must be -x -y
1971 switch( argv[ai][1] ) {
1972 // delay between messages (mu-sec)
1974 delay = atoi( argv[ai+1] );
1983 // timeout in seconds; we need to convert to ms for rmr calls
1985 response_to = atoi( argv[ai+1] ) * 1000;
1992 fprintf( stderr, "<XAPP> response timeout set to: %d\\n", response_to );
1993 fprintf( stderr, "<XAPP> listening on port: %s\\n", port );
1995 // get an instance and wait for a route table to be loaded
1996 xfw = std::unique_ptr<Xapp>( new Xapp( port, true ) );
1997 msg = xfw->Alloc_msg( 2048 );
2000 // raise an unavilable alarm which we'll clear on the first recevied message
2001 alarm = xfw->Alloc_alarm( "meid-1234" );
2002 alarm->Raise( xapp::Alarm::SEV_MINOR, 13, "unavailable", "no data recevied" );
2004 for( i = 0; i < 100; i++ ) {
2010 // we'll reuse a received message; get max size
2011 sz = msg->Get_available_size();
2013 // direct access to payload; add something silly
2014 payload = msg->Get_payload();
2015 len = snprintf( (char *) payload.get(), sz, "This is message %d\\n", i );
2017 // payload updated in place, prevent copy by passing nil
2018 if ( ! msg->Send_msg( mtype, xapp::Message::NO_SUBID, len, NULL )) {
2019 fprintf( stderr, "<SNDR> send failed: %d\\n", i );
2022 // receive anything that might come back
2023 msg = xfw->Receive( response_to );
2026 // clear the alarm on first received message
2027 alarm->Clear( xapp::Alarm::SEV_MINOR, 13, "messages flowing", "" );
2031 rmtype = msg->Get_mtype();
2032 payload = msg->Get_payload();
2033 fprintf( stderr, "got: mtype=%d payload=(%s)\\n",
2034 rmtype, (char *) payload.get() );
2036 msg = xfw->Alloc_msg( 2048 );
2045 Figure 32: Simple looping sender application with alarm
2050 Configuration Interface
2051 -----------------------
2053 This example is a simple illustration of how the
2054 configuration file support (xAPP descriptor) can be used to
2055 suss out configuration parameters before creating the Xapp
2056 object. The example also illustrates how a notification
2057 callback can be used to react to changes in the
2065 #include "ricxfcpp/config.hpp"
2066 #include "ricxfcpp/message.hpp"
2067 #include "ricxfcpp/msg_component.hpp"
2068 #include <ricxfcpp/metrics.hpp>
2069 #include "ricxfcpp/xapp.hpp"
2071 int vlevel = 0; // verbose mode set via config
2074 Just print something to the tty when we receive a message
2075 and are in verbose mode.
2077 void cb1( xapp::Message& mbuf, int mtype, int subid, int len,
2078 xapp::Msg_component payload, void* data ) {
2080 fprintf( stdout, "message received is %d bytes long\\n", len );
2085 Driven when the configuration changes. We snarf the verbose
2086 level from the new config and update it. If it changed to
2087 >0, incoming messages should be recorded with a tty message.
2088 If set to 0, then tty output will be disabled.
2090 void config_cb( xapp::Config& c, void* data ) {
2093 if( (vp = (int *) data) != NULL ) {
2094 *vp = c.Get_control_value( "verbose_level", *vp );
2098 int main( int argc, char** argv ) {
2101 std::unique_ptr<xapp::Config> cfg;
2103 // only parameter recognised is the config file name
2105 cfg = std::unique_ptr<xapp::Config>( new xapp::Config( std::string( argv[1] ) ) );
2107 cfg = std::unique_ptr<xapp::Config>( new xapp::Config( ) );
2110 // must get a port from the config; no default
2111 auto port = cfg->Get_port( "rmr-data" );
2112 if( port.empty() ) {
2113 fprintf( stderr, "<FAIL> no port in config file\\n" );
2117 // dig other data from the config
2118 vlevel = cfg->Get_control_value( "verbose_level", 0 );
2119 nthreads = cfg->Get_control_value( "thread_count", 1 );
2120 // very simple flag processing (no bounds/error checking)
2123 fprintf( stderr, "<XAPP> listening on port: %s\\n", port.c_str() );
2124 fprintf( stderr, "<XAPP> starting %d threads\\n", nthreads );
2127 // register the config change notification callback
2128 cfg->Set_callback( config_cb, (void *) &vlevel );
2130 x = new Xapp( port.c_str(), true );
2131 x->Add_msg_cb( 1, cb1, x ); // register message callback
2133 x->Run( nthreads ); // let framework drive
2134 // control should not return
2137 Figure 33: Simple application making use of the
2138 configuration object.