From: E. Scott Daniels Date: Wed, 22 Apr 2020 16:40:27 +0000 (-0400) Subject: Add user guide X-Git-Tag: 1.0.0~2 X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=3a2533f97b9ebee99a718530d111278cedb613d1;p=ric-plt%2Fxapp-frame-cpp.git Add user guide This change adds the document source for the user guide as well as the mechanics needed to allow for scraping the repo for RTD documents. Issue-ID: RIC-148 Signed-off-by: E. Scott Daniels Change-Id: Ib9d36e2f84bfb29f33cdb877abd3927b205fa991 --- diff --git a/doc/README b/doc/README new file mode 100644 index 0000000..4edb62e --- /dev/null +++ b/doc/README @@ -0,0 +1,12 @@ + +This directory contains documentation source which generates +some (if not all) of the documentation that is put into the +directory scraped for RTD. One of the advantages of this +approach is that example code presented in the document is +pulled from the examples directory in the repo which avoids +a duplicate set of "code" in the document which is likely to +become out of date. + +The documents in the scraped directory (/docs) +can be created with the command 'make publish' executed +in the source (src) directory. diff --git a/doc/src/Makefile b/doc/src/Makefile new file mode 100644 index 0000000..f36ffd1 --- /dev/null +++ b/doc/src/Makefile @@ -0,0 +1,18 @@ +# Copyright (c) 2020 Nokia +# Copyright (c) 2020 AT&T Intellectual Property. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#================================================================================== + +publish: + (cd user; make publish) diff --git a/doc/src/README b/doc/src/README new file mode 100644 index 0000000..4305866 --- /dev/null +++ b/doc/src/README @@ -0,0 +1,17 @@ + +This directory is the root of documentation source. +Below this directory you might find: + + lib - Directory containing common macros for all + documents that live here. + + user - User guide document + + +In this directory running the command 'make publish' will +build and push all documents to the RTD scraper directory. +Simple documents, such as the release notes, are sourced +directly at this level as they require only a single +bit of input. + + diff --git a/doc/src/lib/front_junk.im b/doc/src/lib/front_junk.im new file mode 100644 index 0000000..718cc6a --- /dev/null +++ b/doc/src/lib/front_junk.im @@ -0,0 +1,102 @@ +.if false +================================================================================== + Copyright (c) 2020 Nokia + Copyright (c) 2020 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.if false + Mnemonic: front_junk.im + Absgtract: This is the front matter (junk): + Title page, + table of contents + license (for page oriented output) + + If defined, this uses the following variables: + doc_title + doc_subtitle + orig_date + + Date: 21 April 2020 +.fi + +.if ! _front_junk + .gv e OUTPUT_TYPE ot + .if "&ot" "generic_ps" = + .sp 20 + .st 24 + .bc start + .if doc_title + &doc_title + .fi + .br + .if doc_subtitle + &doc_subtitle + .fi + .bc end + + .st &textsize + + .sp 25 + .if orig_date + Original: &orig_date .br + .fi + .gv Date + Revised: &_date + .pa + + .lw 0 + .pn on noline center roman 0 + .im &{lib}/license.im + .pa + + .gv e XFM_PASS pass + .if &pass 1 = + .tc on + .ei + .tc off + + .im &{fname_base}.toc .** initial page eject inside + .pa + .fi + + .** ensure these happen after toc is rendered + .if pfm + .pn on 0 noline center f=%d + .cd 1 6.0i + .fi + .ei + .if "&ot" "rst" = + .dv raw_comment_sym ^.. + .cd 1 26i + .ll 18i + .im &{lib}/raw_license.im + .ll 6i + + .if doc_title + &many_equals + &doc_title + &many_equals + .fi + .if doc_subtitle + &many_dashes + &doc_subtitle + &many_dashes + .fi + .fi + + .dv _front_junk 1 +.fi + diff --git a/doc/src/lib/generic_ps.im b/doc/src/lib/generic_ps.im new file mode 100644 index 0000000..bea4598 --- /dev/null +++ b/doc/src/lib/generic_ps.im @@ -0,0 +1,102 @@ +.if false +================================================================================== + Copyright (c) 2020 Nokia + Copyright (c) 2020 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.** macros compatable with the roff/troff and rts imbed files +.** this is included when generating postscript from the man source. + + .dv esc : .** rst needs an escape for some things + + .hn off + .dv text_size 10p + .dv ex_size 8p + .dv text_font Helvetica + .dv ital_font Helvetica-Oblique + .dv bold_font Helvetica-Bold + .dv cw_font Courier + .st &text_size + .sf &text_font + .dh 1 f=&bold_font p=12 e=no s=2,1 m=.5i i=0i + .dh 2 f=&bold_font p=10 s=1,.5 m=.5i i=0i + .dh 3 f=&bold_font p=10 s=1,0 m=.5i i=0i + + .dv comment .** ignore + + .dv h1 .h1 $1 + .dv h2 .h2 $1 + .dv h3 .h3 $1 + + .dv fig + .dv set_font_cw .sf ^&cw_font + + .dv nf .nf + .dv fo .fo + + .dv center_start .bc start + .dv center_end .bc end + + .dv line_len .ll $1 .dv cn_line_len $1 ^: + .dv space .sp 1 + .dv mult_space .sp $1 + .dv half_space .sp .5 + + .dv beg_list .bl $1 + .dv end_list .el + .dv lic1 n + .dv lic2 m + .dv lic3 l + + .dv figure .fg x=center $1 + + .dv beg_dlist .bd $1 $2 + .dv end_dlist .ed + .dv ditem .cc 2 .sp .5 .di $1 ^: + .dv di .cc 2 .sp .5 .di $1 ^: + .dv li .cc 2 .sp .5 .li + .dv item .cc 2 .li + + .dv proto_start .sp 1 .cc .5i .st 9 .sf Courier-bold .nf + .dv proto_end .fo on .sf ^&text_font .st ^&text_size .sp .3 + + .dv ex_start .sp .5 .st ^&ex_size .sf ^&cw_font .nf + .dv ex_end .fo on .sf ^&text_font .st ^&text_size .sp 1 + + .dv fo_ex_start .sp .5 .st ^&ex_size .sf ^&cw_font + .dv fo_ex_end .sf ^&text_font .st ^&text_size .sp 1 + + .** fonts and font macros + .dv ital .tf ^&ital_font ^&text_size $1 ^: + .dv bold .tf ^&bold_font ^&text_size $1 ^: + .dv cw .tf ^&cw_font ^&text_size $1 ^: + .dv set_font_prop .sf ^&text_font + + .cd 1 6.0i + .ll 6i + .pn off + .ju on + .in .5i + .dv cn_indent .5i + .dv cn_line_len 6i + + .dv indent .ll -0.5i .in +0.25i + .dv uindent .in -0.25i .ll +0.5i + + .dv super .sm ^[ .sm ^&{ss_num}] + .dv ss_num 1 + .dv note .dv ss_num ^[ ?%.0f ^&ss_num 1 + ] ^: .sm .tf superscript /2 ^&{ss_num} ^: + .dv atbot atbot diff --git a/doc/src/lib/license.im b/doc/src/lib/license.im new file mode 100644 index 0000000..3a9af17 --- /dev/null +++ b/doc/src/lib/license.im @@ -0,0 +1,28 @@ + +.gv e OUTPUT_TYPE ot + +.if "&ot" "generic_ps" = + .sp 5 + The the source used to produce this document, and the source code of the + application and/or libraries described herin are coverd by the following + license. + &mult_space( 1.5 ) + .ln + .nf + Copyright (c) 2020 Nokia + Copyright (c) 2020 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +.fo + .ln +.fi diff --git a/doc/src/lib/markdown.im b/doc/src/lib/markdown.im new file mode 100644 index 0000000..c8f996c --- /dev/null +++ b/doc/src/lib/markdown.im @@ -0,0 +1,134 @@ +.if false +================================================================================== + Copyright (c) 2020 Nokia + Copyright (c) 2020 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.if false + Mnemonic: markdown.im + Abstract: This is a setup for a very basic generation of markdown from the few + macros which are designed for the RMR doc. It is very scaled down from + the markdown imbed file supplied with {X}fm. + + Author: E. Scott Danils + Date: 26 October 2016 +------------------------------------------------------------------------------- +.fi + + +.dh 1 m=.5i e=no s=21 i=0 p=16 f=Helvetica-bold +.dh 2 m=.5i s=21 i=0 p=14 f=Helvetica-bold +.dh 3 m=.5i s=10 i=0 p=12 f=Helvetica-bold + +.ju on +.hn off + + +.dv esc : .** rst needs an escape for some things + +.** these macros are common for prfm/tfm, markdown will override some +.dv indent .ll -.5i .in +.25i +.dv uindent .in -.25i .ll +.5i +.dv smindent .ll -.2i .in +.1i +.dv smuindent .in -.1i .ll +.2i + +.dv def_list .bd $1 +.dv end_dlist .ed +.dv bd .bd $1 +.dv ed .ed + +.dv ex_start .sp .5 .ll -.25i .in +.25i .sf Courier .st ^&extextsize .fo off +.** ex_end macro calls _must_ be placed in col 0 to cause an exit from no-format mode. +.dv ex_end .fo on .sf ^&textfont .st ^&textsize .in -.25i .ll +.25i .sp .1 + +.dv proto_start .sp 1 .cc .5i .st 9 .sf Courier-bold .nf +.dv proto_end .fo on .sf ^&text_font .st ^&text_size .sp .3 + + +.dv h1 .sp .1 .h1 $1 +.dv h2 .sp .1 .h2 $1 +.dv h3 .sp .1 .h3 $1 +.dv di .di $1 ^: + +.** superscript number for start_note macro +.dv ss_num 1 +.dv super .sm ^[ .sm ^&{ss_num}] +.dv note .dv ss_num ^[ ^&ss_num 1 + ] ^: .sm ^[ .sm ^&{ss_num}] +.dv atbot atclose + +.dv start_note .cn start atclose Times-roman 8p .5i +.dv end_note .cn end +.dv bold $1 +.dv cw $1 +.dv set_font_prop +.dv ital $1 +.dv lic1 * +.dv lic2 + + +.dv figure .fg $1 + +.dv line_len .ll $1 +.dv space .sp 1 +.dv half_space .sp 1 +.dv mult_space .sp $1 + +.** ----------------------------------------------- +.** not used, but might be needed if doc expanded +.** .dv ta .br ^.ta .br | +.** .dv et .br ^.et .br +.** .dv cl | +.** .dv tr | .br ^.tr .br | +.** .dv table_head $1 +.** .dv empty_cell   +.** ----------------------------------------------- + +.dv break .sm ^` ^` .br +.dv br .sm ^` ^` .br +.ju off +.dv image .ep ^[ .sm $2] ($3) + +.** no concept of a definition list in markdown; no start/end and just make the text bold to emulate +.dv beg_dlist .sp 1 +.dv bd .sp 1 +.dv end_dlist .sp 1 +.dv ed .sp 1 +.dv di ^&break **$1:** +.dv ditem ^&break **$1:** + +.dv beg_list .bl * +.dv item .li +.dv li .li +.dv end_list .el + +.dv center_start +.dv center_end + +.dv line .sp 1 ------ + +.** leading indention is significant to markdown, so turn it off +.in 0 +.dv indent +.dv uindent .sp 2 +.dv smindent +.dv smuindent .sp 2 +.dv bold **${1}** +.dv ital _${1}_ +.dv h1 .sp 2 # $1 .br +.dv h2 .sp 1 ## $1 .br +.dv h3 .sp 1 ### $1 .br + +.dv ex_start .sp .5 .ll -4 .in .5i .sp 2 .fo off +.dv ex_end .fo on .in 0i .ll +4 .sp 2 diff --git a/doc/src/lib/raw_license.im b/doc/src/lib/raw_license.im new file mode 100644 index 0000000..2334f0a --- /dev/null +++ b/doc/src/lib/raw_license.im @@ -0,0 +1,19 @@ + +.if false + This is the "raw" license which goes into the document itself (where + possible). +.fi + +&raw_comment_sym This work is licensed under a Creative Commons Attribution 4.0 International License. +.br +&raw_comment_sym SPDX-License-Identifier: CC-BY-4.0 +.br +&raw_comment_sym +.br +&raw_comment_sym CAUTION: this document is generated from source in doc/src/* +.br +&raw_comment_sym To make changes edit the source and recompile the document. +.br +&raw_comment_sym Do NOT make changes directly to .rst or ^.md files. +.sp 2 + diff --git a/doc/src/lib/roff.im b/doc/src/lib/roff.im new file mode 100644 index 0000000..2c579aa --- /dev/null +++ b/doc/src/lib/roff.im @@ -0,0 +1,88 @@ +if false +================================================================================== + Copyright (c) 2020 Nokia + Copyright (c) 2020 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.if false + Mnemonic: troff.im + Abstract: This file provides macros allowing {X}fm source to generate + troff input from {X}fm source when the doc is passed through + tfm. This allows the more easily read {X}fm source to be used + to generate man pages, and to be able to generate the man pages + in various output formats (txt, rst, etc.). + + Author: E. Scott Daniels + Date: 27 January 2019 + + Some roff information was gleanded from these pages: + http://cmd.inp.nsk.su/old/cmd2/manuals/unix/UNIX_Unleashed/ch08.htm + https://www.gnu.org/software/groff/manual/html_node/Basics.html +.fi + +.** assume that we're generating roff output when tfm is used. These macros +.** convert {X}fm input into troff. + +.dv h1 .sp 1 ^&bold($1) .br ^&space +.dv h2 .sp 1 ^&bold($1) .br ^&space +.dv h3 .sp 1 ^&bold($1) .br ^&space + +.dv fig +.dv set_font_cw .br ^^.ft CW + +.dv nf ^^.nf .br .nf +.dv fo .fo ^^.fi .br + +.dv center .br ^^.ce 1 .br $1 .br +.dv center_start .br ^^.ce 999 +.dv center_end .br ^.ce 0 + +.dv esc : .** rst needs an escape for some things + +.dv line_len ^^.ll $1 +.dv space .br ^^.sp 1 .br +.dv half_space .br ^^.sp 1 .br +.dv mult_space .br ^^.sp $1 .br +.dv beg_list +.dv end_list +.dv beg_dlist .dv udi -$1 ^: ^^.in +$1 .br ^^.sp 1 .br +.dv end_dlist .br ^^.in &udi .br +.dv ditem ^^.ti ^&udi .br ^&space .br \fB$1\fR .br +.dv item ^&space \(bu +.dv li ^&space \(bu +.** .dv di ^&ditem +.dv di ^^.ti ^&udi .br ^&space .br \fB$1\fR .br + +.dv figure + +.dv ex_start ^^.IP .br ^^.nf .br ^^.ft CW .br .nf +.dv ex_end .fo on .br ^^.ft P .br ^^.fi .br ^&space + +.dv proto_start .sp 1 .cc .5i .st 9 .sf Courier-bold .nf +.dv proto_end .fo on .sf ^&text_font .st ^&text_size .sp .3 + +.** fonts and font macros +.dv ital \fI$1\fR +.dv bold \fB$1\fR +.dv cw \f(CW$1\fP +.dv set_font_prop .br ^^.ft P + +.ju off +.in 0 + +.dv indent ^^.ll -0.5i &space ^^.in +0.25i +.dv uindent ^^.in -0.25i &space ^^.ll +0.5i + diff --git a/doc/src/lib/rst.im b/doc/src/lib/rst.im new file mode 100644 index 0000000..f20165e --- /dev/null +++ b/doc/src/lib/rst.im @@ -0,0 +1,120 @@ +.if false +================================================================================== + Copyright (c) 2020 Nokia + Copyright (c) 2020 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.if false + Mnemonic: rts.im + Abstract: This file provides macros allowing {X}fm source to generate + rts input from {X}fm source when the doc is passed through + tfm, and to generate postscirpt output when passed through + pfm. Simalar to the roff.im macro set that allows the generation + of troff input for man pages. + + Author: E. Scott Daniels + Date: 7 February 2019 + + + Maybe useful (but doesn't explain why real formatters aren't being used) + http://docutils.sourceforge.net/docs/user/rst/quickref.html +.fi + + + .** convert {X}fm input into rts. + .** post processing is needed to strip the leading space that tfm insists on adding. + + .** bloody rst has no consistant marking character, and each header level must be different and as long as the text. + .** and of course they don't generate tags in the resulting HTML, but
tags. WTF? + .dv many_equals ============================================================================================ + .dv many_dashes -------------------------------------------------------------------------------------------- + .dv many_tildas ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + .dv h1 .sp 1 $1 .br &many_equals .sp 1 + .dv h2 .sp 1 $1 .br &many_dashes .sp 1 + .dv h3 .sp 1 $1 .br &many_tildas .sp 1 + + .** bloody rst won't allow breaks in a bullet list so we have to allow the column to go wide. + .dv cd 1 180i m=0i + + .dv h4 **$1** + .** .dv h1 === $1 .br === .sp 1 + .** .dv h2 === $1 .br === .sp 1 + .** .dv h3 === $1 .br === .sp 1 + + .dv fig + .dv set_font_cw + + .dv nf .sp 1 ^:^: .br .ll -2 .in +2 + .dv fo .in -2 .ll +2 .sp 1 + + .dv indent + .dv uindent + + .dv lic1 + + .dv lic2 - + .dv lic3 * + + .in 0i .** bloody rst is indention sensitive like markdown; sheesh + + .dv esc \ : .** bloody need to escape _ and * at the end of a word + .dv line_len .ll $1 + .dv space .sp 1 + .dv half_space .sp 1 + .dv mult_space .sp $1 + .dv beg_list .ll 17i .sp 1 .dv lic $1 ^: + .dv end_list .ll 6i .sp 1 + + .dv figure .fg $1 + + .dv beg_dlist .sp 1 .ll -3 + .dv end_dlist .br .in 0i .ll +3 + + .** for now we allow only a single layer of defitems + .dv di .in 0i .br $1 .br .in +3 + .dv ditem .in 0i .br $1 .br .in +3 + .** diitem is odd and deprecated + .dv diitem .in 0i .br $1 .br .in +3 + .dv item .br &lic + .dv li .br &lic + + .dv ex_start .sp 1 ^:^: .br .ll -2 .in +2 .nf + .dv ex_end .fo on .in -2 .ll +2 .sp 1 + + .dv proto_start .sp 1 .cc .5i .st 9 .sf Courier-bold .nf + .dv proto_end .fo on .sf ^&text_font .st ^&text_size .sp .3 + + .dv center .br $1 .br + .dv center_start .br + .dv center_end .br + + .** fonts and font macros + .dv ital *$1* + .dv bold **$1** + .dv cw $1 + .dv set_font_prop + + .dv table .sp 1 ^[table not supported in rst output] .if false + .dv tab_cell + .dv tab_row + .dv end_table .fi + + .dv super .sm ^[ .sm ^&{ss_num}] + .dv ss_num 1 + .dv note .dv ss_num ^[ ?%.0f ^&ss_num 1 + ] ^: .sm ^^[^&{ss_num}] + .** rst has no concept of a page, so all notes go to the close of the doc + .dv atbot atclose + + .ju off diff --git a/doc/src/lib/setup.im b/doc/src/lib/setup.im new file mode 100644 index 0000000..a99d6d7 --- /dev/null +++ b/doc/src/lib/setup.im @@ -0,0 +1,65 @@ +.if false +================================================================================== + Copyright (c) 2020 Nokia + Copyright (c) 2020 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.if false + Mnemonic: setup.im + Abstract: Look at environment variables and pull in the correct setup prep + imbed file based on the desired output type (when running tfm). + Obviously, when running pfm we are always generating postscript + so this isn't really doing much. + Date: 29 July 2019 +.fi + + +.if ! _setup_im + .gv e XFM_IM_LIB lib + .if ! lib + .dv lib ../lib + .fi + + .** sane default for everything except rst + .dv col_width 6.5i + .dv textsize 10 + .dv textfont Helvetica + + .** imbed output type specific macro file + .gv e OUTPUT_TYPE ot + .im &{lib}/&{ot}.im + + + .** set up for an index when using pfm and snare file is defined + .if pfm + .if index_snare_file + .ix space .sp 1 .cc 5 %c .ln ^: .sp 0 + .ix term .br %s `^` ^`` %d + .ix groupb .sp .5 %s .br .ll -.25i .in +.25i + .ix groupe .sp .1 .in -.25i .ll +.25i + .if ! index_cols + .dv index_cols 2 + .fi + + .dv index_col_width [ 6.5 &index_cols / .25 &index_cols 1 - * - ] + .dv index_here .pa .ju off .st &textsize .cd 1 i=1i w=7 ^: .h1 INDEX ^: .pn off .lw 0 .sp 0 .tt ^: .cd &index_cols i=1i w=&{index_col_width}i g=.25i ^: .in -i .ll &{index_col_width}i .ix insert + .im &index_snare_file + .fi + .fi + + .dv _setup_im 1 + +.fi diff --git a/doc/src/lib/txt.im b/doc/src/lib/txt.im new file mode 100644 index 0000000..684a60b --- /dev/null +++ b/doc/src/lib/txt.im @@ -0,0 +1,108 @@ +.if false +================================================================================== + Copyright (c) 2020 Nokia + Copyright (c) 2020 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.if false + Mnemonic: txt.im + Abstract: To generate raw ASCII we just need to 'disable' the macros which + are defined to support things like troff, markdown and rts. + For each of those macros, we just map to real {X}fm commandd + which do the right thing when generating real .txt output. + + Author: E. Scott Daniels + Date: 29 July 2019 + +.fi + + +.cd 1 6.5i i=0 +.in .25i + +.** paragraph headers +.dv h1 .h1 $1 +.dv h2 .h2 $1 +.dv h3 .h3 $1 +.dv h4 .h4 $1 + +.dh 1 s=2,1 i=.25i m=.25i +.dh 2 s=1,1 i=.25i m=.25i +.dh 3 s=1,0 i=.25i m=.25i +.hn off + +.dv fig .fi t=figure +.dv set_font_cw + +.dv nf .sp 1 ^:^: .br .ll -2 .in +2 +.dv fo .in -2 .ll +2 .sp 1 + +.dv indent .ll -.25i .in +.25i +.dv uindent .in -.25i .ll +.25i + +.dv lic1 * +.dv lic2 + +.dv lic3 - + +.dv esc : .** rst needs an escape for some things + +.dv line_len .ll $1 +.dv space .sp 1 +.dv mult_space .sp $1 +.dv half_space .sp 1 +.dv beg_list .bl $1 +.dv end_list .el +.dv li .li +.dv item .li + +.dv beg_dlist .sp 1 .bd $1 +.dv end_dlist .ed + +.dv center .ce $1 ^: +.dv center_start .bc start +.dv center_end .bc end + +.dv figure .fg $1 + +.** for now we allow only a single layer of defitems +.dv di .di $1 ^: +.dv diitem .di $1 ^: +.dv item .li + +.dv ex_start .sp 1 .ll -2 .in +2 .nf +.dv ex_end .fo on .in -2 .ll +2 .sp 1 + +.dv proto_start .sp 1 .cc .5i .st 9 .sf Courier-bold .nf +.dv proto_end .fo on .sf ^&text_font .st ^&text_size .sp .3 + +.** fonts and font macros +.dv ital $1 +.dv bold $1 +.dv cw $1 +.dv set_font_prop + +.dv table .sp 1 ^[table not supported in plain txt output] .if false +.dv tab_cell +.dv tab_row +.dv end_table .fi + +.dv super .sm ^[ .sm ^&{ss_num}] +.dv ss_num 1 +.dv note .dv ss_num ^[ ?%.0f ^&ss_num 1 + ] ^: .sm ^^[^&{ss_num}] +.** pure ascii out has no concept of a page, so all notes go to the close of the doc +.dv atbot atclose +.ju on + diff --git a/doc/src/user/.gitignore b/doc/src/user/.gitignore new file mode 100644 index 0000000..aa1acd1 --- /dev/null +++ b/doc/src/user/.gitignore @@ -0,0 +1,5 @@ +*.ps +*.rst +*.txt +*.html +*.sp diff --git a/doc/src/user/Makefile b/doc/src/user/Makefile new file mode 100644 index 0000000..729b7fb --- /dev/null +++ b/doc/src/user/Makefile @@ -0,0 +1,70 @@ +#================================================================================== +# Copyright (c) 2020 Nokia +# Copyright (c) 2020 AT&T Intellectual Property. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#================================================================================== + +# this uses {X}fm which can be cloned from: https://gitlab.com/rouxware/xfm + +docs = user_guide +src = user_guide.xfm +imbed_src = cpp_frame.im example1.im example2.im example3.im +desired_out = rst ps + +# use care: the output type is used to source the macros based on the type +# of output being rendered. +# Two pass builds allow for table of contents gen/insert, forward var +# references etc. +# +%.ps: %.xfm + OUTPUT_TYPE=generic_ps XFM_PASS=1 pfm $< /dev/null + OUTPUT_TYPE=generic_ps XFM_PASS=2 pfm $< $@ + +%.md: %.xfm + OUTPUT_TYPE=markdown XFM_PASS=1 tfm $< /dev/null + OUTPUT_TYPE=markdown XFM_PASS=2 tfm $< | sed 's/^ //' >$@ + +%.rst: %.xfm + OUTPUT_TYPE=rst XFM_PASS=1 tfm $< /dev/null + GEN_TITLE=1 OUTPUT_TYPE=rst XFM_PASS=2 tfm $< | sed 's/^ //' >$@ + +%.txt: %.xfm + OUTPUT_TYPE=txt XFM_PASS=1 tfm $< /dev/null + OUTPUT_TYPE=txt XFM_PASS=2 tfm $< $@ + +# ----------------------------------------------------------------------------------- +all: $(desired_out:%=user_guide.%) + +user_guide.ps: user_guide.xfm $(imbed_src) + +# we force the .rst docs to always be out of date so that we don't have to +# jump hoops to ensure that they build for make publish. (See hack rule +# at the end of the file.) +# +$(docs:%=%.rst): always + +publish: user_guide.rst + cp *.rst ../../../docs/ + +# intermeidate junk that might straggle +clean: + rm -fr *.bcnfile *.ca *.ecnfile *.sp + +# Destroy anything that can be built +nuke: clean + rm -fr *.md *.ps *.pdf *.txt *.rst *.toc + +# make hack to force a rule to always be out of date +always: + diff --git a/doc/src/user/cpp_frame.im b/doc/src/user/cpp_frame.im new file mode 100644 index 0000000..aa4b166 --- /dev/null +++ b/doc/src/user/cpp_frame.im @@ -0,0 +1,348 @@ + +.if false +================================================================================== + Copyright (c) 2020 Nokia + Copyright (c) 2020 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== + +.fi + +&h1(Introduction) +The C++ framework allows the programmer to create an xApp object instance, and to use that instance as +the logic base. +The xApp object provides a message level interface to the RIC Message Router (RMR), including the ability +to register callback functions which the instance will drive as messages are received; much in the same +way that an X-windows application is driven by the window manager for all activity. +The xApp may also choose to use its own send/receive loop, and thus is not required to use the callback +driver mechanism provided by the framework. + +&h1(The Framework API) +The C++ framework API consists of the creation of the xApp object, and invoking desired functions via +the instance of the object. +The following paragraphs cover the various steps involved to create an xApp instance, wait for a route +table to arrive, send a message, and wait for messages to arrive. + +&h2(Creating the xApp instance) +The creation of the xApp instance is as simple as invoking the object's constructor with two required +parameters: + +&half_space +&indent +&beg_dlist(.5i:&ditemtext) + &ditem(port) A C string (char *) which defines the port that RMR will open to listen for connections. + &half_space + + &ditem(wait) A Boolean value which indicates whether or not the initialization process should wait for + the arrival of a valid route table before completing. + When true is supplied, the initialization will not complete until RMR has received a valid route table + (or one is located via the &cw(RMR_SEED_RT) environment variable). +&end_dlist +&uindent +&space + +The following code sample illustrates the simplicity of creating the instance of the xApp object. +&half_space + +&ex_start + #include + #include + int main( ) { + std::unique_ptr xapp; + char* listen_port = (char *) "4560"; //RMR listen port + bool wait4table = true; // wait for a route table + + xapp = std::unique_ptr( + new Xapp( listen_port, wait4table ) ); + } +&ex_end +&figure( Creating an xAPP instance.) +&space + +From a compilation perspective, the following is the simple compiler invocation string needed to compile +and link the above program (assuming that the sample code exists in a file called &cw(man_ex1.cpp). +&half_space + + +&ex_start + g++ man_ex1.cpp -o man_ex1 -lricxfcpp -lrmr_si -lpthread +&ex_end +&space + +The above program, while complete and capable of being compiled, does nothing useful. +When invoked, RMR will be initialized and will begin listening for a route table; blocking the return +to the main program until one is received. +When a valid route table arrives, initialization will complete and the program will exit as there is +no code following the instruction to create the object. + +&h1(Listening For Messages) +The program in the previous example can be extended with just a few lines of code to enable it to receive +and process messages. +The application needs to register a callback function for each message type which it desires to process. +&space + +Once registered, each time a message is received the registered callback for the message type will be +invoked by the framework. + +&h2(Callback Signature) +As with most callback related systems, a callback must have a well known function signature which generally +passes event related information and a "user" data pointer which was registered with the function. +The following is the prototype which callback functions must be defined with: +&half_space + +&ex_start + void cb_name( Message& m, int mtype, int subid, + int payload_len, Msg_component payload, + void* usr_data ); +&ex_end +&figure( Callback function signature) +&space + +The parameters passed to the callback function are as follows: +&multi_space( .1 ) + +&indent +&beg_dlist(1i:&ditemtext) + &ditem(m) A reference to the Message that was received. + &half_space + + &ditem(mtype) The message type (allows for disambiguation if the callback is registered for multiple message types). + &half_space + + &ditem(subid) The subscription ID from the message. + &half_space + + &ditem(payload len) The number of bytes which the sender has placed into the payload. + &half_space + + &ditem(payload) A direct reference (smart pointer) to the payload. (The smart pointer is wrapped in a + special class in order to provide a custom destruction function without burdening the xApp developer + with that knowledge.) + &half_space + &ditem(user data) A pointer to user data. This is the pointer that was provided when the function was registered. +&end_dlist +&uindent +&space + +To illustrate the use of a callback function, the previous code example has been extended to add the +function, register it for message types 1000 and 1001, and to invoke the &cw(Run()) function in the +framework (explained in the next section). + +&ex_start + #include + #include + long m1000_count = 0; // message counters, one for each type + long m1001_count = 0; + + // callback function that will increase the appropriate counter + void cbf( Message& mbuf, int mtype, int subid, int len, + Msg_component payload, void* data ) { + long* counter; + + if( (counter = (long *) data) != NULL ) { + (*counter)++; + } + } + + int main( ) { + std::unique_ptr xapp; + char* listen_port = (char *) "4560"; + bool wait4table = false; + + xapp = std::unique_ptr( + new Xapp( listen_port, wait4table ) ); + + // register the same callback function for both msg types + xapp->Add_msg_cb( 1000, cbf, (void *) &m1000_count ); + xapp->Add_msg_cb( 1001, cbf, (void *) &m1001_count ); + + xapp->Run( 1 ); // start the callback driver + } +&ex_end +&figure( Callback function example.) +&space + +As before, the program does nothing useful, but now it will execute and receive messages. +For this example, +the same function can be used to increment the appropriate counter simply by providing a pointer to the +counter as the user data when the callback function is registered. +In addition, a subtle change from the previous example has been to set the wait for table flag to &cw(false.) +&space + +For an xApp that is a receive only application (never sends) it is not necessary to wait for RMR +to receive a table from the Route Manager. + +&h2(Registering A Default Callback) +The xApp may also register a default callback function such that the function will be invoked for any +message that does not have a registered callback. +If the xAPP does not register a default callback, any message which cannot be mapped to a known callback +function is silently dropped. +A default callback is registered by providing a &ital(generic) message type of &cw(xapp->DEFAULT_CALLBACK) +on an &cw(Add_msg_cb) call. + +&h2(The Framework Callback Driver) +The &cw(Run()) function within the Xapp object is invoked to start the callback driver, and the xApp +should not expect the function +to return under most circumstances. +The only parameter that the &cw(Run()) function expects is the number of threads to start. +For each thread requested, the framework will start a listener thread which will allow received messages +to be processed in parallel. +If supplying a value greater than one, the xApp must ensure that the callback functions are thread safe +as it is very likely that +the same callback function will be invoked concurrently from multiple threads. + +&h1(Sending Messages) +It is very likely that most xApps will need to send messages and will not operate in "receive only" mode. +Sending the message is a function of the message object itself and can take one of two forms: + +&half_space +&indent +&beg_list &lic1 + &item Replying to the sender of a received message + &half_space + + &item Sending a message (routed based on the message type and subscription ID) +&end_list +&uindent +&space + +When replying to the sender, the message type and subscription ID are not used to determine the destination +of the message; RMR ensures that the message is sent back to the originating xApp. The xApp may still need +to change the message type and/or the subscription ID in the message prior to using the reply function. +&space + +To provide for both situations, two reply functions are supported by the Message object as illustrated +with the following prototypes. +&half_space + +&ex_start + bool Send_response( int mtype, int subid, int response_len, + std:shared_ptr response ); + + bool Send_response( int response_len, std::shared_ptr response ); +&ex_end +&figure( Reply function prototypes. ) +&space + +In the first prototype the xApp must supply the new message type and subscription ID values, where the +second function uses the values which are currently set in the message. +Further, the new payload contents, and length, are supplied to both functions; the framework ensures +that the message is large enough to accommodate the payload, reallocating it if necessary, and copies +the response into the message payload prior to sending. +Should the xApp need to change either the message type, or the subscription ID, but not both, the &cw(NO_CHANGE) +constant can be used as illustrated below. +&half_space + +&ex_start + msg->Send_response( Message::NO_CHANGE, Message::NO_SUBID, + pl_length, (unsigned char *) payload ); +&ex_end +&figure( Send response prototype. ) +&space + +In addition to the two function prototypes for &cw(Send_response()) there are two additional prototypes +which allow the new payload to be supplied as a shared smart pointer. +The other parameters to these functions are identical to those illustrated above, and thus are not presented +here. +&space + +The &cw(Send_msg()) set of functions supported by the Message object are identical to the &cw(Send_response()) +functions and are shown below. +&half_space + +&ex_start + bool Send_msg( int mtype, int subid, int payload_len, + std::shared_ptr payload ); + + bool Send_msg( int mtype, int subid, int payload_len, + unsigned char* payload ); + + bool Send_msg( int payload_len, + std::shared_ptr payload ); + + bool Send_msg( int payload_len, unsigned char* payload ); +&ex_end +&figure( Send function prototypes. ) +&space + +Each send function accepts the message, copies in the payload provided, sets the message type and subscription +ID (if provided), and then causes the message to be sent. +The only difference between the &cw(Send_msg()) and &cw(Send_response()) functions is that the destination +of the message is selected based on the mapping of the message type and subscription ID using the current +routing table known to RMR. + +&h2(Direct Payload Manipulation) +For some applications, it might be more efficient to manipulate the payload portion of an Xapp Message +in place, rather than creating it and relying on a buffer copy when the message is finally sent. +To achieve this, the xApp must either use the smart pointer to the payload passed to the callback function, +or retrieve one from the message using &cw(Get_payload()) when working with a message outside of a +callback function. +Once the smart pointer is obtained, the pointer's get() function can be used to directly reference the +payload (unsigned char) bytes. +&space + +When working directly with the payload, the xApp must take care not to write more than the actual payload +size which can be extracted from the Message object using the &cw(Get_available_size()) function. +&space + +When sending a message where the payload has been directly altered, and no extra buffer copy is needed, +a NULL pointer should be passed to the Message send function. +The following illustrates how the payload can be directly manipulated and returned to the sender (for +simplicity, there is no error handling if the payload size of the received message isn't large enough +for the response string, the response is just not sent). +&half_space + +&ex_start + Msg_component payload; // smart reference + int pl_size; // max size of payload + + payload = msg->Get_payload(); + pl_size = msg->Get_available_size(); + if( snprintf( (char *) payload.get(), pl_size, + "Msg Received\n" ) < pl_size ) { + msg->Send_response( M_TYPE, SID, strlen( raw_pl ), NULL ); + } +&ex_end +&figure( Send message without buffer copy. ) +&space + +&h2(Sending Multiple Responses) +It is likely that the xApp will wish to send multiple responses back to the process that sent a message +that triggered the callback. +The callback function may invoke the &cw(Send_response()) function multiple times before returning. +&space + +After each call, the Message retains the necessary information to allow for a subsequent invocation to +send more data. +It should be noted though, that after the first call to &cw({Send_response()) the original payload +will be lost; if necessary, the xApp must make a copy of the payload before the first response call is +made. + +&h2(Message Allocation) +Not all xApps will be "responders," meaning that some xApps will need to send one or more messages before +they can expect to receive any messages back. +To accomplish this, the xApp must first allocate a message buffer, optionally initialising the payload, +and then using the message's &cw(Send_msg()) function to send a message out. +The framework's &cw(Alloc_msg()) function can be used to create a Message object with a desired payload +size. + +&h1(Framework Provided Callbacks) +The framework itself may provide message handling via the driver such that the xApp might not need to +implement some message processing functionality. +Initially, the C++ framework will provide a default callback function to handle the RMR based health +check messages. +This callback function will assume that if the message was received, and the callback invoked, that all +is well and will reply with an OK state. +If the xApp should need to override this simplistic response, all it needs to do is to register its +own callback function for the health check message type. diff --git a/doc/src/user/example1.im b/doc/src/user/example1.im new file mode 100644 index 0000000..f54af6d --- /dev/null +++ b/doc/src/user/example1.im @@ -0,0 +1,57 @@ +.if false +================================================================================== + Copyright (c) 2020 Nokia + Copyright (c) 2020 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + + +&h2(RMR Dump xAPP) +The RMR dump application is an example built on top of the C++ xApp framework to both +illustrate the use of the framework, and to provide a useful diagnostic tool when +testing and troubleshooting xApps. +&space + +The RMR dump xApp isn't a traditional xApp inasmuch as its goal is to listen for message +types and to dump information about the messages received to the TTY much as &cw(tcpdump) +does for raw packet traffic. +The full source code, and Makefile, are in the &cw(examples) directory of the C++ framework repo. +&space + +When invoked, the RMR dump program is given one or more message types to listen for. +A callback function is registered for each, and the framework &cw(Run()) function is invoked +to drive the process. +For each recognised message, and depending on the verbosity level supplied at program start, +information about the received message(s) is written to the TTY. +If the forwarding option, -f, is given on the command line, and an appropriate route table is +provided, each received message is forwarded without change. +This allows for the insertion of the RMR dump program into a flow, however if the ultimate +receiver of a message needs to reply to that message, the reply will not reach the original +sender, so RMR dump is not a complete "middle box" application. +&space + +The following is the code for this xAPP. Several functions, which provide logic unrelated to +the framework, have been omitted. The full code is in the framework repository. +&half_space + +&space +&indent +.** pull in the code from the example directory +&ex_start +.im j=start-example ../../../examples/rmr_dump.cpp +&ex_end +&figure( Simple callback application. ) +&uindent + diff --git a/doc/src/user/example2.im b/doc/src/user/example2.im new file mode 100644 index 0000000..90a7f51 --- /dev/null +++ b/doc/src/user/example2.im @@ -0,0 +1,45 @@ +.if false +================================================================================== + Copyright (c) 2020 Nokia + Copyright (c) 2020 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +&h2(Callback Receiver) +This sample programme implements a simple message listener which +registers three callback functions to process two specific +message types and a default callback to handle unrecognised messages. + +&space +When a message of type 1 is received, it will send two response messages +back to the sender. +Two messages are sent in order to illustrate that it is possible to +send multiple responses using the same received message. + +&space +The programme illustrates how multiple listening threads can be +used, but the programme is &bold(not) thread safe; to keep +this example as simple as possible, the counters +are not locked when incremented. + +&space +&indent +.** pull in the code from the example directory +&ex_start +.im j=start-example ../../../examples/xapp_t1.cpp +&ex_end +&figure( Simple callback application. ) +&uindent +&space diff --git a/doc/src/user/example3.im b/doc/src/user/example3.im new file mode 100644 index 0000000..e25f03e --- /dev/null +++ b/doc/src/user/example3.im @@ -0,0 +1,35 @@ +.if false +================================================================================== + Copyright (c) 2020 Nokia + Copyright (c) 2020 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +&h2(Looping Sender) +This is another very simple application which demonstrates how an +application can control its own listen loop while sending messages. +As with the other examples, some error checking is skipped, and short cuts have +been made in order to keep the example small and to the point. + + +&space +&indent +.** pull in the code from the example directory +&ex_start +.im j=start-example ../../../examples/xapp_t2.cpp +&ex_end +&figure( Simple looping sender application. ) +&space + diff --git a/doc/src/user/user_guide.xfm b/doc/src/user/user_guide.xfm new file mode 100644 index 0000000..c34538f --- /dev/null +++ b/doc/src/user/user_guide.xfm @@ -0,0 +1,60 @@ +.if false +================================================================================== + Copyright (c) 2020 Nokia + Copyright (c) 2020 AT&T Intellectual Property. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +================================================================================== +.fi + +.if false + Mnemonic: user_guide.xfm + Abstract: Source for the user guide. Use {X}fm to generate one of several + output types: + - postscript + - roff + - markdown + - rst + - ascii text + + Date: 21 April 2020 + Author: E. Scott Daniels +.fi + + +.dv col_width 6.5i +.dv fname_base user_guide +.dv doc_title RIC xAPP C++ Framework +.dv doc_subtitle User's Guide +.dv orig_date 21 April 2020 + +.dv textfont Helvetica +.dv ditemtext Helvetica-bold + +.in .5i +.ll 6i +.hn off + +.im ../lib/setup.im + +.im &{lib}/front_junk.im + +.im cpp_frame.im + +&h1(Example Programmes) +The following sections contain several example programmes which are written on +top of the C++ framework. + +.im example1.im +.im example2.im +.im example3.im diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..89d0765 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,2 @@ +/_build +/out diff --git a/docs/_static/logo.png b/docs/_static/logo.png new file mode 100644 index 0000000..c3b6ce5 Binary files /dev/null and b/docs/_static/logo.png differ diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..922e22f --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,6 @@ +from docs_conf.conf import * +linkcheck_ignore = [ + 'http://localhost.*', + 'http://127.0.0.1.*', + 'https://gerrit.o-ran-sc.org.*' +] diff --git a/docs/conf.yaml b/docs/conf.yaml new file mode 100644 index 0000000..85e0266 --- /dev/null +++ b/docs/conf.yaml @@ -0,0 +1,3 @@ +--- +project_cfg: oran +project: portal-ric-xappframe-cpp diff --git a/docs/favicon.ico b/docs/favicon.ico new file mode 100644 index 0000000..00b0fd0 Binary files /dev/null and b/docs/favicon.ico differ diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..37eb834 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,26 @@ + +.. This work is licensed under a Creative Commons Attribution 4.0 International License. +.. SPDX-License-Identifier: CC-BY-4.0 + + +.. who in bloody hell knows what format this should be; guessing! +.. it does seem though that every .rst file in the directory must be listed here +.. or tox will complain and fail. + + +RIC xAPP C++ Framework +====================== + +.. toctree:: + :maxdepth: 1 + :caption: Documents: + + user-guide.rst + +.. toctree:: + :maxdepth: 2 + :caption: Misc: + + rel-notes.rst + +* :ref:`search` diff --git a/docs/rel-notes.rst b/docs/rel-notes.rst new file mode 100644 index 0000000..2bb881c --- /dev/null +++ b/docs/rel-notes.rst @@ -0,0 +1,14 @@ + +.. This work is licensed under a Creative Commons Attribution 4.0 International License. +.. SPDX-License-Identifier: CC-BY-4.0 + +.. CAUTION: this document is generated from source in doc/src/rtd. +.. To make changes edit the source and recompile the document. +.. Do NOT make changes directly to .rst or .md files. + +RIC xAPP C++ Framework +====================== + +2020 April 22 Version 1.0.0 + Initial release + diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt new file mode 100644 index 0000000..09a0c1c --- /dev/null +++ b/docs/requirements-docs.txt @@ -0,0 +1,5 @@ +sphinx +sphinx-rtd-theme +sphinxcontrib-httpdomain +recommonmark +lfdocs-conf diff --git a/docs/user_guide.rst b/docs/user_guide.rst new file mode 100644 index 0000000..4c5836a --- /dev/null +++ b/docs/user_guide.rst @@ -0,0 +1,831 @@ + + +.. This work is licensed under a Creative Commons Attribution 4.0 International License. +.. SPDX-License-Identifier: CC-BY-4.0 +.. +.. CAUTION: this document is generated from source in doc/src/* +.. To make changes edit the source and recompile the document. +.. Do NOT make changes directly to .rst or .md files. + + +============================================================================================ +RIC xAPP C++ Framework +============================================================================================ +-------------------------------------------------------------------------------------------- +User's Guide +-------------------------------------------------------------------------------------------- + +Introduction +============================================================================================ + +The C++ framework allows the programmer to create an xApp +object instance, and to use that instance as the logic base. +The xApp object provides a message level interface to the RIC +Message Router (RMR), including the ability to register +callback functions which the instance will drive as messages +are received; much in the same way that an X-windows +application is driven by the window manager for all activity. +The xApp may also choose to use its own send/receive loop, +and thus is not required to use the callback driver mechanism +provided by the framework. + +The Framework API +============================================================================================ + +The C++ framework API consists of the creation of the xApp +object, and invoking desired functions via the instance of +the object. The following paragraphs cover the various steps +involved to create an xApp instance, wait for a route table +to arrive, send a message, and wait for messages to arrive. + +Creating the xApp instance +-------------------------------------------------------------------------------------------- + +The creation of the xApp instance is as simple as invoking +the object's constructor with two required parameters: + + + + port + + A C string (char *) which defines the port that RMR will + open to listen for connections. + + + wait + + A Boolean value which indicates whether or not the + initialization process should wait for the arrival of a + valid route table before completing. When true is + supplied, the initialization will not complete until RMR + has received a valid route table (or one is located via + the RMR_SEED_RT environment variable). + + + The following code sample illustrates the simplicity of + creating the instance of the xApp object. + + + :: + + #include + #include + int main( ) { + std::unique_ptr xapp; + char* listen_port = (char *) "4560"; //RMR listen port + bool wait4table = true; // wait for a route table + xapp = std::unique_ptr( + new Xapp( listen_port, wait4table ) ); + } + + + Figure 1: Creating an xAPP instance. + + From a compilation perspective, the following is the simple + compiler invocation string needed to compile and link the + above program (assuming that the sample code exists in a file + called man_ex1.cpp. + + + :: + + g++ man_ex1.cpp -o man_ex1 -lricxfcpp -lrmr_si -lpthread + + + + The above program, while complete and capable of being + compiled, does nothing useful. When invoked, RMR will be + initialized and will begin listening for a route table; + blocking the return to the main program until one is + received. When a valid route table arrives, initialization + will complete and the program will exit as there is no code + following the instruction to create the object. + + Listening For Messages + ============================================================================================ + + The program in the previous example can be extended with just + a few lines of code to enable it to receive and process + messages. The application needs to register a callback + function for each message type which it desires to process. + + Once registered, each time a message is received the + registered callback for the message type will be invoked by + the framework. + + Callback Signature + -------------------------------------------------------------------------------------------- + + As with most callback related systems, a callback must have a + well known function signature which generally passes event + related information and a "user" data pointer which was + registered with the function. The following is the prototype + which callback functions must be defined with: + + + :: + + void cb_name( Message& m, int mtype, int subid, + int payload_len, Msg_component payload, + void* usr_data ); + + + Figure 2: Callback function signature + + The parameters passed to the callback function are as + follows: &multi_space + + + m + + A reference to the Message that was received. + + + mtype + + The message type (allows for disambiguation if the + callback is registered for multiple message types). + + + subid + + The subscription ID from the message. + + + payload len + + The number of bytes which the sender has placed into the + payload. + + + payload + + A direct reference (smart pointer) to the payload. (The + smart pointer is wrapped in a special class in order to + provide a custom destruction function without burdening + the xApp developer with that knowledge.) + + + user data + + A pointer to user data. This is the pointer that was + provided when the function was registered. + + + To illustrate the use of a callback function, the previous + code example has been extended to add the function, register + it for message types 1000 and 1001, and to invoke the Run() + function in the framework (explained in the next section). + + :: + + #include + #include + long m1000_count = 0; // message counters, one for each type + long m1001_count = 0; + // callback function that will increase the appropriate counter + void cbf( Message& mbuf, int mtype, int subid, int len, + Msg_component payload, void* data ) { + long* counter; + if( (counter = (long *) data) != NULL ) { + (*counter)++; + } + } + int main( ) { + std::unique_ptr xapp; + char* listen_port = (char *) "4560"; + bool wait4table = false; + xapp = std::unique_ptr( + new Xapp( listen_port, wait4table ) ); + // register the same callback function for both msg types + xapp->Add_msg_cb( 1000, cbf, (void *) &m1000_count ); + xapp->Add_msg_cb( 1001, cbf, (void *) &m1001_count ); + xapp->Run( 1 ); // start the callback driver + } + + + Figure 3: Callback function example. + + As before, the program does nothing useful, but now it will + execute and receive messages. For this example, the same + function can be used to increment the appropriate counter + simply by providing a pointer to the counter as the user data + when the callback function is registered. In addition, a + subtle change from the previous example has been to set the + wait for table flag to false. + + For an xApp that is a receive only application (never sends) + it is not necessary to wait for RMR to receive a table from + the Route Manager. + + Registering A Default Callback + -------------------------------------------------------------------------------------------- + + The xApp may also register a default callback function such + that the function will be invoked for any message that does + not have a registered callback. If the xAPP does not register + a default callback, any message which cannot be mapped to a + known callback function is silently dropped. A default + callback is registered by providing a *generic* message type + of xapp->DEFAULT_CALLBACK on an Add_msg_cb call. + + The Framework Callback Driver + -------------------------------------------------------------------------------------------- + + The Run() function within the Xapp object is invoked to start + the callback driver, and the xApp should not expect the + function to return under most circumstances. The only + parameter that the Run() function expects is the number of + threads to start. For each thread requested, the framework + will start a listener thread which will allow received + messages to be processed in parallel. If supplying a value + greater than one, the xApp must ensure that the callback + functions are thread safe as it is very likely that the same + callback function will be invoked concurrently from multiple + threads. + + Sending Messages + ============================================================================================ + + It is very likely that most xApps will need to send messages + and will not operate in "receive only" mode. Sending the + message is a function of the message object itself and can + take one of two forms: + + + + + $1 Replying to the sender of a received message + + $1 Sending a message (routed based on the message type and subscription ID) + + + When replying to the sender, the message type and + subscription ID are not used to determine the destination of + the message; RMR ensures that the message is sent back to the + originating xApp. The xApp may still need to change the + message type and/or the subscription ID in the message prior + to using the reply function. + + To provide for both situations, two reply functions are + supported by the Message object as illustrated with the + following prototypes. + + + :: + + bool Send_response( int mtype, int subid, int response_len, + std:shared_ptr response ); + bool Send_response( int response_len, std::shared_ptr response ); + + + Figure 4: Reply function prototypes. + + In the first prototype the xApp must supply the new message + type and subscription ID values, where the second function + uses the values which are currently set in the message. + Further, the new payload contents, and length, are supplied + to both functions; the framework ensures that the message is + large enough to accommodate the payload, reallocating it if + necessary, and copies the response into the message payload + prior to sending. Should the xApp need to change either the + message type, or the subscription ID, but not both, the + NO_CHANGE constant can be used as illustrated below. + + + :: + + msg->Send_response( Message::NO_CHANGE, Message::NO_SUBID, + pl_length, (unsigned char *) payload ); + + + Figure 5: Send response prototype. + + In addition to the two function prototypes for + Send_response() there are two additional prototypes which + allow the new payload to be supplied as a shared smart + pointer. The other parameters to these functions are + identical to those illustrated above, and thus are not + presented here. + + The Send_msg() set of functions supported by the Message + object are identical to the Send_response() functions and are + shown below. + + + :: + + bool Send_msg( int mtype, int subid, int payload_len, + std::shared_ptr payload ); + bool Send_msg( int mtype, int subid, int payload_len, + unsigned char* payload ); + bool Send_msg( int payload_len, + std::shared_ptr payload ); + bool Send_msg( int payload_len, unsigned char* payload ); + + + Figure 6: Send function prototypes. + + Each send function accepts the message, copies in the payload + provided, sets the message type and subscription ID (if + provided), and then causes the message to be sent. The only + difference between the Send_msg() and Send_response() + functions is that the destination of the message is selected + based on the mapping of the message type and subscription ID + using the current routing table known to RMR. + + Direct Payload Manipulation + -------------------------------------------------------------------------------------------- + + For some applications, it might be more efficient to + manipulate the payload portion of an Xapp Message in place, + rather than creating it and relying on a buffer copy when the + message is finally sent. To achieve this, the xApp must + either use the smart pointer to the payload passed to the + callback function, or retrieve one from the message using + Get_payload() when working with a message outside of a + callback function. Once the smart pointer is obtained, the + pointer's get() function can be used to directly reference + the payload (unsigned char) bytes. + + When working directly with the payload, the xApp must take + care not to write more than the actual payload size which can + be extracted from the Message object using the + Get_available_size() function. + + When sending a message where the payload has been directly + altered, and no extra buffer copy is needed, a NULL pointer + should be passed to the Message send function. The following + illustrates how the payload can be directly manipulated and + returned to the sender (for simplicity, there is no error + handling if the payload size of the received message isn't + large enough for the response string, the response is just + not sent). + + + :: + + Msg_component payload; // smart reference + int pl_size; // max size of payload + payload = msg->Get_payload(); + pl_size = msg->Get_available_size(); + if( snprintf( (char *) payload.get(), pl_size, + "Msg Received\\n" ) < pl_size ) { + msg->Send_response( M_TYPE, SID, strlen( raw_pl ), NULL ); + } + + + Figure 7: Send message without buffer copy. + + + Sending Multiple Responses + -------------------------------------------------------------------------------------------- + + It is likely that the xApp will wish to send multiple + responses back to the process that sent a message that + triggered the callback. The callback function may invoke the + Send_response() function multiple times before returning. + + After each call, the Message retains the necessary + information to allow for a subsequent invocation to send more + data. It should be noted though, that after the first call to + {Send_response() the original payload will be lost; if + necessary, the xApp must make a copy of the payload before + the first response call is made. + + Message Allocation + -------------------------------------------------------------------------------------------- + + Not all xApps will be "responders," meaning that some xApps + will need to send one or more messages before they can expect + to receive any messages back. To accomplish this, the xApp + must first allocate a message buffer, optionally initialising + the payload, and then using the message's Send_msg() function + to send a message out. The framework's Alloc_msg() function + can be used to create a Message object with a desired payload + size. + + Framework Provided Callbacks + ============================================================================================ + + The framework itself may provide message handling via the + driver such that the xApp might not need to implement some + message processing functionality. Initially, the C++ + framework will provide a default callback function to handle + the RMR based health check messages. This callback function + will assume that if the message was received, and the + callback invoked, that all is well and will reply with an OK + state. If the xApp should need to override this simplistic + response, all it needs to do is to register its own callback + function for the health check message type. + + Example Programmes + ============================================================================================ + + The following sections contain several example programmes + which are written on top of the C++ framework. + + RMR Dump xAPP + -------------------------------------------------------------------------------------------- + + The RMR dump application is an example built on top of the + C++ xApp framework to both illustrate the use of the + framework, and to provide a useful diagnostic tool when + testing and troubleshooting xApps. + + The RMR dump xApp isn't a traditional xApp inasmuch as its + goal is to listen for message types and to dump information + about the messages received to the TTY much as tcpdump does + for raw packet traffic. The full source code, and Makefile, + are in the examples directory of the C++ framework repo. + + When invoked, the RMR dump program is given one or more + message types to listen for. A callback function is + registered for each, and the framework Run() function is + invoked to drive the process. For each recognised message, + and depending on the verbosity level supplied at program + start, information about the received message(s) is written + to the TTY. If the forwarding option, -f, is given on the + command line, and an appropriate route table is provided, + each received message is forwarded without change. This + allows for the insertion of the RMR dump program into a flow, + however if the ultimate receiver of a message needs to reply + to that message, the reply will not reach the original + sender, so RMR dump is not a complete "middle box" + application. + + The following is the code for this xAPP. Several functions, + which provide logic unrelated to the framework, have been + omitted. The full code is in the framework repository. + + + + :: + + #include + #include + #include + #include "ricxfcpp/xapp.hpp" + /* + Information that the callback needs outside + of what is given to it via parms on a call + by the framework. + */ + typedef struct { + int vlevel; // verbosity level + bool forward; // if true, message is forwarded + int stats_freq; // header/stats after n messages + std::atomic pcount; // messages processed + std::atomic icount; // messages ignored + std::atomic hdr; // number of messages before next header + } cb_info_t; + // ---------------------------------------------------------------------- + /* + Dump bytes to tty. + */ + void dump( unsigned const char* buf, int len ) { + int i; + int j; + char cheater[17]; + fprintf( stdout, " 0000 | " ); + j = 0; + for( i = 0; i < len; i++ ) { + cheater[j++] = isprint( buf[i] ) ? buf[i] : '.'; + fprintf( stdout, "%02x ", buf[i] ); + if( j == 16 ) { + cheater[j] = 0; + fprintf( stdout, " | %s\\n %04x | ", cheater, i+1 ); + j = 0; + } + } + if( j ) { + i = 16 - (i % 16); + for( ; i > 0; i-- ) { + fprintf( stdout, " " ); + } + cheater[j] = 0; + fprintf( stdout, " | %s\\n", cheater ); + } + } + /* + generate stats when the hdr count reaches 0. Only one active + thread will ever see it be exactly 0, so this is thread safe. + */ + void stats( cb_info_t& cbi ) { + int curv; // current stat trigger value + curv = cbi.hdr--; + if( curv == 0 ) { // stats when we reach 0 + fprintf( stdout, "ignored: %ld processed: %ld\\n", + cbi.icount.load(), cbi.pcount.load() ); + if( cbi.vlevel > 0 ) { + fprintf( stdout, "\\n %5s %5s %2s %5s\\n", + "MTYPE", "SUBID", "ST", "PLLEN" ); + } + cbi.hdr = cbi.stats_freq; // reset must be last + } + } + void cb1( Message& mbuf, int mtype, int subid, int len, + Msg_component payload, void* data ) { + cb_info_t* cbi; + long total_count; + if( (cbi = (cb_info_t *) data) == NULL ) { + return; + } + cbi->pcount++; + stats( *cbi ); // gen stats & header if needed + if( cbi->vlevel > 0 ) { + fprintf( stdout, " %-5d %-5d %02d %-5d \\n", + mtype, subid, mbuf.Get_state(), len ); + if( cbi->vlevel > 1 ) { + dump( payload.get(), len > 64 ? 64 : len ); + } + } + if( cbi->forward ) { + // forward with no change to len or payload + mbuf.Send_msg( Message::NO_CHANGE, NULL ); + } + } + /* + registered as the default callback; it counts the + messages that we aren't giving details about. + */ + void cbd( Message& mbuf, int mtype, int subid, int len, + Msg_component payload, void* data ) { + cb_info_t* cbi; + if( (cbi = (cb_info_t *) data) == NULL ) { + return; + } + cbi->icount++; + stats( *cbi ); + if( cbi->forward ) { + // forward with no change to len or payload + mbuf.Send_msg( Message::NO_CHANGE, NULL ); + } + } + int main( int argc, char** argv ) { + std::unique_ptr x; + char* port = (char *) "4560"; + int ai = 1; // arg processing index + cb_info_t* cbi; + int ncb = 0; // number of callbacks registered + int mtype; + int nthreads = 1; + cbi = (cb_info_t *) malloc( sizeof( *cbi ) ); + cbi->pcount = 0; + cbi->icount = 0; + cbi->stats_freq = 10; + ai = 1; + // very simple flag parsing (no error/bounds checking) + while( ai < argc ) { + if( argv[ai][0] != '-' ) { // break on first non-flag + break; + } + // very simple arg parsing; each must be separate -x -y not -xy. + switch( argv[ai][1] ) { + case 'f': // enable packet forwarding + cbi->forward = true; + break; + case 'p': // define port + port = argv[ai+1]; + ai++; + break; + case 's': // stats frequency + cbi->stats_freq = atoi( argv[ai+1] ); + if( cbi->stats_freq < 5 ) { // enforce sanity + cbi->stats_freq = 5; + } + ai++; + break; + case 't': // thread count + nthreads = atoi( argv[ai+1] ); + if( nthreads < 1 ) { + nthreads = 1; + } + ai++; + break; + case 'v': // simple verbose bump + cbi->vlevel++; + break; + case 'V': // explicit verbose level + cbi->vlevel = atoi( argv[ai+1] ); + ai++; + break; + default: + fprintf( stderr, "unrecognised option: %s\\n", argv[ai] ); + fprintf( stderr, "usage: %s [-f] [-p port] " + "[-s stats-freq] [-t thread-count] " + "[-v | -V n] msg-type1 ... msg-typen\\n", + argv[0] ); + fprintf( stderr, "\\tstats frequency is based on # of messages received\\n" ); + fprintf( stderr, "\\tverbose levels (-V) 0 counts only, " + "1 message info 2 payload dump\\n" ); + exit( 1 ); + } + ai++; + } + cbi->hdr = cbi->stats_freq; + fprintf( stderr, " listening on port: %s\\n", port ); + // create xapp, wait for route table if forwarding + x = std::unique_ptr( new Xapp( port, cbi->forward ) ); + // register callback for each type on the command line + while( ai < argc ) { + mtype = atoi( argv[ai] ); + ai++; + fprintf( stderr, " capturing messages for type %d\\n", mtype ); + x->Add_msg_cb( mtype, cb1, cbi ); + ncb++; + } + if( ncb < 1 ) { + fprintf( stderr, " no message types specified on the command line\\n" ); + exit( 1 ); + } + x->Add_msg_cb( x->DEFAULT_CALLBACK, cbd, cbi ); // register default cb + fprintf( stderr, " starting driver\\n" ); + x->Run( nthreads ); + // return from run() is not expected, but some compilers might + // compilain if there isn't a return value here. + return 0; + } + + + Figure 8: Simple callback application. + + Callback Receiver + -------------------------------------------------------------------------------------------- + + This sample programme implements a simple message listener + which registers three callback functions to process two + specific message types and a default callback to handle + unrecognised messages. + + When a message of type 1 is received, it will send two + response messages back to the sender. Two messages are sent + in order to illustrate that it is possible to send multiple + responses using the same received message. + + The programme illustrates how multiple listening threads can + be used, but the programme is **not** thread safe; to keep + this example as simple as possible, the counters are not + locked when incremented. + + + :: + + #include + #include "ricxfcpp/message.hpp" + #include "ricxfcpp/msg_component.hpp" + #include "ricxfcpp/xapp.hpp" + // counts; not thread safe + long cb1_count = 0; + long cb2_count = 0; + long cbd_count = 0; + long cb1_lastts = 0; + long cb1_lastc = 0; + // respond with 2 messages for each type 1 received + void cb1( Message& mbuf, int mtype, int subid, int len, + Msg_component payload, void* data ) { + long now; + long total_count; + // illustrate that we can use the same buffer for 2 rts calls + mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK1\\n" ); + mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK2\\n" ); + cb1_count++; + } + // just count messages + void cb2( Message& mbuf, int mtype, int subid, int len, + Msg_component payload, void* data ) { + cb2_count++; + } + // default to count all unrecognised messages + void cbd( Message& mbuf, int mtype, int subid, int len, + Msg_component payload, void* data ) { + cbd_count++; + } + int main( int argc, char** argv ) { + Xapp* x; + char* port = (char *) "4560"; + int ai = 1; // arg processing index + int nthreads = 1; + // very simple flag processing (no bounds/error checking) + while( ai < argc ) { + if( argv[ai][0] != '-' ) { + break; + } + switch( argv[ai][1] ) { // we only support -x so -xy must be -x -y + case 'p': + port = argv[ai+1]; + ai++; + break; + case 't': + nthreads = atoi( argv[ai+1] ); + ai++; + break; + } + ai++; + } + fprintf( stderr, " listening on port: %s\\n", port ); + fprintf( stderr, " starting %d threads\\n", nthreads ); + x = new Xapp( port, true ); + x->Add_msg_cb( 1, cb1, NULL ); // register callbacks + x->Add_msg_cb( 2, cb2, NULL ); + x->Add_msg_cb( x->DEFAULT_CALLBACK, cbd, NULL ); + x->Run( nthreads ); // let framework drive + // control should not return + } + + + Figure 9: Simple callback application. + + + Looping Sender + -------------------------------------------------------------------------------------------- + + This is another very simple application which demonstrates + how an application can control its own listen loop while + sending messages. As with the other examples, some error + checking is skipped, and short cuts have been made in order + to keep the example small and to the point. + + + :: + + #include + #include + #include + #include + #include + #include "ricxfcpp/xapp.hpp" + extern int main( int argc, char** argv ) { + std::unique_ptr xfw; + std::unique_ptr msg; + Msg_component payload; // special type of unique pointer to the payload + int sz; + int len; + int i; + int ai; + int response_to = 0; // max timeout wating for a response + char* port = (char *) "4555"; + int mtype = 0; + int rmtype; // received message type + int delay = 1000000; // mu-sec delay; default 1s + // very simple flag processing (no bounds/error checking) + while( ai < argc ) { + if( argv[ai][0] != '-' ) { + break; + } + // we only support -x so -xy must be -x -y + switch( argv[ai][1] ) { + // delay between messages (mu-sec) + case 'd': + delay = atoi( argv[ai+1] ); + ai++; + break; + case 'p': + port = argv[ai+1]; + ai++; + break; + // timeout in seconds; we need to convert to ms for rmr calls + case 't': + response_to = atoi( argv[ai+1] ) * 1000; + ai++; + break; + } + ai++; + } + fprintf( stderr, " response timeout set to: %d\\n", response_to ); + fprintf( stderr, " listening on port: %s\\n", port ); + // get an instance and wait for a route table to be loaded + xfw = std::unique_ptr( new Xapp( port, true ) ); + msg = xfw->Alloc_msg( 2048 ); + for( i = 0; i < 100; i++ ) { + mtype++; + if( mtype > 10 ) { + mtype = 0; + } + // we'll reuse a received message; get max size + sz = msg->Get_available_size(); + // direct access to payload; add something silly + payload = msg->Get_payload(); + len = snprintf( (char *) payload.get(), sz, "This is message %d\\n", i ); + // payload updated in place, prevent copy by passing nil + if ( ! msg->Send_msg( mtype, Message::NO_SUBID, len, NULL )) { + fprintf( stderr, " send failed: %d\\n", i ); + } + // receive anything that might come back + msg = xfw->Receive( response_to ); + if( msg != NULL ) { + rmtype = msg->Get_mtype(); + payload = msg->Get_payload(); + fprintf( stderr, "got: mtype=%d payload=(%s)\\n", + rmtype, (char *) payload.get() ); + } else { + msg = xfw->Alloc_msg( 2048 ); + } + if( delay > 0 ) { + usleep( delay ); + } + } + } + + + Figure 10: Simple looping sender application. + diff --git a/examples/rmr_dump.cpp b/examples/rmr_dump.cpp index 5cced21..52fe23d 100644 --- a/examples/rmr_dump.cpp +++ b/examples/rmr_dump.cpp @@ -27,9 +27,9 @@ on by the application. The verbosity level may be used to increase the amount of detail given for the tracked messages. - This writes to the TTY which is slow, so do not expect it to be able + This writes to the TTY which is slow, so do not expect it to be able to process and report on a high rate of messages. Also, forwarded - messages will reach the intended target, however if the target + messages will reach the intended target, however if the target attempts to send a response the response will come back to THIS application, and not the message origination; this cannot be a bit of middleware in it's current form. @@ -37,7 +37,12 @@ Date: 25 March 2020 Author: E. Scott Daniels + Caution: This example code is pulled directly into one or more of the documents + (starting from the "start-example" tag below. Use caution with + line lengths and contents because of the requirement that this + be documentation as well as working code. */ +// start-example #include #include #include @@ -50,15 +55,15 @@ by the framework. */ typedef struct { - int vlevel; // verbosity level - bool forward; // if true, message is forwarded - int stats_freq; // header/stats after n messages - std::atomic pcount; // messages processed - std::atomic icount; // messages ignored - std::atomic hdr; // number of messages before next header + int vlevel; // verbosity level + bool forward; // if true, message is forwarded + int stats_freq; // header/stats after n messages + std::atomic pcount; // messages processed + std::atomic icount; // messages ignored + std::atomic hdr; // number of messages before next header } cb_info_t; -// ---------------------------------------------------------- +// ---------------------------------------------------------------------- /* Dump bytes to tty. @@ -73,10 +78,10 @@ void dump( unsigned const char* buf, int len ) { for( i = 0; i < len; i++ ) { cheater[j++] = isprint( buf[i] ) ? buf[i] : '.'; fprintf( stdout, "%02x ", buf[i] ); - + if( j == 16 ) { cheater[j] = 0; - fprintf( stdout, " | %s\n %04x | ", cheater, i+1 ); + fprintf( stdout, " | %s\n %04x | ", cheater, i+1 ); j = 0; } } @@ -87,7 +92,7 @@ void dump( unsigned const char* buf, int len ) { fprintf( stdout, " " ); } cheater[j] = 0; - fprintf( stdout, " | %s\n", cheater ); + fprintf( stdout, " | %s\n", cheater ); } } @@ -101,10 +106,10 @@ void stats( cb_info_t& cbi ) { curv = cbi.hdr--; if( curv == 0 ) { // stats when we reach 0 - fprintf( stdout, "ignored: %ld processed: %ld\n", + fprintf( stdout, "ignored: %ld processed: %ld\n", cbi.icount.load(), cbi.pcount.load() ); if( cbi.vlevel > 0 ) { - fprintf( stdout, "\n %5s %5s %2s %5s\n", + fprintf( stdout, "\n %5s %5s %2s %5s\n", "MTYPE", "SUBID", "ST", "PLLEN" ); } @@ -219,9 +224,13 @@ int main( int argc, char** argv ) { default: fprintf( stderr, "unrecognised option: %s\n", argv[ai] ); - fprintf( stderr, "usage: %s [-f] [-p port] [-s stats-freq] [-t thread-count] [-v | -V n] msg-type1 ... msg-typen\n", argv[0] ); - fprintf( stderr, "\tstats frequency is in number of messages received\n" ); - fprintf( stderr, "\tverbose levels (-V) 0 counts only, 1 message info 2 payload dump\n" ); + fprintf( stderr, "usage: %s [-f] [-p port] " + "[-s stats-freq] [-t thread-count] " + "[-v | -V n] msg-type1 ... msg-typen\n", + argv[0] ); + fprintf( stderr, "\tstats frequency is based on # of messages received\n" ); + fprintf( stderr, "\tverbose levels (-V) 0 counts only, " + "1 message info 2 payload dump\n" ); exit( 1 ); } @@ -235,7 +244,7 @@ int main( int argc, char** argv ) { x = std::unique_ptr( new Xapp( port, cbi->forward ) ); // register callback for each type on the command line - while( ai < argc ) { + while( ai < argc ) { mtype = atoi( argv[ai] ); ai++; fprintf( stderr, " capturing messages for type %d\n", mtype ); diff --git a/examples/xapp_t1.cpp b/examples/xapp_t1.cpp index e525891..51b3ba6 100644 --- a/examples/xapp_t1.cpp +++ b/examples/xapp_t1.cpp @@ -23,8 +23,8 @@ Abstract: This is a simple demo xapp which registers 3 callback functions (2 for specific message types, and one default callback). It defaults to starting 1 thread, but can cause the xapp environment - to start n listeners. When running 1 thread it should emit - message countes ever few seconds with a crudly computed msg/sec + to start n listeners. When running 1 thread it should emit + message countes ever few seconds with a crudly computed msg/sec receive rate value. In addition, the callback for message type 1 will send two response @@ -34,17 +34,20 @@ Date: 18 March 2020 Author: E. Scott Daniels - + + Caution: This example code is pulled directly into one or more of the documents + (starting from the "start-example" tag below. Use caution with + line lengths and contents because of the requirement that this + be documentation as well as working code. */ +// start-example #include #include "ricxfcpp/message.hpp" #include "ricxfcpp/msg_component.hpp" #include "ricxfcpp/xapp.hpp" -// ---------------------------------------------------------- - -// counts/time values for crude rate estimation; only valid when threads == 1 +// counts; not thread safe long cb1_count = 0; long cb2_count = 0; long cbd_count = 0; @@ -52,34 +55,28 @@ long cbd_count = 0; long cb1_lastts = 0; long cb1_lastc = 0; -void cb1( Message& mbuf, int mtype, int subid, int len, Msg_component payload, void* data ) { +// respond with 2 messages for each type 1 received +void cb1( Message& mbuf, int mtype, int subid, int len, + Msg_component payload, void* data ) { long now; long total_count; - //fprintf( stderr, "callback 1 got a message type = %d len = %d\n", mtype, len ); - mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK1\n" ); // validate that we can use the same buffer for 2 rts calls + // illustrate that we can use the same buffer for 2 rts calls + mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK1\n" ); mbuf.Send_response( 101, -1, 5, (unsigned char *) "OK2\n" ); + cb1_count++; - - now = time( NULL ); - if( now - cb1_lastts > 5 ) { // crude rate estimation starting with second timer pop - if( cb1_lastts ) { - total_count = cb1_count + cb2_count; - fprintf( stderr, "cb1: %ld diff=%ld ~rate=%ld\n", total_count, now - cb1_lastts, (total_count-cb1_lastc)/(now - cb1_lastts) ); - cb1_lastc = total_count; - } - cb1_lastts = now; - } } -void cb2( Message& mbuf, int mtype, int subid, int len, Msg_component payload, void* data ) { - //fprintf( stderr, "callback 2 got a message type = %d len = %d\n", mtype, len ); - //mbuf.Send_msg( 101, -1, 4, (unsigned char *) "HI\n" ); // send, including the trailing 0 +// just count messages +void cb2( Message& mbuf, int mtype, int subid, int len, + Msg_component payload, void* data ) { cb2_count++; } -void cbd( Message& mbuf, int mtype, int subid, int len, Msg_component payload, void* data ) { - //fprintf( stderr, "default callback got a message type = %d len = %d\n", mtype, len ); +// default to count all unrecognised messages +void cbd( Message& mbuf, int mtype, int subid, int len, + Msg_component payload, void* data ) { cbd_count++; } @@ -89,15 +86,15 @@ int main( int argc, char** argv ) { int ai = 1; // arg processing index int nthreads = 1; - ai = 1; - while( ai < argc ) { // very simple flag processing (no bounds/error checking) + // very simple flag processing (no bounds/error checking) + while( ai < argc ) { if( argv[ai][0] != '-' ) { break; } switch( argv[ai][1] ) { // we only support -x so -xy must be -x -y - case 'p': - port = argv[ai+1]; + case 'p': + port = argv[ai+1]; ai++; break; @@ -109,14 +106,15 @@ int main( int argc, char** argv ) { ai++; } - + fprintf( stderr, " listening on port: %s\n", port ); fprintf( stderr, " starting %d threads\n", nthreads ); x = new Xapp( port, true ); - x->Add_msg_cb( 1, cb1, NULL ); + x->Add_msg_cb( 1, cb1, NULL ); // register callbacks x->Add_msg_cb( 2, cb2, NULL ); x->Add_msg_cb( x->DEFAULT_CALLBACK, cbd, NULL ); - x->Run( nthreads ); + x->Run( nthreads ); // let framework drive + // control should not return } diff --git a/examples/xapp_t2.cpp b/examples/xapp_t2.cpp index dd24d2b..67aa72f 100644 --- a/examples/xapp_t2.cpp +++ b/examples/xapp_t2.cpp @@ -26,8 +26,13 @@ Date: 18 March 2020 Author: E. Scott Daniels - + + Caution: This example code is pulled directly into one or more of the documents + (starting from the "start-example" tag below. Use caution with + line lengths and contents because of the requirement that this + be documentation as well as working code. */ +// start-example #include #include @@ -38,14 +43,13 @@ #include "ricxfcpp/xapp.hpp" -// ---------------------------------------------------------- - extern int main( int argc, char** argv ) { std::unique_ptr xfw; std::unique_ptr msg; - Msg_component payload; // special type of unique pointer to the payload + Msg_component payload; // special type of unique pointer to the payload int sz; + int len; int i; int ai; int response_to = 0; // max timeout wating for a response @@ -53,26 +57,29 @@ extern int main( int argc, char** argv ) { int mtype = 0; int rmtype; // received message type int delay = 1000000; // mu-sec delay; default 1s - - ai = 1; - while( ai < argc ) { // very simple flag processing (no bounds/error checking) + + // very simple flag processing (no bounds/error checking) + while( ai < argc ) { if( argv[ai][0] != '-' ) { break; } - switch( argv[ai][1] ) { // we only support -x so -xy must be -x -y - case 'd': // delay between messages (mu-sec) + // we only support -x so -xy must be -x -y + switch( argv[ai][1] ) { + // delay between messages (mu-sec) + case 'd': delay = atoi( argv[ai+1] ); ai++; break; - - case 'p': - port = argv[ai+1]; + + case 'p': + port = argv[ai+1]; ai++; break; - case 't': // timeout in seconds; we need to convert to ms for rmr calls + // timeout in seconds; we need to convert to ms for rmr calls + case 't': response_to = atoi( argv[ai+1] ) * 1000; ai++; break; @@ -83,7 +90,8 @@ extern int main( int argc, char** argv ) { fprintf( stderr, " response timeout set to: %d\n", response_to ); fprintf( stderr, " listening on port: %s\n", port ); - xfw = std::unique_ptr( new Xapp( port, true ) ); // new xAPP thing; wait for a route table + // get an instance and wait for a route table to be loaded + xfw = std::unique_ptr( new Xapp( port, true ) ); msg = xfw->Alloc_msg( 2048 ); for( i = 0; i < 100; i++ ) { @@ -92,27 +100,27 @@ extern int main( int argc, char** argv ) { mtype = 0; } - sz = msg->Get_available_size(); // we'll reuse a message if we received one back; ensure it's big enough - if( sz < 2048 ) { - fprintf( stderr, " fail: message returned did not have enough size: %d [%d]\n", sz, i ); - exit( 1 ); - } + // we'll reuse a received message; get max size + sz = msg->Get_available_size(); - payload = msg->Get_payload(); // direct access to payload - snprintf( (char *) payload.get(), 2048, "This is message %d\n", i ); // something silly to send + // direct access to payload; add something silly + payload = msg->Get_payload(); + len = snprintf( (char *) payload.get(), sz, "This is message %d\n", i ); - // payload updated in place, nothing to copy from, so payload parm is nil - if ( ! msg->Send_msg( mtype, Message::NO_SUBID, strlen( (char *) payload.get() )+1, NULL )) { + // payload updated in place, prevent copy by passing nil + if ( ! msg->Send_msg( mtype, Message::NO_SUBID, len, NULL )) { fprintf( stderr, " send failed: %d\n", i ); } + // receive anything that might come back msg = xfw->Receive( response_to ); if( msg != NULL ) { rmtype = msg->Get_mtype(); payload = msg->Get_payload(); - fprintf( stderr, "got: mtype=%d payload=(%s)\n", rmtype, (char *) payload.get() ); + fprintf( stderr, "got: mtype=%d payload=(%s)\n", + rmtype, (char *) payload.get() ); } else { - msg = xfw->Alloc_msg( 2048 ); // nothing back, need a new message to send + msg = xfw->Alloc_msg( 2048 ); } if( delay > 0 ) {