diff options
author | defanor <defanor@uberspace.net> | 2020-02-29 07:50:32 +0300 |
---|---|---|
committer | defanor <defanor@uberspace.net> | 2020-02-29 07:50:32 +0300 |
commit | 239248f77bb21139b158950a548ff84ada4b3bf1 (patch) | |
tree | c489e2c16703bff37b984772a5ab87dd57015594 |
Add the draft
-rw-r--r-- | COPYING | 20 | ||||
-rw-r--r-- | Doxyfile.in | 2280 | ||||
-rw-r--r-- | Makefile.am | 4 | ||||
-rw-r--r-- | README | 85 | ||||
-rw-r--r-- | configure.ac | 48 | ||||
-rw-r--r-- | examples/basic.c | 138 | ||||
-rw-r--r-- | rexmpp.pc.in | 11 | ||||
-rw-r--r-- | src/Makefile.am | 14 | ||||
-rw-r--r-- | src/rexmpp.c | 1580 | ||||
-rw-r--r-- | src/rexmpp.h | 286 | ||||
-rw-r--r-- | src/rexmpp_tcp.c | 358 | ||||
-rw-r--r-- | src/rexmpp_tcp.h | 177 |
12 files changed, 5001 insertions, 0 deletions
@@ -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 @@ -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 |