summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordefanor <defanor@uberspace.net>2020-02-29 07:50:32 +0300
committerdefanor <defanor@uberspace.net>2020-02-29 07:50:32 +0300
commit239248f77bb21139b158950a548ff84ada4b3bf1 (patch)
treec489e2c16703bff37b984772a5ab87dd57015594
Add the draft
-rw-r--r--COPYING20
-rw-r--r--Doxyfile.in2280
-rw-r--r--Makefile.am4
-rw-r--r--README85
-rw-r--r--configure.ac48
-rw-r--r--examples/basic.c138
-rw-r--r--rexmpp.pc.in11
-rw-r--r--src/Makefile.am14
-rw-r--r--src/rexmpp.c1580
-rw-r--r--src/rexmpp.h286
-rw-r--r--src/rexmpp_tcp.c358
-rw-r--r--src/rexmpp_tcp.h177
12 files changed, 5001 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..f8d7a4f
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,20 @@
+Copyright (c) 2020 defanor <defanor@uberspace.net>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Doxyfile.in b/Doxyfile.in
new file mode 100644
index 0000000..9e07d38
--- /dev/null
+++ b/Doxyfile.in
@@ -0,0 +1,2280 @@
+# Doxyfile 1.8.5
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
+# for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME = "@PACKAGE_NAME@"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF =
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is included in
+# the documentation. The maximum height of the logo should not exceed 55 pixels
+# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo
+# to the output directory.
+
+PROJECT_LOGO =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY =
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-
+# Traditional, Croatian, Czech, Danish, Dutch, English, Esperanto, Farsi,
+# Finnish, French, German, Greek, Hungarian, Italian, Japanese, Japanese-en,
+# Korean, Korean-en, Latvian, Norwegian, Macedonian, Persian, Polish,
+# Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish,
+# Turkish, Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a
+# new page for each member. If set to NO, the documentation of a member will be
+# part of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines.
+
+ALIASES =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C.
+#
+# Note For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT = YES
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by by putting a % sign in front of the word
+# or globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. When set to YES local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO these classes will be included in the various overviews. This option has
+# no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO the members will appear in declaration order.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the
+# todo list. This list is created by putting \todo commands in the
+# documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the
+# test list. This list is created by putting \test commands in the
+# documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES the list
+# will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. Do not use file names with spaces, bibtex cannot handle them. See
+# also \cite for info how to create references.
+
+CITE_BIB_FILES =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO doxygen will only warn about wrong or incomplete parameter
+# documentation, but not about the absence of documentation.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces.
+# Note: If this tag is empty the current directory is searched.
+
+INPUT = @srcdir@/src/
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: http://www.gnu.org/software/libiconv) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank the
+# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii,
+# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp,
+# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown,
+# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,
+# *.qsf, *.as and *.js.
+
+FILE_PATTERNS =
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER ) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES, then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see http://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX = YES
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user-
+# defined cascading style sheet that is included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefor more robust against future updates.
+# Doxygen will copy the style sheet file to the output directory. For an example
+# see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the stylesheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP = NO
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: http://developer.apple.com/tools/xcode/), introduced with
+# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler ( hhc.exe). If non-empty
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated (
+# YES) or that it should be included in the master .chm file ( NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated (
+# YES) or a normal table of contents ( NO) in the .chm file.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# http://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using prerendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from http://www.mathjax.org before deployment.
+# The default value is: http://cdn.mathjax.org/mathjax/latest.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavours of web server based searching depending on the
+# EXTERNAL_SEARCH setting. When disabled, doxygen will generate a PHP script for
+# searching and an index file used by the script. When EXTERNAL_SEARCH is
+# enabled the indexing and searching needs to be provided by external tools. See
+# the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. To get the times font for
+# instance you can specify
+# EXTRA_PACKAGES=times
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber. Doxygen will
+# replace them by respectively the title of the page, the current date and time,
+# only the current date, the version number of doxygen, the project name (see
+# PROJECT_NAME), or the project number (see PROJECT_NUMBER).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS = YES
+
+# If the LATEX_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE = plain
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify a XML schema, which can be used by a
+# validating XML parser to check the syntax of the XML files.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_SCHEMA =
+
+# The XML_DTD tag can be used to specify a XML DTD, which can be used by a
+# validating XML parser to check the syntax of the XML files.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_DTD =
+
+# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT = docbook
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen
+# Definitions (see http://autogen.sf.net) file that captures the structure of
+# the code including all documentation. Note that this feature is still
+# experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names
+# in the source code. If set to NO only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES the includes files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all refrences to function-like macros that are alone on a line, have an
+# all uppercase name, and do not end with a semicolon. Such function macros are
+# typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have an unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES all external class will be listed in the
+# class index. If set to NO only the inherited external classes will be listed.
+# The default value is: NO.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed in
+# the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS = YES
+
+# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# If set to YES, the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
+
+HAVE_DOT = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS = 0
+
+# When you want a differently looking font n the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot.
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif and svg.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP = YES
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..f6d103a
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,4 @@
+ACLOCAL_AMFLAGS = -I m4
+SUBDIRS = src
+
+EXTRA_DIST = examples/basic.c
diff --git a/README b/README
new file mode 100644
index 0000000..825f64a
--- /dev/null
+++ b/README
@@ -0,0 +1,85 @@
+rexmpp - a reusable XMPP IM client library
+
+This is currently at a draft/prototype stage.
+
+The goal is to produce a library reusable from different languages
+(via C FFI), without hijacking an event loop, requiring any specific
+one, or otherwise restricting a user, and fairly feature-rich. The
+motivation for that is multitude of clients using custom and
+incomplete XMPP implementations, occasionally even failing to deliver
+one-to-one textual messages.
+
+The exact scope is yet to be determined, but it shouldn't implement or
+rely on any particular UI, should be flexible and not stay in the way
+of implementing additional XEPs on top of it, and should try to make
+it easy to implement a decent client application using it.
+
+Current dependencies: c-ares, libxml2, gnutls, gsasl.
+
+
+A rough roadmap:
+
+
+- Basic protocol:
+
+[+] XMPP core (RFC 6120). A prototype is mostly ready, though it can
+ use more of error handling and refinement.
+
+
+- Reliable and predictable message delivery:
+
+[+] XEP-0198: Stream Management. Implemented (both acknowledgements
+ and resumption, making use of XEP-0203: Delayed Delivery).
+
+[+] XEP-0280: Message Carbons. Enables them in case if they are
+ supported by the server (discovering it using XEP-0030: Service
+ Discovery). Though maybe it shouldn't be done by the library.
+
+
+- Better connectivity:
+
+[+] "Happy Eyeballs" (RFC 8305).
+[+] XEP-0368: SRV records for XMPP over TLS.
+[ ] SOCKS5 (RFC 1928) support. Perhaps a module with functions usable
+ for SOCKS5 bytestreams at once.
+
+
+- Library refinement:
+
+[.] Documentation. Partial Doxygen documentation is added, but
+ planning a texinfo manual as well.
+[ ] Proper JID handling (RFC 7622).
+[ ] Abstraction of the used XML, SASL, TLS, and DNS libraries, and
+ optional usage of alternative ones. Though maybe shouldn't
+ abstract out XML functions and structures: could reuse existing
+ libxml2 bindings that way.
+[ ] Automated testing.
+
+
+- Primary IM features (?):
+
+[ ] XMPP IM (RFC 6121): loading and managing of the roster and
+ presence subscriptions? Maybe it'd be better done by a client. Or
+ just some utility functions can be provided.
+
+
+- Common and reliable IM features (?):
+
+[.] XEP-0030: Service Discovery (implemented partially, just to
+ discover server features)
+[ ] XEP-0115: Entity Capabilities
+[ ] XEP-0045: Multi-User Chat
+[ ] XEP-0166: Jingle
+[ ] XEP-0234: Jingle File Transfer
+[ ] XEP-0261: Jingle In-Band Bytestreams Transport Method
+
+
+- Other features (?):
+
+[ ] XEP-0313: Message Archive Management
+[ ] XEP-0163: Personal Eventing Protocol
+[ ] XEP-0260: Jingle SOCKS5 Bytestreams Transport Method
+[ ] XEP-0391: Jingle Encrypted Transports
+[ ] XEP-0363: HTTP File Upload
+[ ] XEP-0384: OMEMO Encryption
+[ ] XEP-0184: Message Delivery Receipts
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..942c7a3
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,48 @@
+# -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+
+AC_PREREQ([2.69])
+AC_INIT([rexmpp], [0.0.0], [defanor@uberspace.net])
+AM_INIT_AUTOMAKE([-Werror -Wall])
+AC_CONFIG_MACRO_DIR([m4])
+AC_CONFIG_SRCDIR([src/rexmpp.c])
+AC_CONFIG_HEADERS([config.h])
+AC_CONFIG_FILES([Makefile src/Makefile rexmpp.pc Doxyfile])
+
+# Checks for programs.
+AC_PROG_CC
+AM_PROG_AR
+
+LT_INIT
+
+# Checks for libraries.
+PKG_CHECK_MODULES([LIBXML], [libxml-2.0])
+AC_SUBST(LIBXML_CFLAGS)
+AC_SUBST(LIBXML_LIBS)
+
+PKG_CHECK_MODULES([GNUTLS], [gnutls])
+AC_SUBST(GNUTLS_CFLAGS)
+AC_SUBST(GNUTLS_LIBS)
+
+PKG_CHECK_MODULES([GSASL], [libgsasl])
+AC_SUBST(GSASL_CFLAGS)
+AC_SUBST(GSASL_LIBS)
+
+PKG_CHECK_MODULES([CARES], [libcares])
+AC_SUBST(CARES_CFLAGS)
+AC_SUBST(CARES_LIBS)
+
+
+# Checks for header files.
+AC_CHECK_HEADERS([arpa/inet.h netdb.h netinet/in.h sys/socket.h syslog.h])
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_TYPE_SIZE_T
+AC_TYPE_SSIZE_T
+AC_TYPE_UINT32_T
+
+# Checks for library functions.
+AC_FUNC_MALLOC
+AC_CHECK_FUNCS([gettimeofday select socket strchr strerror strtoul])
+
+AC_OUTPUT
diff --git a/examples/basic.c b/examples/basic.c
new file mode 100644
index 0000000..681a0ac
--- /dev/null
+++ b/examples/basic.c
@@ -0,0 +1,138 @@
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <syslog.h>
+#include <gnutls/gnutls.h>
+
+#include <rexmpp.h>
+
+
+void my_logger (rexmpp_t *s, int priority, const char *fmt, va_list args) {
+ char *priority_str = "unknown";
+ switch (priority) {
+ case LOG_EMERG: priority_str = "emerg"; break;
+ case LOG_ALERT: priority_str = "alert"; break;
+ case LOG_CRIT: priority_str = "crit"; break;
+ case LOG_ERR: priority_str = "err"; break;
+ case LOG_WARNING: priority_str = "warning"; break;
+ case LOG_NOTICE: priority_str = "notice"; break;
+ case LOG_INFO: priority_str = "info"; break;
+ case LOG_DEBUG: priority_str = "debug"; break;
+ }
+ fprintf(stderr, "[%s] ", priority_str);
+ vfprintf(stderr, fmt, args);
+ fprintf(stderr, "\n");
+}
+
+int my_sasl_property_cb (Gsasl * ctx, Gsasl_session * sctx, Gsasl_property prop) {
+ if (prop == GSASL_PASSWORD) {
+ char buf[4096];
+ printf("password: ");
+ gets(buf);
+ gsasl_property_set (sctx, GSASL_PASSWORD, buf);
+ return GSASL_OK;
+ }
+ if (prop == GSASL_AUTHID) {
+ gsasl_property_set (sctx, GSASL_AUTHID, "test");
+ return GSASL_OK;
+ }
+ printf("unhandled gsasl property: %d\n", prop);
+ return GSASL_NO_CALLBACK;
+}
+
+int my_xml_in_cb (rexmpp_t *s, xmlNodePtr node) {
+ char *xml_buf = rexmpp_xml_serialize(node);
+ printf("recv: %s\n", xml_buf);
+ free(xml_buf);
+ return 0;
+}
+
+int my_xml_out_cb (rexmpp_t *s, xmlNodePtr node) {
+ char *xml_buf = rexmpp_xml_serialize(node);
+ printf("send: %s\n", xml_buf);
+ free(xml_buf);
+ return 0;
+}
+
+main () {
+ rexmpp_t s;
+ rexmpp_err_t err;
+ err = rexmpp_init(&s,
+ "test@foo.custom",
+ my_logger,
+ my_sasl_property_cb,
+ my_xml_in_cb,
+ my_xml_out_cb);
+ if (err != REXMPP_SUCCESS) {
+ puts("error");
+ return -1;
+ }
+ /* gnutls_certificate_set_x509_key_file(s.gnutls_cred, */
+ /* "cert.pem", */
+ /* "key.pem", */
+ /* GNUTLS_X509_FMT_PEM); */
+ fd_set read_fds, write_fds;
+ int nfds;
+ struct timeval tv;
+ struct timeval *mtv;
+ int n = 0;
+
+
+ do {
+
+ if (n > 0 && FD_ISSET(STDIN_FILENO, &read_fds)) {
+ char input[4096];
+ ssize_t input_len;
+ input_len = read(STDIN_FILENO, input, 4096);
+ if (input_len == -1) {
+ puts("input error");
+ } else {
+ input[input_len - 1] = '\0';
+ if (strlen(input) != 0) {
+ if (strcmp(input, ".") == 0) {
+ rexmpp_stop(&s);
+ } else if (strcmp(input, "connerr") == 0) {
+ close(s.server_socket);
+ s.server_socket = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
+ gnutls_transport_set_int(s.gnutls_session, s.server_socket);
+ } else {
+ xmlNodePtr msg = rexmpp_xml_add_id(&s, xmlNewNode(NULL, "message"));
+ xmlNewProp(msg, "to", "test2@foo.custom");
+ xmlNewProp(msg, "type", "chat");
+ xmlNewTextChild(msg, NULL, "body", input);
+ rexmpp_send(&s, msg);
+ }
+ }
+ }
+ }
+ err = rexmpp_run(&s, &read_fds, &write_fds);
+ if (err == REXMPP_SUCCESS) {
+ puts("done");
+ break;
+ }
+ if (err != REXMPP_E_AGAIN) {
+ puts("error");
+ break;
+ }
+ /* printf("res %d / conn %d / tls %d / sasl %d / stream %d / carbons %d\n", */
+ /* s.resolver_state, */
+ /* s.tcp_state, */
+ /* s.tls_state, */
+ /* s.sasl_state, */
+ /* s.stream_state, */
+ /* s.carbons_state); */
+ FD_ZERO(&read_fds);
+ FD_ZERO(&write_fds);
+
+ nfds = rexmpp_fds(&s, &read_fds, &write_fds);
+ mtv = rexmpp_timeout(&s, NULL, (struct timeval*)&tv);
+
+ FD_SET(STDIN_FILENO, &read_fds);
+ n = select(nfds, &read_fds, &write_fds, NULL, mtv);
+ if (n == -1) {
+ printf("select error: %s\n", strerror(errno));
+ break;
+ }
+ } while (1);
+ rexmpp_done(&s);
+}
diff --git a/rexmpp.pc.in b/rexmpp.pc.in
new file mode 100644
index 0000000..64bc934
--- /dev/null
+++ b/rexmpp.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: @PACKAGE_NAME@
+Version: @PACKAGE_VERSION@
+Description: A reusable XMPP library
+
+Cflags: -I${includedir}
+Libs: -L${libdir} -lrexmpp
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..6883960
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,14 @@
+AM_CFLAGS = -Werror -Wall -Wextra -Wno-pointer-sign -Wno-unused-parameter
+
+# -Wno-pointer-sign is used to suppress libxml2-related warnings.
+# Since we only care about UTF-8, and in almost all cases just its
+# ASCII subset (comparing or setting fixed namespaces, element names,
+# etc), it shouldn't matter. Later it would be nice to abstract XML
+# manipulations anyway, to allow libexpat as an alternative.
+
+lib_LTLIBRARIES = librexmpp.la
+
+librexmpp_la_SOURCES = rexmpp_tcp.h rexmpp_tcp.c rexmpp.h rexmpp.c
+include_HEADERS = rexmpp_tcp.h rexmpp.h
+librexmpp_la_CFLAGS = $(AM_CFLAGS) $(LIBXML_CFLAGS) $(GNUTLS_CFLAGS) $(GSASL_CFLAGS) $(CARES_CFLAGS)
+librexmpp_la_LIBADD = $(LIBXML_LIBS) $(GNUTLS_LIBS) $(GSASL_LIBS) $(CARES_LIBS)
diff --git a/src/rexmpp.c b/src/rexmpp.c
new file mode 100644
index 0000000..04ec798
--- /dev/null
+++ b/src/rexmpp.c
@@ -0,0 +1,1580 @@
+/**
+ @file rexmpp.c
+ @brief rexmpp, a reusable XMPP IM client library.
+ @author defanor <defanor@uberspace.net>
+ @date 2020
+ @copyright MIT license.
+*/
+
+#include <string.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <syslog.h>
+#include <arpa/nameser.h>
+
+#include <ares.h>
+#include <libxml/tree.h>
+#include <libxml/xmlsave.h>
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+#include <gsasl.h>
+
+#include "rexmpp.h"
+#include "rexmpp_tcp.h"
+
+
+void rexmpp_sax_start_elem_ns (rexmpp_t *s,
+ const char *localname,
+ const char *prefix,
+ const char *URI,
+ int nb_namespaces,
+ const char **namespaces,
+ int nb_attributes,
+ int nb_defaulted,
+ const char **attributes);
+
+void rexmpp_sax_end_elem_ns(rexmpp_t *s,
+ const char *localname,
+ const char *prefix,
+ const char *URI);
+
+void rexmpp_sax_characters (rexmpp_t *s, const char * ch, int len);
+
+void rexmpp_log (rexmpp_t *s, int priority, const char *format, ...)
+{
+ va_list args;
+ if (s->log_function != NULL) {
+ va_start(args, format);
+ s->log_function (s, priority, format, args);
+ va_end(args);
+ }
+}
+
+rexmpp_err_t rexmpp_init (rexmpp_t *s,
+ const char *jid,
+ log_function_t log_function,
+ sasl_property_cb_t sasl_property_cb,
+ xml_in_cb_t xml_in_cb,
+ xml_out_cb_t xml_out_cb)
+{
+ int err;
+ xmlSAXHandler sax = {
+ .initialized = XML_SAX2_MAGIC,
+ .characters = (charactersSAXFunc)rexmpp_sax_characters,
+ .startElementNs = (startElementNsSAX2Func)rexmpp_sax_start_elem_ns,
+ .endElementNs = (endElementNsSAX2Func)rexmpp_sax_end_elem_ns,
+ };
+
+ s->tcp_state = REXMPP_TCP_NONE;
+ s->resolver_state = REXMPP_RESOLVER_NONE;
+ s->stream_state = REXMPP_STREAM_NONE;
+ s->tls_state = REXMPP_TLS_INACTIVE;
+ s->sasl_state = REXMPP_SASL_INACTIVE;
+ s->sm_state = REXMPP_SM_INACTIVE;
+ s->carbons_state = REXMPP_CARBONS_INACTIVE;
+ s->send_buffer = NULL;
+ s->send_queue = NULL;
+ s->server_srv = NULL;
+ s->server_srv_cur = NULL;
+ s->server_srv_tls = NULL;
+ s->server_srv_tls_cur = NULL;
+ s->server_socket = -1;
+ s->current_element_root = NULL;
+ s->current_element = NULL;
+ s->stream_features = NULL;
+ s->stanza_queue = NULL;
+ s->stream_id = NULL;
+ s->active_iq = NULL;
+ s->tls_session_data = NULL;
+ s->tls_session_data_size = 0;
+ s->id_counter = 0;
+ s->reconnect_number = 0;
+ s->next_reconnect_time.tv_sec = 0;
+ s->next_reconnect_time.tv_usec = 0;
+ s->initial_jid = jid;
+ s->assigned_jid = NULL;
+ s->stanza_queue_size = 1024;
+ s->send_queue_size = 1024;
+ s->iq_queue_size = 1024;
+ s->log_function = log_function;
+ s->sasl_property_cb = sasl_property_cb;
+ s->xml_in_cb = xml_in_cb;
+ s->xml_out_cb = xml_out_cb;
+
+ s->xml_parser = xmlCreatePushParserCtxt(&sax, s, "", 0, NULL);
+
+ if (s->xml_parser == NULL) {
+ rexmpp_log(s, LOG_CRIT, "Failed to create an XML parser context.");
+ return REXMPP_E_XML;
+ }
+
+ err = ares_library_init(ARES_LIB_INIT_ALL);
+ if (err != 0) {
+ rexmpp_log(s, LOG_CRIT, "ares library initialisation error: %s",
+ ares_strerror(err));
+ xmlFreeParserCtxt(s->xml_parser);
+ return REXMPP_E_DNS;
+ }
+
+ err = ares_init(&(s->resolver_channel));
+ if (err) {
+ rexmpp_log(s, LOG_CRIT, "ares channel initialisation error: %s",
+ ares_strerror(err));
+ ares_library_cleanup();
+ xmlFreeParserCtxt(s->xml_parser);
+ return REXMPP_E_DNS;
+ }
+
+ err = gnutls_certificate_allocate_credentials(&(s->gnutls_cred));
+ if (err) {
+ rexmpp_log(s, LOG_CRIT, "gnutls credentials allocation error: %s",
+ gnutls_strerror(err));
+ ares_destroy(s->resolver_channel);
+ ares_library_cleanup();
+ xmlFreeParserCtxt(s->xml_parser);
+ return REXMPP_E_TLS;
+ }
+
+ err = gsasl_init(&(s->sasl_ctx));
+ if (err) {
+ rexmpp_log(s, LOG_CRIT, "gsasl initialisation error: %s",
+ gsasl_strerror(err));
+ gnutls_certificate_free_credentials(s->gnutls_cred);
+ ares_destroy(s->resolver_channel);
+ ares_library_cleanup();
+ xmlFreeParserCtxt(s->xml_parser);
+ return REXMPP_E_SASL;
+ }
+ gsasl_callback_set(s->sasl_ctx, s->sasl_property_cb);
+
+ return REXMPP_SUCCESS;
+}
+
+/* Prepares for a reconnect: cleans up some things (e.g., SASL and TLS
+ structures), but keeps others (e.g., stanza queue and stream ID,
+ since we may resume the stream afterwards). */
+void rexmpp_cleanup (rexmpp_t *s) {
+ if (s->tls_state != REXMPP_TLS_INACTIVE) {
+ gnutls_deinit(s->gnutls_session);
+ s->tls_state = REXMPP_TLS_INACTIVE;
+ }
+ if (s->sasl_state != REXMPP_SASL_INACTIVE) {
+ gsasl_finish(s->sasl_session);
+ s->sasl_session = NULL;
+ s->sasl_state = REXMPP_SASL_INACTIVE;
+ }
+ if (s->tcp_state == REXMPP_TCP_CONNECTING) {
+ int sock = rexmpp_tcp_conn_finish(&s->server_connection);
+ if (sock != -1) {
+ close(sock);
+ }
+ s->tcp_state = REXMPP_TCP_NONE;
+ }
+ if (s->server_socket != -1) {
+ close(s->server_socket);
+ s->server_socket = -1;
+ }
+ if (s->send_buffer != NULL) {
+ free(s->send_buffer);
+ s->send_buffer = NULL;
+ }
+ if (s->stream_features != NULL) {
+ xmlFreeNode(s->stream_features);
+ s->stream_features = NULL;
+ }
+ while (s->send_queue != NULL) {
+ xmlNodePtr next = xmlNextElementSibling(s->send_queue);
+ xmlFreeNode(s->send_queue);
+ s->send_queue = next;
+ }
+ if (s->current_element_root != NULL) {
+ xmlFreeNode(s->current_element_root);
+ s->current_element_root = NULL;
+ s->current_element = NULL;
+ }
+ if (s->server_srv != NULL) {
+ ares_free_data(s->server_srv);
+ s->server_srv = NULL;
+ s->server_srv_cur = NULL;
+ }
+ if (s->server_srv_tls != NULL) {
+ ares_free_data(s->server_srv_tls);
+ s->server_srv_tls = NULL;
+ s->server_srv_tls_cur = NULL;
+ }
+ s->sm_state = REXMPP_SM_INACTIVE;
+ if (s->carbons_state != REXMPP_CARBONS_DISABLED) {
+ s->carbons_state = REXMPP_CARBONS_INACTIVE;
+ }
+}
+
+/* Frees the things that persist through reconnects. */
+void rexmpp_done (rexmpp_t *s) {
+ rexmpp_cleanup(s);
+ gsasl_done(s->sasl_ctx);
+ gnutls_certificate_free_credentials(s->gnutls_cred);
+ ares_destroy(s->resolver_channel);
+ ares_library_cleanup();
+ xmlFreeParserCtxt(s->xml_parser);
+ if (s->stream_id != NULL) {
+ free(s->stream_id);
+ s->stream_id = NULL;
+ }
+ while (s->stanza_queue != NULL) {
+ xmlNodePtr next = xmlNextElementSibling(s->stanza_queue);
+ xmlFreeNode(s->send_queue);
+ s->send_queue = next;
+ }
+ while (s->active_iq != NULL) {
+ rexmpp_iq_t *next = s->active_iq->next;
+ xmlFreeNode(s->active_iq->request);
+ free(s->active_iq);
+ s->active_iq = next;
+ }
+ if (s->tls_session_data != NULL) {
+ free(s->tls_session_data);
+ }
+}
+
+void rexmpp_schedule_reconnect(rexmpp_t *s) {
+ gettimeofday(&(s->next_reconnect_time), NULL);
+ if (s->reconnect_number < 12) {
+ s->next_reconnect_time.tv_sec += (2 << s->reconnect_number);
+ } else {
+ s->next_reconnect_time.tv_sec += 3600;
+ }
+ rexmpp_log(s, LOG_DEBUG, "Scheduled reconnect number %d, for %u.%u",
+ s->reconnect_number,
+ s->next_reconnect_time.tv_sec, s->next_reconnect_time.tv_usec);
+ s->reconnect_number++;
+}
+
+
+const char *jid_bare_to_host (const char *jid_bare) {
+ char *jid_host;
+ jid_host = strchr(jid_bare, '@');
+ if (jid_host != NULL) {
+ return jid_host + 1;
+ }
+ return NULL;
+}
+
+xmlNodePtr rexmpp_xml_add_id (rexmpp_t *s, xmlNodePtr node) {
+ char buf[11];
+ snprintf(buf, 11, "%u", s->id_counter);
+ s->id_counter++;
+ xmlNewProp(node, "id", buf);
+ return node;
+}
+
+unsigned int rexmpp_xml_siblings_count (xmlNodePtr node) {
+ unsigned int i;
+ for (i = 0; node != NULL; i++) {
+ node = xmlNextElementSibling(node);
+ }
+ return i;
+}
+
+int rexmpp_xml_match (xmlNodePtr node,
+ const char *namespace,
+ const char *name)
+{
+ if (node == NULL) {
+ return 0;
+ }
+ if (name != NULL) {
+ if (strcmp(name, node->name) != 0) {
+ return 0;
+ }
+ }
+ if (namespace != NULL) {
+ if (node->ns == NULL) {
+ if (strcmp(namespace, "jabber:client") != 0) {
+ return 0;
+ }
+ } else {
+ if (strcmp(namespace, node->ns->href) != 0) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+}
+
+xmlNodePtr rexmpp_xml_find_child (xmlNodePtr node,
+ const char *namespace,
+ const char *name)
+{
+ if (node == NULL) {
+ return NULL;
+ }
+ xmlNodePtr child;
+ for (child = xmlFirstElementChild(node);
+ child != NULL;
+ child = xmlNextElementSibling(child))
+ {
+ if (rexmpp_xml_match(child, namespace, name)) {
+ return child;
+ }
+ }
+ return NULL;
+}
+
+xmlNodePtr rexmpp_xml_set_delay (rexmpp_t *s, xmlNodePtr node) {
+ if (rexmpp_xml_find_child (node, NULL, "delay")) {
+ return node;
+ }
+ char buf[42];
+ time_t t = time(NULL);
+ struct tm *local_time = localtime(&t);
+ strftime(buf, 42, "%FT%T%z", local_time);
+ xmlNodePtr delay = xmlNewChild(node, NULL, "delay", NULL);
+ xmlNewProp(delay, "stamp", buf);
+ if (s != NULL && s->assigned_jid != NULL) {
+ xmlNewProp(delay, "from", s->assigned_jid);
+ }
+ return node;
+}
+
+char *rexmpp_xml_serialize(xmlNodePtr node) {
+ xmlBufferPtr buf = xmlBufferCreate();
+ xmlSaveCtxtPtr ctx = xmlSaveToBuffer(buf, "utf-8", 0);
+ xmlSaveTree(ctx, node);
+ xmlSaveFlush(ctx);
+ unsigned char *out = xmlBufferDetach(buf);
+ xmlBufferFree(buf);
+ return out;
+}
+
+int rexmpp_xml_is_stanza (xmlNodePtr node) {
+ return rexmpp_xml_match(node, "jabber:client", "message") ||
+ rexmpp_xml_match(node, "jabber:client", "iq") ||
+ rexmpp_xml_match(node, "jabber:client", "presence");
+}
+
+
+rexmpp_err_t rexmpp_send_start (rexmpp_t *s, const void *data, size_t data_len)
+{
+ int sasl_err;
+ if (s->send_buffer != NULL) {
+ rexmpp_log(s, LOG_CRIT, "send buffer is not empty: %s", s->send_buffer);
+ return REXMPP_E_SEND_BUFFER_NOT_EMPTY;
+ }
+ if (s->sasl_state == REXMPP_SASL_ACTIVE) {
+ sasl_err = gsasl_encode (s->sasl_session, data, data_len,
+ &(s->send_buffer), &(s->send_buffer_len));
+ if (sasl_err != GSASL_OK) {
+ rexmpp_log(s, LOG_ERR, "SASL encoding error: %s",
+ gsasl_strerror(sasl_err));
+ s->sasl_state = REXMPP_SASL_ERROR;
+ return REXMPP_E_SASL;
+ }
+ } else {
+ s->send_buffer = malloc(data_len);
+ if (s->send_buffer == NULL) {
+ return REXMPP_E_MALLOC;
+ }
+ memcpy(s->send_buffer, data, data_len);
+ s->send_buffer_len = data_len;
+ }
+ s->send_buffer_sent = 0;
+ return REXMPP_E_AGAIN;
+}
+
+rexmpp_err_t rexmpp_send_continue (rexmpp_t *s)
+{
+ if (s->send_buffer == NULL) {
+ rexmpp_log(s, LOG_ERR, "nothing to send");
+ return REXMPP_E_SEND_BUFFER_EMPTY;
+ }
+ int ret;
+ while (1) {
+ if (s->tls_state == REXMPP_TLS_ACTIVE) {
+ ret = gnutls_record_send (s->gnutls_session,
+ s->send_buffer,
+ s->send_buffer_len);
+ } else {
+ ret = send (s->server_socket,
+ s->send_buffer + s->send_buffer_sent,
+ s->send_buffer_len - s->send_buffer_sent,
+ 0);
+ }
+ if (ret > 0) {
+ s->send_buffer_sent += ret;
+ if (s->send_buffer_sent == s->send_buffer_len) {
+ free(s->send_buffer);
+ s->send_buffer = NULL;
+ if (s->send_queue != NULL) {
+ xmlNodePtr node = s->send_queue;
+ unsigned char *buf = rexmpp_xml_serialize(node);
+ ret = rexmpp_send_start(s, buf, strlen(buf));
+ free(buf);
+ if (ret != REXMPP_E_AGAIN) {
+ return ret;
+ }
+ s->send_queue = xmlNextElementSibling(s->send_queue);
+ xmlFreeNode(node);
+ } else {
+ return REXMPP_SUCCESS;
+ }
+ }
+ } else {
+ if (s->tls_state == REXMPP_TLS_ACTIVE) {
+ if (ret != GNUTLS_E_AGAIN) {
+ s->tls_state = REXMPP_TLS_ERROR;
+ /* Assume a TCP error for now as well. */
+ s->tcp_state = REXMPP_TCP_ERROR;
+ rexmpp_log(s, LOG_ERR, "TLS send error: %s", gnutls_strerror(ret));
+ rexmpp_cleanup(s);
+ rexmpp_schedule_reconnect(s);
+ return REXMPP_E_TLS;
+ }
+ } else {
+ if (errno != EAGAIN) {
+ s->tcp_state = REXMPP_TCP_ERROR;
+ rexmpp_log(s, LOG_ERR, "TCP send error: %s", strerror(errno));
+ rexmpp_cleanup(s);
+ rexmpp_schedule_reconnect(s);
+ return REXMPP_E_TCP;
+ }
+ }
+ return REXMPP_E_AGAIN;
+ }
+ }
+}
+
+rexmpp_err_t rexmpp_send_raw (rexmpp_t *s, const void *data, size_t data_len)
+{
+ int ret = rexmpp_send_start(s, data, data_len);
+ if (ret != REXMPP_E_AGAIN) {
+ return ret;
+ }
+ return rexmpp_send_continue(s);
+}
+
+rexmpp_err_t rexmpp_sm_send_req (rexmpp_t *s);
+
+rexmpp_err_t rexmpp_send (rexmpp_t *s, xmlNodePtr node)
+{
+ int need_ack = 0;
+ int ret;
+
+ if (s->xml_out_cb != NULL && s->xml_out_cb(s, node) == 1) {
+ xmlFreeNode(node);
+ rexmpp_log(s, LOG_WARNING, "Message sending was cancelled by xml_out_cb.");
+ return REXMPP_E_CANCELLED;
+ }
+
+ if (rexmpp_xml_siblings_count(s->send_queue) >= s->send_queue_size) {
+ xmlFreeNode(node);
+ rexmpp_log(s, LOG_ERR, "The send queue is full, not sending.");
+ return REXMPP_E_SEND_QUEUE_FULL;
+ }
+
+ if (rexmpp_xml_is_stanza(node)) {
+ if (s->sm_state == REXMPP_SM_ACTIVE) {
+ if (s->stanzas_out_count - s->stanzas_out_acknowledged >=
+ s->stanza_queue_size) {
+ xmlFreeNode(node);
+ rexmpp_log(s, LOG_ERR, "The stanza queue is full, not sending.");
+ return REXMPP_E_STANZA_QUEUE_FULL;
+ }
+ need_ack = 1;
+ xmlNodePtr queued_stanza = rexmpp_xml_set_delay(s, xmlCopyNode(node, 1));
+ if (s->stanza_queue == NULL) {
+ s->stanza_queue = queued_stanza;
+ } else {
+ xmlNodePtr last = s->stanza_queue;
+ while (xmlNextElementSibling(last) != NULL) {
+ last = xmlNextElementSibling(last);
+ }
+ xmlAddNextSibling(last, queued_stanza);
+ }
+ }
+ if (s->sm_state != REXMPP_SM_INACTIVE) {
+ s->stanzas_out_count++;
+ }
+ }
+
+ if (s->send_buffer == NULL) {
+ unsigned char *buf = rexmpp_xml_serialize(node);
+ ret = rexmpp_send_raw(s, buf, strlen(buf));
+ free(buf);
+ xmlFreeNode(node);
+ if (ret != REXMPP_SUCCESS && ret != REXMPP_E_AGAIN) {
+ return ret;
+ }
+ } else {
+ if (s->send_queue == NULL) {
+ s->send_queue = node;
+ } else {
+ xmlNodePtr last = s->send_queue;
+ while (xmlNextElementSibling(last) != NULL) {
+ last = xmlNextElementSibling(last);
+ }
+ xmlAddNextSibling(last, node);
+ }
+ ret = REXMPP_E_AGAIN;
+ }
+ if (need_ack) {
+ return rexmpp_sm_send_req(s);
+ }
+ return ret;
+}
+
+
+void rexmpp_iq_new (rexmpp_t *s,
+ const char *type,
+ const char *to,
+ xmlNodePtr payload,
+ rexmpp_iq_callback_t cb)
+{
+ unsigned int i;
+ rexmpp_iq_t *prev = NULL, *last = s->active_iq;
+ for (i = 0; last != NULL && last->next != NULL; i++) {
+ prev = last;
+ last = last->next;
+ }
+ if (i >= s->iq_queue_size && s->iq_queue_size > 0) {
+ rexmpp_log(s, LOG_WARNING,
+ "The IQ queue limit is reached, giving up on the oldest IQ.");
+ prev->next = NULL;
+ if (last->cb != NULL) {
+ last->cb(s, last->request, NULL);
+ }
+ xmlFreeNode(last->request);
+ free(last);
+ }
+
+ xmlNodePtr iq_stanza = rexmpp_xml_add_id(s, xmlNewNode(NULL, "iq"));
+ xmlNewNs(iq_stanza, "jabber:client", NULL);
+ xmlNewProp(iq_stanza, "type", type);
+ if (to != NULL) {
+ xmlNewProp(iq_stanza, "to", to);
+ }
+ if (s->assigned_jid != NULL) {
+ xmlNewProp(iq_stanza, "from", s->assigned_jid);
+ }
+ xmlAddChild(iq_stanza, payload);
+ rexmpp_iq_t *iq = malloc(sizeof(rexmpp_iq_t));
+ iq->request = xmlCopyNode(iq_stanza, 1);
+ iq->cb = cb;
+ iq->next = s->active_iq;
+ s->active_iq = iq;
+ rexmpp_send(s, iq_stanza);
+}
+
+rexmpp_err_t rexmpp_sm_ack (rexmpp_t *s) {
+ char buf[11];
+ xmlNodePtr ack = xmlNewNode(NULL, "a");
+ xmlNewNs(ack, "urn:xmpp:sm:3", NULL);
+ snprintf(buf, 11, "%u", s->stanzas_in_count);
+ xmlNewProp(ack, "h", buf);
+ return rexmpp_send(s, ack);
+}
+
+rexmpp_err_t rexmpp_sm_send_req (rexmpp_t *s) {
+ xmlNodePtr ack = xmlNewNode(NULL, "r");
+ xmlNewNs(ack, "urn:xmpp:sm:3", NULL);
+ return rexmpp_send(s, ack);
+}
+
+void rexmpp_recv (rexmpp_t *s) {
+ char chunk_raw[4096], *chunk;
+ ssize_t chunk_raw_len, chunk_len;
+ int sasl_err;
+ /* Loop here in order to consume data from TLS buffers, which
+ wouldn't show up on select(). */
+ do {
+ if (s->tls_state == REXMPP_TLS_ACTIVE) {
+ chunk_raw_len = gnutls_record_recv(s->gnutls_session, chunk_raw, 4096);
+ } else {
+ chunk_raw_len = recv(s->server_socket, chunk_raw, 4096, 0);
+ }
+ if (chunk_raw_len > 0) {
+ if (s->sasl_state == REXMPP_SASL_ACTIVE) {
+ sasl_err = gsasl_decode(s->sasl_session, chunk_raw, chunk_raw_len,
+ &chunk, &chunk_len);
+ if (sasl_err != GSASL_OK) {
+ rexmpp_log(s, LOG_ERR, "SASL decoding error: %s",
+ gsasl_strerror(sasl_err));
+ s->sasl_state = REXMPP_SASL_ERROR;
+ return;
+ }
+ } else {
+ chunk = chunk_raw;
+ chunk_len = chunk_raw_len;
+ }
+ xmlParseChunk(s->xml_parser, chunk, chunk_len, 0);
+ } else if (chunk_raw_len == 0) {
+ if (s->tls_state == REXMPP_TLS_ACTIVE) {
+ s->tls_state = REXMPP_TLS_CLOSED;
+ rexmpp_log(s, LOG_INFO, "TLS disconnected");
+ }
+ s->tcp_state = REXMPP_TCP_CLOSED;
+ rexmpp_log(s, LOG_INFO, "TCP disconnected");
+ rexmpp_cleanup(s);
+ if (s->stream_state == REXMPP_STREAM_READY) {
+ rexmpp_schedule_reconnect(s);
+ }
+ } else {
+ if (s->tls_state == REXMPP_TLS_ACTIVE) {
+ if (chunk_raw_len != GNUTLS_E_AGAIN) {
+ s->tls_state = REXMPP_TLS_ERROR;
+ /* Assume a TCP error for now as well. */
+ s->tcp_state = REXMPP_TCP_ERROR;
+ rexmpp_log(s, LOG_ERR, "TLS recv error: %s",
+ gnutls_strerror(chunk_raw_len));
+ rexmpp_cleanup(s);
+ rexmpp_schedule_reconnect(s);
+ }
+ } else if (errno != EAGAIN) {
+ s->tcp_state = REXMPP_TCP_ERROR;
+ rexmpp_log(s, LOG_ERR, "TCP recv error: %s", strerror(errno));
+ rexmpp_cleanup(s);
+ rexmpp_schedule_reconnect(s);
+ }
+ }
+ } while (chunk_raw_len > 0);
+}
+
+int rexmpp_stream_open (rexmpp_t *s) {
+ char buf[2048];
+ snprintf(buf, 2048,
+ "<?xml version='1.0'?>\n"
+ "<stream:stream to='%s' version='1.0' "
+ "xml:lang='en' xmlns='jabber:client' "
+ "xmlns:stream='http://etherx.jabber.org/streams'>",
+ jid_bare_to_host(s->initial_jid));
+ s->stream_state = REXMPP_STREAM_OPENING;
+ rexmpp_send_raw(s, buf, strlen(buf));
+
+ return 0;
+}
+
+void rexmpp_process_conn_err (rexmpp_t *s, int err);
+
+void rexmpp_try_next_host (rexmpp_t *s) {
+ const char *host;
+ int port;
+ /* todo: check priorities and weights */
+ s->tls_state = REXMPP_TLS_INACTIVE;
+ if (s->server_srv_tls != NULL && s->server_srv_tls_cur == NULL) {
+ /* We have xmpps-client records available, but haven't tried any
+ of them yet. */
+ s->server_srv_tls_cur = s->server_srv_tls;
+ host = s->server_srv_tls_cur->host;
+ port = s->server_srv_tls_cur->port;
+ s->tls_state = REXMPP_TLS_AWAITING_DIRECT;
+ } else if (s->server_srv_tls_cur != NULL &&
+ s->server_srv_tls_cur->next != NULL) {
+ /* We have tried some xmpps-client records, but there is more. */
+ s->server_srv_tls_cur = s->server_srv_tls_cur->next;
+ host = s->server_srv_tls_cur->host;
+ port = s->server_srv_tls_cur->port;
+ s->tls_state = REXMPP_TLS_AWAITING_DIRECT;
+ } else if (s->server_srv != NULL && s->server_srv_cur == NULL) {
+ /* Starting with xmpp-client records. */
+ s->server_srv_cur = s->server_srv;
+ host = s->server_srv_cur->host;
+ port = s->server_srv_cur->port;
+ } else if (s->server_srv_tls_cur != NULL &&
+ s->server_srv_tls_cur->next != NULL) {
+ /* Advancing in xmpp-client records. */
+ s->server_srv_cur = s->server_srv_cur->next;
+ host = s->server_srv_cur->host;
+ port = s->server_srv_cur->port;
+ } else {
+ /* No candidate records left to try. Schedule a reconnect. */
+ rexmpp_cleanup(s);
+ rexmpp_schedule_reconnect(s);
+ return;
+ }
+ rexmpp_log(s, LOG_DEBUG, "Connecting to %s:%d", host, port);
+ rexmpp_process_conn_err(s,
+ rexmpp_tcp_conn_init(&s->server_connection,
+ host, port));
+}
+
+void rexmpp_tls_handshake (rexmpp_t *s) {
+ s->tls_state = REXMPP_TLS_HANDSHAKE;
+ int ret = gnutls_handshake(s->gnutls_session);
+ if (ret == GNUTLS_E_AGAIN) {
+ rexmpp_log(s, LOG_DEBUG, "Waiting for TLS handshake to complete");
+ } else if (ret == 0) {
+ rexmpp_log(s, LOG_DEBUG, "TLS ready");
+ s->tls_state = REXMPP_TLS_ACTIVE;
+
+ if (gnutls_session_is_resumed(s->gnutls_session)) {
+ rexmpp_log(s, LOG_INFO, "TLS session is resumed");
+ } else {
+ if (s->tls_session_data != NULL) {
+ rexmpp_log(s, LOG_DEBUG, "TLS session is not resumed");
+ free(s->tls_session_data);
+ s->tls_session_data = NULL;
+ }
+ gnutls_session_get_data(s->gnutls_session, NULL,
+ &s->tls_session_data_size);
+ s->tls_session_data = malloc(s->tls_session_data_size);
+ ret = gnutls_session_get_data(s->gnutls_session, s->tls_session_data,
+ &s->tls_session_data_size);
+ if (ret != GNUTLS_E_SUCCESS) {
+ rexmpp_log(s, LOG_ERR, "Failed to get TLS session data: %s",
+ gnutls_strerror(ret));
+ }
+ }
+
+ if (s->stream_state == REXMPP_STREAM_NONE) {
+ /* It's a direct TLS connection, so open a stream after
+ connecting. */
+ rexmpp_stream_open(s);
+ } else {
+ /* A STARTTLS connection, restart the stream. */
+ s->stream_state = REXMPP_STREAM_RESTART;
+ }
+
+ } else {
+ rexmpp_log(s, LOG_ERR, "Unexpected TLS handshake error: %s",
+ gnutls_strerror(ret));
+ if (s->stream_state == REXMPP_STREAM_NONE) {
+ /* It was a direct TLS connection attempt: cleanup the session,
+ continue connection attempts. */
+ gnutls_deinit(s->gnutls_session);
+ s->tls_state = REXMPP_TLS_INACTIVE;
+ rexmpp_try_next_host(s);
+ } else {
+ rexmpp_cleanup(s);
+ }
+ }
+}
+
+void rexmpp_tls_start (rexmpp_t *s) {
+ gnutls_datum_t xmpp_client_protocol = {"xmpp-client", strlen("xmpp-client")};
+ rexmpp_log(s, LOG_DEBUG, "starting TLS");
+ gnutls_init(&s->gnutls_session, GNUTLS_CLIENT);
+ gnutls_session_set_ptr(s->gnutls_session, s);
+ gnutls_alpn_set_protocols(s->gnutls_session, &xmpp_client_protocol, 1, 0);
+ gnutls_server_name_set(s->gnutls_session, GNUTLS_NAME_DNS,
+ jid_bare_to_host(s->initial_jid),
+ strlen(jid_bare_to_host(s->initial_jid)));
+ gnutls_set_default_priority(s->gnutls_session);
+ gnutls_credentials_set(s->gnutls_session, GNUTLS_CRD_CERTIFICATE,
+ s->gnutls_cred);
+ gnutls_transport_set_int(s->gnutls_session, s->server_socket);
+ gnutls_handshake_set_timeout(s->gnutls_session,
+ GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
+ if (s->tls_session_data != NULL) {
+ int ret = gnutls_session_set_data(s->gnutls_session,
+ s->tls_session_data,
+ s->tls_session_data_size);
+ if (ret != GNUTLS_E_SUCCESS) {
+ rexmpp_log(s, LOG_ERR, "Failed to set TLS session data: %s",
+ gnutls_strerror(ret));
+ }
+ }
+ s->tls_state = REXMPP_TLS_HANDSHAKE;
+ rexmpp_tls_handshake(s);
+}
+
+
+void rexmpp_process_conn_err (rexmpp_t *s, int err) {
+ s->tcp_state = REXMPP_TCP_CONNECTING;
+ if (err == REXMPP_CONN_DONE) {
+ rexmpp_log(s, LOG_INFO, "Established a TCP connection");
+ s->reconnect_number = 0;
+ xmlCtxtResetPush(s->xml_parser, "", 0, "", "utf-8");
+ s->server_socket = rexmpp_tcp_conn_finish(&s->server_connection);
+ s->tcp_state = REXMPP_TCP_CONNECTED;
+ if (s->tls_state == REXMPP_TLS_AWAITING_DIRECT) {
+ rexmpp_tls_start(s);
+ } else {
+ rexmpp_stream_open(s);
+ }
+ } else if (err != REXMPP_CONN_IN_PROGRESS) {
+ rexmpp_log(s, LOG_WARNING, "Failed to connect");
+ if (err == REXMPP_CONN_ERROR) {
+ s->tcp_state = REXMPP_TCP_NONE;
+ } else {
+ s->tcp_state = REXMPP_TCP_CONNECTION_FAILURE;
+ }
+ rexmpp_tcp_conn_finish(&s->server_connection);
+ rexmpp_try_next_host(s);
+ }
+}
+
+void rexmpp_after_srv (rexmpp_t *s) {
+ if (s->resolver_state == REXMPP_RESOLVER_SRV) {
+ s->resolver_state = REXMPP_RESOLVER_SRV_2;
+ } else if (s->resolver_state == REXMPP_RESOLVER_SRV_2) {
+ s->resolver_state = REXMPP_RESOLVER_READY;
+ }
+ if (s->resolver_state != REXMPP_RESOLVER_READY) {
+ return;
+ }
+
+ /* todo: sort the records */
+
+ if (s->server_srv == NULL && s->server_srv_tls == NULL) {
+ /* Failed to resolve anything: a fallback. */
+ const char *host = jid_bare_to_host(s->initial_jid);
+ int port = 5222;
+ rexmpp_log(s, LOG_DEBUG, "Connecting to %s:%d", host, port);
+ rexmpp_process_conn_err(s, rexmpp_tcp_conn_init(&s->server_connection,
+ host, port));
+ } else {
+ rexmpp_try_next_host(s);
+ }
+}
+
+void rexmpp_srv_tls_cb (void *s_ptr,
+ int status,
+ int timeouts,
+ unsigned char *abuf,
+ int alen)
+{
+ rexmpp_t *s = s_ptr;
+ if (status == ARES_SUCCESS) {
+ ares_parse_srv_reply(abuf, alen, &(s->server_srv_tls));
+ } else {
+ rexmpp_log(s, LOG_WARNING, "Failed to query an xmpps-client SRV record: %s",
+ ares_strerror(status));
+ }
+ if (status != ARES_EDESTRUCTION) {
+ rexmpp_after_srv(s);
+ }
+}
+
+void rexmpp_srv_cb (void *s_ptr,
+ int status,
+ int timeouts,
+ unsigned char *abuf,
+ int alen)
+{
+ rexmpp_t *s = s_ptr;
+ if (status == ARES_SUCCESS) {
+ ares_parse_srv_reply(abuf, alen, &(s->server_srv));
+ } else {
+ rexmpp_log(s, LOG_WARNING, "Failed to query an xmpp-client SRV record: %s",
+ ares_strerror(status));
+ }
+ if (status != ARES_EDESTRUCTION) {
+ rexmpp_after_srv(s);
+ }
+}
+
+
+/* Should be called after reconnect, and after rexmpp_sm_handle_ack in
+ case of resumption. */
+rexmpp_err_t rexmpp_resend_stanzas (rexmpp_t *s) {
+ uint32_t i, count;
+ rexmpp_err_t ret = REXMPP_SUCCESS;
+ xmlNodePtr sq;
+ count = s->stanzas_out_count - s->stanzas_out_acknowledged;
+ for (i = 0; i < count && s->stanza_queue != NULL; i++) {
+ sq = xmlNextElementSibling(s->stanza_queue);
+ ret = rexmpp_send(s, s->stanza_queue);
+ if (ret != REXMPP_SUCCESS && ret != REXMPP_E_AGAIN) {
+ return ret;
+ }
+ s->stanza_queue = sq;
+ }
+ if (i != count) {
+ rexmpp_log(s, LOG_ERR,
+ "not enough stanzas in the queue: needed %u, had %u",
+ count, i);
+ }
+ /* Don't count these stanzas twice. */
+ s->stanzas_out_count -= i;
+ return ret;
+}
+
+void rexmpp_sm_handle_ack (rexmpp_t *s, xmlNodePtr elem) {
+ char *h = xmlGetProp(elem, "h");
+ if (h != NULL) {
+ uint32_t prev_ack = s->stanzas_out_acknowledged;
+ s->stanzas_out_acknowledged = strtoul(h, NULL, 10);
+ xmlFree(h);
+ rexmpp_log(s, LOG_DEBUG,
+ "server acknowledged %u out of %u sent stanzas",
+ s->stanzas_out_acknowledged,
+ s->stanzas_out_count);
+ if (s->stanzas_out_count >= s->stanzas_out_acknowledged) {
+ if (prev_ack <= s->stanzas_out_acknowledged) {
+ uint32_t i;
+ for (i = prev_ack; i < s->stanzas_out_acknowledged; i++) {
+ xmlNodePtr sq = xmlNextElementSibling(s->stanza_queue);
+ xmlFreeNode(s->stanza_queue);
+ s->stanza_queue = sq;
+ }
+ } else {
+ rexmpp_log(s, LOG_ERR,
+ "the server acknowledged %u stanzas previously, and %u now",
+ prev_ack, s->stanzas_out_acknowledged);
+ }
+ } else {
+ rexmpp_log(s, LOG_ERR,
+ "the server acknowledged more stanzas than we have sent");
+ }
+ } else {
+ rexmpp_log(s, LOG_ERR, "no 'h' attribute in <a>");
+ }
+}
+
+void rexmpp_carbons_enabled (rexmpp_t *s, xmlNodePtr req, xmlNodePtr response) {
+ char *type = xmlGetProp(response, "type");
+ if (strcmp(type, "result") == 0) {
+ rexmpp_log(s, LOG_INFO, "carbons enabled");
+ s->carbons_state = REXMPP_CARBONS_ACTIVE;
+ } else {
+ rexmpp_log(s, LOG_WARNING, "failed to enable carbons");
+ s->carbons_state = REXMPP_CARBONS_INACTIVE;
+ }
+ free(type);
+}
+
+void rexmpp_discovery_info (rexmpp_t *s, xmlNodePtr req, xmlNodePtr response) {
+ xmlNodePtr query = xmlFirstElementChild(response);
+ if (rexmpp_xml_match(query, "http://jabber.org/protocol/disco#info",
+ "query")) {
+ xmlNodePtr child;
+ for (child = xmlFirstElementChild(query);
+ child != NULL;
+ child = xmlNextElementSibling(child))
+ {
+ if (rexmpp_xml_match(child, "http://jabber.org/protocol/disco#info",
+ "feature")) {
+ char *var = xmlGetProp(child, "var");
+ if (s->carbons_state != REXMPP_CARBONS_DISABLED &&
+ strcmp(var, "urn:xmpp:carbons:2") == 0) {
+ xmlNodePtr carbons_enable = xmlNewNode(NULL, "enable");
+ xmlNewNs(carbons_enable, "urn:xmpp:carbons:2", NULL);
+ s->carbons_state = REXMPP_CARBONS_NEGOTIATION;
+ rexmpp_iq_new(s, "set", NULL, carbons_enable,
+ rexmpp_carbons_enabled);
+ }
+ free(var);
+ }
+ }
+ }
+}
+
+void rexmpp_stream_is_ready(rexmpp_t *s) {
+ s->stream_state = REXMPP_STREAM_READY;
+ rexmpp_resend_stanzas(s);
+ rexmpp_send(s, xmlNewNode(NULL, "presence"));
+ xmlNodePtr disco_query = xmlNewNode(NULL, "query");
+ xmlNewNs(disco_query, "http://jabber.org/protocol/disco#info", NULL);
+ rexmpp_iq_new(s, "get", jid_bare_to_host(s->initial_jid),
+ disco_query, rexmpp_discovery_info);
+}
+
+/* Resource binding,
+ https://tools.ietf.org/html/rfc6120#section-7 */
+void rexmpp_bound (rexmpp_t *s, xmlNodePtr req, xmlNodePtr response) {
+ /* todo: handle errors */
+ xmlNodePtr child = xmlFirstElementChild(response);
+ if (rexmpp_xml_match(child, "urn:ietf:params:xml:ns:xmpp-bind", "bind")) {
+ xmlNodePtr jid = xmlFirstElementChild(child);
+ if (rexmpp_xml_match(jid, "urn:ietf:params:xml:ns:xmpp-bind", "jid")) {
+ rexmpp_log(s, LOG_INFO, "jid: %s", xmlNodeGetContent(jid));
+ s->assigned_jid = malloc(strlen(xmlNodeGetContent(jid)) + 1);
+ strcpy(s->assigned_jid, xmlNodeGetContent(jid));
+ }
+ if (s->stream_id == NULL &&
+ (child = rexmpp_xml_find_child(s->stream_features, "urn:xmpp:sm:3",
+ "sm"))) {
+ /* Try to resume a stream. */
+ s->sm_state = REXMPP_SM_NEGOTIATION;
+ s->stream_state = REXMPP_STREAM_SM_FULL;
+ xmlNodePtr sm_enable = xmlNewNode(NULL, "enable");
+ xmlNewNs(sm_enable, "urn:xmpp:sm:3", NULL);
+ xmlNewProp(sm_enable, "resume", "true");
+ rexmpp_send(s, sm_enable);
+ s->stanzas_out_count = 0;
+ s->stanzas_out_acknowledged = 0;
+ s->stanzas_in_count = 0;
+ } else {
+ s->sm_state = REXMPP_SM_INACTIVE;
+ rexmpp_stream_is_ready(s);
+ }
+ }
+}
+
+void rexmpp_stream_bind (rexmpp_t *s) {
+ /* Issue a bind request. */
+ s->stream_state = REXMPP_STREAM_BIND;
+ xmlNodePtr bind_cmd = xmlNewNode(NULL, "bind");
+ xmlNewNs(bind_cmd, "urn:ietf:params:xml:ns:xmpp-bind", NULL);
+ rexmpp_iq_new(s, "set", NULL, bind_cmd, rexmpp_bound);
+}
+
+void rexmpp_process_element(rexmpp_t *s) {
+ xmlNodePtr elem = s->current_element;
+
+ /* IQ responses */
+ if (rexmpp_xml_match(elem, "jabber:client", "iq")) {
+ char *type = xmlGetProp(elem, "type");
+ if (strcmp(type, "result") == 0 || strcmp(type, "error") == 0) {
+ char *id = xmlGetProp(elem, "id");
+ rexmpp_iq_t *req = s->active_iq;
+ int found = 0;
+ while (req != NULL && found == 0) {
+ char *req_id = xmlGetProp(req->request, "id");
+ if (strcmp(id, req_id) == 0) {
+ found = 1;
+ if (req->cb != NULL) {
+ req->cb(s, req->request, elem);
+ }
+ /* Remove the callback from the list, but keep in mind that
+ it could have added more entries. */
+ if (s->active_iq == req) {
+ s->active_iq = req->next;
+ } else {
+ rexmpp_iq_t *prev_req = s->active_iq;
+ for (prev_req = s->active_iq;
+ prev_req != NULL;
+ prev_req = prev_req->next)
+ {
+ if (prev_req->next == req) {
+ prev_req->next = req->next;
+ break;
+ }
+ }
+ }
+ xmlFreeNode(req->request);
+ free(req);
+ }
+ free(req_id);
+ req = req->next;
+ }
+ free(id);
+ }
+ free(type);
+ }
+
+ /* Stream negotiation,
+ https://tools.ietf.org/html/rfc6120#section-4.3 */
+ if (s->stream_state == REXMPP_STREAM_NEGOTIATION &&
+ rexmpp_xml_match(elem, "http://etherx.jabber.org/streams", "features")) {
+
+ /* Remember features. */
+ if (s->stream_features != NULL) {
+ xmlFreeNode(s->stream_features);
+ }
+ s->stream_features = xmlCopyNode(elem, 1);
+
+ /* Nothing to negotiate. */
+ if (xmlFirstElementChild(elem) == NULL) {
+ rexmpp_stream_is_ready(s);
+ return;
+ }
+
+ /* TODO: check for required features properly here. Currently
+ assuming that STARTTLS, SASL, and BIND (with an exception for
+ SM) are always required if they are present. */
+ xmlNodePtr child =
+ rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-tls",
+ "starttls");
+ if (child != NULL) {
+ s->stream_state = REXMPP_STREAM_STARTTLS;
+ xmlNodePtr starttls_cmd = xmlNewNode(NULL, "starttls");
+ xmlNewNs(starttls_cmd, "urn:ietf:params:xml:ns:xmpp-tls", NULL);
+ rexmpp_send(s, starttls_cmd);
+ return;
+ }
+
+ child = rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-sasl",
+ "mechanisms");
+ if (child != NULL) {
+ s->stream_state = REXMPP_STREAM_SASL;
+ s->sasl_state = REXMPP_SASL_NEGOTIATION;
+ char mech_list[2048]; /* todo: perhaps grow it dynamically */
+ mech_list[0] = '\0';
+ xmlNodePtr mechanism;
+ for (mechanism = xmlFirstElementChild(child);
+ mechanism != NULL;
+ mechanism = xmlNextElementSibling(mechanism)) {
+ if (rexmpp_xml_match(mechanism, "urn:ietf:params:xml:ns:xmpp-sasl",
+ "mechanism")) {
+ snprintf(mech_list + strlen(mech_list),
+ 2048 - strlen(mech_list),
+ "%s ",
+ xmlNodeGetContent(mechanism));
+ }
+ }
+ const char *mech =
+ gsasl_client_suggest_mechanism(s->sasl_ctx, mech_list);
+ rexmpp_log(s, LOG_INFO, "Selected SASL mechanism: %s", mech);
+ int sasl_err;
+ char *sasl_buf;
+ sasl_err = gsasl_client_start(s->sasl_ctx, mech, &(s->sasl_session));
+ if (sasl_err != GSASL_OK) {
+ rexmpp_log(s, LOG_CRIT, "Failed to initialise SASL session: %s",
+ gsasl_strerror(sasl_err));
+ s->sasl_state = REXMPP_SASL_ERROR;
+ return;
+ }
+ sasl_err = gsasl_step64 (s->sasl_session, "", (char**)&sasl_buf);
+ if (sasl_err != GSASL_OK) {
+ if (sasl_err == GSASL_NEEDS_MORE) {
+ rexmpp_log(s, LOG_DEBUG, "SASL needs more data");
+ } else {
+ rexmpp_log(s, LOG_ERR, "SASL error: %s",
+ gsasl_strerror(sasl_err));
+ s->sasl_state = REXMPP_SASL_ERROR;
+ return;
+ }
+ }
+ xmlNodePtr auth_cmd = xmlNewNode(NULL, "auth");
+ xmlNewProp(auth_cmd, "mechanism", mech);
+ xmlNewNs(auth_cmd, "urn:ietf:params:xml:ns:xmpp-sasl", NULL);
+ xmlNodeAddContent(auth_cmd, sasl_buf);
+ free(sasl_buf);
+ rexmpp_send(s, auth_cmd);
+ return;
+ }
+
+ child = rexmpp_xml_find_child(elem, "urn:xmpp:sm:3", "sm");
+ if (s->stream_id != NULL && child != NULL) {
+ s->stream_state = REXMPP_STREAM_SM_RESUME;
+ char buf[11];
+ snprintf(buf, 11, "%u", s->stanzas_in_count);
+ xmlNodePtr sm_resume = xmlNewNode(NULL, "resume");
+ xmlNewNs(sm_resume, "urn:xmpp:sm:3", NULL);
+ xmlNewProp(sm_resume, "previd", s->stream_id);
+ xmlNewProp(sm_resume, "h", buf);
+ rexmpp_send(s, sm_resume);
+ return;
+ }
+
+ child =
+ rexmpp_xml_find_child(elem, "urn:ietf:params:xml:ns:xmpp-bind", "bind");
+ if (child != NULL) {
+ rexmpp_stream_bind(s);
+ return;
+ }
+ }
+
+ /* Stream errors, https://tools.ietf.org/html/rfc6120#section-4.9 */
+ if (rexmpp_xml_match(elem, "http://etherx.jabber.org/streams",
+ "error")) {
+ rexmpp_log(s, LOG_ERR, "stream error");
+ s->stream_state = REXMPP_STREAM_ERROR;
+ return;
+ }
+
+ /* STARTTLS negotiation,
+ https://tools.ietf.org/html/rfc6120#section-5 */
+ if (s->stream_state == REXMPP_STREAM_STARTTLS) {
+ if (rexmpp_xml_match(elem, "urn:ietf:params:xml:ns:xmpp-tls",
+ "proceed")) {
+ rexmpp_tls_start(s);
+ return;
+ } else if (rexmpp_xml_match(elem, "urn:ietf:params:xml:ns:xmpp-tls",
+ "failure")) {
+ rexmpp_log(s, LOG_ERR, "STARTTLS failure");
+ return;
+ }
+ }
+
+ /* SASL negotiation,
+ https://tools.ietf.org/html/rfc6120#section-6 */
+ if (s->stream_state == REXMPP_STREAM_SASL) {
+ char *sasl_buf;
+ int sasl_err;
+ if (rexmpp_xml_match(elem, "urn:ietf:params:xml:ns:xmpp-sasl",
+ "challenge")) {
+ sasl_err = gsasl_step64 (s->sasl_session, xmlNodeGetContent(elem),
+ (char**)&sasl_buf);
+ if (sasl_err != GSASL_OK) {
+ if (sasl_err == GSASL_NEEDS_MORE) {
+ rexmpp_log(s, LOG_DEBUG, "SASL needs more data");
+ } else {
+ rexmpp_log(s, LOG_ERR, "SASL error: %s",
+ gsasl_strerror(sasl_err));
+ s->sasl_state = REXMPP_SASL_ERROR;
+ return;
+ }
+ }
+ xmlNodePtr response = xmlNewNode(NULL, "response");
+ xmlNewNs(response, "urn:ietf:params:xml:ns:xmpp-sasl", NULL);
+ xmlNodeAddContent(response, sasl_buf);
+ free(sasl_buf);
+ rexmpp_send(s, response);
+ return;
+ } else if (rexmpp_xml_match(elem, "urn:ietf:params:xml:ns:xmpp-sasl",
+ "success")) {
+ sasl_err = gsasl_step64 (s->sasl_session, xmlNodeGetContent(elem),
+ (char**)&sasl_buf);
+ free(sasl_buf);
+ if (sasl_err == GSASL_OK) {
+ rexmpp_log(s, LOG_DEBUG, "SASL success");
+ } else {
+ rexmpp_log(s, LOG_ERR, "SASL error: %s",
+ gsasl_strerror(sasl_err));
+ s->sasl_state = REXMPP_SASL_ERROR;
+ return;
+ }
+ s->sasl_state = REXMPP_SASL_ACTIVE;
+ s->stream_state = REXMPP_STREAM_RESTART;
+ return;
+ } else if (rexmpp_xml_match(elem, "urn:ietf:params:xml:ns:xmpp-sasl",
+ "failure")) {
+ /* todo: would be nice to retry here, but just giving up for now */
+ rexmpp_log(s, LOG_ERR, "SASL failure");
+ rexmpp_stop(s);
+ return;
+ }
+ }
+
+ /* Stream management, https://xmpp.org/extensions/xep-0198.html */
+ if (s->stream_state == REXMPP_STREAM_SM_FULL) {
+ if (rexmpp_xml_match(elem, "urn:xmpp:sm:3", "enabled")) {
+ s->sm_state = REXMPP_SM_ACTIVE;
+ char *resume = xmlGetProp(elem, "resume");
+ if (resume != NULL) {
+ if (s->stream_id != NULL) {
+ free(s->stream_id);
+ }
+ s->stream_id = xmlGetProp(elem, "id");
+ xmlFree(resume);
+ }
+ rexmpp_stream_is_ready(s);
+ } else if (rexmpp_xml_match(elem, "urn:xmpp:sm:3", "failed")) {
+ s->stream_state = REXMPP_STREAM_SM_ACKS;
+ s->sm_state = REXMPP_SM_NEGOTIATION;
+ xmlNodePtr sm_enable = xmlNewNode(NULL, "enable");
+ xmlNewNs(sm_enable, "urn:xmpp:sm:3", NULL);
+ rexmpp_send(s, sm_enable);
+ }
+ } else if (s->stream_state == REXMPP_STREAM_SM_ACKS) {
+ if (rexmpp_xml_match(elem, "urn:xmpp:sm:3", "enabled")) {
+ s->sm_state = REXMPP_SM_ACTIVE;
+ if (s->stream_id != NULL) {
+ free(s->stream_id);
+ s->stream_id = NULL;
+ }
+ } else if (rexmpp_xml_match(elem, "urn:xmpp:sm:3", "failed")) {
+ s->sm_state = REXMPP_SM_INACTIVE;
+ xmlNodePtr sm_enable = xmlNewNode(NULL, "enable");
+ xmlNewNs(sm_enable, "urn:xmpp:sm:3", NULL);
+ rexmpp_send(s, sm_enable);
+ }
+ rexmpp_stream_is_ready(s);
+ } else if (s->stream_state == REXMPP_STREAM_SM_RESUME) {
+ if (rexmpp_xml_match(elem, "urn:xmpp:sm:3", "resumed")) {
+ s->sm_state = REXMPP_SM_ACTIVE;
+ s->stream_state = REXMPP_STREAM_READY;
+ rexmpp_sm_handle_ack(s, elem);
+ rexmpp_resend_stanzas(s);
+ } else if (rexmpp_xml_match(elem, "urn:xmpp:sm:3", "failed")) {
+ /* Back to binding, but cleanup stream state first. */
+ free(s->stream_id);
+ s->stream_id = NULL;
+ while (s->active_iq != NULL) {
+ /* todo: check that those are not queued for resending? */
+ rexmpp_iq_t *next = s->active_iq->next;
+ xmlFreeNode(s->active_iq->request);
+ free(s->active_iq);
+ s->active_iq = next;
+ }
+ xmlNodePtr child =
+ rexmpp_xml_find_child(s->stream_features,
+ "urn:ietf:params:xml:ns:xmpp-bind",
+ "bind");
+ if (child != NULL) {
+ rexmpp_stream_bind(s);
+ return;
+ }
+ }
+ }
+
+ if (s->sm_state == REXMPP_SM_ACTIVE && rexmpp_xml_is_stanza(elem)) {
+ s->stanzas_in_count++;
+ }
+ if (rexmpp_xml_match(elem, "urn:xmpp:sm:3", "r")) {
+ rexmpp_sm_ack(s);
+ } else if (rexmpp_xml_match(elem, "urn:xmpp:sm:3", "a")) {
+ rexmpp_sm_handle_ack(s, elem);
+ }
+}
+
+
+void rexmpp_sax_characters (rexmpp_t *s, const char *ch, int len)
+{
+ if (s->current_element != NULL) {
+ xmlNodeAddContentLen(s->current_element, ch, len);
+ }
+}
+
+void rexmpp_sax_start_elem_ns (rexmpp_t *s,
+ const char *localname,
+ const char *prefix,
+ const char *URI,
+ int nb_namespaces,
+ const char **namespaces,
+ int nb_attributes,
+ int nb_defaulted,
+ const char **attributes)
+{
+ int i;
+ if (s->stream_state == REXMPP_STREAM_OPENING &&
+ strcmp(localname, "stream") == 0 &&
+ strcmp(URI, "http://etherx.jabber.org/streams") == 0) {
+ rexmpp_log(s, LOG_DEBUG, "stream start");
+ s->stream_state = REXMPP_STREAM_NEGOTIATION;
+ return;
+ }
+
+ if (s->stream_state != REXMPP_STREAM_OPENING) {
+ if (s->current_element == NULL) {
+ s->current_element = xmlNewNode(NULL, localname);
+ s->current_element_root = s->current_element;
+ } else {
+ xmlNodePtr node = xmlNewNode(NULL, localname);
+ xmlAddChild(s->current_element, node);
+ s->current_element = node;
+ }
+ xmlNsPtr ns = xmlNewNs(s->current_element, URI, prefix);
+ s->current_element->ns = ns;
+ for (i = 0; i < nb_attributes; i++) {
+ size_t attr_len = attributes[i * 5 + 4] - attributes[i * 5 + 3];
+ char *attr_val = malloc(attr_len + 1);
+ attr_val[attr_len] = '\0';
+ strncpy(attr_val, attributes[i * 5 + 3], attr_len);
+ xmlNewProp(s->current_element, attributes[i * 5], attr_val);
+ free(attr_val);
+ }
+ }
+}
+
+void rexmpp_sax_end_elem_ns (rexmpp_t *s,
+ const char *localname,
+ const char *prefix,
+ const char *URI)
+{
+ if ((s->stream_state == REXMPP_STREAM_CLOSING ||
+ s->stream_state == REXMPP_STREAM_ERROR) &&
+ strcmp(localname, "stream") == 0 &&
+ strcmp(URI, "http://etherx.jabber.org/streams") == 0) {
+ rexmpp_log(s, LOG_DEBUG, "stream end");
+ if (s->sasl_state == REXMPP_SASL_ACTIVE) {
+ gsasl_finish(s->sasl_session);
+ s->sasl_session = NULL;
+ s->sasl_state = REXMPP_SASL_INACTIVE;
+ }
+ s->stream_state = REXMPP_STREAM_CLOSED;
+ if (s->tls_state == REXMPP_TLS_ACTIVE) {
+ s->tls_state = REXMPP_TLS_CLOSING;
+ } else {
+ rexmpp_log(s, LOG_DEBUG, "closing the socket");
+ close(s->server_socket);
+ s->server_socket = -1;
+ rexmpp_cleanup(s);
+ s->tcp_state = REXMPP_TCP_CLOSED;
+ }
+ return;
+ }
+
+ if (s->current_element != s->current_element_root) {
+ s->current_element = s->current_element->parent;
+ } else {
+ if (s->xml_in_cb != NULL && s->xml_in_cb(s, s->current_element) != 0) {
+ rexmpp_log(s, LOG_WARNING,
+ "Message processing was cancelled by xml_in_cb.");
+ } else {
+ rexmpp_process_element(s);
+ }
+
+ xmlFreeNode(s->current_element);
+ s->current_element = NULL;
+ s->current_element_root = NULL;
+ }
+}
+
+rexmpp_err_t rexmpp_close (rexmpp_t *s) {
+ s->stream_state = REXMPP_STREAM_CLOSING;
+ char *close_stream = "</stream:stream>";
+ return rexmpp_send_raw(s, close_stream, strlen(close_stream));
+}
+
+rexmpp_err_t rexmpp_stop (rexmpp_t *s) {
+ if (s->sm_state == REXMPP_SM_ACTIVE) {
+ int ret = rexmpp_sm_ack(s);
+ if (ret != REXMPP_SUCCESS && ret != REXMPP_E_AGAIN) {
+ return ret;
+ }
+ }
+ s->stream_state = REXMPP_STREAM_CLOSE_REQUESTED;
+ if (s->send_buffer == NULL) {
+ return rexmpp_close(s);
+ } else {
+ return REXMPP_E_AGAIN;
+ }
+}
+
+rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
+ struct timeval now;
+ gettimeofday(&now, NULL);
+
+ /* Inactive: start by querying SRV records. */
+ if ((s->resolver_state == REXMPP_RESOLVER_NONE ||
+ s->resolver_state == REXMPP_RESOLVER_READY) &&
+ (s->tcp_state == REXMPP_TCP_NONE ||
+ ((s->tcp_state == REXMPP_TCP_ERROR ||
+ s->tcp_state == REXMPP_TCP_CONNECTION_FAILURE) &&
+ s->reconnect_number > 0 &&
+ s->next_reconnect_time.tv_sec <= now.tv_sec))) {
+ rexmpp_log(s, LOG_DEBUG, "start (or reconnect)");
+ size_t srv_query_buf_len = strlen(jid_bare_to_host(s->initial_jid)) +
+ strlen("_xmpps-client._tcp..") +
+ 1;
+ char *srv_query = malloc(srv_query_buf_len);
+ snprintf(srv_query, srv_query_buf_len,
+ "_xmpps-client._tcp.%s.", jid_bare_to_host(s->initial_jid));
+ ares_query(s->resolver_channel, srv_query,
+ ns_c_in, ns_t_srv, rexmpp_srv_tls_cb, s);
+ snprintf(srv_query, srv_query_buf_len,
+ "_xmpp-client._tcp.%s.", jid_bare_to_host(s->initial_jid));
+ ares_query(s->resolver_channel, srv_query,
+ ns_c_in, ns_t_srv, rexmpp_srv_cb, s);
+ s->resolver_state = REXMPP_RESOLVER_SRV;
+ free(srv_query);
+ }
+
+ /* Resolving SRV records. This continues in rexmpp_srv_tls_cb,
+ rexmpp_srv_cb, and rexmpp_after_srv, possibly leading to
+ connection initiation. */
+ if (s->resolver_state != REXMPP_RESOLVER_NONE &&
+ s->resolver_state != REXMPP_RESOLVER_READY) {
+ ares_process(s->resolver_channel, read_fds, write_fds);
+ }
+
+ /* Connecting. Continues in rexmpp_process_conn_err, possibly
+ leading to stream opening. */
+ if (s->tcp_state == REXMPP_TCP_CONNECTING) {
+ rexmpp_process_conn_err(s,
+ rexmpp_tcp_conn_proceed(&s->server_connection,
+ read_fds, write_fds));
+ }
+
+ /* The things we do while connected. */
+ if (s->tcp_state == REXMPP_TCP_CONNECTED) {
+
+ /* Sending queued data. */
+ if (FD_ISSET(s->server_socket, write_fds) &&
+ s->send_buffer != NULL) {
+ rexmpp_send_continue(s);
+ }
+
+ /* Receiving data. Leads to all kinds of things. */
+ if (FD_ISSET(s->server_socket, read_fds) &&
+ s->stream_state != REXMPP_STREAM_NONE &&
+ s->tcp_state == REXMPP_TCP_CONNECTED &&
+ s->tls_state != REXMPP_TLS_HANDSHAKE) {
+ rexmpp_recv(s);
+ }
+
+ /* Performing a TLS handshake. A stream restart happens after
+ this, if everything goes well. */
+ if (s->tls_state == REXMPP_TLS_HANDSHAKE) {
+ rexmpp_tls_handshake(s);
+ }
+
+ /* Restarting a stream if needed after the above actions. Since it
+ involves resetting the parser, functions called by that parser
+ can't do it on their own. */
+ if (s->stream_state == REXMPP_STREAM_RESTART) {
+ xmlCtxtResetPush(s->xml_parser, "", 0, "", "utf-8");
+ rexmpp_stream_open(s);
+ }
+
+ /* Closing the stream once everything is sent. */
+ if (s->stream_state == REXMPP_STREAM_CLOSE_REQUESTED &&
+ s->send_buffer == NULL) {
+ rexmpp_close(s);
+ }
+
+ /* Closing TLS and TCP connections once stream is closed. If
+ there's no TLS, the TCP connection is closed at once
+ elsewhere. */
+ if (s->stream_state == REXMPP_STREAM_CLOSED &&
+ s->tls_state == REXMPP_TLS_CLOSING) {
+ int ret = gnutls_bye(s->gnutls_session, GNUTLS_SHUT_RDWR);
+ if (ret == GNUTLS_E_SUCCESS) {
+ s->tls_state = REXMPP_TLS_INACTIVE;
+ rexmpp_cleanup(s);
+ s->tcp_state = REXMPP_TCP_CLOSED;
+ }
+ }
+ }
+ if (s->tcp_state == REXMPP_TCP_CLOSED) {
+ return REXMPP_SUCCESS;
+ }
+ return REXMPP_E_AGAIN;
+}
+
+int rexmpp_fds(rexmpp_t *s, fd_set *read_fds, fd_set *write_fds) {
+ int conn_fd, max_fd = 0;
+
+ if (s->resolver_state != REXMPP_RESOLVER_NONE &&
+ s->resolver_state != REXMPP_RESOLVER_READY) {
+ max_fd = ares_fds(s->resolver_channel, read_fds, write_fds);
+ }
+
+ if (s->tcp_state == REXMPP_TCP_CONNECTING) {
+ conn_fd = rexmpp_tcp_conn_fds(&s->server_connection, read_fds, write_fds);
+ if (conn_fd > max_fd) {
+ max_fd = conn_fd;
+ }
+ }
+
+ if (s->tls_state == REXMPP_TLS_HANDSHAKE) {
+ if (gnutls_record_get_direction(s->gnutls_session) == 0) {
+ FD_SET(s->server_socket, read_fds);
+ } else {
+ FD_SET(s->server_socket, write_fds);
+ }
+ if (s->server_socket + 1 > max_fd) {
+ max_fd = s->server_socket + 1;
+ }
+ }
+
+ if (s->tcp_state == REXMPP_TCP_CONNECTED) {
+ FD_SET(s->server_socket, read_fds);
+ if (s->send_buffer != NULL) {
+ FD_SET(s->server_socket, write_fds);
+ }
+ if (s->server_socket + 1 > max_fd) {
+ max_fd = s->server_socket + 1;
+ }
+ }
+
+ return max_fd;
+}
+
+struct timeval *rexmpp_timeout (rexmpp_t *s,
+ struct timeval *max_tv,
+ struct timeval *tv)
+{
+ struct timeval *ret = max_tv;
+
+ if (s->resolver_state != REXMPP_RESOLVER_NONE &&
+ s->resolver_state != REXMPP_RESOLVER_READY) {
+ ret = ares_timeout(s->resolver_channel, max_tv, tv);
+ } else if (s->tcp_state == REXMPP_TCP_CONNECTING) {
+ ret = rexmpp_tcp_conn_timeout(&s->server_connection, max_tv, tv);
+ }
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ if (s->reconnect_number > 0 &&
+ s->next_reconnect_time.tv_sec > now.tv_sec &&
+ (ret == NULL ||
+ s->next_reconnect_time.tv_sec - now.tv_sec < ret->tv_sec)) {
+ tv->tv_sec = s->next_reconnect_time.tv_sec - now.tv_sec;
+ tv->tv_usec = 0;
+ ret = tv;
+ }
+
+ return ret;
+}
diff --git a/src/rexmpp.h b/src/rexmpp.h
new file mode 100644
index 0000000..662d540
--- /dev/null
+++ b/src/rexmpp.h
@@ -0,0 +1,286 @@
+/**
+ @file rexmpp.h
+ @brief rexmpp, a reusable XMPP IM client library.
+ @author defanor <defanor@uberspace.net>
+ @date 2020
+ @copyright MIT license.
+*/
+
+#ifndef REXMPP_H
+#define REXMPP_H
+
+#include <ares.h>
+#include <gnutls/gnutls.h>
+#include <gsasl.h>
+#include <libxml/tree.h>
+#include "rexmpp_tcp.h"
+
+
+typedef struct rexmpp rexmpp_t;
+typedef void (*rexmpp_iq_callback_t) (rexmpp_t *s, xmlNodePtr request, xmlNodePtr response);
+
+typedef struct rexmpp_iq rexmpp_iq_t;
+struct rexmpp_iq
+{
+ xmlNodePtr request;
+ rexmpp_iq_callback_t cb;
+ rexmpp_iq_t *next;
+};
+
+
+/** @brief DNS resolver state */
+enum resolver_st {
+ REXMPP_RESOLVER_NONE,
+ REXMPP_RESOLVER_READY,
+ REXMPP_RESOLVER_SRV,
+ REXMPP_RESOLVER_SRV_2,
+ REXMPP_RESOLVER_FAILURE
+};
+
+/** @brief TCP connection state */
+enum tcp_st {
+ REXMPP_TCP_NONE,
+ REXMPP_TCP_CONNECTING,
+ REXMPP_TCP_CONNECTED,
+ REXMPP_TCP_CLOSED,
+ REXMPP_TCP_CONNECTION_FAILURE,
+ REXMPP_TCP_ERROR
+};
+
+/** @brief XML stream state */
+enum stream_st {
+ REXMPP_STREAM_NONE,
+ REXMPP_STREAM_OPENING,
+ REXMPP_STREAM_NEGOTIATION,
+ REXMPP_STREAM_STARTTLS,
+ REXMPP_STREAM_SASL,
+ REXMPP_STREAM_BIND,
+ REXMPP_STREAM_SM_FULL,
+ REXMPP_STREAM_SM_ACKS,
+ REXMPP_STREAM_SM_RESUME,
+ REXMPP_STREAM_RESTART,
+ REXMPP_STREAM_READY,
+ REXMPP_STREAM_CLOSE_REQUESTED,
+ REXMPP_STREAM_CLOSING,
+ REXMPP_STREAM_CLOSED,
+ REXMPP_STREAM_ERROR
+};
+
+/** @brief TLS state */
+enum tls_st {
+ REXMPP_TLS_INACTIVE,
+ REXMPP_TLS_AWAITING_DIRECT,
+ REXMPP_TLS_HANDSHAKE,
+ REXMPP_TLS_ACTIVE,
+ REXMPP_TLS_CLOSING,
+ REXMPP_TLS_CLOSED,
+ REXMPP_TLS_ERROR
+};
+
+/** @brief SASL state */
+enum sasl_st {
+ REXMPP_SASL_INACTIVE,
+ REXMPP_SASL_NEGOTIATION,
+ REXMPP_SASL_ACTIVE,
+ REXMPP_SASL_ERROR
+};
+
+/** @brief Stream management state */
+enum sm_st {
+ REXMPP_SM_INACTIVE,
+ REXMPP_SM_NEGOTIATION,
+ REXMPP_SM_ACTIVE
+};
+
+/** @brief Carbons state */
+enum carbons_st {
+ REXMPP_CARBONS_INACTIVE,
+ REXMPP_CARBONS_NEGOTIATION,
+ REXMPP_CARBONS_ACTIVE,
+ REXMPP_CARBONS_DISABLED
+};
+
+/** Error codes. */
+enum rexmpp_err {
+ /** An operation is finished. */
+ REXMPP_SUCCESS,
+ /** An operation is in progress. */
+ REXMPP_E_AGAIN,
+ /** A message can't be queued for sending, because the queue is
+ full. */
+ REXMPP_E_SEND_QUEUE_FULL,
+ /** The library can't take responsibility for message delivery (and
+ doesn't try to send it), because XEP-0198 stanza queue is
+ full. */
+ REXMPP_E_STANZA_QUEUE_FULL,
+ /** An operation (reading or sending) was cancelled by a user. */
+ REXMPP_E_CANCELLED,
+ /** An attempt to send while send buffer is empty. */
+ REXMPP_E_SEND_BUFFER_EMPTY,
+ /** An attempt to start sending while send buffer is not empty. */
+ REXMPP_E_SEND_BUFFER_NOT_EMPTY,
+ /** SASL-related error. */
+ REXMPP_E_SASL,
+ /** TLS-related error. */
+ REXMPP_E_TLS,
+ /** TCP-related error. */
+ REXMPP_E_TCP,
+ /** DNS-related error. */
+ REXMPP_E_DNS,
+ /** XML-related error. */
+ REXMPP_E_XML,
+ /** Failure to allocate memory. */
+ REXMPP_E_MALLOC
+};
+typedef enum rexmpp_err rexmpp_err_t;
+
+typedef void (*log_function_t) (rexmpp_t *s, int priority, const char *format, va_list args);
+typedef int (*sasl_property_cb_t) (Gsasl *ctx, Gsasl_session *sctx, Gsasl_property prop);
+typedef int (*xml_in_cb_t) (rexmpp_t *s, xmlNodePtr node);
+typedef int (*xml_out_cb_t) (rexmpp_t *s, xmlNodePtr node);
+
+/** @brief Complete connection state */
+struct rexmpp
+{
+ /* Numeric states: governing future actions, helping to recall where
+ we were at before returning from rexmpp_run, and communicating
+ the overall current state to a user. */
+ enum resolver_st resolver_state;
+ enum tcp_st tcp_state;
+ enum stream_st stream_state;
+ enum tls_st tls_state;
+ enum sasl_st sasl_state;
+ enum sm_st sm_state;
+ enum carbons_st carbons_state;
+
+ /* Basic configuration. */
+ const char *initial_jid;
+
+ /* Resource limits. */
+ uint32_t stanza_queue_size;
+ uint32_t send_queue_size;
+ uint32_t iq_queue_size;
+
+ /* Callbacks. */
+ log_function_t log_function;
+ sasl_property_cb_t sasl_property_cb;
+ xml_in_cb_t xml_in_cb;
+ xml_out_cb_t xml_out_cb;
+
+ /* Stream-related state. */
+ char *assigned_jid;
+ xmlNodePtr stream_features;
+
+ /* IQs we're waiting for responses to. */
+ rexmpp_iq_t *active_iq;
+
+ /* Connection and stream management. */
+ unsigned int id_counter;
+ unsigned int reconnect_number;
+ struct timeval next_reconnect_time;
+ xmlNodePtr stanza_queue;
+ uint32_t stanzas_out_count;
+ uint32_t stanzas_out_acknowledged;
+ uint32_t stanzas_in_count;
+ char *stream_id;
+
+ /* DNS-related structures. */
+ ares_channel resolver_channel;
+ struct ares_srv_reply *server_srv;
+ struct ares_srv_reply *server_srv_cur;
+ struct ares_srv_reply *server_srv_tls;
+ struct ares_srv_reply *server_srv_tls_cur;
+
+ /* The primary socket used for communication with the server. */
+ int server_socket;
+
+ /* A structure used to establish a TCP connection. */
+ rexmpp_tcp_conn_t server_connection;
+
+ /* Send buffer. NULL if there is nothing to send (and must not be
+ NULL if there is anything in the send queue). Not appending data
+ to it, see send_queue for queuing. */
+ char *send_buffer;
+ ssize_t send_buffer_len;
+ ssize_t send_buffer_sent;
+
+ /* A queue of XML elements to send. */
+ xmlNodePtr send_queue;
+
+ /* XML parser context, and current element pointer for building
+ XML nodes with a SAX2 parser interface. */
+ xmlParserCtxtPtr xml_parser;
+ xmlNodePtr current_element_root;
+ xmlNodePtr current_element;
+
+ /* TLS structures. */
+ void *tls_session_data;
+ size_t tls_session_data_size;
+ gnutls_session_t gnutls_session;
+ gnutls_certificate_credentials_t gnutls_cred;
+
+ /* SASL structures. */
+ Gsasl *sasl_ctx;
+ Gsasl_session *sasl_session;
+};
+
+/**
+ @brief ::rexmpp structure initialisation.
+ @param[out] s An allocated structure.
+ @param[in] jid Initial bare JID.
+ @param[in] log_function A user-provided logging function, can be
+ NULL.
+ @param[in] sasl_property_cb A callback to ask for SASL properties
+ (such as password).
+ @param[in] xml_in_cb A function to handle incoming XML elements. It
+ is called before other processing, so it can alter the elements, or
+ interrupt processing by returning a non-zero value. Optional.
+ @param[in] xml_out_cb Akin to the previous one, but for outbound
+ elements.
+ @returns ::REXMPP_SUCCESS or some ::rexmpp_err error.
+ */
+rexmpp_err_t rexmpp_init (rexmpp_t *s,
+ const char *jid,
+ log_function_t log_function,
+ sasl_property_cb_t sasl_property_cb,
+ xml_in_cb_t xml_in_cb,
+ xml_out_cb_t xml_out_cb);
+/**
+ @brief ::rexmpp structure deinitialisation. This will free all the
+ allocated resources.
+ @param[in,out] s A structure to deinitialise.
+*/
+void rexmpp_done (rexmpp_t *s);
+
+/**
+ @brief Runs a single iteration.
+ @param[in,out] s An initialised :rexmpp structure.
+ @param[in] File descriptors available for reading from.
+ @param[in] write_fds File descriptors available for writing to.
+
+ \callergraph
+*/
+rexmpp_err_t rexmpp_run (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds);
+
+/**
+ @brief Requests stream closing.
+*/
+rexmpp_err_t rexmpp_stop (rexmpp_t *s);
+
+/**
+ @brief Sends (or queues, or at least tries to, if everything goes
+ well) an XML element.
+ @param[in,out] s A ::rexmpp structure.
+ @param[in] node An XML element to send. The library assumes
+ ownership of the element, so it must not be freed by the caller.
+*/
+rexmpp_err_t rexmpp_send (rexmpp_t *s, xmlNodePtr node);
+
+struct timeval *rexmpp_timeout (rexmpp_t *s, struct timeval *max_tv, struct timeval *tv);
+int rexmpp_fds (rexmpp_t *s, fd_set *read_fds, fd_set *write_fds);
+
+
+char *rexmpp_xml_serialize (xmlNodePtr node);
+xmlNodePtr rexmpp_xml_add_id (rexmpp_t *s, xmlNodePtr node);
+
+#endif
diff --git a/src/rexmpp_tcp.c b/src/rexmpp_tcp.c
new file mode 100644
index 0000000..55e6c1b
--- /dev/null
+++ b/src/rexmpp_tcp.c
@@ -0,0 +1,358 @@
+/**
+ @file rexmpp_tcp.c
+ @brief TCP connection establishment.
+ @author defanor <defanor@uberspace.net>
+ @date 2020
+ @copyright MIT license.
+*/
+
+#include <ares.h>
+#include <netdb.h>
+#include <arpa/nameser.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <memory.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+
+#include "rexmpp_tcp.h"
+
+
+void rexmpp_dns_aaaa_cb (void *ptr,
+ int status,
+ int timeouts,
+ unsigned char *abuf,
+ int alen)
+{
+ rexmpp_tcp_conn_t *conn = ptr;
+ conn->resolver_status_v6 = status;
+ if (status == ARES_SUCCESS) {
+ conn->resolution_v6 = REXMPP_CONN_RESOLUTION_SUCCESS;
+ ares_parse_aaaa_reply(abuf, alen, &(conn->addr_v6), NULL, NULL);
+ conn->addr_cur_v6 = -1;
+ } else {
+ conn->resolution_v6 = REXMPP_CONN_RESOLUTION_FAILURE;
+ }
+}
+
+void rexmpp_dns_a_cb (void *ptr,
+ int status,
+ int timeouts,
+ unsigned char *abuf,
+ int alen)
+{
+ rexmpp_tcp_conn_t *conn = ptr;
+ conn->resolver_status_v4 = status;
+ if (status == ARES_SUCCESS) {
+ conn->resolution_v4 = REXMPP_CONN_RESOLUTION_SUCCESS;
+ ares_parse_a_reply(abuf, alen, &(conn->addr_v4), NULL, NULL);
+ conn->addr_cur_v4 = -1;
+ if (conn->resolution_v6 == REXMPP_CONN_RESOLUTION_WAITING) {
+ /* Wait for 50 ms for IPv6. */
+ gettimeofday(&(conn->next_connection_time), NULL);
+ conn->next_connection_time.tv_usec += REXMPP_TCP_IPV6_DELAY_MS * 1000;
+ if (conn->next_connection_time.tv_usec >= 1000000) {
+ conn->next_connection_time.tv_usec -= 1000000;
+ conn->next_connection_time.tv_sec++;
+ }
+ }
+ } else {
+ conn->resolution_v4 = REXMPP_CONN_RESOLUTION_FAILURE;
+ }
+}
+
+rexmpp_tcp_conn_error_t
+rexmpp_tcp_conn_init (rexmpp_tcp_conn_t *conn,
+ const char *host,
+ int port)
+{
+ int i;
+ for (i = 0; i < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS; i++) {
+ conn->sockets[i] = -1;
+ }
+ conn->connection_attempts = 0;
+ conn->port = port;
+ conn->addr_v4 = NULL;
+ conn->addr_v6 = NULL;
+ conn->resolver_error = ares_init(&(conn->resolver_channel));
+ conn->fd = -1;
+ conn->next_connection_time.tv_sec = 0;
+ conn->next_connection_time.tv_usec = 0;
+ if (conn->resolver_error) {
+ return REXMPP_CONN_RESOLVER_ERROR;
+ }
+
+ conn->resolution_v4 = REXMPP_CONN_RESOLUTION_INACTIVE;
+ conn->resolution_v6 = REXMPP_CONN_RESOLUTION_INACTIVE;
+
+ struct sockaddr_in addr_v4;
+ int flags;
+ if (inet_pton(AF_INET, host, &addr_v4)) {
+ addr_v4.sin_family = AF_INET;
+ addr_v4.sin_port = htons(port);
+ conn->sockets[conn->connection_attempts] =
+ socket(AF_INET, SOCK_STREAM, 0);
+ flags = fcntl(conn->sockets[conn->connection_attempts], F_GETFL, 0);
+ fcntl(conn->sockets[conn->connection_attempts], F_SETFL, flags | O_NONBLOCK);
+ if (connect(conn->sockets[conn->connection_attempts],
+ (struct sockaddr*)&addr_v4,
+ sizeof(addr_v4))) {
+ if (errno != EINPROGRESS) {
+ return REXMPP_CONN_ERROR;
+ }
+ } else {
+ return REXMPP_CONN_DONE;
+ }
+ conn->connection_attempts++;
+ return REXMPP_CONN_IN_PROGRESS;
+ }
+ struct sockaddr_in addr_v6;
+ if (inet_pton(AF_INET6, host, &addr_v6)) {
+ addr_v6.sin_family = AF_INET6;
+ addr_v6.sin_port = htons(port);
+ conn->sockets[conn->connection_attempts] =
+ socket(AF_INET6, SOCK_STREAM, 0);
+ flags = fcntl(conn->sockets[conn->connection_attempts], F_GETFL, 0);
+ fcntl(conn->sockets[conn->connection_attempts], F_SETFL, flags | O_NONBLOCK);
+ if (connect(conn->sockets[conn->connection_attempts],
+ (struct sockaddr*)&addr_v6,
+ sizeof(addr_v6))) {
+ if (errno != EINPROGRESS) {
+ return REXMPP_CONN_ERROR;
+ }
+ } else {
+ return REXMPP_CONN_DONE;
+ }
+ conn->connection_attempts++;
+ return REXMPP_CONN_IN_PROGRESS;
+ }
+
+ conn->resolution_v4 = REXMPP_CONN_RESOLUTION_WAITING;
+ conn->resolution_v6 = REXMPP_CONN_RESOLUTION_WAITING;
+
+ ares_query(conn->resolver_channel, host,
+ ns_c_in, ns_t_aaaa, rexmpp_dns_aaaa_cb, conn);
+ ares_query(conn->resolver_channel, host,
+ ns_c_in, ns_t_a, rexmpp_dns_a_cb, conn);
+
+ return REXMPP_CONN_IN_PROGRESS;
+}
+
+int rexmpp_tcp_conn_finish (rexmpp_tcp_conn_t *conn) {
+ int i;
+ for (i = 0; i < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS; i++) {
+ if (conn->sockets[i] != -1 && conn->sockets[i] != conn->fd) {
+ close(conn->sockets[i]);
+ conn->sockets[i] = -1;
+ }
+ }
+ ares_destroy(conn->resolver_channel);
+ if (conn->addr_v4 != NULL) {
+ ares_free_hostent(conn->addr_v4);
+ conn->addr_v4 = NULL;
+ }
+ if (conn->addr_v6 != NULL) {
+ ares_free_hostent(conn->addr_v6);
+ conn->addr_v6 = NULL;
+ }
+ return conn->fd;
+}
+
+int rexmpp_tcp_conn_ipv4_available(rexmpp_tcp_conn_t *conn) {
+ return (conn->resolution_v4 == REXMPP_CONN_RESOLUTION_SUCCESS &&
+ conn->addr_v4 != NULL &&
+ conn->addr_v4->h_addr_list[conn->addr_cur_v4 + 1] != NULL);
+}
+
+int rexmpp_tcp_conn_ipv6_available(rexmpp_tcp_conn_t *conn) {
+ return (conn->resolution_v6 == REXMPP_CONN_RESOLUTION_SUCCESS &&
+ conn->addr_v6 != NULL &&
+ conn->addr_v6->h_addr_list[conn->addr_cur_v6 + 1] != NULL);
+}
+
+rexmpp_tcp_conn_error_t
+rexmpp_tcp_conn_proceed (rexmpp_tcp_conn_t *conn,
+ fd_set *read_fds,
+ fd_set *write_fds)
+{
+ struct timeval now;
+ int i;
+
+ /* Check for successful connections. */
+ for (i = 0; i < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS; i++) {
+ int err;
+ socklen_t err_len = sizeof(err);
+ if (conn->sockets[i] != -1 && FD_ISSET(conn->sockets[i], write_fds)) {
+ if (getsockopt(conn->sockets[i], SOL_SOCKET, SO_ERROR, &err, &err_len)) {
+ return REXMPP_CONN_ERROR;
+ } else {
+ if (err == 0) {
+ conn->fd = conn->sockets[i];
+ return REXMPP_CONN_DONE;
+ } else if (err != EINPROGRESS) {
+ close(conn->sockets[i]);
+ conn->sockets[i] = -1;
+ }
+ }
+ }
+ }
+
+ /* Name resolution. */
+ if (conn->resolution_v4 == REXMPP_CONN_RESOLUTION_WAITING ||
+ conn->resolution_v6 == REXMPP_CONN_RESOLUTION_WAITING) {
+ ares_process(conn->resolver_channel, read_fds, write_fds);
+ }
+
+ /* New connections. */
+ int repeat;
+ do {
+ repeat = 0;
+ if (conn->connection_attempts < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS &&
+ (rexmpp_tcp_conn_ipv4_available(conn) ||
+ rexmpp_tcp_conn_ipv6_available(conn))) {
+ gettimeofday(&now, NULL);
+ if (now.tv_sec > conn->next_connection_time.tv_sec ||
+ (now.tv_sec == conn->next_connection_time.tv_sec &&
+ now.tv_usec >= conn->next_connection_time.tv_usec)) {
+ /* Time to attempt a new connection. */
+ int use_ipv6 = 0;
+ if (rexmpp_tcp_conn_ipv4_available(conn) &&
+ rexmpp_tcp_conn_ipv6_available(conn)) {
+ if (conn->addr_cur_v4 >= conn->addr_cur_v6) {
+ use_ipv6 = 1;
+ }
+ } else if (rexmpp_tcp_conn_ipv6_available(conn)) {
+ use_ipv6 = 1;
+ }
+
+ struct sockaddr_in6 addr_v6;
+ struct sockaddr_in addr_v4;
+ struct sockaddr *addr;
+ socklen_t addrlen;
+ int domain;
+
+ if (use_ipv6) {
+ conn->addr_cur_v6++;
+ memcpy(&addr_v6.sin6_addr,
+ conn->addr_v6->h_addr_list[conn->addr_cur_v6],
+ conn->addr_v6->h_length);
+ addr_v6.sin6_family = conn->addr_v6->h_addrtype;
+ addr_v6.sin6_port = htons(conn->port);
+ domain = conn->addr_v6->h_addrtype;
+ addr = (struct sockaddr*)&addr_v6;
+ addrlen = sizeof(addr_v6);
+ } else {
+ conn->addr_cur_v4++;
+ memcpy(&addr_v4.sin_addr,
+ conn->addr_v4->h_addr_list[conn->addr_cur_v4],
+ conn->addr_v4->h_length);
+ addr_v4.sin_family = conn->addr_v4->h_addrtype;
+ addr_v4.sin_port = htons(conn->port);
+ domain = conn->addr_v4->h_addrtype;
+ addr = (struct sockaddr*)&addr_v4;
+ addrlen = sizeof(addr_v4);
+ }
+
+ conn->sockets[conn->connection_attempts] =
+ socket(domain, SOCK_STREAM, 0);
+ int flags = fcntl(conn->sockets[conn->connection_attempts], F_GETFL, 0);
+ fcntl(conn->sockets[conn->connection_attempts], F_SETFL, flags | O_NONBLOCK);
+ if (connect(conn->sockets[conn->connection_attempts], addr, addrlen)) {
+ if (errno == EINPROGRESS) {
+ gettimeofday(&(conn->next_connection_time), NULL);
+ conn->next_connection_time.tv_usec += REXMPP_TCP_CONN_DELAY_MS * 1000;
+ if (conn->next_connection_time.tv_usec >= 1000000) {
+ conn->next_connection_time.tv_usec -= 1000000;
+ conn->next_connection_time.tv_sec++;
+ }
+ conn->connection_attempts++;
+ } else {
+ close(conn->sockets[conn->connection_attempts]);
+ conn->sockets[conn->connection_attempts] = -1;
+ if (conn->connection_attempts < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS &&
+ (rexmpp_tcp_conn_ipv4_available(conn) ||
+ rexmpp_tcp_conn_ipv6_available(conn))) {
+ repeat = 1;
+ }
+ }
+ } else {
+ conn->fd = conn->sockets[conn->connection_attempts];
+ return REXMPP_CONN_DONE;
+ }
+ }
+ }
+ } while (repeat);
+
+ int active_connections = 0;
+ for (i = 0; i < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS; i++) {
+ if (conn->sockets[i] != -1) {
+ active_connections++;
+ break;
+ }
+ }
+
+ gettimeofday(&now, NULL);
+
+ if (active_connections ||
+ conn->resolution_v4 == REXMPP_CONN_RESOLUTION_WAITING ||
+ conn->resolution_v6 == REXMPP_CONN_RESOLUTION_WAITING ||
+ (conn->next_connection_time.tv_sec > now.tv_sec ||
+ (conn->next_connection_time.tv_sec == now.tv_sec &&
+ conn->next_connection_time.tv_usec > now.tv_usec))) {
+ return REXMPP_CONN_IN_PROGRESS;
+ } else {
+ return REXMPP_CONN_FAILURE;
+ }
+}
+
+int rexmpp_tcp_conn_fds (rexmpp_tcp_conn_t *conn,
+ fd_set *read_fds,
+ fd_set *write_fds)
+{
+ int max_fd = 0, i;
+ max_fd = ares_fds(conn->resolver_channel, read_fds, write_fds);
+ for (i = 0; i < REXMPP_TCP_MAX_CONNECTION_ATTEMPTS; i++) {
+ if (conn->sockets[i] != -1) {
+ FD_SET(conn->sockets[i], write_fds);
+ if (max_fd < conn->sockets[i]) {
+ max_fd = conn->sockets[i] + 1;
+ }
+ }
+ }
+ return max_fd;
+}
+
+struct timeval *rexmpp_tcp_conn_timeout (rexmpp_tcp_conn_t *conn,
+ struct timeval *max_tv,
+ struct timeval *tv)
+{
+ struct timeval now;
+ struct timeval *ret;
+ ret = ares_timeout(conn->resolver_channel, max_tv, tv);
+ if (conn->resolution_v4 == REXMPP_CONN_RESOLUTION_SUCCESS ||
+ conn->resolution_v6 == REXMPP_CONN_RESOLUTION_SUCCESS) {
+ gettimeofday(&now, NULL);
+ if (now.tv_sec < conn->next_connection_time.tv_sec ||
+ (now.tv_sec == conn->next_connection_time.tv_sec &&
+ now.tv_usec <= conn->next_connection_time.tv_usec)) {
+ if (ret == NULL ||
+ ret->tv_sec > conn->next_connection_time.tv_sec - now.tv_sec ||
+ (ret->tv_sec == conn->next_connection_time.tv_sec - now.tv_sec &&
+ ret->tv_usec > conn->next_connection_time.tv_usec - now.tv_usec)) {
+ ret = tv;
+ tv->tv_sec = conn->next_connection_time.tv_sec - now.tv_sec;
+ if (conn->next_connection_time.tv_usec > now.tv_usec) {
+ tv->tv_usec = conn->next_connection_time.tv_usec - now.tv_usec;
+ } else {
+ tv->tv_usec = conn->next_connection_time.tv_usec + 1000000 - now.tv_usec;
+ tv->tv_sec--;
+ }
+ }
+ }
+ }
+ return ret;
+}
diff --git a/src/rexmpp_tcp.h b/src/rexmpp_tcp.h
new file mode 100644
index 0000000..124da2a
--- /dev/null
+++ b/src/rexmpp_tcp.h
@@ -0,0 +1,177 @@
+/**
+ @file rexmpp_tcp.h
+ @brief TCP connection establishment.
+ @author defanor <defanor@uberspace.net>
+ @date 2020
+ @copyright MIT license.
+
+ This module tries to establish a TCP connection to a given host
+ and port.
+
+ A connection establishment procedure begins with
+ ::rexmpp_tcp_conn_init, followed by repeated calls to
+ ::rexmpp_tcp_conn_proceed while the return code is
+ ::REXMPP_CONN_IN_PROGRESS, at the times suggested by
+ ::rexmpp_tcp_conn_timeout and on events suggested by
+ ::rexmpp_tcp_conn_fds, and ends with ::rexmpp_tcp_conn_finish.
+*/
+
+#ifndef REXMPP_TCP_H
+#define REXMPP_TCP_H
+
+#define REXMPP_TCP_MAX_CONNECTION_ATTEMPTS 20
+#define REXMPP_TCP_IPV6_DELAY_MS 50
+#define REXMPP_TCP_CONN_DELAY_MS 250
+
+typedef enum rexmpp_tcp_conn_resolution_status
+rexmpp_tcp_conn_resolution_status_t;
+
+/**
+ @brief Resolution status.
+ */
+enum rexmpp_tcp_conn_resolution_status {
+ /** The resolution is not active. */
+ REXMPP_CONN_RESOLUTION_INACTIVE,
+ /** Waiting for resolution. */
+ REXMPP_CONN_RESOLUTION_WAITING,
+ /** Resolved successfully. */
+ REXMPP_CONN_RESOLUTION_SUCCESS,
+ /** Failed to resolve. */
+ REXMPP_CONN_RESOLUTION_FAILURE
+};
+
+typedef enum rexmpp_tcp_conn_error rexmpp_tcp_conn_error_t;
+
+/**
+ @brief Connection errors.
+*/
+enum rexmpp_tcp_conn_error {
+ /** Connected, no error. */
+ REXMPP_CONN_DONE,
+ /** Resolver error occurred. The exact error code can be read from
+ the connection structure. */
+ REXMPP_CONN_RESOLVER_ERROR,
+ /** Connection in progress, no error yet. */
+ REXMPP_CONN_IN_PROGRESS,
+ /** All the connection attempts failed. */
+ REXMPP_CONN_FAILURE,
+ /** An unexpected error during connection. */
+ REXMPP_CONN_ERROR
+};
+
+typedef struct rexmpp_tcp_connection rexmpp_tcp_conn_t;
+
+/** @brief A connection establishment structure. */
+struct rexmpp_tcp_connection {
+ /** @brief A host we are connecting to. */
+ const char *host;
+ /** @brief A port we are connecting to. */
+ int port;
+
+ /** @brief Resolver channel. */
+ ares_channel resolver_channel;
+ /** @brief Resolver error is stored here when
+ ::REXMPP_CONN_RESOLVER_ERROR is returned. */
+ int resolver_error;
+
+ /** @brief State of A record resolution. */
+ enum rexmpp_tcp_conn_resolution_status resolution_v4;
+ /** @brief Status of A record resolution, as returned by the
+ resolver. */
+ int resolver_status_v4;
+ /** @brief AF_INET (IPv4) hostent structure. */
+ struct hostent *addr_v4;
+ /** @brief The AF_INET address number we are currently at. */
+ int addr_cur_v4;
+
+ /** @brief State of AAAA record resolution. */
+ enum rexmpp_tcp_conn_resolution_status resolution_v6;
+ /** @brief Status of AAAA record resolution, as returned by the
+ resolver. */
+ int resolver_status_v6;
+ /** @brief AF_INET6 (IPv6) hostent structure. */
+ struct hostent *addr_v6;
+ /** @brief The AF_INET6 address number we are currently at. */
+ int addr_cur_v6;
+
+ /** @brief Socket array, one for each connection attempt. */
+ int sockets[REXMPP_TCP_MAX_CONNECTION_ATTEMPTS];
+ /** @brief The number of connection attempts so far. */
+ int connection_attempts;
+
+ /** @brief Next scheduled connection time. */
+ struct timeval next_connection_time;
+ /** @brief File descriptor of a connected socket. */
+ int fd;
+};
+
+/**
+ @brief Initiates a connection.
+ @param[out] conn An allocated connection structure.
+ @param[in] host A host to connect to. This could be a domain name,
+ or a textual representation of an IPv4 or an IPv6 address.
+ @param[in] port A port to connect to.
+ @returns A ::rexmpp_tcp_conn_error state.
+*/
+rexmpp_tcp_conn_error_t
+rexmpp_tcp_conn_init (rexmpp_tcp_conn_t *conn,
+ const char *host,
+ int port);
+
+/**
+ @brief Continues a connection process.
+ @param[in,out] conn An active connection structure.
+ @param[in] read_fds File descriptors available for reading from.
+ @param[in] write_fds File descriptors available for writing to.
+ @returns A ::rexmpp_tcp_conn_error state.
+*/
+rexmpp_tcp_conn_error_t
+rexmpp_tcp_conn_proceed (rexmpp_tcp_conn_t *conn,
+ fd_set *read_fds,
+ fd_set *write_fds);
+
+/**
+ @brief Finalises a connection process.
+
+ Closes pending connections except for the established one, frees
+ additionally allocated resources.
+
+ Normally must be called on any state other than
+ ::REXMPP_CONN_IN_PROGRESS. The connection structure can be freed
+ after this.
+
+ @param[in,out] conn An active connection structure.
+ @returns A connected socket's file descriptor, or -1.
+ */
+int rexmpp_tcp_conn_finish (rexmpp_tcp_conn_t *conn);
+
+/**
+ @brief Reports file descriptors a connection process is interested in.
+
+ File descriptors are only added to an @c fd_set, so the ones it
+ already contains will not be lost.
+
+ @param[in] conn An active connection structure.
+ @param[out] read_fds File descriptors a connection process is
+ interested in reading from.
+ @param[out] write_fds File descriptors a connection process is
+ interested in writing to.
+ @returns Maximum file descriptor number, plus 1.
+ */
+int rexmpp_tcp_conn_fds (rexmpp_tcp_conn_t *conn,
+ fd_set *read_fds,
+ fd_set *write_fds);
+
+/**
+ @brief Reports timeouts.
+ @param[in] conn An active connection structure.
+ @param[in] max_tv An existing maximum timeout.
+ @param[out] tv A timeval structure to store a new timeout in.
+ @returns A pointer to either max_tv or tv, depending on which one
+ is smaller.
+*/
+struct timeval *rexmpp_tcp_conn_timeout (rexmpp_tcp_conn_t *conn,
+ struct timeval *max_tv,
+ struct timeval *tv);
+
+#endif