Commit 76d74509 authored by Ian Craggs's avatar Ian Craggs

The rest of the initial release commit

parent 585b5acb
# Doxyfile 1.5.8
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project
#
# All text after a 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.
DOXYFILE_ENCODING = UTF-8
# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
# by quotes) that should identify the project.
PROJECT_NAME = "MQTT Client"
# 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 =
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
# base path where the generated documentation will be put.
# 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 = "docs/"
# 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 cause performance problems for the file system.
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.
# The default language is English, other supported languages are:
# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek,
# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages),
# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish,
# Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene,
# Spanish, Swedish, and Ukrainian.
OUTPUT_LANGUAGE = English
# If the BRIEF_MEMBER_DESC tag is set to YES (the default) 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.
BRIEF_MEMBER_DESC = YES
# If the REPEAT_BRIEF tag is set to YES (the default) 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.
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" "the"
ABBREVIATE_BRIEF = "The $name class" \
"The $name widget" \
"The $name file" \
is \
provides \
specifies \
contains \
represents \
a \
an \
the
# 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.
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.
INLINE_INHERITED_MEMB = NO
# If the FULL_PATH_NAMES tag is set to YES then 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.
FULL_PATH_NAMES = NO
# If the FULL_PATH_NAMES tag is set to YES then 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.
STRIP_FROM_PATH = /Users/dimitri/doxygen/mail/1.5.7/doxywizard/
# 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 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.
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
# comments will behave just like regular Qt-style comments
# (thus requiring an explicit @brief command for a brief description.)
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 comments
# will behave just like regular Qt-style comments (thus requiring
# an explicit \brief command for a brief description.)
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 behaviour.
# 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 behaviour instead.
MULTILINE_CPP_IS_BRIEF = NO
# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
# member inherits the documentation from any documented member that it
# re-implements.
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.
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.
TAB_SIZE = 8
# This tag can be used to specify a number of aliases that acts
# 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 =
# 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.
OPTIMIZE_OUTPUT_FOR_C = YES
# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
# sources only. Doxygen will then generate output that is more tailored for
# Java. For instance, namespaces will be presented as packages, qualified
# scopes will look different, etc.
OPTIMIZE_OUTPUT_JAVA = NO
# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
# sources only. Doxygen will then generate output that is more tailored for
# Fortran.
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.
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, C, C++. 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
EXTENSION_MAPPING =
# 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); v.s.
# func(std::string) {}). This also make the inheritance and collaboration
# diagrams that involve STL classes more complete and accurate.
BUILTIN_STL_SUPPORT = NO
# If you use Microsoft's C++/CLI language, you should set this option to YES to
# enable parsing support.
CPP_CLI_SUPPORT = NO
# Set the SIP_SUPPORT tag to YES if your project consists of sip 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.
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 (the default)
# 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.
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.
DISTRIBUTE_GROUP_DOC = NO
# Set the SUBGROUPING tag to YES (the default) 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.
SUBGROUPING = YES
# When TYPEDEF_HIDES_STRUCT 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.
TYPEDEF_HIDES_STRUCT = NO
# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
# determine which symbols to keep in memory and which to flush to disk.
# When the cache is full, less often used symbols will be written to disk.
# For small to medium size projects (<1000 input files) the default value is
# probably good enough. For larger projects a too small cache size can cause
# doxygen to be busy swapping symbols to and from disk most of the time
# causing a significant performance penality.
# If the system has enough physical memory increasing the cache will improve the
# performance by keeping more symbols in memory. Note that the value works on
# a logarithmic scale so increasing the size by one will rougly double the
# memory usage. The cache size is given by this formula:
# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
# corresponding to a cache size of 2^16 = 65536 symbols
SYMBOL_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 and EXTRACT_STATIC tags are set to YES
EXTRACT_ALL = YES
# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
# will be included in the documentation.
EXTRACT_PRIVATE = NO
# If the EXTRACT_STATIC tag is set to YES all static members of a file
# will be included in the documentation.
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.
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 (the default) only methods in the interface are included.
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.
EXTRACT_ANON_NSPACES = NO
# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
# undocumented members of documented classes, files or namespaces.
# If set to NO (the default) 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.
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 (the default) these classes will be included in the various
# overviews. This option has no effect if EXTRACT_ALL is enabled.
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 (the default) these declarations will be included in the
# documentation.
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 (the default) these blocks will be appended to the
# function's detailed documentation block.
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 (the default) then the documentation will be excluded.
# Set it to YES to include the internal documentation.
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.
CASE_SENSE_NAMES = NO
# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
# will show members with their full class and namespace scopes in the
# documentation. If set to YES the scope will be hidden.
HIDE_SCOPE_NAMES = YES
# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
# will put a list of the files that are included by a file in the documentation
# of that file.
SHOW_INCLUDE_FILES = YES
# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
# is inserted in the documentation for inline members.
INLINE_INFO = YES
# If the SORT_MEMBER_DOCS tag is set to YES (the default) 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.
SORT_MEMBER_DOCS = NO
# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
# brief documentation of file, namespace and class members alphabetically
# by member name. If set to NO (the default) the members will appear in
# declaration order.
SORT_BRIEF_DOCS = 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 default)
# the group names will appear in their defined order.
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 default), 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.
SORT_BY_SCOPE_NAME = 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.
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.
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.
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.
GENERATE_DEPRECATEDLIST= YES
# The ENABLED_SECTIONS tag can be used to enable conditional
# documentation sections, marked by \if sectionname ... \endif.
ENABLED_SECTIONS =
# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
# the initial value of a variable or define consists of 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 initializer of individual variables and defines in the
# documentation can be controlled using \showinitializer or \hideinitializer
# command in the documentation regardless of this setting.
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.
SHOW_USED_FILES = YES
# If the sources in your project are distributed over multiple directories
# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
# in the documentation. The default is NO.
SHOW_DIRECTORIES = NO
# 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 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 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. See the manual for examples.
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. The 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.
LAYOUT_FILE =
#---------------------------------------------------------------------------
# configuration options related to warning and progress messages
#---------------------------------------------------------------------------
# The QUIET tag can be used to turn on/off the messages that are generated
# by doxygen. Possible values are YES and NO. If left blank NO is used.
QUIET = NO
# The WARNINGS tag can be used to turn on/off the warning messages that are
# generated by doxygen. Possible values are YES and NO. If left blank
# NO is used.
WARNINGS = YES
# If WARN_IF_UNDOCUMENTED 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.
WARN_IF_UNDOCUMENTED = YES
# If WARN_IF_DOC_ERROR 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.
WARN_IF_DOC_ERROR = YES
# This WARN_NO_PARAMDOC option can be abled to get warnings for
# functions that are documented, but have no documentation for their parameters
# or return value. If set to NO (the default) doxygen will only warn about
# wrong or incomplete parameter documentation, but not about the absence of
# documentation.
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)
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 stderr.
WARN_LOGFILE =
#---------------------------------------------------------------------------
# configuration options related to the input files
#---------------------------------------------------------------------------
# The INPUT tag can be 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.
INPUT = MQTTClient.h \
MQTTClientPersistence.h
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
# also the default input encoding. 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.
INPUT_ENCODING = UTF-8
# The RECURSIVE tag can be used to turn specify whether or not subdirectories
# should be searched for input files as well. Possible values are YES and NO.
# If left blank NO is used.
RECURSIVE = NO
# The EXCLUDE tag can be used to specify files and/or directories that should
# 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.
EXCLUDE =
# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
# directories that are symbolic links (a Unix filesystem feature) are excluded
# from the input.
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
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.
# Possible values are YES and NO. If left blank NO is used.
EXAMPLE_RECURSIVE = NO
# The IMAGE_PATH tag can be used to specify one or more files or
# directories that contain image that are 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.
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
# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
# is applied to all files.
FILTER_PATTERNS =
# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
# INPUT_FILTER) will be used to filter the input files when producing source
# files to browse (i.e. when SOURCE_BROWSER is set to YES).
FILTER_SOURCE_FILES = NO
#---------------------------------------------------------------------------
# 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 also
# VERBATIM_HEADERS is set to NO.
SOURCE_BROWSER = NO
# Setting the INLINE_SOURCES tag to YES will include the body
# of functions and classes directly in the documentation.
INLINE_SOURCES = NO
# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
# doxygen to hide any special comment blocks from generated source code
# fragments. Normal C and C++ comments will always remain visible.
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.
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.
REFERENCES_RELATION = NO
# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
# 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.
REFERENCES_LINK_SOURCE = 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.
USE_HTAGS = NO
# If the VERBATIM_HEADERS tag is set to YES (the default) 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.
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.
ALPHABETICAL_INDEX = NO
# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
# in which this list will be split (can be a number in the range [1..20])
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 one or more prefixes that
# should be ignored while generating the index headers.
IGNORE_PREFIX =
#---------------------------------------------------------------------------
# configuration options related to the HTML output
#---------------------------------------------------------------------------
# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
# generate HTML output.
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. If left blank `html' will be used as the default path.
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). If it is left blank
# doxygen will generate files with .html extension.
HTML_FILE_EXTENSION = .html
# The HTML_HEADER tag can be used to specify a personal HTML header for
# each generated HTML page. If it is left blank doxygen will generate a
# standard header.
HTML_HEADER =
# The HTML_FOOTER tag can be used to specify a personal HTML footer for
# each generated HTML page. If it is left blank doxygen will generate a
# standard footer.
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 the tag is left blank doxygen
# will generate a default style sheet. Note that doxygen will try to copy
# the style sheet file to the HTML output directory, so don't put your own
# stylesheet in the HTML output directory as well, or it will be erased!
HTML_STYLESHEET =
# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
# files or namespaces will be aligned in HTML using tables. If set to
# NO a bullet list will be used.
HTML_ALIGN_MEMBERS = YES
# 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. For this to work a browser that supports
# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
HTML_DYNAMIC_SECTIONS = NO
# 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, 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.
GENERATE_DOCSET = NO
# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
# 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.
DOCSET_FEEDNAME = "Doxygen generated docs"
# When GENERATE_DOCSET tag is set to YES, 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.
DOCSET_BUNDLE_ID = org.doxygen.Project
# If the GENERATE_HTMLHELP tag is set to YES, additional index files
# will be generated that can be used as input for tools like the
# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
# of the generated HTML documentation.
GENERATE_HTMLHELP = NO
# If the GENERATE_HTMLHELP tag is set to YES, 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.
CHM_FILE =
# If the GENERATE_HTMLHELP tag is set to YES, 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.
HHC_LOCATION =
# If the GENERATE_HTMLHELP tag is set to YES, 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).
GENERATE_CHI = NO
# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
# is used to encode HtmlHelp index (hhk), content (hhc) and project file
# content.
CHM_INDEX_ENCODING =
# If the GENERATE_HTMLHELP tag is set to YES, 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.
BINARY_TOC = NO
# The TOC_EXPAND flag can be set to YES to add extra items for group members
# to the contents of the HTML help documentation and to the tree view.
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.
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.
QCH_FILE =
# The QHP_NAMESPACE tag specifies the namespace to use when generating
# Qt Help Project output. For more information please see
# http://doc.trolltech.com/qthelpproject.html#namespace
QHP_NAMESPACE =
# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
# Qt Help Project output. For more information please see
# http://doc.trolltech.com/qthelpproject.html#virtual-folders
QHP_VIRTUAL_FOLDER = doc
# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add.
# For more information please see
# http://doc.trolltech.com/qthelpproject.html#custom-filters
QHP_CUST_FILTER_NAME =
# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see
# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">Qt Help Project / Custom Filters</a>.
QHP_CUST_FILTER_ATTRS =
# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's
# filter section matches.
# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">Qt Help Project / Filter Attributes</a>.
QHP_SECT_FILTER_ATTRS =
# If the GENERATE_QHP tag is set to YES, 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.
QHG_LOCATION =
# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
# top of each HTML page. The value NO (the default) enables the index and
# the value YES disables it.
DISABLE_INDEX = NO
# This tag can be used to set the number of enum values (range [1..20])
# that doxygen will group on one line in the generated HTML documentation.
ENUM_VALUES_PER_LINE = 4
# 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 FRAME, 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 (for instance Mozilla 1.0+,
# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are
# probably better off using the HTML help feature. Other possible values
# for this tag are: HIERARCHIES, which will generate the Groups, Directories,
# and Class Hierarchy pages using a tree view instead of an ordered list;
# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which
# disables this behavior completely. For backwards compatibility with previous
# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE
# respectively.
GENERATE_TREEVIEW = NONE
# 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.
TREEVIEW_WIDTH = 250
# Use this tag to change the font size of Latex formulas included
# as images in the HTML documentation. The default is 10. Note that
# 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.
FORMULA_FONTSIZE = 10
#---------------------------------------------------------------------------
# configuration options related to the LaTeX output
#---------------------------------------------------------------------------
# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
# generate Latex output.
GENERATE_LATEX = NO
# 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. If left blank `latex' will be used as the default path.
LATEX_OUTPUT = latex
# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
# invoked. If left blank `latex' will be used as the default command name.
LATEX_CMD_NAME = latex
# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
# generate index for LaTeX. If left blank `makeindex' will be used as the
# default command name.
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.
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, a4wide, letter, legal and
# executive. If left blank a4wide will be used.
PAPER_TYPE = a4wide
# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
# packages that should be included in the LaTeX output.
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. Notice: only use this tag if you know what you are doing!
LATEX_HEADER =
# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
# is prepared for conversion to pdf (using ps2pdf). 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.
PDF_HYPERLINKS = YES
# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
# plain latex in the generated Makefile. Set this option to YES to get a
# higher quality PDF documentation.
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.
LATEX_BATCHMODE = NO
# If LATEX_HIDE_INDICES is set to YES then doxygen will not
# include the index chapters (such as File Index, Compound Index, etc.)
# in the output.
LATEX_HIDE_INDICES = NO
#---------------------------------------------------------------------------
# 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 very pretty with
# other RTF readers or editors.
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. If left blank `rtf' will be used as the default path.
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.
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 other
# programs which support those fields.
# Note: wordpad (write) and others do not support links.
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.
RTF_STYLESHEET_FILE =
# Set optional variables used in the generation of an rtf document.
# Syntax is similar to doxygen's config file.
RTF_EXTENSIONS_FILE =
#---------------------------------------------------------------------------
# configuration options related to the man page output
#---------------------------------------------------------------------------
# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
# generate man pages
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. If left blank `man' will be used as the default path.
MAN_OUTPUT = man
# The MAN_EXTENSION tag determines the extension that is added to
# the generated man pages (default is the subroutine's section .3)
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 is NO.
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.
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. If left blank `xml' will be used as the default path.
XML_OUTPUT = xml
# The XML_SCHEMA tag can be used to specify an XML schema,
# which can be used by a validating XML parser to check the
# syntax of the XML files.
XML_SCHEMA =
# The XML_DTD tag can be used to specify an XML DTD,
# which can be used by a validating XML parser to check the
# syntax of the XML files.
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.
XML_PROGRAMLISTING = YES
#---------------------------------------------------------------------------
# configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------
# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
# generate an AutoGen Definitions (see 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.
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.
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.
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.
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.
PERLMOD_MAKEVAR_PREFIX =
#---------------------------------------------------------------------------
# Configuration options related to the preprocessor
#---------------------------------------------------------------------------
# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
# evaluate all C-preprocessor directives found in the sources and include
# files.
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 (the default) only conditional
# compilation will be performed. Macro expansion can be done in a controlled
# way by setting EXPAND_ONLY_PREDEF to YES.
MACRO_EXPANSION = YES
# 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.
EXPAND_ONLY_PREDEF = NO
# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
# in the INCLUDE_PATH (see below) will be search if a #include is found.
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.
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.
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
# 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.
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.
EXPAND_AS_DEFINED =
# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
# doxygen's preprocessor will remove all 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.
SKIP_FUNCTION_MACROS = YES
#---------------------------------------------------------------------------
# Configuration::additions related to external references
#---------------------------------------------------------------------------
# The TAGFILES option can be used to specify one or more tagfiles.
# Optionally an initial location of the external documentation
# can be added for each tagfile. 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. If a location is present for each tag, the installdox tool
# does not have to be run to correct the links.
# Note that each tag file must have a 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.
GENERATE_TAGFILE =
# If the ALLEXTERNALS tag is set to YES all external classes will be listed
# in the class index. If set to NO only the inherited external classes
# will be listed.
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.
EXTERNAL_GROUPS = YES
# The PERL_PATH should be the absolute path and name of the perl script
# interpreter (i.e. the result of `which perl').
PERL_PATH = /usr/bin/perl
#---------------------------------------------------------------------------
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
# or super classes. Setting the tag to NO turns the diagrams off. Note that
# this option is superseded by the HAVE_DOT option below. This is only a
# fallback. It is recommended to install and use dot, since it yields more
# powerful graphs.
CLASS_DIAGRAMS = NO
# 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.
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, 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)
HAVE_DOT = NO
# By default doxygen will write a font called FreeSans.ttf to the output
# directory and reference it in all dot files that doxygen generates. This
# font does not include all possible unicode characters however, so when you need
# these (or just want a differently looking font) you can specify the font name
# using DOT_FONTNAME. You need 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.
DOT_FONTNAME = FreeSans
# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
# The default size is 10pt.
DOT_FONTSIZE = 10
# By default doxygen will tell dot to use the output directory to look for the
# FreeSans.ttf font (which doxygen will put there itself). If you specify a
# different font using DOT_FONTNAME you can set the path where dot
# can find it using this tag.
DOT_FONTPATH =
# If the CLASS_GRAPH and HAVE_DOT tags are 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
# the CLASS_DIAGRAMS tag to NO.
CLASS_GRAPH = YES
# If the COLLABORATION_GRAPH and HAVE_DOT tags are 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.
COLLABORATION_GRAPH = YES
# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
# will generate a graph for groups, showing the direct groups dependencies
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.
UML_LOOK = NO
# If set to YES, the inheritance and collaboration graphs will show the
# relations between templates and their instances.
TEMPLATE_RELATIONS = NO
# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
# 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.
INCLUDE_GRAPH = YES
# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
# documented header file showing the documented files that directly or
# indirectly include this file.
INCLUDED_BY_GRAPH = YES
# If the CALL_GRAPH and HAVE_DOT options are 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.
CALL_GRAPH = YES
# If the CALLER_GRAPH and HAVE_DOT tags are 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.
CALLER_GRAPH = YES
# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
# will graphical hierarchy of all classes instead of a textual one.
GRAPHICAL_HIERARCHY = YES
# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are 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.
DIRECTORY_GRAPH = YES
# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
# generated by dot. Possible values are png, jpg, or gif
# If left blank png will be used.
DOT_IMAGE_FORMAT = png
# The tag DOT_PATH 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.
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).
DOTFILE_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.
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.
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).
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.
DOT_MULTI_TARGETS = NO
# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
# generate a legend page explaining the meaning of the various boxes and
# arrows in the dot generated graphs.
GENERATE_LEGEND = YES
# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
# remove the intermediate dot files that are used to generate
# the various graphs.
DOT_CLEANUP = YES
#---------------------------------------------------------------------------
# Options related to the search engine
#---------------------------------------------------------------------------
# The SEARCHENGINE tag specifies whether or not a search engine should be
# used. If set to NO the values of all tags below this one will be ignored.
SEARCHENGINE = NO
# Doxyfile 1.5.6
#---------------------------------------------------------------------------
# Project related configuration options
#---------------------------------------------------------------------------
DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = "MQTT C Client"
PROJECT_NUMBER =
OUTPUT_DIRECTORY = "/home/icraggs/workspaces/6.2.1/Small Client/"
CREATE_SUBDIRS = NO
OUTPUT_LANGUAGE = English
BRIEF_MEMBER_DESC = YES
REPEAT_BRIEF = YES
ABBREVIATE_BRIEF = "The $name class" \
"The $name widget" \
"The $name file" \
is \
provides \
specifies \
contains \
represents \
a \
an \
the
ALWAYS_DETAILED_SEC = NO
INLINE_INHERITED_MEMB = NO
FULL_PATH_NAMES = YES
STRIP_FROM_PATH = "/home/icraggs/workspaces/6.2.1/Small Client/"
STRIP_FROM_INC_PATH =
SHORT_NAMES = NO
JAVADOC_AUTOBRIEF = YES
QT_AUTOBRIEF = NO
MULTILINE_CPP_IS_BRIEF = NO
DETAILS_AT_TOP = NO
INHERIT_DOCS = YES
SEPARATE_MEMBER_PAGES = NO
TAB_SIZE = 8
ALIASES =
OPTIMIZE_OUTPUT_FOR_C = YES
OPTIMIZE_OUTPUT_JAVA = NO
OPTIMIZE_FOR_FORTRAN = NO
OPTIMIZE_OUTPUT_VHDL = NO
BUILTIN_STL_SUPPORT = NO
CPP_CLI_SUPPORT = NO
SIP_SUPPORT = NO
IDL_PROPERTY_SUPPORT = YES
DISTRIBUTE_GROUP_DOC = NO
SUBGROUPING = YES
TYPEDEF_HIDES_STRUCT = NO
#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------
EXTRACT_ALL = NO
EXTRACT_PRIVATE = YES
EXTRACT_STATIC = YES
EXTRACT_LOCAL_CLASSES = YES
EXTRACT_LOCAL_METHODS = NO
EXTRACT_ANON_NSPACES = NO
HIDE_UNDOC_MEMBERS = NO
HIDE_UNDOC_CLASSES = NO
HIDE_FRIEND_COMPOUNDS = NO
HIDE_IN_BODY_DOCS = NO
INTERNAL_DOCS = NO
CASE_SENSE_NAMES = YES
HIDE_SCOPE_NAMES = NO
SHOW_INCLUDE_FILES = YES
INLINE_INFO = YES
SORT_MEMBER_DOCS = YES
SORT_BRIEF_DOCS = NO
SORT_GROUP_NAMES = NO
SORT_BY_SCOPE_NAME = NO
GENERATE_TODOLIST = YES
GENERATE_TESTLIST = YES
GENERATE_BUGLIST = YES
GENERATE_DEPRECATEDLIST= YES
ENABLED_SECTIONS =
MAX_INITIALIZER_LINES = 30
SHOW_USED_FILES = YES
SHOW_DIRECTORIES = NO
SHOW_FILES = YES
SHOW_NAMESPACES = YES
FILE_VERSION_FILTER =
#---------------------------------------------------------------------------
# configuration options related to warning and progress messages
#---------------------------------------------------------------------------
QUIET = NO
WARNINGS = YES
WARN_IF_UNDOCUMENTED = YES
WARN_IF_DOC_ERROR = YES
WARN_NO_PARAMDOC = NO
WARN_FORMAT = "$file:$line: $text"
WARN_LOGFILE =
#---------------------------------------------------------------------------
# configuration options related to the input files
#---------------------------------------------------------------------------
INPUT = "/home/icraggs/workspaces/6.2.1/Small Client/"
INPUT_ENCODING = UTF-8
FILE_PATTERNS = *.cc \
*.cxx \
*.cpp \
*.c++ \
*.d \
*.java \
*.ii \
*.ixx \
*.ipp \
*.i++ \
*.inl \
*.h \
*.hh \
*.hxx \
*.hpp \
*.h++ \
*.idl \
*.odl \
*.cs \
*.php \
*.php3 \
*.inc \
*.m \
*.mm \
*.dox \
*.py \
*.f90 \
*.f \
*.vhd \
*.vhdl \
*.C \
*.CC \
*.C++ \
*.II \
*.I++ \
*.H \
*.HH \
*.H++ \
*.CS \
*.PHP \
*.PHP3 \
*.M \
*.MM \
*.PY \
*.F90 \
*.F \
*.VHD \
*.VHDL \
*.c
RECURSIVE = NO
EXCLUDE =
EXCLUDE_SYMLINKS = NO
EXCLUDE_PATTERNS =
EXCLUDE_SYMBOLS =
EXAMPLE_PATH =
EXAMPLE_PATTERNS = *
EXAMPLE_RECURSIVE = NO
IMAGE_PATH =
INPUT_FILTER =
FILTER_PATTERNS =
FILTER_SOURCE_FILES = NO
#---------------------------------------------------------------------------
# configuration options related to source browsing
#---------------------------------------------------------------------------
SOURCE_BROWSER = NO
INLINE_SOURCES = NO
STRIP_CODE_COMMENTS = YES
REFERENCED_BY_RELATION = NO
REFERENCES_RELATION = NO
REFERENCES_LINK_SOURCE = YES
USE_HTAGS = NO
VERBATIM_HEADERS = NO
#---------------------------------------------------------------------------
# configuration options related to the alphabetical class index
#---------------------------------------------------------------------------
ALPHABETICAL_INDEX = NO
COLS_IN_ALPHA_INDEX = 5
IGNORE_PREFIX =
#---------------------------------------------------------------------------
# configuration options related to the HTML output
#---------------------------------------------------------------------------
GENERATE_HTML = YES
HTML_OUTPUT = html
HTML_FILE_EXTENSION = .html
HTML_HEADER =
HTML_FOOTER =
HTML_STYLESHEET =
HTML_ALIGN_MEMBERS = YES
GENERATE_HTMLHELP = NO
GENERATE_DOCSET = NO
DOCSET_FEEDNAME = "Doxygen generated docs"
DOCSET_BUNDLE_ID = org.doxygen.Project
HTML_DYNAMIC_SECTIONS = NO
CHM_FILE =
HHC_LOCATION =
GENERATE_CHI = NO
CHM_INDEX_ENCODING =
BINARY_TOC = NO
TOC_EXPAND = NO
DISABLE_INDEX = NO
ENUM_VALUES_PER_LINE = 4
GENERATE_TREEVIEW = NONE
TREEVIEW_WIDTH = 250
FORMULA_FONTSIZE = 10
#---------------------------------------------------------------------------
# configuration options related to the LaTeX output
#---------------------------------------------------------------------------
GENERATE_LATEX = NO
LATEX_OUTPUT = latex
LATEX_CMD_NAME = latex
MAKEINDEX_CMD_NAME = makeindex
COMPACT_LATEX = NO
PAPER_TYPE = a4wide
EXTRA_PACKAGES =
LATEX_HEADER =
PDF_HYPERLINKS = YES
USE_PDFLATEX = YES
LATEX_BATCHMODE = NO
LATEX_HIDE_INDICES = NO
#---------------------------------------------------------------------------
# configuration options related to the RTF output
#---------------------------------------------------------------------------
GENERATE_RTF = NO
RTF_OUTPUT = rtf
COMPACT_RTF = NO
RTF_HYPERLINKS = NO
RTF_STYLESHEET_FILE =
RTF_EXTENSIONS_FILE =
#---------------------------------------------------------------------------
# configuration options related to the man page output
#---------------------------------------------------------------------------
GENERATE_MAN = NO
MAN_OUTPUT = man
MAN_EXTENSION = .3
MAN_LINKS = NO
#---------------------------------------------------------------------------
# configuration options related to the XML output
#---------------------------------------------------------------------------
GENERATE_XML = NO
XML_OUTPUT = xml
XML_SCHEMA =
XML_DTD =
XML_PROGRAMLISTING = YES
#---------------------------------------------------------------------------
# configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------
GENERATE_AUTOGEN_DEF = NO
#---------------------------------------------------------------------------
# configuration options related to the Perl module output
#---------------------------------------------------------------------------
GENERATE_PERLMOD = NO
PERLMOD_LATEX = NO
PERLMOD_PRETTY = YES
PERLMOD_MAKEVAR_PREFIX =
#---------------------------------------------------------------------------
# Configuration options related to the preprocessor
#---------------------------------------------------------------------------
ENABLE_PREPROCESSING = YES
MACRO_EXPANSION = NO
EXPAND_ONLY_PREDEF = NO
SEARCH_INCLUDES = YES
INCLUDE_PATH =
INCLUDE_FILE_PATTERNS =
PREDEFINED =
EXPAND_AS_DEFINED =
SKIP_FUNCTION_MACROS = YES
#---------------------------------------------------------------------------
# Configuration::additions related to external references
#---------------------------------------------------------------------------
TAGFILES =
GENERATE_TAGFILE =
ALLEXTERNALS = NO
EXTERNAL_GROUPS = YES
PERL_PATH = /usr/bin/perl
#---------------------------------------------------------------------------
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
CLASS_DIAGRAMS = NO
MSCGEN_PATH =
HIDE_UNDOC_RELATIONS = YES
HAVE_DOT = YES
DOT_FONTNAME = FreeSans
DOT_FONTPATH =
CLASS_GRAPH = NO
COLLABORATION_GRAPH = YES
GROUP_GRAPHS = YES
UML_LOOK = NO
TEMPLATE_RELATIONS = NO
INCLUDE_GRAPH = YES
INCLUDED_BY_GRAPH = YES
CALL_GRAPH = YES
CALLER_GRAPH = NO
GRAPHICAL_HIERARCHY = YES
DIRECTORY_GRAPH = YES
DOT_IMAGE_FORMAT = png
DOT_PATH =
DOTFILE_DIRS =
DOT_GRAPH_MAX_NODES = 50
MAX_DOT_GRAPH_DEPTH = 1000
DOT_TRANSPARENT = YES
DOT_MULTI_TARGETS = NO
GENERATE_LEGEND = YES
DOT_CLEANUP = YES
#---------------------------------------------------------------------------
# Configuration::additions related to the search engine
#---------------------------------------------------------------------------
SEARCHENGINE = NO
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
/**
* @file
* \brief functions which apply to client structures
* */
#include "Clients.h"
#include <string.h>
#include <stdio.h>
/**
* List callback function for comparing clients by clientid
* @param a first integer value
* @param b second integer value
* @return boolean indicating whether a and b are equal
*/
int clientIDCompare(void* a, void* b)
{
Clients* client = (Clients*)a;
/*printf("comparing clientdIDs %s with %s\n", client->clientID, (char*)b);*/
return strcmp(client->clientID, (char*)b) == 0;
}
/**
* List callback function for comparing clients by socket
* @param a first integer value
* @param b second integer value
* @return boolean indicating whether a and b are equal
*/
int clientSocketCompare(void* a, void* b)
{
Clients* client = (Clients*)a;
/*printf("comparing %d with %d\n", (char*)a, (char*)b); */
return client->socket == *(int*)b;
}
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#if !defined(CLIENTS_H)
#define CLIENTS_H
#include <time.h>
#include "LinkedList.h"
#include "MQTTClientPersistence.h"
/*BE
include "LinkedList"
BE*/
/*BE
def PUBLICATIONS
{
n32 ptr STRING open "topic"
n32 ptr DATA "payload"
n32 dec "payloadlen"
n32 dec "refcount"
}
BE*/
/**
* Stored publication data to minimize copying
*/
typedef struct
{
char *topic;
int topiclen;
char* payload;
int payloadlen;
int refcount;
} Publications;
/*BE
// This should get moved to MQTTProtocol, but the includes don't quite work yet
map MESSAGE_TYPES
{
"PUBREC" 5
"PUBREL" .
"PUBCOMP" .
}
def MESSAGES
{
n32 dec "qos"
n32 map bool "retain"
n32 dec "msgid"
n32 ptr PUBLICATIONS "publish"
n32 time "lastTouch"
n8 map MESSAGE_TYPES "nextMessageType"
n32 dec "len"
}
defList(MESSAGES)
BE*/
/**
* Client publication message data
*/
typedef struct
{
int qos;
int retain;
int msgid;
Publications *publish;
time_t lastTouch; /**> used for retry and expiry */
char nextMessageType; /**> PUBREC, PUBREL, PUBCOMP */
int len; /**> length of the whole structure+data */
} Messages;
/*BE
def WILLMESSAGES
{
n32 ptr STRING open "topic"
n32 ptr DATA open "msg"
n32 dec "retained"
n32 dec "qos"
}
BE*/
/**
* Client will message data
*/
typedef struct
{
char *topic;
char *msg;
int retained;
int qos;
} willMessages;
/*BE
map CLIENT_BITS
{
"cleansession" 1 : .
"connected" 2 : .
"good" 4 : .
"ping_outstanding" 8 : .
}
def CLIENTS
{
n32 ptr STRING open "clientID"
n32 ptr STRING open "username"
n32 ptr STRING open "password"
n32 map CLIENT_BITS "bits"
at 4 n8 bits 7:6 dec "connect_state"
at 8
n32 dec "socket"
n32 dec "msgID"
n32 dec "keepAliveInterval"
n32 dec "maxInflightMessages"
n32 ptr BRIDGECONNECTIONS "bridge_context"
n32 time "lastContact"
n32 ptr WILLMESSAGES "will"
n32 ptr MESSAGESList open "inboundMsgs"
n32 ptr MESSAGESList open "outboundMsgs"
n32 ptr MESSAGESList open "messageQueue"
n32 dec "discardedMsgs"
}
defList(CLIENTS)
BE*/
/**
* Data related to one client
*/
typedef struct
{
char* clientID; /**< the string id of the client */
char* username; /**< MQTT v3.1 user name */
char* password; /**< MQTT v3.1 password */
unsigned int cleansession : 1; /**< MQTT clean session flag */
unsigned int connected : 1; /**< whether it is currently connected */
unsigned int good : 1; /**< if we have an error on the socket we turn this off */
unsigned int ping_outstanding : 1;
unsigned int connect_state : 2;
int socket;
int msgID;
int keepAliveInterval;
int retryInterval;
int maxInflightMessages;
time_t lastContact;
willMessages* will;
List* inboundMsgs;
List* outboundMsgs; /**< in flight */
List* messageQueue;
void* phandle; /* the persistence handle */
MQTTClient_persistence* persistence; /* a persistence implementation */
int connectOptionsVersion;
} Clients;
int clientIDCompare(void* a, void* b);
int clientSocketCompare(void* a, void* b);
/**
* Configuration data related to all clients
*/
typedef struct
{
char* version;
List* clients;
} ClientStates;
#endif
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
/**
* @file
* \brief functions to manage the heap with the goal of eliminating memory leaks
*
* For any module to use these functions transparently, simply include the Heap.h
* header file. Malloc and free will be redefined, but will behave in exactly the same
* way as normal, so no recoding is necessary.
*
* */
#include "LinkedList.h"
#include "Log.h"
#include "StackTrace.h"
#include "Thread.h"
#include <memory.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "Heap.h"
#undef malloc
#undef realloc
#undef free
static heap_info state = {0, 0};
int ListRemoveCurrentItem(List* aList);
typedef struct
{
char* file;
int line;
void* ptr;
size_t size;
char* stack;
} storageElement;
static List heap = {NULL, NULL, NULL};
/**
* Allocates a block of memory. A direct replacement for malloc, but keeps track of items
* allocated in a list, so that free can check that a item is being freed correctly and that
* we can check that all memory is freed at shutdown.
* @param file use the __FILE__ macro to indicate which file this item was allocated in
* @param line use the __LINE__ macro to indicate which line this item was allocated at
* @param size the size of the item to be allocated
* @return pointer to the allocated item, or NULL if there was an error
*/
void* mymalloc(char* file, int line, size_t size)
{
ListElement* e = NULL;
storageElement* s = NULL;
static char* errmsg = "Memory allocation error";
if ((s = malloc(sizeof(storageElement))) == NULL)
{
Log(LOG_ERROR, -1, errmsg);
return NULL;
}
if ((s->file = malloc(strlen(file)+1)) == NULL)
{
Log(LOG_ERROR, -1, errmsg);
free(s);
return NULL;
}
strcpy(s->file, file);
s->line = line;
if ((s->ptr = malloc(size)) == NULL)
{
Log(LOG_ERROR, -1, errmsg);
free(s->file);
free(s);
return NULL;
}
s->size = size;
s->stack = NULL;
if (trace_settings.trace_level == TRACE_MAXIMUM)
{
if ((s->stack = StackTrace_get(Thread_getid())) == NULL)
{
Log(LOG_ERROR, -1, errmsg);
free(s->ptr);
free(s->file);
free(s);
return NULL;
}
else
{
Log(TRACE_MAX, -1, "Allocating %d bytes in heap at line %d of file %s, ptr %p, heap use now %d bytes",
s->size, line, file, s->ptr, state.current_size);
Log(TRACE_MAX, -1, "Stack trace is %s", s->stack);
}
}
if ((e = malloc(sizeof(ListElement))) == NULL)
{
Log(LOG_ERROR, -1, errmsg);
if (s->stack)
free(s->stack);
free(s->ptr);
free(s->file);
free(s);
return NULL;
}
ListAppendNoMalloc(&heap, s, e, sizeof(ListElement));
state.current_size += size;
if (state.current_size > state.max_size)
state.max_size = state.current_size;
return s->ptr;
}
/**
* List callback function for comparing storage elements
* @param a first integer value
* @param b second integer value
* @return boolean indicating whether a and b are equal
*/
int ptrCompare(void* a, void* b)
{
storageElement* s = (storageElement*)a;
return s->ptr == b;
}
/**
* Utility to find an item in the heap. Lets you know if the heap already contains
* the memory location in question.
* @param p pointer to a memory location
* @return pointer to the storage element if found, or NULL
*/
void* Heap_findItem(void* p)
{
ListElement* e = ListFindItem(&heap, p, ptrCompare);
return (e == NULL) ? NULL : e->content;
}
/**
* Remove an item from the recorded heap without actually freeing it.
* Use sparingly!
* @param file use the __FILE__ macro to indicate which file this item was allocated in
* @param line use the __LINE__ macro to indicate which line this item was allocated at
* @param p pointer to the item to be removed
*/
void Internal_heap_unlink(char* file, int line, void* p)
{
ListElement* e = NULL;
if ((e = ListFindItem(&heap, p, ptrCompare)) == NULL)
Log(LOG_ERROR, -1, "Failed to remove heap item at file %s line %d", file, line);
else
{
storageElement* s = (storageElement*)(heap.current->content);
Log(TRACE_MAX, -1, "Freeing %d bytes in heap at file %s line %d, heap use now %d bytes",
s->size, file, line, state.current_size);
free(s->file);
if (s->stack)
free(s->stack);
state.current_size -= s->size;
ListRemoveCurrentItem(&heap);
}
}
/**
* Remove an item from the recorded heap without actually freeing it.
* Use sparingly!
* @param file use the __FILE__ macro to indicate which file this item was allocated in
* @param line use the __LINE__ macro to indicate which line this item was allocated at
* @param p pointer to the item to be removed
*/
void Heap_unlink(char* file, int line, void* p)
{
Internal_heap_unlink(file, line, p);
}
/**
* Frees a block of memory. A direct replacement for free, but checks that a item is in
* the allocates list first.
* @param file use the __FILE__ macro to indicate which file this item was allocated in
* @param line use the __LINE__ macro to indicate which line this item was allocated at
* @param p pointer to the item to be freed
*/
void myfree(char* file, int line, void* p)
{
Internal_heap_unlink(file, line, p);
free(p);
}
/**
* Reallocates a block of memory. A direct replacement for realloc, but keeps track of items
* allocated in a list, so that free can check that a item is being freed correctly and that
* we can check that all memory is freed at shutdown.
* @param file use the __FILE__ macro to indicate which file this item was reallocated in
* @param line use the __LINE__ macro to indicate which line this item was reallocated at
* @param p pointer to the item to be reallocated
* @param size the new size of the item
* @return pointer to the allocated item, or NULL if there was an error
*/
void *myrealloc(char* file, int line, void* p, size_t size)
{
void* rc = NULL;
ListElement* e = ListFindItem(&heap, p, ptrCompare);
if (e == NULL)
Log(LOG_ERROR, -1, "Failed to reallocate heap item at file %s line %d", file, line);
else
{
storageElement* s = (storageElement*)(heap.current->content);
state.current_size += size - s->size;
if (state.current_size > state.max_size)
state.max_size = state.current_size;
rc = s->ptr = realloc(s->ptr, size);
s->size = size;
s->file = realloc(s->file, strlen(file)+1);
strcpy(s->file, file);
s->line = line;
if (s->stack)
{
free(s->stack);
s->stack = StackTrace_get(Thread_getid());
}
}
return rc;
}
/**
* Removes and frees the current item in a list. A list function only used by myfree.
* @param aList the list from which the item is to be removed
* @return 1=item removed, 0=item not removed
*/
int ListRemoveCurrentItem(List* aList)
{
ListElement* next = NULL;
if (aList->current->prev == NULL)
/* so this is the first element, and we have to update the "first" pointer */
aList->first = aList->current->next;
else
aList->current->prev->next = aList->current->next;
if (aList->current->next == NULL)
aList->last = aList->current->prev;
else
aList->current->next->prev = aList->current->prev;
next = aList->current->next;
free(aList->current->content);
free(aList->current);
aList->current = next;
return 1; /* successfully removed item */
}
/**
* Scans the heap and reports any items currently allocated.
* To be used at shutdown if any heap items have not been freed.
*/
void HeapScan()
{
ListElement* current = NULL;
Log(TRACE_MIN, -1, "Heap scan start, total %d bytes", state.current_size);
while (ListNextElement(&heap, &current))
{
storageElement* s = (storageElement*)(current->content);
Log(TRACE_MIN, -1, "Heap element size %d, line %d, file %s, ptr %p", s->size, s->line, s->file, s->ptr);
Log(TRACE_MIN, -1, " Content %*.s", (10 > s->size) ? s->size : 10, (char*)s->ptr);
if (s->stack)
Log(TRACE_MIN, -1, " Stack trace: %s", s->stack);
}
Log(TRACE_MIN, -1, "Heap scan end");
}
/**
* Heap initialization.
*/
int Heap_initialize()
{
return 0;
}
/**
* Heap termination.
*/
void Heap_terminate()
{
Log(TRACE_MIN, -1, "Maximum heap use was %d bytes", state.max_size);
if (state.current_size > 20) /* One log list is freed after this function is called */
{
Log(LOG_ERROR, -1, "Some memory not freed at shutdown, possible memory leak");
HeapScan();
}
}
/**
* Access to heap state
* @return pointer to the heap state structure
*/
heap_info* Heap_get_info()
{
return &state;
}
/**
* Dump a string from the heap so that it can be displayed conveniently
* @param file file handle to dump the heap contents to
* @param str the string to dump
*/
int HeapDumpString(FILE* file, char* str)
{
int rc = 0;
if (str)
{
int* i = (int*)str;
int j;
int len = strlen(str);
if (fwrite(&(str), sizeof(int), 1, file) != 1)
rc = -1;
if (fwrite(&(len), sizeof(int), 1 ,file) != 1)
rc = -1;
for (j = 0; j < len; j++)
{
if (fwrite(i, sizeof(int), 1, file) != 1)
rc = -1;
i++;
}
}
return rc;
}
/**
* Dump the state of the heap
* @param file file handle to dump the heap contents to
* @return completion code, success == 0
*/
int HeapDump(FILE* file)
{
int rc = 0;
if (file != NULL)
{
ListElement* current = NULL;
while (ListNextElement(&heap, &current))
{
storageElement* s = (storageElement*)(current->content);
int* i = s->ptr;
unsigned int j;
if (fwrite(&(s->ptr), sizeof(int), 1, file) != 1)
rc = -1;
if (fwrite(&(s->size), sizeof(int), 1, file) != 1)
rc = -1;
for (j = 0; j < s->size; j++)
{
if (fwrite(i, sizeof(int), 1, file) != -1)
rc = -1;
i++;
}
}
}
return rc;
}
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#if !defined(HEAP_H)
#define HEAP_H
#include <stdio.h>
#include <memory.h>
#include <stdlib.h>
#define HEAP_TRACKING 1
#if HEAP_TRACKING
/**
* redefines malloc to use "mymalloc" so that heap allocation can be tracked
* @param x the size of the item to be allocated
* @return the pointer to the item allocated, or NULL
*/
#define malloc(x) mymalloc(__FILE__, __LINE__, x)
/**
* redefines realloc to use "myrealloc" so that heap allocation can be tracked
* @param a the heap item to be reallocated
* @param b the new size of the item
* @return the new pointer to the heap item
*/
#define realloc(a, b) myrealloc(__FILE__, __LINE__, a, b)
/**
* redefines free to use "myfree" so that heap allocation can be tracked
* @param x the size of the item to be freed
*/
#define free(x) myfree(__FILE__, __LINE__, x)
#endif
void* mymalloc(char*, int, size_t size);
void* myrealloc(char*, int, void* p, size_t size);
void myfree(char*, int, void* p);
void* Heap_findItem(void* p);
void Heap_unlink(char*, int, void* p);
typedef struct
{
int current_size;
int max_size;
} heap_info;
void HeapScan();
int Heap_initialize(void);
void Heap_terminate(void);
heap_info* Heap_get_info(void);
int HeapDump(FILE* file);
int HeapDumpString(FILE* file, char* str);
#endif
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
/**
* @file
* \brief functions which apply to linked list structures.
*
* These linked lists can hold data of any sort, pointed to by the content pointer of the
* ListElement structure. ListElements hold the points to the next and previous items in the
* list.
* */
#include "LinkedList.h"
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#include "Heap.h"
/**
* Sets a list structure to empty - all null values. Does not remove any items from the list.
* @param newl a pointer to the list structure to be initialized
*/
void ListZero(List* newl)
{
memset(newl, '\0', sizeof(List));
/*newl->first = NULL;
newl->last = NULL;
newl->current = NULL;
newl->count = newl->size = 0;*/
}
/**
* Allocates and initializes a new list structure.
* @return a pointer to the new list structure
*/
List* ListInitialize(void)
{
List* newl = malloc(sizeof(List));
ListZero(newl);
return newl;
}
/**
* Append an already allocated ListElement and content to a list. Can be used to move
* an item from one list to another.
* @param aList the list to which the item is to be added
* @param content the list item content itself
* @param newel the ListElement to be used in adding the new item
* @param size the size of the element
*/
void ListAppendNoMalloc(List* aList, void* content, ListElement* newel, int size)
{ /* for heap use */
newel->content = content;
newel->next = NULL;
newel->prev = aList->last;
if (aList->first == NULL)
aList->first = newel;
else
aList->last->next = newel;
aList->last = newel;
++(aList->count);
aList->size += size;
}
/**
* Append an item to a list.
* @param aList the list to which the item is to be added
* @param content the list item content itself
* @param size the size of the element
*/
void ListAppend(List* aList, void* content, int size)
{
ListElement* newel = malloc(sizeof(ListElement));
ListAppendNoMalloc(aList, content, newel, size);
}
/**
* Insert an item to a list at a specific position.
* @param aList the list to which the item is to be added
* @param content the list item content itself
* @param size the size of the element
* @param index the position in the list. If NULL, this function is equivalent
* to ListAppend.
*/
void ListInsert(List* aList, void* content, int size, ListElement* index)
{
ListElement* newel = malloc(sizeof(ListElement));
if ( index == NULL )
ListAppendNoMalloc(aList, content, newel, size);
else
{
newel->content = content;
newel->next = index;
newel->prev = index->prev;
index->prev = newel;
if ( newel->prev != NULL )
newel->prev->next = newel;
else
aList->first = newel;
++(aList->count);
aList->size += size;
}
}
/**
* Finds an element in a list by comparing the content pointers, rather than the contents
* @param aList the list in which the search is to be conducted
* @param content pointer to the list item content itself
* @return the list item found, or NULL
*/
ListElement* ListFind(List* aList, void* content)
{
return ListFindItem(aList, content, NULL);
}
/**
* Finds an element in a list by comparing the content or pointer to the content. A callback
* function is used to define the method of comparison for each element.
* @param aList the list in which the search is to be conducted
* @param content pointer to the content to look for
* @param callback pointer to a function which compares each element (NULL means compare by content pointer)
* @return the list element found, or NULL
*/
ListElement* ListFindItem(List* aList, void* content, int(*callback)(void*, void*))
{
ListElement* rc = NULL;
if (aList->current != NULL && ((callback == NULL && aList->current->content == content) ||
(callback != NULL && callback(aList->current->content, content))))
rc = aList->current;
else
{
ListElement* current = NULL;
/* find the content */
while (ListNextElement(aList, &current) != NULL)
{
if (callback == NULL)
{
if (current->content == content)
{
rc = current;
break;
}
}
else
{
if (callback(current->content, content))
{
rc = current;
break;
}
}
}
if (rc != NULL)
aList->current = rc;
}
return rc;
}
/**
* Removes and optionally frees an element in a list by comparing the content.
* A callback function is used to define the method of comparison for each element.
* @param aList the list in which the search is to be conducted
* @param content pointer to the content to look for
* @param callback pointer to a function which compares each element
* @param freeContent boolean value to indicate whether the item found is to be freed
* @return 1=item removed, 0=item not removed
*/
int ListUnlink(List* aList, void* content, int(*callback)(void*, void*), int freeContent)
{
ListElement* next = NULL;
ListElement* saved = aList->current;
int saveddeleted = 0;
if (!ListFindItem(aList, content, callback))
return 0; /* false, did not remove item */
if (aList->current->prev == NULL)
/* so this is the first element, and we have to update the "first" pointer */
aList->first = aList->current->next;
else
aList->current->prev->next = aList->current->next;
if (aList->current->next == NULL)
aList->last = aList->current->prev;
else
aList->current->next->prev = aList->current->prev;
next = aList->current->next;
if (freeContent)
free(aList->current->content);
if (saved == aList->current)
saveddeleted = 1;
free(aList->current);
if (saveddeleted)
aList->current = next;
else
aList->current = saved;
--(aList->count);
return 1; /* successfully removed item */
}
/**
* Removes but does not free an item in a list by comparing the pointer to the content.
* @param aList the list in which the search is to be conducted
* @param content pointer to the content to look for
* @return 1=item removed, 0=item not removed
*/
int ListDetach(List* aList, void* content)
{
return ListUnlink(aList, content, NULL, 0);
}
/**
* Removes and frees an item in a list by comparing the pointer to the content.
* @param aList the list from which the item is to be removed
* @param content pointer to the content to look for
* @return 1=item removed, 0=item not removed
*/
int ListRemove(List* aList, void* content)
{
return ListUnlink(aList, content, NULL, 1);
}
/**
* Removes and frees an the first item in a list.
* @param aList the list from which the item is to be removed
* @return 1=item removed, 0=item not removed
*/
int ListRemoveHead(List* aList)
{
int rc = 0;
if (aList->count > 0)
{
ListElement* first = aList->first;
if (aList->current == first)
aList->current = first->next;
if (aList->last == first) /* i.e. no of items in list == 1 */
aList->last = NULL;
free(first->content);
aList->first = aList->first->next;
if (aList->first)
aList->first->prev = NULL;
free(first);
--(aList->count);
}
return rc;
}
/**
* Removes but does not free the last item in a list.
* @param aList the list from which the item is to be removed
* @return the last item removed (or NULL if none was)
*/
void* ListPopTail(List* aList)
{
void* content = NULL;
if (aList->count > 0)
{
ListElement* last = aList->last;
if (aList->current == last)
aList->current = last->prev;
if (aList->first == last) /* i.e. no of items in list == 1 */
aList->first = NULL;
content = last->content;
aList->last = aList->last->prev;
if (aList->last)
aList->last->next = NULL;
free(last);
--(aList->count);
}
return content;
}
/**
* Removes but does not free an element in a list by comparing the content.
* A callback function is used to define the method of comparison for each element.
* @param aList the list in which the search is to be conducted
* @param content pointer to the content to look for
* @param callback pointer to a function which compares each element
* @return 1=item removed, 0=item not removed
*/
int ListDetachItem(List* aList, void* content, int(*callback)(void*, void*))
{ /* do not free the content */
return ListUnlink(aList, content, callback, 0);
}
/**
* Removes and frees an element in a list by comparing the content.
* A callback function is used to define the method of comparison for each element
* @param aList the list in which the search is to be conducted
* @param content pointer to the content to look for
* @param callback pointer to a function which compares each element
* @return 1=item removed, 0=item not removed
*/
int ListRemoveItem(List* aList, void* content, int(*callback)(void*, void*))
{ /* remove from list and free the content */
return ListUnlink(aList, content, callback, 1);
}
/**
* Removes and frees all items in a list, leaving the list ready for new items.
* @param aList the list to which the operation is to be applied
*/
void ListEmpty(List* aList)
{
while (aList->first != NULL)
{
ListElement* first = aList->first;
if (first->content != NULL)
free(first->content);
aList->first = first->next;
free(first);
}
aList->count = aList->size = 0;
aList->current = aList->first = aList->last = NULL;
}
/**
* Removes and frees all items in a list, and frees the list itself
* @param aList the list to which the operation is to be applied
*/
void ListFree(List* aList)
{
ListEmpty(aList);
free(aList);
}
/**
* Removes and but does not free all items in a list, and frees the list itself
* @param aList the list to which the operation is to be applied
*/
void ListFreeNoContent(List* aList)
{
while (aList->first != NULL)
{
ListElement* first = aList->first;
aList->first = first->next;
free(first);
}
free(aList);
}
/**
* Forward iteration through a list
* @param aList the list to which the operation is to be applied
* @param pos pointer to the current position in the list. NULL means start from the beginning of the list
* This is updated on return to the same value as that returned from this function
* @return pointer to the current list element
*/
ListElement* ListNextElement(List* aList, ListElement** pos)
{
return *pos = (*pos == NULL) ? aList->first : (*pos)->next;
}
/**
* Backward iteration through a list
* @param aList the list to which the operation is to be applied
* @param pos pointer to the current position in the list. NULL means start from the end of the list
* This is updated on return to the same value as that returned from this function
* @return pointer to the current list element
*/
ListElement* ListPrevElement(List* aList, ListElement** pos)
{
return *pos = (*pos == NULL) ? aList->last : (*pos)->prev;
}
/**
* List callback function for comparing integers
* @param a first integer value
* @param b second integer value
* @return boolean indicating whether a and b are equal
*/
int intcompare(void* a, void* b)
{
return *((int*)a) == *((int*)b);
}
/**
* List callback function for comparing C strings
* @param a first integer value
* @param b second integer value
* @return boolean indicating whether a and b are equal
*/
int stringcompare(void* a, void* b)
{
return strcmp((char*)a, (char*)b) == 0;
}
#if defined(UNIT_TESTS)
int main(int argc, char *argv[])
{
int i, *ip, *todelete;
ListElement* current = NULL;
List* l = ListInitialize();
printf("List initialized\n");
for (i = 0; i < 10; i++)
{
ip = malloc(sizeof(int));
*ip = i;
ListAppend(l, (void*)ip, sizeof(int));
if (i==5)
todelete = ip;
printf("List element appended %d\n", *((int*)(l->last->content)));
}
printf("List contents:\n");
current = NULL;
while (ListNextElement(l, &current) != NULL)
printf("List element: %d\n", *((int*)(current->content)));
printf("List contents in reverse order:\n");
current = NULL;
while (ListPrevElement(l, &current) != NULL)
printf("List element: %d\n", *((int*)(current->content)));
//if ListFindItem(l, *ip, intcompare)->content
printf("List contents having deleted element %d:\n", *todelete);
ListRemove(l, todelete);
current = NULL;
while (ListNextElement(l, &current) != NULL)
printf("List element: %d\n", *((int*)(current->content)));
i = 9;
ListRemoveItem(l, &i, intcompare);
printf("List contents having deleted another element, %d, size now %d:\n", i, l->size);
current = NULL;
while (ListNextElement(l, &current) != NULL)
printf("List element: %d\n", *((int*)(current->content)));
ListFree(l);
printf("List freed\n");
}
#endif
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#if !defined(LINKEDLIST_H)
#define LINKEDLIST_H
/*BE
defm defList(T)
def T concat Item
{
at 4
n32 ptr T concat Item suppress "next"
at 0
n32 ptr T concat Item suppress "prev"
at 8
n32 ptr T id2str(T)
}
def T concat List
{
n32 ptr T concat Item suppress "first"
n32 ptr T concat Item suppress "last"
n32 ptr T concat Item suppress "current"
n32 dec "count"
n32 suppress "size"
}
endm
defList(INT)
defList(STRING)
defList(TMP)
BE*/
/**
* Structure to hold all data for one list element
*/
typedef struct ListElementStruct
{
struct ListElementStruct *prev, /**< pointer to previous list element */
*next; /**< pointer to next list element */
void* content; /**< pointer to element content */
} ListElement;
/**
* Structure to hold all data for one list
*/
typedef struct
{
ListElement *first, /**< first element in the list */
*last, /**< last element in the list */
*current; /**< current element in the list, for iteration */
int count, /**< no of items */
size; /**< heap storage used */
} List;
void ListZero(List*);
List* ListInitialize(void);
void ListAppend(List* aList, void* content, int size);
void ListAppendNoMalloc(List* aList, void* content, ListElement* newel, int size);
void ListInsert(List* aList, void* content, int size, ListElement* index);
int ListRemove(List* aList, void* content);
int ListRemoveItem(List* aList, void* content, int(*callback)(void*, void*));
int ListRemoveHead(List* aList);
void* ListPopTail(List* aList);
int ListDetach(List* aList, void* content);
int ListDetachItem(List* aList, void* content, int(*callback)(void*, void*));
void ListFree(List* aList);
void ListEmpty(List* aList);
void ListFreeNoContent(List* aList);
ListElement* ListNextElement(List* aList, ListElement** pos);
ListElement* ListPrevElement(List* aList, ListElement** pos);
ListElement* ListFind(List* aList, void* content);
ListElement* ListFindItem(List* aList, void* content, int(*callback)(void*, void*));
int intcompare(void* a, void* b);
int stringcompare(void* a, void* b);
#endif
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#include "Log.h"
#include "MQTTPacket.h"
#include "MQTTProtocol.h"
#include "MQTTProtocolClient.h"
#include "Messages.h"
#include "LinkedList.h"
#include "StackTrace.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#include <string.h>
#if !defined(WIN32)
#include <syslog.h>
#define GETTIMEOFDAY 1
#else
#define snprintf _snprintf
#endif
#if defined(GETTIMEOFDAY)
#include <sys/time.h>
#else
#include <sys/timeb.h>
#endif
#if !defined(min)
#define min(A,B) ( (A) < (B) ? (A):(B))
#endif
trace_settings_type trace_settings =
{
TRACE_MINIMUM,
400,
-1
};
#define MAX_FUNCTION_NAME_LENGTH 256
typedef struct
{
#if defined(GETTIMEOFDAY)
struct timeval ts;
#else
struct timeb ts;
#endif
int sametime_count;
int number;
int thread_id;
int depth;
char name[MAX_FUNCTION_NAME_LENGTH + 1];
int line;
int has_rc;
int rc;
int level;
} traceEntry;
static int start_index = -1,
next_index = 0;
static traceEntry* trace_queue = NULL;
static int trace_queue_size = 0;
static FILE* trace_destination = NULL; /**< flag to indicate if trace is to be sent to a stream */
static int trace_output_level = -1;
static int sametime_count = 0;
#if defined(GETTIMEOFDAY)
struct timeval ts, last_ts;
#else
struct timeb ts, last_ts;
#endif
static char msg_buf[512];
int Log_initialize()
{
int rc = -1;
char* envval = NULL;
if ((trace_queue = malloc(sizeof(traceEntry) * trace_settings.max_trace_entries)) == NULL)
return rc;
trace_queue_size = trace_settings.max_trace_entries;
if ((envval = getenv("MQTT_C_CLIENT_TRACE")) != NULL && strlen(envval) > 0)
{
if (strcmp(envval, "ON") == 0 || (trace_destination = fopen(envval, "wt")) == NULL)
trace_destination = stdout;
}
if ((envval = getenv("MQTT_C_CLIENT_TRACE_LEVEL")) != NULL && strlen(envval) > 0)
{
if (strcmp(envval, "MAXIMUM") == 0 || strcmp(envval, "TRACE_MAXIMUM") == 0)
trace_settings.trace_level = TRACE_MAXIMUM;
else if (strcmp(envval, "MEDIUM") == 0 || strcmp(envval, "TRACE_MEDIUM") == 0)
trace_settings.trace_level = TRACE_MEDIUM;
else if (strcmp(envval, "MINIMUM") == 0 || strcmp(envval, "TRACE_MEDIUM") == 0)
trace_settings.trace_level = TRACE_MINIMUM;
else if (strcmp(envval, "PROTOCOL") == 0 || strcmp(envval, "TRACE_PROTOCOL") == 0)
trace_output_level = TRACE_PROTOCOL;
}
return rc;
}
void Log_terminate()
{
free(trace_queue);
trace_queue = NULL;
trace_queue_size = 0;
if (trace_destination)
{
if (trace_destination != stdout)
fclose(trace_destination);
trace_destination = NULL;
}
start_index = -1;
next_index = 0;
trace_output_level = -1;
sametime_count = 0;
}
static traceEntry* Log_pretrace()
{
traceEntry *cur_entry = NULL;
/* calling ftime/gettimeofday seems to be comparatively expensive, so we need to limit its use */
if (++sametime_count % 20 == 0)
{
#if defined(GETTIMEOFDAY)
gettimeofday(&ts, NULL);
if (ts.tv_sec != last_ts.tv_sec || ts.tv_usec != last_ts.tv_usec)
#else
ftime(&ts);
if (ts.time != last_ts.time || ts.millitm != last_ts.millitm)
#endif
{
sametime_count = 0;
last_ts = ts;
}
}
if (trace_queue_size != trace_settings.max_trace_entries)
{
traceEntry* new_trace_queue = malloc(sizeof(traceEntry) * trace_settings.max_trace_entries);
memcpy(new_trace_queue, trace_queue, min(trace_queue_size, trace_settings.max_trace_entries) * sizeof(traceEntry));
free(trace_queue);
trace_queue = new_trace_queue;
trace_queue_size = trace_settings.max_trace_entries;
if (start_index > trace_settings.max_trace_entries + 1 ||
next_index > trace_settings.max_trace_entries + 1)
{
start_index = -1;
next_index = 0;
}
}
/* add to trace buffer */
cur_entry = &trace_queue[next_index];
if (next_index == start_index) /* means the buffer is full */
{
if (++start_index == trace_settings.max_trace_entries)
start_index = 0;
} else if (start_index == -1)
start_index = 0;
if (++next_index == trace_settings.max_trace_entries)
next_index = 0;
return cur_entry;
}
static char* Log_formatTraceEntry(traceEntry* cur_entry)
{
struct tm *timeinfo;
int buf_pos = 31;
#if defined(GETTIMEOFDAY)
timeinfo = localtime(&cur_entry->ts.tv_sec);
#else
timeinfo = localtime(&cur_entry->ts.time);
#endif
strftime(&msg_buf[7], 80, "%Y%m%d %H%M%S ", timeinfo);
#if defined(GETTIMEOFDAY)
sprintf(&msg_buf[22], ".%.3lu ", cur_entry->ts.tv_usec / 1000L);
#else
sprintf(&msg_buf[22], ".%.3hu ", cur_entry->ts.millitm);
#endif
buf_pos = 27;
sprintf(msg_buf, "(%.4d)", cur_entry->sametime_count);
msg_buf[6] = ' ';
if (cur_entry->has_rc == 2)
strncpy(&msg_buf[buf_pos], cur_entry->name, sizeof(msg_buf)-buf_pos);
else
{
char* format = Messages_get(cur_entry->number, cur_entry->level);
if (cur_entry->has_rc == 1)
snprintf(&msg_buf[buf_pos], sizeof(msg_buf)-buf_pos, format, cur_entry->thread_id,
cur_entry->depth, "", cur_entry->depth, cur_entry->name, cur_entry->line, cur_entry->rc);
else
snprintf(&msg_buf[buf_pos], sizeof(msg_buf)-buf_pos, format, cur_entry->thread_id,
cur_entry->depth, "", cur_entry->depth, cur_entry->name, cur_entry->line);
}
return msg_buf;
}
static void Log_posttrace(int log_level, traceEntry* cur_entry)
{
if (trace_destination &&
((trace_output_level == -1) ? log_level >= trace_settings.trace_level : log_level >= trace_output_level))
{
fprintf(trace_destination, "%s\n", &Log_formatTraceEntry(cur_entry)[7]);
fflush(trace_destination);
}
}
static void Log_trace(int log_level, char* buf)
{
traceEntry *cur_entry = NULL;
if (trace_queue == NULL)
return;
cur_entry = Log_pretrace();
memcpy(&(cur_entry->ts), &ts, sizeof(ts));
cur_entry->sametime_count = sametime_count;
cur_entry->has_rc = 2;
strncpy(cur_entry->name, buf, sizeof(cur_entry->name));
cur_entry->name[MAX_FUNCTION_NAME_LENGTH] = '\0';
Log_posttrace(log_level, cur_entry);
}
/**
* Log a message. If possible, all messages should be indexed by message number, and
* the use of the format string should be minimized or negated altogether. If format is
* provided, the message number is only used as a message label.
* @param log_level the log level of the message
* @param msgno the id of the message to use if the format string is NULL
* @param aFormat the printf format string to be used if the message id does not exist
* @param ... the printf inserts
*/
void Log(int log_level, int msgno, char* format, ...)
{
char* temp = NULL;
static char msg_buf[512];
if (log_level >= trace_settings.trace_level)
{
va_list args;
if (format == NULL && (temp = Messages_get(msgno, log_level)) != NULL)
format = temp;
va_start(args, format);
vsnprintf(msg_buf, sizeof(msg_buf), format, args);
Log_trace(log_level, msg_buf);
va_end(args);
}
/*if (log_level >= LOG_ERROR)
{
char* filename = NULL;
Log_recordFFDC(&msg_buf[7]);
}
if (log_level == LOG_FATAL)
exit(-1);*/
}
/**
* The reason for this function is to make trace logging as fast as possible so that the
* function exit/entry history can be captured by default without unduly impacting
* performance. Therefore it must do as little as possible.
* @param log_level the log level of the message
* @param msgno the id of the message to use if the format string is NULL
* @param aFormat the printf format string to be used if the message id does not exist
* @param ... the printf inserts
*/
void Log_stackTrace(int log_level, int msgno, int thread_id, int current_depth, const char* name, int line, int* rc)
{
traceEntry *cur_entry = NULL;
if (trace_queue == NULL)
return;
if (log_level < trace_settings.trace_level)
return;
cur_entry = Log_pretrace();
memcpy(&(cur_entry->ts), &ts, sizeof(ts));
cur_entry->sametime_count = sametime_count;
cur_entry->number = msgno;
cur_entry->thread_id = thread_id;
cur_entry->depth = current_depth;
strcpy(cur_entry->name, name);
cur_entry->level = log_level;
cur_entry->line = line;
if (rc == NULL)
cur_entry->has_rc = 0;
else
{
cur_entry->has_rc = 1;
cur_entry->rc = *rc;
}
Log_posttrace(log_level, cur_entry);
}
FILE* Log_destToFile(char* dest)
{
FILE* file = NULL;
if (strcmp(dest, "stdout") == 0)
file = stdout;
else if (strcmp(dest, "stderr") == 0)
file = stderr;
else
{
if (strstr(dest, "FFDC"))
file = fopen(dest, "ab");
else
file = fopen(dest, "wb");
}
return file;
}
int Log_compareEntries(char* entry1, char* entry2)
{
int comp = strncmp(&entry1[7], &entry2[7], 19);
/* if timestamps are equal, use the sequence numbers */
if (comp == 0)
comp = strncmp(&entry1[1], &entry2[1], 4);
return comp;
}
#if 0
/**
* Write the contents of the stored trace to a stream
* @param dest string which contains a file name or the special strings stdout or stderr
*/
int Log_dumpTrace(char* dest)
{
FILE* file = NULL;
ListElement* cur_trace_entry = NULL;
const int msgstart = 7;
int rc = -1;
int trace_queue_index = 0;
if ((file = Log_destToFile(dest)) == NULL)
{
Log(LOG_ERROR, 9, NULL, "trace", dest, "trace entries");
goto exit;
}
fprintf(file, "=========== Start of trace dump ==========\n");
/* Interleave the log and trace entries together appropriately */
ListNextElement(trace_buffer, &cur_trace_entry);
trace_queue_index = start_index;
if (trace_queue_index == -1)
trace_queue_index = next_index;
else
{
Log_formatTraceEntry(&trace_queue[trace_queue_index++]);
if (trace_queue_index == trace_settings.max_trace_entries)
trace_queue_index = 0;
}
while (cur_trace_entry || trace_queue_index != next_index)
{
if (cur_trace_entry && trace_queue_index != -1)
{ /* compare these timestamps */
if (Log_compareEntries((char*)cur_trace_entry->content, msg_buf) > 0)
cur_trace_entry = NULL;
}
if (cur_trace_entry)
{
fprintf(file, "%s\n", &((char*)(cur_trace_entry->content))[msgstart]);
ListNextElement(trace_buffer, &cur_trace_entry);
}
else
{
fprintf(file, "%s\n", &msg_buf[7]);
if (trace_queue_index != next_index)
{
Log_formatTraceEntry(&trace_queue[trace_queue_index++]);
if (trace_queue_index == trace_settings.max_trace_entries)
trace_queue_index = 0;
}
}
}
fprintf(file, "========== End of trace dump ==========\n\n");
if (file != stdout && file != stderr && file != NULL)
fclose(file);
rc = 0;
exit:
return rc;
}
#endif
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#if !defined(LOG_H)
#define LOG_H
/*BE
map LOG_LEVELS
{
"TRACE_MAXIMUM" 1
"TRACE_MEDIUM" 2
"TRACE_MINIMUM" 3
"TRACE_PROTOCOL" 4
"ERROR" 5
"SEVERE" 6
"FATAL" 7
}
BE*/
enum {
TRACE_MAXIMUM = 1,
TRACE_MEDIUM,
TRACE_MINIMUM,
TRACE_PROTOCOL,
LOG_ERROR,
LOG_SEVERE,
LOG_FATAL,
} Log_levels;
/*BE
def trace_settings_type
{
n32 map LOG_LEVELS "trace_level"
n32 dec "max_trace_entries"
n32 dec "trace_output_level"
}
BE*/
typedef struct
{
int trace_level; /**< trace level */
int max_trace_entries; /**< max no of entries in the trace buffer */
int trace_output_level; /**< trace level to output to destination */
} trace_settings_type;
extern trace_settings_type trace_settings;
#define LOG_PROTOCOL TRACE_PROTOCOL
#define TRACE_MAX TRACE_MAXIMUM
#define TRACE_MIN TRACE_MINIMUM
#define TRACE_MED TRACE_MEDIUM
int Log_initialize();
void Log_terminate();
void Log(int, int, char *, ...);
void Log_stackTrace(int, int, int, int, const char*, int, int*);
#endif
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#include <stdlib.h>
#if !defined(WIN32)
#include <sys/time.h>
#endif
#if !defined(NO_PERSISTENCE)
#include "MQTTPersistence.h"
#endif
#include "MQTTClient.h"
#include "utf-8.h"
#include "MQTTProtocol.h"
#include "MQTTProtocolOut.h"
#include "Thread.h"
#include "SocketBuffer.h"
#include "StackTrace.h"
#include "Heap.h"
#define URI_TCP "tcp://"
char* MQTTClient_Level = "MQTTClientV3_Level ##MICROBROKER_LEVEL_TAG##";
char* MQTTClient_Version = "MQTTClientV3_Version ##MICROBROKER_VERSION_TAG##";
#define BUILD_TIMESTAMP __DATE__ " " __TIME__ /* __TIMESTAMP__ */
char* client_timestamp_eye = "MQTTClientV3_Timestamp " BUILD_TIMESTAMP;
static ClientStates ClientState =
{
"1.0.0.5", /* version */
NULL /* client list */
};
ClientStates* bstate = &ClientState;
MQTTProtocol state;
#if defined(WIN32)
static mutex_type mqttclient_mutex = NULL;
extern mutex_type stack_mutex;
BOOL APIENTRY DllMain(HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
Log(TRACE_MAX, -1, "DLL process attach");
if (mqttclient_mutex == NULL)
{
mqttclient_mutex = CreateMutex(NULL, 0, NULL);
stack_mutex = CreateMutex(NULL, 0, NULL);
}
case DLL_THREAD_ATTACH:
Log(TRACE_MAX, -1, "DLL thread attach");
case DLL_THREAD_DETACH:
Log(TRACE_MAX, -1, "DLL thread detach");
case DLL_PROCESS_DETACH:
Log(TRACE_MAX, -1, "DLL process detach");
}
return TRUE;
}
#else
static pthread_mutex_t mqttclient_mutex_store = PTHREAD_MUTEX_INITIALIZER;
static mutex_type mqttclient_mutex = &mqttclient_mutex_store;
#define WINAPI
#endif
static volatile int initialized = 0;
static List* handles = NULL;
static time_t last;
static int running = 0;
static int tostop = 0;
static thread_id_type run_id = 0;
MQTTPacket* MQTTClient_waitfor(MQTTClient handle, int packet_type, int* rc, long timeout);
int MQTTClient_receiveOrComplete(MQTTClient handle, char** topicName, int* topicLen, MQTTClient_message** message,
unsigned long timeout, MQTTPacket** packetaddr);
MQTTPacket* MQTTClient_cycle(int* sock, unsigned long timeout, int* rc);
int MQTTClient_cleanSession(Clients* client);
void MQTTClient_stop();
int MQTTClient_disconnect_internal(MQTTClient handle, int timeout);
typedef struct
{
MQTTClient_message* msg;
char* topicName;
int topicLen;
} qEntry;
typedef struct
{
char* serverURI;
Clients* c;
MQTTClient_connectionLost* cl;
MQTTClient_messageArrived* ma;
MQTTClient_deliveryComplete* dc;
void* context;
sem_type connect_sem;
int rc; /* getsockopt return code in connect */
sem_type connack_sem;
sem_type suback_sem;
sem_type unsuback_sem;
MQTTPacket* pack;
} MQTTClients;
void MQTTClient_sleep(long milliseconds)
{
FUNC_ENTRY;
#if defined(WIN32)
Sleep(milliseconds);
#else
usleep(milliseconds*1000);
#endif
FUNC_EXIT;
}
#if defined(WIN32)
#define START_TIME_TYPE DWORD
START_TIME_TYPE MQTTClient_start_clock(void)
{
return GetTickCount();
}
#elif defined(AIX)
#define START_TIME_TYPE struct timespec
START_TIME_TYPE MQTTClient_start_clock(void)
{
static struct timespec start;
clock_gettime(CLOCK_REALTIME, &start);
return start;
}
#else
#define START_TIME_TYPE struct timeval
START_TIME_TYPE MQTTClient_start_clock(void)
{
static struct timeval start;
gettimeofday(&start, NULL);
return start;
}
#endif
#if defined(WIN32)
long MQTTClient_elapsed(DWORD milliseconds)
{
return GetTickCount() - milliseconds;
}
#elif defined(AIX)
#define assert(a)
long MQTTClient_elapsed(struct timespec start)
{
struct timespec now, res;
clock_gettime(CLOCK_REALTIME, &now);
ntimersub(now, start, res);
return (res.tv_sec)*1000L + (res.tv_nsec)/1000000L;
}
#else
long MQTTClient_elapsed(struct timeval start)
{
struct timeval now, res;
gettimeofday(&now, NULL);
timersub(&now, &start, &res);
return (res.tv_sec)*1000 + (res.tv_usec)/1000;
}
#endif
int MQTTClient_create(MQTTClient* handle, char* serverURI, char* clientId,
int persistence_type, void* persistence_context)
{
int rc = 0;
MQTTClients *m = NULL;
FUNC_ENTRY;
rc = Thread_lock_mutex(mqttclient_mutex);
if (serverURI == NULL || clientId == NULL)
{
rc = MQTTCLIENT_NULL_PARAMETER;
goto exit;
}
if (!UTF8_validateString(clientId))
{
rc = MQTTCLIENT_BAD_UTF8_STRING;
goto exit;
}
if (!initialized)
{
#if defined(HEAP_H)
Heap_initialize();
#endif
Log_initialize();
bstate->clients = ListInitialize();
Socket_outInitialize();
handles = ListInitialize();
initialized = 1;
}
m = malloc(sizeof(MQTTClients));
*handle = m;
memset(m, '\0', sizeof(MQTTClients));
if (strncmp(URI_TCP, serverURI, strlen(URI_TCP)) == 0)
serverURI += strlen(URI_TCP);
m->serverURI = malloc(strlen(serverURI)+1);
strcpy(m->serverURI, serverURI);
ListAppend(handles, m, sizeof(MQTTClients));
m->c = malloc(sizeof(Clients));
memset(m->c, '\0', sizeof(Clients));
m->c->outboundMsgs = ListInitialize();
m->c->inboundMsgs = ListInitialize();
m->c->messageQueue = ListInitialize();
m->c->clientID = malloc(strlen(clientId)+1);
strcpy(m->c->clientID, clientId);
m->connect_sem = Thread_create_sem();
m->connack_sem = Thread_create_sem();
m->suback_sem = Thread_create_sem();
m->unsuback_sem = Thread_create_sem();
#if !defined(NO_PERSISTENCE)
rc = MQTTPersistence_create(&(m->c->persistence), persistence_type, persistence_context);
if (rc == 0)
rc = MQTTPersistence_initialize(m->c, m->serverURI);
#endif
ListAppend(bstate->clients, m->c, sizeof(Clients) + 3*sizeof(List));
exit:
if (Thread_unlock_mutex(mqttclient_mutex) != 0)
/* FFDC? */;
FUNC_EXIT_RC(rc);
return rc;
}
void MQTTClient_terminate(void)
{
FUNC_ENTRY;
MQTTClient_stop();
if (initialized)
{
ListFree(bstate->clients);
ListFree(handles);
handles = NULL;
Socket_outTerminate();
#if defined(HEAP_H)
Heap_terminate();
#endif
Log_terminate();
initialized = 0;
}
FUNC_EXIT;
}
void MQTTClient_emptyMessageQueue(Clients* client)
{
FUNC_ENTRY;
/* empty message queue */
if (client->messageQueue->count > 0)
{
ListElement* current = NULL;
while (ListNextElement(client->messageQueue, &current))
{
qEntry* qe = (qEntry*)(current->content);
free(qe->topicName);
free(qe->msg->payload);
free(qe->msg);
}
ListEmpty(client->messageQueue);
}
FUNC_EXIT;
}
void MQTTClient_destroy(MQTTClient* handle)
{
MQTTClients* m = *handle;
FUNC_ENTRY;
Thread_lock_mutex(mqttclient_mutex);
if (m == NULL)
goto exit;
if (m->c)
{
int saved_socket = m->c->socket;
char* saved_clientid = malloc(strlen(m->c->clientID)+1);
strcpy(saved_clientid, m->c->clientID);
#if !defined(NO_PERSISTENCE)
MQTTPersistence_close(m->c);
#endif
MQTTClient_emptyMessageQueue(m->c);
MQTTProtocol_freeClient(m->c);
if (!ListRemove(bstate->clients, m->c))
Log(LOG_ERROR, 0, NULL);
else
Log(TRACE_MIN, 1, NULL, saved_clientid, saved_socket);
free(saved_clientid);
}
if (m->serverURI)
free(m->serverURI);
if (!ListRemove(handles, m))
Log(LOG_ERROR, -1, "free error");
*handle = NULL;
if (bstate->clients->count == 0)
MQTTClient_terminate();
exit:
Thread_unlock_mutex(mqttclient_mutex);
FUNC_EXIT;
}
#if defined(HEAP_H)
#undef malloc
#undef realloc
#undef free
#endif
void MQTTClient_freeMessage(MQTTClient_message** message)
{
FUNC_ENTRY;
free((*message)->payload);
free(*message);
*message = NULL;
FUNC_EXIT;
}
void MQTTClient_free(void* memory)
{
FUNC_ENTRY;
free(memory);
FUNC_EXIT;
}
#if defined(HEAP_H)
#define malloc(x) mymalloc(__FILE__, __LINE__, x)
#define realloc(a, b) myrealloc(__FILE__, __LINE__, a, b)
#define free(x) myfree(__FILE__, __LINE__, x)
#endif
int MQTTClient_deliverMessage(int rc, MQTTClients* m, char** topicName, int* topicLen, MQTTClient_message** message)
{
qEntry* qe = (qEntry*)(m->c->messageQueue->first->content);
FUNC_ENTRY;
*message = qe->msg;
*topicName = qe->topicName;
*topicLen = qe->topicLen;
if (strlen(*topicName) != *topicLen)
rc = MQTTCLIENT_TOPICNAME_TRUNCATED;
ListRemove(m->c->messageQueue, m->c->messageQueue->first->content);
Heap_unlink(__FILE__, __LINE__, (*message)->payload);
Heap_unlink(__FILE__, __LINE__, *message);
Heap_unlink(__FILE__, __LINE__, *topicName);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* List callback function for comparing clients by socket
* @param a first integer value
* @param b second integer value
* @return boolean indicating whether a and b are equal
*/
int clientSockCompare(void* a, void* b)
{
MQTTClients* m = (MQTTClients*)a;
return m->c->socket == *(int*)b;
}
/**
* Wrapper function to call connection lost on a separate thread. A separate thread is needed to allow the
* connectionLost function to make API calls (e.g. connect)
* @param context a pointer to the relevant client
* @return thread_return_type standard thread return value - not used here
*/
thread_return_type WINAPI connectionLost_call(void* context)
{
MQTTClients* m = (MQTTClients*)context;
(*(m->cl))(m->context, NULL);
return 0;
}
/* This is the thread function that handles the calling of callback functions if set */
thread_return_type WINAPI MQTTClient_run(void* n)
{
long timeout = 10L; /* first time in we have a small timeout. Gets things started more quickly */
FUNC_ENTRY;
running = 1;
run_id = Thread_getid();
Thread_lock_mutex(mqttclient_mutex);
while (!tostop)
{
int rc = SOCKET_ERROR;
int sock = -1;
MQTTClients* m = NULL;
MQTTPacket* pack = NULL;
Thread_unlock_mutex(mqttclient_mutex);
pack = MQTTClient_cycle(&sock, timeout, &rc);
Thread_lock_mutex(mqttclient_mutex);
if (tostop)
break;
timeout = 1000L;
/* find client corresponding to socket */
if (ListFindItem(handles, &sock, clientSockCompare) == NULL)
{
/* assert: should not happen */
continue;
}
m = (MQTTClient)(handles->current->content);
if (m == NULL)
{
/* assert: should not happen */
continue;
}
if (rc == SOCKET_ERROR)
{
Thread_unlock_mutex(mqttclient_mutex);
MQTTClient_disconnect_internal(m, 0);
Thread_lock_mutex(mqttclient_mutex);
}
else
{
if (m->c->messageQueue->count > 0)
{
qEntry* qe = (qEntry*)(m->c->messageQueue->first->content);
int topicLen = qe->topicLen;
void* payload_ptr = qe->msg->payload; /* saved so we can unlink it after a successful messageArrived call,
because it is held in a structure which might be freed */
if (strlen(qe->topicName) == topicLen)
topicLen = 0;
Log(TRACE_MIN, -1, "Calling messageArrived for client %s, queue depth %d",
m->c->clientID, m->c->messageQueue->count);
Thread_unlock_mutex(mqttclient_mutex);
rc = (*(m->ma))(m->context, qe->topicName, topicLen, qe->msg);
Thread_lock_mutex(mqttclient_mutex);
/* if 0 (false) is returned by the callback then it failed, so we don't remove the message from
* the queue, and it will be retried later. If 1 is returned then the message data may have been freed,
* so we must be careful how we use it.
*/
if (rc)
{
Heap_unlink(__FILE__, __LINE__, qe->topicName);
Heap_unlink(__FILE__, __LINE__, payload_ptr);
Heap_unlink(__FILE__, __LINE__, qe->msg);
ListRemove(m->c->messageQueue, qe);
}
else
Log(TRACE_MIN, -1, "False returned from messageArrived for client %s, message remains on queue",
m->c->clientID);
}
if (pack)
{
if (pack->header.bits.type == CONNACK)
{
Log(TRACE_MIN, -1, "Posting connack semaphore for client %s", m->c->clientID);
m->pack = pack;
Thread_post_sem(m->connack_sem);
}
else if (pack->header.bits.type == SUBACK)
{
Log(TRACE_MIN, -1, "Posting suback semaphore for client %s", m->c->clientID);
m->pack = pack;
Thread_post_sem(m->suback_sem);
}
else if (pack->header.bits.type == UNSUBACK)
{
Log(TRACE_MIN, -1, "Posting unsuback semaphore for client %s", m->c->clientID);
m->pack = pack;
Thread_post_sem(m->unsuback_sem);
}
}
else if (m->c->connect_state == 1 && !Thread_check_sem(m->connect_sem))
{
int error;
socklen_t len = sizeof(error);
if ((m->rc = getsockopt(m->c->socket, SOL_SOCKET, SO_ERROR, (char*)&error, &len)) == 0)
m->rc = error;
Log(TRACE_MIN, -1, "Posting connect semaphore for client %s rc %d", m->c->clientID, m->rc);
Thread_post_sem(m->connect_sem);
}
}
}
run_id = 0;
running = 0;
Thread_unlock_mutex(mqttclient_mutex);
FUNC_EXIT;
return 0;
}
void MQTTClient_stop()
{
int rc = 0;
FUNC_ENTRY;
if (running == 1 && tostop == 0 && Thread_getid() != run_id)
{
int conn_count = 0;
ListElement* current = NULL;
if (handles != NULL)
{
/* find out how many handles are still connected */
while (ListNextElement(handles, &current))
{
if (((MQTTClients*)(current->content))->c->connect_state > 0 ||
((MQTTClients*)(current->content))->c->connected)
++conn_count;
}
}
Log(TRACE_MIN, -1, "Conn_count is %d", conn_count);
/* stop the background thread, if we are the last one to be using it */
if (conn_count == 0)
{
int count = 0;
tostop = 1;
while (running && ++count < 100)
{
Thread_unlock_mutex(mqttclient_mutex);
Log(TRACE_MIN, -1, "sleeping");
MQTTClient_sleep(100L);
Thread_lock_mutex(mqttclient_mutex);
}
rc = 1;
tostop = 0;
}
}
FUNC_EXIT_RC(rc);
}
int MQTTClient_setCallbacks(MQTTClient handle, void* context, MQTTClient_connectionLost* cl,
MQTTClient_messageArrived* ma, MQTTClient_deliveryComplete* dc)
{
int rc = MQTTCLIENT_SUCCESS;
MQTTClients* m = handle;
FUNC_ENTRY;
Thread_lock_mutex(mqttclient_mutex);
if (m == NULL || ma == NULL || m->c->connect_state != 0)
rc = MQTTCLIENT_FAILURE;
else
{
m->context = context;
m->cl = cl;
m->ma = ma;
m->dc = dc;
}
Thread_unlock_mutex(mqttclient_mutex);
FUNC_EXIT_RC(rc);
return rc;
}
void MQTTProtocol_closeSession(Clients* client, int sendwill)
{
FUNC_ENTRY;
client->good = 0;
if (client->socket > 0)
{
if (client->connected || client->connect_state)
{
MQTTPacket_send_disconnect(client->socket, client->clientID);
client->connected = 0;
client->connect_state = 0;
}
Socket_close(client->socket);
client->socket = 0;
}
if (client->cleansession)
MQTTClient_cleanSession(client);
FUNC_EXIT;
}
int MQTTClient_cleanSession(Clients* client)
{
int rc = 0;
FUNC_ENTRY;
#if !defined(NO_PERSISTENCE)
rc = MQTTPersistence_clear(client);
#endif
MQTTProtocol_emptyMessageList(client->inboundMsgs);
MQTTProtocol_emptyMessageList(client->outboundMsgs);
MQTTClient_emptyMessageQueue(client);
client->msgID = 0;
FUNC_EXIT_RC(rc);
return rc;
}
void Protocol_processPublication(Publish* publish, Clients* client)
{
qEntry* qe = NULL;
MQTTClient_message* mm = NULL;
FUNC_ENTRY;
qe = malloc(sizeof(qEntry));
mm = malloc(sizeof(MQTTClient_message));
qe->msg = mm;
qe->topicName = publish->topic;
qe->topicLen = publish->topiclen;
publish->topic = NULL;
/* If the message is QoS 2, then we have already stored the incoming payload
* in an allocated buffer, so we don't need to copy again.
*/
if (publish->header.bits.qos == 2)
mm->payload = publish->payload;
else
{
mm->payload = malloc(publish->payloadlen);
memcpy(mm->payload, publish->payload, publish->payloadlen);
}
mm->payloadlen = publish->payloadlen;
mm->qos = publish->header.bits.qos;
mm->retained = publish->header.bits.retain;
if (publish->header.bits.qos == 2)
mm->dup = 0; /* ensure that a QoS2 message is not passed to the application with dup = 1 */
else
mm->dup = publish->header.bits.dup;
mm->msgid = publish->msgId;
ListAppend(client->messageQueue, qe, sizeof(qe) + sizeof(mm) + mm->payloadlen + strlen(qe->topicName)+1);
FUNC_EXIT;
}
int MQTTClient_connect(MQTTClient handle, MQTTClient_connectOptions* options)
{
MQTTClients* m = handle;
int rc = SOCKET_ERROR;
START_TIME_TYPE start;
long millisecsTimeout = 30000L;
FUNC_ENTRY;
Thread_lock_mutex(mqttclient_mutex);
if (options == NULL)
{
rc = MQTTCLIENT_NULL_PARAMETER;
goto exit;
}
if (strncmp(options->struct_id, "MQTC", 4) != 0 || options->struct_version != 0)
{
rc = MQTTCLIENT_BAD_STRUCTURE;
goto exit;
}
if (options->will) /* check validity of will options structure */
{
if (strncmp(options->will->struct_id, "MQTW", 4) != 0 || options->will->struct_version != 0)
{
rc = MQTTCLIENT_BAD_STRUCTURE;
goto exit;
}
}
if ((options->username && !UTF8_validateString(options->username)) ||
(options->password && !UTF8_validateString(options->password)))
{
rc = MQTTCLIENT_BAD_UTF8_STRING;
goto exit;
}
millisecsTimeout = options->connectTimeout * 1000;
start = MQTTClient_start_clock();
if (m->ma && !running)
{
Thread_start(MQTTClient_run, handle);
if (MQTTClient_elapsed(start) >= millisecsTimeout)
{
rc = SOCKET_ERROR;
goto exit;
}
MQTTClient_sleep(100L);
}
m->c->keepAliveInterval = options->keepAliveInterval;
m->c->cleansession = options->cleansession;
m->c->maxInflightMessages = (options->reliable) ? 1 : 10;
if (options->will && options->will->struct_version == 0)
{
m->c->will->msg = options->will->message;
m->c->will->qos = options->will->qos;
m->c->will->retained = options->will->retained;
m->c->will->topic = options->will->topicName;
}
m->c->username = options->username;
m->c->password = options->password;
m->c->retryInterval = options->retryInterval;
m->c->connectOptionsVersion = options->struct_version;
Log(TRACE_MIN, -1, "Connecting to serverURI %s", m->serverURI);
rc = MQTTProtocol_connect(m->serverURI, m->c);
if (m->c->connect_state == 0)
{
rc = SOCKET_ERROR;
goto exit;
}
if (m->c->connect_state == 1)
{
Thread_unlock_mutex(mqttclient_mutex);
MQTTClient_waitfor(handle, CONNECT, &rc, millisecsTimeout - MQTTClient_elapsed(start));
Thread_lock_mutex(mqttclient_mutex);
if (rc != 0)
{
rc = SOCKET_ERROR;
goto exit;
}
m->c->connect_state = 2; /* TCP connect completed, in which case send the MQTT connect packet */
if (MQTTPacket_send_connect(m->c) == SOCKET_ERROR)
{
rc = SOCKET_ERROR;
goto exit;
}
}
if (m->c->connect_state == 2)
{
MQTTPacket* pack = NULL;
Thread_unlock_mutex(mqttclient_mutex);
pack = MQTTClient_waitfor(handle, CONNACK, &rc, millisecsTimeout - MQTTClient_elapsed(start));
Thread_lock_mutex(mqttclient_mutex);
if (pack == NULL)
rc = SOCKET_ERROR;
else
{
Connack* connack = (Connack*)pack;
if ((rc = connack->rc) == MQTTCLIENT_SUCCESS)
{
m->c->connected = 1;
m->c->good = 1;
m->c->connect_state = 3;
time(&(m->c->lastContact));
if (m->c->cleansession)
rc = MQTTClient_cleanSession(m->c);
if (m->c->outboundMsgs->count > 0)
{
ListElement* outcurrent = NULL;
while (ListNextElement(m->c->outboundMsgs, &outcurrent))
{
Messages* m = (Messages*)(outcurrent->content);
m->lastTouch = 0;
}
MQTTProtocol_retry(m->c->lastContact, 1);
if (m->c->connected != 1)
rc = MQTTCLIENT_DISCONNECTED;
}
}
free(connack);
m->pack = NULL;
}
}
if (rc == SOCKET_ERROR || rc == MQTTCLIENT_PERSISTENCE_ERROR)
{
Thread_unlock_mutex(mqttclient_mutex);
MQTTClient_disconnect(handle, 0); /* not "internal" because we don't want to call connection lost */
Thread_lock_mutex(mqttclient_mutex);
}
exit:
Thread_unlock_mutex(mqttclient_mutex);
FUNC_EXIT_RC(rc);
return rc;
}
int MQTTClient_disconnect1(MQTTClient handle, int timeout, int internal)
{
MQTTClients* m = handle;
START_TIME_TYPE start;
int rc = MQTTCLIENT_SUCCESS;
int was_connected = 0;
FUNC_ENTRY;
Thread_lock_mutex(mqttclient_mutex);
if (m == NULL || m->c == NULL)
{
rc = MQTTCLIENT_FAILURE;
goto exit;
}
if (m->c->connected == 0)
{
rc = MQTTCLIENT_DISCONNECTED;
goto exit;
}
was_connected = m->c->connected; /* should be 1 */
start = MQTTClient_start_clock();
m->c->connect_state = -2; /* indicate disconnecting */
while (m->c->inboundMsgs->count > 0 || m->c->outboundMsgs->count > 0)
{ /* wait for all inflight message flows to finish, up to timeout */
if (MQTTClient_elapsed(start) >= timeout)
break;
Thread_unlock_mutex(mqttclient_mutex);
MQTTClient_yield();
Thread_lock_mutex(mqttclient_mutex);
}
MQTTProtocol_closeSession(m->c, 0);
if (Thread_check_sem(m->connect_sem))
Thread_post_sem(m->connect_sem);
if (Thread_check_sem(m->connack_sem))
Thread_post_sem(m->connack_sem);
if (Thread_check_sem(m->suback_sem))
Thread_post_sem(m->suback_sem);
if (Thread_check_sem(m->unsuback_sem))
Thread_post_sem(m->unsuback_sem);
exit:
MQTTClient_stop();
if (internal && m->cl && was_connected)
{
Log(TRACE_MIN, -1, "Calling connectionLost for client %s", m->c->clientID);
Thread_start(connectionLost_call, m);
}
Thread_unlock_mutex(mqttclient_mutex);
FUNC_EXIT_RC(rc);
return rc;
}
int MQTTClient_disconnect_internal(MQTTClient handle, int timeout)
{
return MQTTClient_disconnect1(handle, timeout, 1);
}
int MQTTClient_disconnect(MQTTClient handle, int timeout)
{
return MQTTClient_disconnect1(handle, timeout, 0);
}
int MQTTClient_isConnected(MQTTClient handle)
{
MQTTClients* m = handle;
int rc = 0;
FUNC_ENTRY;
Thread_lock_mutex(mqttclient_mutex);
if (m && m->c)
rc = m->c->connected;
Thread_unlock_mutex(mqttclient_mutex);
FUNC_EXIT_RC(rc);
return rc;
}
int MQTTClient_subscribeMany(MQTTClient handle, int count, char** topic, int* qos)
{
MQTTClients* m = handle;
List* topics = ListInitialize();
List* qoss = ListInitialize();
int i = 0;
int rc = MQTTCLIENT_FAILURE;
FUNC_ENTRY;
Thread_lock_mutex(mqttclient_mutex);
if (m == NULL || m->c == NULL)
{
rc = MQTTCLIENT_FAILURE;
goto exit;
}
if (m->c->connected == 0)
{
rc = MQTTCLIENT_DISCONNECTED;
goto exit;
}
for (i = 0; i < count; i++)
{
if (!UTF8_validateString(topic[i]))
{
rc = MQTTCLIENT_BAD_UTF8_STRING;
goto exit;
}
}
for (i = 0; i < count; i++)
{
ListAppend(topics, topic[i], strlen(topic[i]));
ListAppend(qoss, &qos[i], sizeof(int));
}
rc = MQTTProtocol_subscribe(m->c, topics, qoss);
ListFreeNoContent(topics);
ListFreeNoContent(qoss);
if (rc == TCPSOCKET_COMPLETE)
{
MQTTPacket* pack = NULL;
Thread_unlock_mutex(mqttclient_mutex);
pack = MQTTClient_waitfor(handle, SUBACK, &rc, 10000L);
Thread_lock_mutex(mqttclient_mutex);
if (pack != NULL)
{
rc = MQTTProtocol_handleSubacks(pack, m->c->socket);
m->pack = NULL;
}
else
rc = SOCKET_ERROR;
}
if (rc == SOCKET_ERROR)
{
Thread_unlock_mutex(mqttclient_mutex);
MQTTClient_disconnect_internal(handle, 0);
Thread_lock_mutex(mqttclient_mutex);
}
else if (rc == TCPSOCKET_COMPLETE)
rc = MQTTCLIENT_SUCCESS;
exit:
Thread_unlock_mutex(mqttclient_mutex);
FUNC_EXIT_RC(rc);
return rc;
}
int MQTTClient_subscribe(MQTTClient handle, char* topic, int qos)
{
int rc = 0;
FUNC_ENTRY;
rc = MQTTClient_subscribeMany(handle, 1, &topic, &qos);
FUNC_EXIT_RC(rc);
return rc;
}
int MQTTClient_unsubscribeMany(MQTTClient handle, int count, char** topic)
{
MQTTClients* m = handle;
List* topics = ListInitialize();
int i = 0;
int rc = SOCKET_ERROR;
FUNC_ENTRY;
Thread_lock_mutex(mqttclient_mutex);
if (m == NULL || m->c == NULL)
{
rc = MQTTCLIENT_FAILURE;
goto exit;
}
if (m->c->connected == 0)
{
rc = MQTTCLIENT_DISCONNECTED;
goto exit;
}
for (i = 0; i < count; i++)
{
if (!UTF8_validateString(topic[i]))
{
rc = MQTTCLIENT_BAD_UTF8_STRING;
goto exit;
}
}
for (i = 0; i < count; i++)
ListAppend(topics, topic[i], strlen(topic[i]));
rc = MQTTProtocol_unsubscribe(m->c, topics);
ListFreeNoContent(topics);
if (rc == TCPSOCKET_COMPLETE)
{
MQTTPacket* pack = NULL;
Thread_unlock_mutex(mqttclient_mutex);
pack = MQTTClient_waitfor(handle, UNSUBACK, &rc, 10000L);
Thread_lock_mutex(mqttclient_mutex);
if (pack != NULL)
{
rc = MQTTProtocol_handleUnsubacks(pack, m->c->socket);
m->pack = NULL;
}
else
rc = SOCKET_ERROR;
}
if (rc == SOCKET_ERROR)
{
Thread_unlock_mutex(mqttclient_mutex);
MQTTClient_disconnect_internal(handle, 0);
Thread_lock_mutex(mqttclient_mutex);
}
exit:
Thread_unlock_mutex(mqttclient_mutex);
FUNC_EXIT_RC(rc);
return rc;
}
int MQTTClient_unsubscribe(MQTTClient handle, char* topic)
{
int rc = 0;
FUNC_ENTRY;
rc = MQTTClient_unsubscribeMany(handle, 1, &topic);
FUNC_EXIT_RC(rc);
return rc;
}
int MQTTClient_publish(MQTTClient handle, char* topicName, int payloadlen, void* payload,
int qos, int retained, MQTTClient_deliveryToken* deliveryToken)
{
int rc = MQTTCLIENT_SUCCESS;
MQTTClients* m = handle;
Messages* msg = NULL;
Publish* p = NULL;
int blocked = 0;
FUNC_ENTRY;
Thread_lock_mutex(mqttclient_mutex);
if (m == NULL || m->c == NULL)
rc = MQTTCLIENT_FAILURE;
else if (m->c->connected == 0)
rc = MQTTCLIENT_DISCONNECTED;
else if (!UTF8_validateString(topicName))
rc = MQTTCLIENT_BAD_UTF8_STRING;
if (rc != MQTTCLIENT_SUCCESS)
goto exit;
/* If outbound queue is full, block until it is not */
while (m->c->outboundMsgs->count >= m->c->maxInflightMessages)
{
if (blocked == 0)
{
blocked = 1;
Log(TRACE_MIN, -1, "Blocking publish on queue full for client %s", m->c->clientID);
}
Thread_unlock_mutex(mqttclient_mutex);
MQTTClient_yield();
Thread_lock_mutex(mqttclient_mutex);
if (m->c->connected == 0)
{
rc = MQTTCLIENT_FAILURE;
goto exit;
}
}
if (blocked == 1)
Log(TRACE_MIN, -1, "Resuming publish now queue not full for client %s", m->c->clientID);
p = malloc(sizeof(Publish));
p->payload = payload;
p->payloadlen = payloadlen;
p->topic = topicName;
p->msgId = -1;
rc = MQTTProtocol_startPublish(m->c, p, qos, retained, &msg);
/* If the packet was partially written to the socket, wait for it to complete.
* However, if the client is disconnected during this time and qos is not 0, still return success, as
* the packet has already been written to persistence and assigned a message id so will
* be sent when the client next connects.
*/
if (rc == TCPSOCKET_INTERRUPTED)
{
while (m->c->connected == 1 && SocketBuffer_getWrite(m->c->socket))
{
Thread_unlock_mutex(mqttclient_mutex);
MQTTClient_yield();
Thread_lock_mutex(mqttclient_mutex);
}
rc = (qos > 0 || m->c->connected == 1) ? MQTTCLIENT_SUCCESS : MQTTCLIENT_FAILURE;
}
if (deliveryToken && qos > 0)
*deliveryToken = msg->msgid;
free(p);
if (rc == SOCKET_ERROR)
{
Thread_unlock_mutex(mqttclient_mutex);
MQTTClient_disconnect_internal(handle, 0);
Thread_lock_mutex(mqttclient_mutex);
/* Return success for qos > 0 as the send will be retried automatically */
rc = (qos > 0) ? MQTTCLIENT_SUCCESS : MQTTCLIENT_FAILURE;
}
exit:
Thread_unlock_mutex(mqttclient_mutex);
FUNC_EXIT_RC(rc);
return rc;
}
int MQTTClient_publishMessage(MQTTClient handle, char* topicName, MQTTClient_message* message,
MQTTClient_deliveryToken* deliveryToken)
{
int rc = MQTTCLIENT_SUCCESS;
FUNC_ENTRY;
if (message == NULL)
{
rc = MQTTCLIENT_NULL_PARAMETER;
goto exit;
}
if (strncmp(message->struct_id, "MQTM", 4) != 0 || message->struct_version != 0)
{
rc = MQTTCLIENT_BAD_STRUCTURE;
goto exit;
}
rc = MQTTClient_publish(handle, topicName, message->payloadlen, message->payload,
message->qos, message->retained, deliveryToken);
exit:
FUNC_EXIT_RC(rc);
return rc;
}
void MQTTClient_retry(void)
{
time_t now;
FUNC_ENTRY;
time(&(now));
if (difftime(now, last) > 5)
{
time(&(last));
MQTTProtocol_keepalive(now);
MQTTProtocol_retry(now, 1);
}
else
MQTTProtocol_retry(now, 0);
FUNC_EXIT;
}
MQTTPacket* MQTTClient_cycle(int* sock, unsigned long timeout, int* rc)
{
struct timeval tp = {0L, 0L};
static Ack ack;
MQTTPacket* pack = NULL;
FUNC_ENTRY;
if (timeout > 0L)
{
tp.tv_sec = timeout / 1000;
tp.tv_usec = (timeout % 1000) * 1000; /* this field is microseconds! */
}
/* 0 from getReadySocket indicates no work to do, -1 == error, but can happen normally */
*sock = Socket_getReadySocket(0, &tp);
Thread_lock_mutex(mqttclient_mutex);
if (*sock > 0)
{
MQTTClients* m = NULL;
if (ListFindItem(handles, sock, clientSockCompare) != NULL)
m = (MQTTClient)(handles->current->content);
if (m != NULL && m->c->connect_state == 1)
*rc = 0; /* waiting for connect state to clear */
else
pack = MQTTPacket_Factory(*sock, rc);
if (pack)
{
int freed = 1;
/* Note that these handle... functions free the packet structure that they are dealing with */
if (pack->header.bits.type == PUBLISH)
*rc = MQTTProtocol_handlePublishes(pack, *sock);
else if (pack->header.bits.type == PUBACK || pack->header.bits.type == PUBCOMP)
{
int msgid;
ack = (pack->header.bits.type == PUBCOMP) ? *(Pubcomp*)pack : *(Puback*)pack;
msgid = ack.msgId;
*rc = (pack->header.bits.type == PUBCOMP) ?
MQTTProtocol_handlePubcomps(pack, *sock) : MQTTProtocol_handlePubacks(pack, *sock);
if (m && m->dc)
{
Log(TRACE_MIN, -1, "Calling deliveryComplete for client %s, msgid %d", m->c->clientID, msgid);
(*(m->dc))(m->context, msgid);
}
}
else if (pack->header.bits.type == PUBREC)
*rc = MQTTProtocol_handlePubrecs(pack, *sock);
else if (pack->header.bits.type == PUBREL)
*rc = MQTTProtocol_handlePubrels(pack, *sock);
else if (pack->header.bits.type == PINGRESP)
*rc = MQTTProtocol_handlePingresps(pack, *sock);
else
freed = 0;
if (freed)
pack = NULL;
}
}
MQTTClient_retry();
Thread_unlock_mutex(mqttclient_mutex);
FUNC_EXIT;
return pack;
}
MQTTPacket* MQTTClient_waitfor(MQTTClient handle, int packet_type, int* rc, long timeout)
{
MQTTPacket* pack = NULL;
MQTTClients* m = handle;
START_TIME_TYPE start = MQTTClient_start_clock();
FUNC_ENTRY;
if (((MQTTClients*)handle) == NULL)
{
*rc = MQTTCLIENT_FAILURE;
goto exit;
}
if (running)
{
if (packet_type == CONNECT)
{
if ((*rc = Thread_wait_sem(m->connect_sem)) == 0)
*rc = m->rc;
}
else if (packet_type == CONNACK)
*rc = Thread_wait_sem(m->connack_sem);
else if (packet_type == SUBACK)
*rc = Thread_wait_sem(m->suback_sem);
else if (packet_type == UNSUBACK)
*rc = Thread_wait_sem(m->unsuback_sem);
if (*rc == 0 && packet_type != CONNECT && m->pack == NULL)
Log(TRACE_MIN, -1, "waitfor unexpectedly is NULL for client %s, packet_type %d", m->c->clientID, packet_type);
pack = m->pack;
}
else
{
*rc = TCPSOCKET_COMPLETE;
while (1)
{
int sock = -1;
pack = MQTTClient_cycle(&sock, 100L, rc);
if (sock == m->c->socket)
{
if (pack && (pack->header.bits.type == packet_type))
break;
if (m->c->connect_state == 1)
{
int error;
socklen_t len = sizeof(error);
if ((*rc = getsockopt(m->c->socket, SOL_SOCKET, SO_ERROR, (char*)&error, &len)) == 0)
*rc = error;
break;
}
}
else if (MQTTClient_elapsed(start) > timeout)
{
pack = NULL;
break;
}
}
}
exit:
FUNC_EXIT_RC(*rc);
return pack;
}
int MQTTClient_receive(MQTTClient handle, char** topicName, int* topicLen, MQTTClient_message** message,
unsigned long timeout)
{
int rc = TCPSOCKET_COMPLETE;
START_TIME_TYPE start = MQTTClient_start_clock();
unsigned long elapsed = 0L;
MQTTClients* m = handle;
FUNC_ENTRY;
if (m == NULL || m->c == NULL)
{
rc = MQTTCLIENT_FAILURE;
goto exit;
}
if (m->c->connected == 0)
{
rc = MQTTCLIENT_DISCONNECTED;
goto exit;
}
*topicName = NULL;
*message = NULL;
/* if there is already a message waiting, don't hang around but still do some packet handling */
if (m->c->messageQueue->count > 0)
timeout = 0L;
elapsed = MQTTClient_elapsed(start);
do
{
int sock = 0;
MQTTClient_cycle(&sock, (timeout > elapsed) ? timeout - elapsed : 0L, &rc);
elapsed = MQTTClient_elapsed(start);
}
while (elapsed < timeout && m->c->messageQueue->count == 0);
if (m->c->messageQueue->count > 0)
rc = MQTTClient_deliverMessage(rc, m, topicName, topicLen, message);
if (rc == SOCKET_ERROR)
MQTTClient_disconnect_internal(handle, 0);
exit:
FUNC_EXIT_RC(rc);
return rc;
}
void MQTTClient_yield(void)
{
START_TIME_TYPE start = MQTTClient_start_clock();
unsigned long elapsed = 0L;
unsigned long timeout = 100L;
int rc = 0;
FUNC_ENTRY;
if (running)
{
MQTTClient_sleep(timeout);
goto exit;
}
elapsed = MQTTClient_elapsed(start);
do
{
int sock = -1;
MQTTClient_cycle(&sock, (timeout > elapsed) ? timeout - elapsed : 0L, &rc);
if (rc == SOCKET_ERROR && ListFindItem(handles, &sock, clientSockCompare))
{
MQTTClients* m = (MQTTClient)(handles->current->content);
if (m->c->connect_state != -2)
MQTTClient_disconnect_internal(m, 0);
}
elapsed = MQTTClient_elapsed(start);
}
while (elapsed < timeout);
exit:
FUNC_EXIT;
}
int pubCompare(void* a, void* b)
{
Messages* msg = (Messages*)a;
return msg->publish == (Publications*)b;
}
int MQTTClient_waitForCompletion(MQTTClient handle, MQTTClient_deliveryToken mdt, unsigned long timeout)
{
int rc = MQTTCLIENT_FAILURE;
START_TIME_TYPE start = MQTTClient_start_clock();
unsigned long elapsed = 0L;
MQTTClients* m = handle;
FUNC_ENTRY;
Thread_lock_mutex(mqttclient_mutex);
if (m == NULL || m->c == NULL)
{
rc = MQTTCLIENT_FAILURE;
goto exit;
}
if (m->c->connected == 0)
{
rc = MQTTCLIENT_DISCONNECTED;
goto exit;
}
if (ListFindItem(m->c->outboundMsgs, &mdt, messageIDCompare) == NULL)
{
rc = MQTTCLIENT_SUCCESS; /* well we couldn't find it */
goto exit;
}
elapsed = MQTTClient_elapsed(start);
while (elapsed < timeout)
{
Thread_unlock_mutex(mqttclient_mutex);
MQTTClient_yield();
Thread_lock_mutex(mqttclient_mutex);
if (ListFindItem(m->c->outboundMsgs, &mdt, messageIDCompare) == NULL)
{
rc = MQTTCLIENT_SUCCESS; /* well we couldn't find it */
goto exit;
}
elapsed = MQTTClient_elapsed(start);
}
exit:
Thread_unlock_mutex(mqttclient_mutex);
FUNC_EXIT_RC(rc);
return rc;
}
int MQTTClient_getPendingDeliveryTokens(MQTTClient handle, MQTTClient_deliveryToken **tokens)
{
int rc = MQTTCLIENT_SUCCESS;
MQTTClients* m = handle;
*tokens = NULL;
FUNC_ENTRY;
Thread_lock_mutex(mqttclient_mutex);
if (m == NULL)
{
rc = MQTTCLIENT_FAILURE;
goto exit;
}
if (m->c && m->c->outboundMsgs->count > 0)
{
ListElement* current = NULL;
int count = 0;
*tokens = malloc(sizeof(MQTTClient_deliveryToken) * (m->c->outboundMsgs->count + 1));
Heap_unlink(__FILE__, __LINE__, *tokens);
while (ListNextElement(m->c->outboundMsgs, &current))
{
Messages* m = (Messages*)(current->content);
(*tokens)[count++] = m->msgid;
}
(*tokens)[count] = -1;
}
exit:
Thread_unlock_mutex(mqttclient_mutex);
FUNC_EXIT_RC(rc);
return rc;
}
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
/**
* @mainpage MQTT Client library for C
* &copy; Copyright IBM Corp. 2009, 2011
*
* @brief An MQTT client library in C.
*
* An MQTT client application connects to MQTT-capable servers such as WebSphere
* MQ Telemetry channels and the WebSphere MQ Telemetry daemon for devices.
* A typical client is responsible for collecting information from a telemetry
* device and publishing the information to the server. It can also subscribe
* to topics, receive messages, and use this information to control the
* telemetry device.
*
* MQTT clients implement the published MQTT v3 protocol. You can write your own
* API to the MQTT protocol using the programming language and platform of your
* choice. This can be time-consuming and error-prone.
*
* To simplify writing MQTT client applications, WebSphere MQ Telemetry provides
* C and Java client libraries that encapsulate the MQTT v3 protocol for a
* number of platforms. If you incorporate these libraries in your MQTT
* applications, a fully functional MQTT client can be written in a few lines of
* code. The information presented here documents the API provided by the IBM
* MQTT Client library for C.
*
* <b>Using the client</b><br>
* Applications that use the client library typically use a similar structure:
* <ul>
* <li>Create a clent</li>
* <li>Set the options to connect to an MQTT server</li>
* <li>Set up callback functions if multi-threaded (asynchronous mode)
* operation is being used (see @ref async).</li>
* <li>Subscribe to any topics the client needs to receive</li>
* <li>Repeat until finished:</li>
* <ul>
* <li>Publish any messages the client needs to</li>
* <li>Handle any incoming messages</li>
* </ul>
* <li>Disconnect the client</li>
* <li>Free any memory being used by the client</li>
* </ul>
* Some simple examples are shown here:
* <ul>
* <li>@ref pubsync</li>
* <li>@ref pubasync</li>
* <li>@ref subasync</li>
* </ul>
* Additional information about important concepts is provided here:
* <ul>
* <li>@ref async</li>
* <li>@ref wildcard</li>
* <li>@ref qos</li>
* </ul>
*/
/// @cond EXCLUDE
#if !defined(MQTTCLIENT_H)
#define MQTTCLIENT_H
#if defined(WIN32)
#define DLLImport __declspec(dllimport)
#define DLLExport __declspec(dllexport)
#else
#define DLLImport extern
#define DLLExport
#endif
#include <stdio.h>
/// @endcond
#if !defined(NO_PERSISTENCE)
#include "MQTTClientPersistence.h"
#endif
/**
* Return code: No error. Indicates successful completion of an MQTT client
* operation.
*/
#define MQTTCLIENT_SUCCESS 0
/**
* Return code: A generic error code indicating the failure of an MQTT client
* operation.
*/
#define MQTTCLIENT_FAILURE -1
/* error code -2 is MQTTCLIENT_PERSISTENCE_ERROR */
/**
* Return code: The client is disconnected.
*/
#define MQTTCLIENT_DISCONNECTED -3
/**
* Return code: The maximum number of messages allowed to be simultaneously
* in-flight has been reached.
*/
#define MQTTCLIENT_MAX_MESSAGES_INFLIGHT -4
/**
* Return code: An invalid UTF-8 string has been detected.
*/
#define MQTTCLIENT_BAD_UTF8_STRING -5
/**
* Return code: A NULL parameter has been supplied when this is invalid.
*/
#define MQTTCLIENT_NULL_PARAMETER -6
/**
* Return code: The topic has been truncated (the topic string includes
* embedded NULL characters). String functions will not access the full topic.
* Use the topic length value to access the full topic.
*/
#define MQTTCLIENT_TOPICNAME_TRUNCATED -7
/**
* Return code: A structure parameter does not have the correct eyecatcher
* and version number.
*/
#define MQTTCLIENT_BAD_STRUCTURE -8
/**
* A handle representing an MQTT client. A valid client handle is available
* following a successful call to MQTTClient_create().
*/
typedef void* MQTTClient;
/**
* A value representing an MQTT message. A delivery token is returned to the
* client application when a message is published. The token can then be used to
* check that the message was successfully delivered to its destination (see
* MQTTClient_publish(),
* MQTTClient_publishMessage(),
* MQTTClient_deliveryComplete(),
* MQTTClient_waitForCompletion() and
* MQTTClient_getPendingDeliveryTokens()).
*/
typedef int MQTTClient_deliveryToken;
/**
* A structure representing the payload and attributes of an MQTT message. The
* message topic is not part of this structure (see MQTTClient_publishMessage(),
* MQTTClient_publish(), MQTTClient_receive(), MQTTClient_freeMessage()
* and MQTTClient_messageArrived()).
*/
typedef struct
{
/** The eyecatcher for this structure. must be MQTM. */
char struct_id[4];
/** The version number of this structure. Must be 0 */
int struct_version;
/** The length of the MQTT message payload in bytes. */
int payloadlen;
/** A pointer to the payload of the MQTT message. */
void* payload;
/**
* The quality of service (QoS) assigned to the message.
* There are three levels of QoS:
* <DL>
* <DT><B>QoS0</B></DT>
* <DD>Fire and forget - the message may not be delivered</DD>
* <DT><B>QoS1</B></DT>
* <DD>At least once - the message will be delivered, but may be
* delivered more than once in some circumstances.</DD>
* <DT><B>QoS2</B></DT>
* <DD>Once and one only - the message will be delivered exactly once.</DD>
* </DL>
*/
int qos;
/**
* The retained flag serves two purposes depending on whether the message
* it is associated with is being published or received.
*
* <b>retained = true</b><br>
* For messages being published, a true setting indicates that the MQTT
* server should retain a copy of the message. The message will then be
* transmitted to new subscribers to a topic that matches the message topic.
* For subscribers registering a new subscription, the flag being true
* indicates that the received message is not a new one, but one that has
* been retained by the MQTT server.
*
* <b>retained = false</b> <br>
* For publishers, this ndicates that this message should not be retained
* by the MQTT server. For subscribers, a false setting indicates this is
* a normal message, received as a result of it being published to the
* server.
*/
int retained;
/**
* The dup flag indicates whether or not this message is a duplicate.
* It is only meaningful when receiving QoS1 messages. When true, the
* client application should take appropriate action to deal with the
* duplicate message.
*/
int dup;
/** The message identifier is normally reserved for internal use by the
* MQTT client and server.
*/
int msgid;
} MQTTClient_message;
#define MQTTClient_message_initializer { "MQTM", 0, 0, NULL, 0, 0, 0, 0 }
/**
* This is a callback function. The client application
* must provide an implementation of this function to enable asynchronous
* receipt of messages. The function is registered with the client library by
* passing it as an argument to MQTTClient_setCallbacks(). It is
* called by the client library when a new message that matches a client
* subscription has been received from the server. This function is executed on
* a separate thread to the one on which the client application is running.
* @param context A pointer to the <i>context</i> value originally passed to
* MQTTClient_setCallbacks(), which contains any application-specific context.
* @param topicName The topic associated with the received message.
* @param topicLen The length of the topic if there are one
* more NULL characters embedded in <i>topicName</i>, otherwise <i>topicLen</i>
* is 0. If <i>topicLen</i> is 0, the value returned by <i>strlen(topicName)</i>
* can be trusted. If <i>topicLen</i> is greater than 0, the full topic name
* can be retrieved by accessing <i>topicName</i> as a byte array of length
* <i>topicLen</i>.
* @param message The MQTTClient_message structure for the received message.
* This structure contains the message payload and attributes.
* @return This function must return a boolean value indicating whether or not
* the message has been safely received by the client application. Returning
* true indicates that the message has been successfully handled.
* Returning false indicates that there was a problem. In this
* case, the client library will reinvoke MQTTClient_messageArrived() to
* attempt to deliver the message to the application again.
*/
typedef int MQTTClient_messageArrived(void* context, char* topicName, int topicLen, MQTTClient_message* message);
/**
* This is a callback function. The client application
* must provide an implementation of this function to enable asynchronous
* notification of delivery of messages. The function is registered with the
* client library by passing it as an argument to MQTTClient_setCallbacks().
* It is called by the client library after the client application has
* published a message to the server. It indicates that the necessary
* handshaking and acknowledgements for the requested quality of service (see
* MQTTClient_message.qos) have been completed. This function is executed on a
* separate thread to the one on which the client application is running.
* <b>Note:</b>MQTTClient_deliveryComplete() is not called when messages are
* published at QoS0.
* @param context A pointer to the <i>context</i> value originally passed to
* MQTTClient_setCallbacks(), which contains any application-specific context.
* @param dt The ::MQTTClient_deliveryToken associated with
* the published message. Applications can check that all messages have been
* correctly published by matching the delivery tokens returned from calls to
* MQTTClient_publish() and MQTTClient_publishMessage() with the tokens passed
* to this callback.
*/
typedef void MQTTClient_deliveryComplete(void* context, MQTTClient_deliveryToken dt);
/**
* This is a callback function. The client application
* must provide an implementation of this function to enable asynchronous
* notification of the loss of connection to the server. The function is
* registered with the client library by passing it as an argument to
* MQTTClient_setCallbacks(). It is called by the client library if the client
* loses its connection to the server. The client application must take
* appropriate action, such as trying to reconnect or reporting the problem.
* This function is executed on a separate thread to the one on which the
* client application is running.
* @param context A pointer to the <i>context</i> value originally passed to
* MQTTClient_setCallbacks(), which contains any application-specific context.
* @param cause The reason for the disconnection.
* Currently, <i>cause</i> is always set to NULL.
*/
typedef void MQTTClient_connectionLost(void* context, char* cause);
/**
* This function sets the callback functions for a specific client.
* If your client application doesn't use a particular callback, set the
* relevant parameter to NULL. Calling MQTTClient_setCallbacks() puts the
* client into multi-threaded mode. Any necessary message acknowledgements and
* status communications are handled in the background without any intervention
* from the client application. See @ref async for more information.
*
* <b>Note:</b> The MQTT client must be disconnected when this function is
* called.
* @param handle A valid client handle from a successful call to
* MQTTClient_create().
* @param context A pointer to any application-specific context. The
* the <i>context</i> pointer is passed to each of the callback functions to
* provide access to the context information in the callback.
* @param cl A pointer to an MQTTClient_connectionLost() callback
* function. You can set this to NULL if your application doesn't handle
* disconnections.
* @param ma A pointer to an MQTTClient_messageArrived() callback
* function. This callback function must be specified when you call
* MQTTClient_setCallbacks().
* @param dc A pointer to an MQTTClient_deliveryComplete() callback
* function. You can set this to NULL if your application publishes
* synchronously or if you do not want to check for successful delivery.
* @return ::MQTTCLIENT_SUCCESS if the callbacks were correctly set,
* ::MQTTCLIENT_FAILURE if an error occurred.
*/
DLLExport int MQTTClient_setCallbacks(MQTTClient handle, void* context, MQTTClient_connectionLost* cl,
MQTTClient_messageArrived* ma, MQTTClient_deliveryComplete* dc);
/**
* This function creates an MQTT client ready for connection to the
* specified server and using the specified persistent storage (see
* MQTTClient_persistence). See also MQTTClient_destroy().
* @param handle A pointer to an ::MQTTClient handle. The handle is
* populated with a valid client reference following a successful return from
* this function.
* @param serverURI A null-terminated string specifying the server to
* which the client will connect. It takes the form <i>protocol://host:port</i>.
* Currently, <i>protocol</i> must be <i>tcp</i>. For <i>host</i>, you can
* specify either an IP address or a domain name. For instance, to connect to
* a server running on the local machines with the default MQTT port, specify
* <i>tcp://localhost:1883</i>.
* @param clientId The client identifier passed to the server when the
* client connects to it. It is a null-terminated UTF-8 encoded string.
* ClientIDs must be no longer than 23 characters according to the MQTT
* specification.
* @param persistence_type The type of persistence to be used by the client:
* <br>
* ::MQTTCLIENT_PERSISTENCE_NONE: Use in-memory persistence. If the device or
* system on which the client is running fails or is switched off, the current
* state of any in-flight messages is lost and some messages may not be
* delivered even at QoS1 and QoS2.
* <br>
* ::MQTTCLIENT_PERSISTENCE_DEFAULT: Use the default (file system-based)
* persistence mechanism. Status about in-flight messages is held in persistent
* storage and provides some protection against message loss in the case of
* unexpected failure.
* <br>
* ::MQTTCLIENT_PERSISTENCE_USER: Use an application-specific persistence
* implementation. Using this type of persistence gives control of the
* persistence mechanism to the application. The application has to implement
* the MQTTClient_persistence interface.
* @param persistence_context If the application uses
* ::MQTTCLIENT_PERSISTENCE_NONE persistence, this argument is unused and should
* be set to NULL. For ::MQTTCLIENT_PERSISTENCE_DEFAULT persistence, it
* should be set to the location of the persistence directory (if set
* to NULL, the persistence directory used is the working directory).
* Applications that use ::MQTTCLIENT_PERSISTENCE_USER persistence set this
* argument to point to a valid MQTTClient_persistence structure.
* @return ::MQTTCLIENT_SUCCESS if the client is successfully created, otherwise
* an error code is returned.
*/
DLLExport int MQTTClient_create(MQTTClient* handle, char* serverURI, char* clientId,
int persistence_type, void* persistence_context);
/**
* MQTTClient_willOptions defines the MQTT "Last Will and Testament" (LWT) settings for
* the client. In the event that a client unexpectedly loses its connection to
* the server, the server publishes the LWT message to the LWT topic on
* behalf of the client. This allows other clients (subscribed to the LWT topic)
* to be made aware that the client has disconnected. To enable the LWT
* function for a specific client, a valid pointer to an MQTTClient_willOptions
* structure is passed in the MQTTClient_connectOptions structure used in the
* MQTTClient_connect() call that connects the client to the server. The pointer
* to MQTTClient_willOptions can be set to NULL if the LWT function is not
* required.
*/
typedef struct
{
/** The eyecatcher for this structure. must be MQTW. */
char struct_id[4];
/** The version number of this structure. Must be 0 */
int struct_version;
/** The LWT topic to which the LWT message will be published. */
char* topicName;
/** The LWT payload. */
char* message;
/**
* The retained flag for the LWT message (see MQTTClient_message.retained).
*/
int retained;
/**
* The quality of service setting for the LWT message (see
* MQTTClient_message.qos and @ref qos).
*/
int qos;
} MQTTClient_willOptions;
#define MQTTClient_willOptions_initializer { "MQTW", 0, NULL, NULL, 0, 0 }
/**
* MQTTClient_connectOptions defines several settings that control the way the
* client connects to an MQTT server.
*
* <b>Note:</b> Default values are not defined for members of
* MQTTClient_connectOptions so it is good practice to specify all settings.
* If the MQTTClient_connectOptions structure is defined as an automatic
* variable, all members are set to random values and thus must be set by the
* client application. If the MQTTClient_connectOptions structure is defined
* as a static variable, initialization (in compliant compilers) sets all
* values to 0 (NULL for pointers). A #keepAliveInterval setting of 0 prevents
* correct operation of the client and so you <b>must</b> at least set a value
* for #keepAliveInterval.
*/
typedef struct
{
/** The eyecatcher for this structure. must be MQTC. */
char struct_id[4];
/** The version number of this structure. Must be 0 */
int struct_version;
/** The "keep alive" interval, measured in seconds, defines the maximum time
* that should pass without communication between the client and the server
* The client will ensure that at least one message travels across the
* network within each keep alive period. In the absence of a data-related
* message during the time period, the client sends a very small MQTT
* "ping" message, which the server will acknowledge. The keep alive
* interval enables the client to detect when the server is no longer
* available without having to wait for the long TCP/IP timeout.
*/
int keepAliveInterval;
/**
* This is a boolean value. The cleansession setting controls the behaviour
* of both the client and the server at connection and disconnection time.
* The client and server both maintain session state information. This
* information is used to ensure "at least once" and "exactly once"
* delivery, and "exactly once" receipt of messages. Session state also
* includes subscriptions created by an MQTT client. You can choose to
* maintain or discard state information between sessions.
*
* When cleansession is true, the state information is discarded at
* connect and disconnect. Setting cleansession to false keeps the state
* information. When you connect an MQTT client application with
* MQTTClient_connect(), the client identifies the connection using the
* client identifier and the address of the server. The server checks
* whether session information for this client
* has been saved from a previous connection to the server. If a previous
* session still exists, and cleansession=true, then the previous session
* information at the client and server is cleared. If cleansession=false,
* the previous session is resumed. If no previous session exists, a new
* session is started.
*/
int cleansession;
/**
* This is a boolean value that controls how many messages can be in-flight
* simultaneously. Setting <i>reliable</i> to true means that a published
* message must be completed (acknowledgements received) before another
* can be sent. Attempts to publish additional messages receive an
* ::MQTTCLIENT_MAX_MESSAGES_INFLIGHT return code. Setting this flag to
* false allows up to 10 messages to be in-flight. This can increase
* overall throughput in some circumstances.
*/
int reliable;
/**
* This is a pointer to an MQTTClient_willOptions structure. If your
* application does not make use of the Last Will and Testament feature,
* set this pointer to NULL.
*/
MQTTClient_willOptions* will;
/**
* MQTT servers that support the MQTT v3.1 protocol provide authentication
* and authorisation by user name and password. This is the user name
* parameter.
*/
char* username;
/**
* MQTT servers that support the MQTT v3.1 protocol provide authentication
* and authorisation by user name and password. This is the password
* parameter.
*/
char* password;
/**
* The time interval in seconds to allow a connect to complete.
*/
int connectTimeout;
/**
* The time interval in seconds
*/
int retryInterval;
} MQTTClient_connectOptions;
#define MQTTClient_connectOptions_initializer { "MQTC", 0, 60, 1, 1, NULL, NULL, NULL, 30, 20 }
/**
* This function attempts to connect a previously-created client (see
* MQTTClient_create()) to an MQTT server using the specified options. If you
* want to enable asynchronous message and status notifications, you must call
* MQTTClient_setCallbacks() prior to MQTTClient_connect().
* @param handle A valid client handle from a successful call to
* MQTTClient_create().
* @param options A pointer to a valid MQTTClient_connectOptions
* structure.
* @return ::MQTTCLIENT_SUCCESS if the client successfully connects to the
* server. An error code is returned if the client was unable to connect to
* the server.
* Error codes greater than 0 are returned by the MQTT protocol:<br><br>
* <b>1</b>: Connection refused: Unacceptable protocol version<br>
* <b>2</b>: Connection refused: Identifier rejected<br>
* <b>3</b>: Connection refused: Server unavailable<br>
* <b>4</b>: Connection refused: Bad user name or password<br>
* <b>5</b>: Connection refused: Not authorized<br>
* <b>6-255</b>: Reserved for future use<br>
*/
DLLExport int MQTTClient_connect(MQTTClient handle, MQTTClient_connectOptions* options);
/**
* This function attempts to disconnect the client from the MQTT
* server. In order to allow the client time to complete handling of messages
* that are in-flight when this function is called, a timeout period is
* specified. When the timeout period has expired, the client disconnects even
* if there are still outstanding message acknowledgements.
* The next time the client connects to the same server, any QoS 1 or 2
* messages which have not completed will be retried depending on the
* cleansession settings for both the previous and the new connection (see
* MQTTClient_connectOptions.cleansession and MQTTClient_connect()).
* @param handle A valid client handle from a successful call to
* MQTTClient_create().
* @param timeout The client delays disconnection for up to this time (in
* milliseconds) in order to allow in-flight message transfers to complete.
* @return ::MQTTCLIENT_SUCCESS if the client successfully disconnects from
* the server. An error code is returned if the client was unable to disconnect
* from the server
*/
DLLExport int MQTTClient_disconnect(MQTTClient handle, int timeout);
/**
* This function allows the client application to test whether or not a
* client is currently connected to the MQTT server.
* @param handle A valid client handle from a successful call to
* MQTTClient_create().
* @return Boolean true if the client is connected, otherwise false.
*/
DLLExport int MQTTClient_isConnected(MQTTClient handle);
/* Subscribe is synchronous. QoS list parameter is changed on return to granted QoSs.
Returns return code, MQTTCLIENT_SUCCESS == success, non-zero some sort of error (TBD) */
/**
* This function attempts to subscribe a client to a single topic, which may
* contain wildcards (see @ref wildcard). This call also specifies the
* @ref qos requested for the subscription
* (see also MQTTClient_subscribeMany()).
* @param handle A valid client handle from a successful call to
* MQTTClient_create().
* @param topic The subscription topic, which may include wildcards.
* @param qos The requested quality of service for the subscription.
* @return ::MQTTCLIENT_SUCCESS if the subscription request is successful.
* An error code is returned if there was a problem registering the
* subscription.
*/
DLLExport int MQTTClient_subscribe(MQTTClient handle, char* topic, int qos);
/**
* This function attempts to subscribe a client to a list of topics, which may
* contain wildcards (see @ref wildcard). This call also specifies the
* @ref qos requested for each topic (see also MQTTClient_subscribe()).
* @param handle A valid client handle from a successful call to
* MQTTClient_create().
* @param count The number of topics for which the client is requesting
* subscriptions.
* @param topic An array (of length <i>count</i>) of pointers to
* topics, each of which may include wildcards.
* @param qos An array (of length <i>count</i>) of @ref qos
* values. qos[n] is the requested QoS for topic[n].
* @return ::MQTTCLIENT_SUCCESS if the subscription request is successful.
* An error code is returned if there was a problem registering the
* subscriptions.
*/
DLLExport int MQTTClient_subscribeMany(MQTTClient handle, int count, char** topic, int* qos);
/**
* This function attempts to remove an existing subscription made by the
* specified client.
* @param handle A valid client handle from a successful call to
* MQTTClient_create().
* @param topic The topic for the subscription to be removed, which may
* include wildcards (see @ref wildcard).
* @return ::MQTTCLIENT_SUCCESS if the subscription is removed.
* An error code is returned if there was a problem removing the
* subscription.
*/
DLLExport int MQTTClient_unsubscribe(MQTTClient handle, char* topic);
/**
* This function attempts to remove existing subscriptions to a list of topics
* made by the specified client.
* @param handle A valid client handle from a successful call to
* MQTTClient_create().
* @param count The number subscriptions to be removed.
* @param topic An array (of length <i>count</i>) of pointers to the topics of
* the subscriptions to be removed, each of which may include wildcards.
* @return ::MQTTCLIENT_SUCCESS if the subscriptions are removed.
* An error code is returned if there was a problem removing the subscriptions.
*/
DLLExport int MQTTClient_unsubscribeMany(MQTTClient handle, int count, char** topic);
/**
* This function attempts to publish a message to a given topic (see also
* MQTTClient_publishMessage()). An ::MQTTClient_deliveryToken is issued when
* this function returns successfully. If the client application needs to
* test for succesful delivery of QoS1 and QoS2 messages, this can be done
* either asynchronously or synchronously (see @ref async,
* ::MQTTClient_waitForCompletion and MQTTClient_deliveryComplete()).
* @param handle A valid client handle from a successful call to
* MQTTClient_create().
* @param topicName The topic associated with this message.
* @param payloadlen The length of the payload in bytes.
* @param payload A pointer to the byte array payload of the message.
* @param qos The @ref qos of the message.
* @param retained The retained flag for the message.
* @param dt A pointer to an ::MQTTClient_deliveryToken. This is populated
* with a token representing the message when the function returns
* successfully. If your application does not use delivery tokens, set this
* argument to NULL.
* @return ::MQTTCLIENT_SUCCESS if the message is accepted for publication.
* An error code is returned if there was a problem accepting the message.
*/
DLLExport int MQTTClient_publish(MQTTClient handle, char* topicName, int payloadlen, void* payload, int qos, int retained,
MQTTClient_deliveryToken* dt);
/**
* This function attempts to publish a message to a given topic (see also
* MQTTClient_publish()). An ::MQTTClient_deliveryToken is issued when
* this function returns successfully. If the client application needs to
* test for succesful delivery of QoS1 and QoS2 messages, this can be done
* either asynchronously or synchronously (see @ref async,
* ::MQTTClient_waitForCompletion and MQTTClient_deliveryComplete()).
* @param handle A valid client handle from a successful call to
* MQTTClient_create().
* @param topicName The topic associated with this message.
* @param msg A pointer to a valid MQTTClient_message structure containing
* the payload and attributes of the message to be published.
* @param dt A pointer to an ::MQTTClient_deliveryToken. This is populated
* with a token representing the message when the function returns
* successfully. If your application does not use delivery tokens, set this
* argument to NULL.
* @return ::MQTTCLIENT_SUCCESS if the message is accepted for publication.
* An error code is returned if there was a problem accepting the message.
*/
DLLExport int MQTTClient_publishMessage(MQTTClient handle, char* topicName, MQTTClient_message* msg, MQTTClient_deliveryToken* dt);
/**
* This function is called by the client application to synchronize execution
* of the main thread with completed publication of a message. When called,
* MQTTClient_waitForCompletion() blocks execution until the message has been
* successful delivered or the specified timeout has expired. See @ref async.
* @param handle A valid client handle from a successful call to
* MQTTClient_create().
* @param dt The ::MQTTClient_deliveryToken that represents the message being
* tested for successful delivery. Delivery tokens are issued by the
* publishing functions MQTTClient_publish() and MQTTClient_publishMessage().
* @param timeout The maximum time to wait in milliseconds.
* @return ::MQTTCLIENT_SUCCESS if the message was successfully delivered.
* An error code is returned if the timeout expires or there was a problem
* checking the token.
*/
DLLExport int MQTTClient_waitForCompletion(MQTTClient handle, MQTTClient_deliveryToken dt, unsigned long timeout);
/**
* This function sets a pointer to an array of delivery tokens for
* messages that are currently in-flight (pending completion).
*
* <b>Important note:</b> The memory used to hold the array of tokens is
* malloc()'d in this function. The client application is responsible for
* freeing this memory when it is no longer required.
* @param handle A valid client handle from a successful call to
* MQTTClient_create().
* @param tokens The address of a pointer to an ::MQTTClient_deliveryToken.
* When the function returns successfully, the pointer is set to point to an
* array of tokens representing messages pending completion. The last member of
* the array is set to -1 to indicate there are no more tokens. If no tokens
* are pending, the pointer is set to NULL.
* @return ::MQTTCLIENT_SUCCESS if the function returns successfully.
* An error code is returned if there was a problem obtaining the list of
* pending tokens.
*/
DLLExport int MQTTClient_getPendingDeliveryTokens(MQTTClient handle, MQTTClient_deliveryToken **tokens);
/**
* When implementing a single-threaded client, call this function periodically
* to allow processing of message retries and to send MQTT keepalive pings.
* If the application is calling MQTTClient_receive() regularly, then it is
* not necessary to call this function.
*/
DLLExport void MQTTClient_yield(void);
/**
* This function performs a synchronous receive of incoming messages. It should
* be used only when the client application has not set callback methods to
* support asynchronous receipt of messages (see @ref async and
* MQTTClient_setCallbacks()). Using this function allows a single-threaded
* client subscriber application to be written. When called, this function
* blocks until the next message arrives or the specified timeout expires
*(see also MQTTClient_yield()).
*
* <b>Important note:</b> The application must free() the memory allocated
* to the topic and the message when processing is complete (see
* MQTTClient_freeMessage()).
* @param handle A valid client handle from a successful call to
* MQTTClient_create().
* @param topicName The address of a pointer to a topic. This function
* allocates the memory for the topic and returns it to the application
* by setting <i>topicName</i> to point to the topic.
* @param topicLen The length of the topic. If the return code from this
* function is ::MQTTCLIENT_TOPICNAME_TRUNCATED, the topic contains embedded
* NULL characters and the full topic should be retrieved by using
* <i>topicLen</i>.
* @param message The address of a pointer to the received message. This
* function allocates the memory for the message and returns it to the
* application by setting <i>message</i> to point to the received message.
* The pointer is set to NULL if the timeout expires.
* @param timeout The length of time to wait for a message in milliseconds.
* @return ::MQTTCLIENT_SUCCESS or ::MQTTCLIENT_TOPICNAME_TRUNCATED if a
* message is received. ::MQTTCLIENT_SUCCESS can also indicate that the
* timeout expired, in which case <i>message</i> is NULL. An error code is
* returned if there was a problem trying to receive a message.
*/
DLLExport int MQTTClient_receive(MQTTClient handle, char** topicName, int* topicLen, MQTTClient_message** message,
unsigned long timeout);
/**
* This function frees memory allocated to an MQTT message, including the
* additional memory allocated to the message payload. The client application
* calls this function when the message has been fully processed. <b>Important
* note:</b> This function does not free the memory allocated to a message
* topic string. It is the responsibility of the client application to free
* this memory using the MQTTClient_free() library function.
* @param msg The address of a pointer to the ::MQTTClient_message structure
* to be freed.
*/
DLLExport void MQTTClient_freeMessage(MQTTClient_message** msg);
/**
* This function frees memory allocated by the MQTT C client library, especially the
* topic name. This is needed on Windows when the client libary and application
* program have been compiled with different versions of the C compiler. It is
* thus good policy to always use this function when freeing any MQTT C client-
* allocated memory.
* @param ptr The pointer to the client library storage to be freed.
*/
DLLExport void MQTTClient_free(void* ptr);
/**
* This function frees the memory allocated to an MQTT client (see
* MQTTClient_create()). It should be called when the client is no longer
* required.
* @param handle A pointer to the handle referring to the ::MQTTClient
* structure to be freed.
*/
DLLExport void MQTTClient_destroy(MQTTClient* handle);
#endif
/**
* @page async Asynchronous vs synchronous client applications
* The client library supports two modes of operation. These are referred to
* as <b>synchronous</b> and <b>asynchronous</b> modes. If your application
* calls MQTTClient_setCallbacks(), this puts the client into asynchronous
* mode, otherwise it operates in synchronous mode.
*
* In synchronous mode, the client application runs on a single thread.
* Messages are published using the MQTTClient_publish() and
* MQTTClient_publishMessage() functions. To determine that a QoS1 or QoS2
* (see @ref qos) message has been successfully delivered, the application
* must call the MQTTClient_waitForCompletion() function. An example showing
* synchronous publication is shown in @ref pubsync. Receiving messages in
* synchronous mode uses the MQTTClient_receive() function. Client applicaitons
* must call either MQTTClient_receive() or MQTTClient_yield() relatively
* frequently in order to allow processing of acknowledgements and the MQTT
* "pings" that keep the network connection to the server alive.
*
* In asynchronous mode, the client application runs on several threads. The
* main program calls functions in the client library to publish and subscribe,
* just as for the synchronous mode. Processing of handshaking and maintaining
* the network connection is performed in the background, however.
* Notifications of status and message reception are provided to the client
* application using callbacks registered with the library by the call to
* MQTTClient_setCallbacks() (see MQTTClient_messageArrived(),
* MQTTClient_connectionLost() and MQTTClient_deliveryComplete()).
*
* @page wildcard Subscription wildcards
* Every MQTT message includes a topic that classifies it. MQTT servers use
* topics to determine which subscribers should receive messages published to
* the server.
*
* Consider the server receiving messages from several environmental sensors.
* Each sensor publishes its measurement data as a message with an associated
* topic. Subscribing applications need to know which sensor originally
* published each received message. A unique topic is thus used to identify
* each sensor and measurement type. Topics such as SENSOR1TEMP,
* SENSOR1HUMIDITY, SENSOR2TEMP and so on achieve this but are not very
* flexible. If additional sensors are added to the system at a later date,
* subscribing applications must be modified to receive them.
*
* To provide more flexibility, MQTT supports a hierarchical topic namespace.
* This allows application designers to organize topics to simplify their
* management. Levels in the hierarchy are delimited by the '/' character,
* such as SENSOR/1/HUMIDITY. Publishers and subscribers use these
* hierarchical topics as already described.
*
* For subscriptions, two wildcard characters are supported:
* <ul>
* <li>A '#' character represents a complete sub-tree of the hierarchy and
* thus must be the last character in a subscription topic string, such as
* SENSOR/#. This will match any topic starting with SENSOR/, such as
* SENSOR/1/TEMP and SENSOR/2/HUMIDITY.</li>
* <li> A '+' character represents a single level of the hierarchy and is
* used between delimiters. For example, SENSOR/+/TEMP will match
* SENSOR/1/TEMP and SENSOR/2/TEMP.</li>
* </ul>
* Publishers are not allowed to use the wildcard characters in their topic
* names.
*
* Deciding on your topic hierarchy is an important step in your system design.
*
* @page qos Quality of service
* The MQTT protocol provides three qualities of service for delivering
* messages between clients and servers: "at most once", "at least once" and
* "exactly once".
*
* Quality of service (QoS) is an attribute of an individual message being
* published. An application sets the QoS for a specific message by setting the
* MQTTClient_message.qos field to the required value.
*
* A subscribing client can set the maximum quality of service a server uses
* to send messages that match the client subscriptions. The
* MQTTClient_subscribe() and MQTTClient_subscribeMany() functions set this
* maximum. The QoS of a message forwarded to a subscriber thus might be
* different to the QoS given to the message by the original publisher.
* The lower of the two values is used to forward a message.
*
* The three levels are:
*
* <b>QoS0, At most once:</b> The message is delivered at most once, or it
* may not be delivered at all. Its delivery across the network is not
* acknowledged. The message is not stored. The message could be lost if the
* client is disconnected, or if the server fails. QoS0 is the fastest mode of
* transfer. It is sometimes called "fire and forget".
*
* The MQTT protocol does not require servers to forward publications at QoS0
* to a client. If the client is disconnected at the time the server receives
* the publication, the publication might be discarded, depending on the
* server implementation.
*
* <b>QoS1, At least once:</b> The message is always delivered at least once.
* It might be delivered multiple times if there is a failure before an
* acknowledgment is received by the sender. The message must be stored
* locally at the sender, until the sender receives confirmation that the
* message has been published by the receiver. The message is stored in case
* the message must be sent again.
*
* <b>QoS2, Exactly once:</b> The message is always delivered exactly once.
* The message must be stored locally at the sender, until the sender receives
* confirmation that the message has been published by the receiver. The
* message is stored in case the message must be sent again. QoS2 is the
* safest, but slowest mode of transfer. A more sophisticated handshaking
* and acknowledgement sequence is used than for QoS1 to ensure no duplication
* of messages occurs.
* @page pubsync Synchronous publication example
* @code
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "MQTTClient.h"
#define ADDRESS "tcp://localhost:1883"
#define CLIENTID "ExampleClientPub"
#define TOPIC "MQTT Examples"
#define PAYLOAD "Hello World!"
#define QOS 1
#define TIMEOUT 10000L
int main(int argc, char* argv[])
{
MQTTClient client;
MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
MQTTClient_message pubmsg = MQTTClient_message_initializer;
MQTTClient_deliveryToken token;
int rc;
MQTTClient_create(&client, ADDRESS, CLIENTID,
MQTTCLIENT_PERSISTENCE_NONE, NULL);
conn_opts.keepAliveInterval = 20;
conn_opts.cleansession = 1;
if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS)
{
printf("Failed to connect, return code %d\n", rc);
exit(-1);
}
pubmsg.payload = PAYLOAD;
pubmsg.payloadlen = strlen(PAYLOAD);
pubmsg.qos = QOS;
pubmsg.retained = 0;
MQTTClient_publishMessage(client, TOPIC, &pubmsg, &token);
printf("Waiting for up to %d seconds for publication of %s\n"
"on topic %s for client with ClientID: %s\n",
(int)(TIMEOUT/1000), PAYLOAD, TOPIC, CLIENTID);
rc = MQTTClient_waitForCompletion(client, token, TIMEOUT);
printf("Message with delivery token %d delivered\n", token);
MQTTClient_disconnect(client, 10000);
MQTTClient_destroy(&client);
return rc;
}
* @endcode
*
* @page pubasync Asynchronous publication example
* @code
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "MQTTClient.h"
#define ADDRESS "tcp://localhost:1883"
#define CLIENTID "ExampleClientPub"
#define TOPIC "MQTT Examples"
#define PAYLOAD "Hello World!"
#define QOS 1
#define TIMEOUT 10000L
volatile MQTTClient_deliveryToken deliveredtoken;
void delivered(void *context, MQTTClient_deliveryToken dt)
{
printf("Message with token value %d delivery confirmed\n", dt);
deliveredtoken = dt;
}
int msgarrvd(void *context, char *topicName, int topicLen, MQTTClient_message *message)
{
int i;
char* payloadptr;
printf("Message arrived\n");
printf(" topic: %s\n", topicName);
printf(" message: ");
payloadptr = message->payload;
for(i=0; i<message->payloadlen; i++)
{
putchar(*payloadptr++);
}
putchar('\n');
MQTTClient_freeMessage(&message);
MQTTClient_free(topicName);
return 1;
}
void connlost(void *context, char *cause)
{
printf("\nConnection lost\n");
printf(" cause: %s\n", cause);
}
int main(int argc, char* argv[])
{
MQTTClient client;
MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
MQTTClient_message pubmsg = MQTTClient_message_initializer;
MQTTClient_deliveryToken token;
int rc;
MQTTClient_create(&client, ADDRESS, CLIENTID,
MQTTCLIENT_PERSISTENCE_NONE, NULL);
conn_opts.keepAliveInterval = 20;
conn_opts.cleansession = 1;
MQTTClient_setCallbacks(client, NULL, connlost, msgarrvd, delivered);
if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS)
{
printf("Failed to connect, return code %d\n", rc);
exit(-1);
}
pubmsg.payload = PAYLOAD;
pubmsg.payloadlen = strlen(PAYLOAD);
pubmsg.qos = QOS;
pubmsg.retained = 0;
deliveredtoken = 0;
MQTTClient_publishMessage(client, TOPIC, &pubmsg, &token);
printf("Waiting for publication of %s\n"
"on topic %s for client with ClientID: %s\n",
PAYLOAD, TOPIC, CLIENTID);
while(deliveredtoken != token);
MQTTClient_disconnect(client, 10000);
MQTTClient_destroy(&client);
return rc;
}
* @endcode
* @page subasync Asynchronous subscription example
* @code
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "MQTTClient.h"
#define ADDRESS "tcp://localhost:1883"
#define CLIENTID "ExampleClientSub"
#define TOPIC "MQTT Examples"
#define PAYLOAD "Hello World!"
#define QOS 1
#define TIMEOUT 10000L
volatile MQTTClient_deliveryToken deliveredtoken;
void delivered(void *context, MQTTClient_deliveryToken dt)
{
printf("Message with token value %d delivery confirmed\n", dt);
deliveredtoken = dt;
}
int msgarrvd(void *context, char *topicName, int topicLen, MQTTClient_message *message)
{
int i;
char* payloadptr;
printf("Message arrived\n");
printf(" topic: %s\n", topicName);
printf(" message: ");
payloadptr = message->payload;
for(i=0; i<message->payloadlen; i++)
{
putchar(*payloadptr++);
}
putchar('\n');
MQTTClient_freeMessage(&message);
MQTTClient_free(topicName);
return 1;
}
void connlost(void *context, char *cause)
{
printf("\nConnection lost\n");
printf(" cause: %s\n", cause);
}
int main(int argc, char* argv[])
{
MQTTClient client;
MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
int rc;
int ch;
MQTTClient_create(&client, ADDRESS, CLIENTID,
MQTTCLIENT_PERSISTENCE_NONE, NULL);
conn_opts.keepAliveInterval = 20;
conn_opts.cleansession = 1;
MQTTClient_setCallbacks(client, NULL, connlost, msgarrvd, delivered);
if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS)
{
printf("Failed to connect, return code %d\n", rc);
exit(-1);
}
printf("Subscribing to topic %s\nfor client %s using QoS%d\n\n"
"Press Q<Enter> to quit\n\n", TOPIC, CLIENTID, QOS);
MQTTClient_subscribe(client, TOPIC, QOS);
do
{
ch = getchar();
} while(ch!='Q' && ch != 'q');
MQTTClient_disconnect(client, 10000);
MQTTClient_destroy(&client);
return rc;
}
* @endcode
*/
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
/**
* @file
* \brief This structure represents a persistent data store, used to store
* outbound and inbound messages, in order to achieve reliable messaging.
*
* The MQTT Client persists QoS1 and QoS2 messages in order to meet the
* assurances of delivery associated with these @ref qos levels. The messages
* are saved in persistent storage
* The type and context of the persistence implementation are specified when
* the MQTT client is created (see MQTTClient_create()). The default
* persistence type (::MQTTCLIENT_PERSISTENCE_DEFAULT) uses a file system-based
* persistence mechanism. The <i>persistence_context</i> argument passed to
* MQTTClient_create() when using the default peristence is a string
* representing the location of the persistence directory. If the context
* argument is NULL, the working directory will be used.
*
* To use memory-based persistence, an application passes
* ::MQTTCLIENT_PERSISTENCE_NONE as the <i>persistence_type</i> to
* MQTTClient_create(). This can lead to message loss in certain situations,
* but can be appropriate in some cases (see @ref qos).
*
* Client applications can provide their own persistence mechanism by passing
* ::MQTTCLIENT_PERSISTENCE_USER as the <i>persistence_type</i>. To implement a
* custom persistence mechanism, the application must pass an initialized
* ::MQTTClient_persistence structure as the <i>persistence_context</i>
* argument to MQTTClient_create().
*
* If the functions defined return an ::MQTTCLIENT_PERSISTENCE_ERROR then the
* state of the persisted data should remain as it was prior to the function
* being called. For example, if Persistence_put() returns
* ::MQTTCLIENT_PERSISTENCE_ERROR, then it is assumed tha tthe persistent store
* does not contain the data that was passed to the function. Similarly, if
* Persistence_remove() returns ::MQTTCLIENT_PERSISTENCE_ERROR then it is
* assumed that the data to be removed is still held in the persistent store.
*
* It is up to the persistence implementation to log any error information that
* may be required to diagnose a persistence mechanism failure.
*/
/// @cond EXCLUDE
#if !defined(MQTTCLIENTPERSISTENCE_H)
#define MQTTCLIENTPERSISTENCE_H
/// @endcond
/**
* This <i>persistence_type</i> value specifies the default file system-based
* persistence mechanism (see MQTTClient_create()).
*/
#define MQTTCLIENT_PERSISTENCE_DEFAULT 0
/**
* This <i>persistence_type</i> value specifies a memory-based
* persistence mechanism (see MQTTClient_create()).
*/
#define MQTTCLIENT_PERSISTENCE_NONE 1
/**
* This <i>persistence_type</i> value specifies an application-specific
* persistence mechanism (see MQTTClient_create()).
*/
#define MQTTCLIENT_PERSISTENCE_USER 2
/**
* Application-specific persistence functions must return this error code if
* there is a problem executing the function.
*/
#define MQTTCLIENT_PERSISTENCE_ERROR -2
/**
* @brief Initialize the persistent store.
*
* Either open the existing persistent store for this client ID or create a new
* one if one doesn't exist. If the persistent store is already open, return
* without taking any action.
*
* An application can use the same client identifier to connect to many
* different servers. The <i>clientid</i> in conjunction with the
* <i>serverURI</i> uniquely identifies the persistence store required.
*
* @param handle The address of a pointer to a handle for this persistence
* implementation. This function must set handle to a valid reference to the
* persistence following a successful return.
* The handle pointer is passed as an argument to all the other
* persistence functions. It may include the context parameter and/or any other
* data for use by the persistence functions.
* @param clientID The client identifier for which the persistent store should
* be opened.
* @param serverURI The connection string specified when the MQTT client was
* created (see MQTTClient_create()).
* @param context A pointer to any data required to initialize the persistent
* store (see ::MQTTClient_persistence).
* @return Return 0 if the function completes successfully, otherwise return
* ::MQTTCLIENT_PERSISTENCE_ERROR.
*/
typedef int (*Persistence_open)(void** handle, char* clientID, char* serverURI, void* context);
/**
* @brief Close the persistent store referred to by the handle.
*
* @param handle The handle pointer from a successful call to
* Persistence_open().
* @return Return 0 if the function completes successfully, otherwise return
* ::MQTTCLIENT_PERSISTENCE_ERROR.
*/
typedef int (*Persistence_close)(void* handle);
/**
* @brief Put the specified data into the persistent store.
*
* @param handle The handle pointer from a successful call to
* Persistence_open().
* @param key A string used as the key for the data to be put in the store. The
* key is later used to retrieve data from the store with Persistence_get().
* @param bufcount The number of buffers to write to the persistence store.
* @param buffers An array of pointers to the data buffers associated with
* this <i>key</i>.
* @param buflens An array of lengths of the data buffers. <i>buflen[n]</i>
* gives the length of <i>buffer[n]</i>.
* @return Return 0 if the function completes successfully, otherwise return
* ::MQTTCLIENT_PERSISTENCE_ERROR.
*/
typedef int (*Persistence_put)(void* handle, char* key, int bufcount, char* buffers[], int buflens[]);
/**
* @brief Retrieve the specified data from the persistent store.
*
* @param handle The handle pointer from a successful call to
* Persistence_open().
* @param key A string that is the key for the data to be retrieved. This is
* the same key used to save the data to the store with Persistence_put().
* @param buffer The address of a pointer to a buffer. This function sets the
* pointer to point at the retrieved data, if successful.
* @param buflen The address of an int that is set to the length of
* <i>buffer</i> by this function if successful.
* @return Return 0 if the function completes successfully, otherwise return
* ::MQTTCLIENT_PERSISTENCE_ERROR.
*/
typedef int (*Persistence_get)(void* handle, char* key, char** buffer, int* buflen);
/**
* @brief Remove the data for the specified key from the store.
*
* @param handle The handle pointer from a successful call to
* Persistence_open().
* @param key A string that is the key for the data to be removed from the
* store. This is the same key used to save the data to the store with
* Persistence_put().
* @return Return 0 if the function completes successfully, otherwise return
* ::MQTTCLIENT_PERSISTENCE_ERROR.
*/
typedef int (*Persistence_remove)(void* handle, char* key);
/**
* @brief Returns the keys in this persistent data store.
*
* @param handle The handle pointer from a successful call to
* Persistence_open().
* @param keys The address of a pointer to pointers to strings. Assuming
* successful execution, this function allocates memory to hold the returned
* keys (strings used to store the data with Persistence_put()). It also
* allocates memory to hold an array of pointers to these strings. <i>keys</i>
* is set to point to the array of pointers to strings.
* @param nkeys A pointer to the number of keys in this persistent data store.
* This function sets the number of keys, if successful.
* @return Return 0 if the function completes successfully, otherwise return
* ::MQTTCLIENT_PERSISTENCE_ERROR.
*/
typedef int (*Persistence_keys)(void* handle, char*** keys, int* nkeys);
/**
* @brief Clears the persistence store, so that it no longer contains any
* persisted data.
*
* @param handle The handle pointer from a successful call to
* Persistence_open().
* @return Return 0 if the function completes successfully, otherwise return
* ::MQTTCLIENT_PERSISTENCE_ERROR.
*/
typedef int (*Persistence_clear)(void* handle);
/**
* @brief Returns whether any data has been persisted using the specified key.
*
* @param handle The handle pointer from a successful call to
* Persistence_open().
* @param key The string to be tested for existence in the store.
* @return Return 0 if the key was found in the store, otherwise return
* ::MQTTCLIENT_PERSISTENCE_ERROR.
*/
typedef int (*Persistence_containskey)(void* handle, char* key);
/**
* @brief A structure containing the function pointers to a persistence
* implementation and the context or state that will be shared across all
* the persistence functions.
*/
typedef struct {
/**
* A pointer to any data required to initialize the persistent store.
*/
void* context;
/**
* A function pointer to an implementation of Persistence_open().
*/
Persistence_open popen;
/**
* A function pointer to an implementation of Persistence_close().
*/
Persistence_close pclose;
/**
* A function pointer to an implementation of Persistence_put().
*/
Persistence_put pput;
/**
* A function pointer to an implementation of Persistence_get().
*/
Persistence_get pget;
/**
* A function pointer to an implementation of Persistence_remove().
*/
Persistence_remove premove;
/**
* A function pointer to an implementation of Persistence_keys().
*/
Persistence_keys pkeys;
/**
* A function pointer to an implementation of Persistence_clear().
*/
Persistence_clear pclear;
/**
* A function pointer to an implementation of Persistence_containskey().
*/
Persistence_containskey pcontainskey;
} MQTTClient_persistence;
#endif
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
/**
* @file
* \brief functions to deal with reading and writing of MQTT packets from and to sockets
*
* Some other related functions are in the MQTTPacketOut module
*/
#include "MQTTPacket.h"
#include "Log.h"
#if !defined(NO_PERSISTENCE)
#include "MQTTPersistence.h"
#endif
#include "Messages.h"
#include "StackTrace.h"
#include <stdlib.h>
#include <string.h>
#include "Heap.h"
#if !defined(min)
#define min(A,B) ( (A) < (B) ? (A):(B))
#endif
/**
* List of the predefined MQTT v3 packet names.
*/
static char* packet_names[] =
{
"RESERVED", "CONNECT", "CONNACK", "PUBLISH", "PUBACK", "PUBREC", "PUBREL",
"PUBCOMP", "SUBSCRIBE", "SUBACK", "UNSUBSCRIBE", "UNSUBACK",
"PINGREQ", "PINGRESP", "DISCONNECT"
};
/**
* Converts an MQTT packet code into its name
* @param ptype packet code
* @return the corresponding string, or "UNKNOWN"
*/
char* MQTTPacket_name(int ptype)
{
return (ptype >= 0 && ptype <= DISCONNECT) ? packet_names[ptype] : "UNKNOWN";
}
/**
* Array of functions to build packets, indexed according to packet code
*/
pf new_packets[] =
{
NULL, /**< reserved */
NULL, /**< MQTTPacket_connect*/
MQTTPacket_connack, /**< CONNACK */
MQTTPacket_publish, /**< PUBLISH */
MQTTPacket_ack, /**< PUBACK */
MQTTPacket_ack, /**< PUBREC */
MQTTPacket_ack, /**< PUBREL */
MQTTPacket_ack, /**< PUBCOMP */
NULL, /**< MQTTPacket_subscribe*/
MQTTPacket_suback, /**< SUBACK */
NULL, /**< MQTTPacket_unsubscribe*/
MQTTPacket_ack, /**< UNSUBACK */
MQTTPacket_header_only, /**< PINGREQ */
MQTTPacket_header_only, /**< PINGRESP */
MQTTPacket_header_only /**< DISCONNECT */
};
/**
* Reads one MQTT packet from a socket.
* @param socket a socket from which to read an MQTT packet
* @param error pointer to the error code which is completed if no packet is returned
* @return the packet structure or NULL if there was an error
*/
void* MQTTPacket_Factory(int socket, int* error)
{
char* data = NULL;
static Header header;
int remaining_length, ptype;
void* pack = NULL;
int actual_len = 0;
FUNC_ENTRY;
*error = SOCKET_ERROR; /* indicate whether an error occurred, or not */
/* read the packet data from the socket */
if ((*error = Socket_getch(socket, &(header.byte))) != TCPSOCKET_COMPLETE) /* first byte is the header byte */
goto exit; /* packet not read, *error indicates whether SOCKET_ERROR occurred */
/* now read the remaining length, so we know how much more to read */
if ((*error = MQTTPacket_decode(socket, &remaining_length)) != TCPSOCKET_COMPLETE)
goto exit; /* packet not read, *error indicates whether SOCKET_ERROR occurred */
/* now read the rest, the variable header and payload */
if ((data = Socket_getdata(socket, remaining_length, &actual_len)) == NULL)
{
*error = SOCKET_ERROR;
goto exit; /* socket error */
}
if (actual_len != remaining_length)
*error = TCPSOCKET_INTERRUPTED;
else
{
ptype = header.bits.type;
if (ptype < CONNECT || ptype > DISCONNECT || new_packets[ptype] == NULL)
Log(TRACE_MIN, 2, NULL, ptype);
else
{
if ((pack = (*new_packets[ptype])(header.byte, data, remaining_length)) == NULL)
*error = BAD_MQTT_PACKET;
#if !defined(NO_PERSISTENCE)
else if (header.bits.type == PUBLISH && header.bits.qos == 2)
{
int buf0len;
char *buf = malloc(10);
buf[0] = header.byte;
buf0len = 1 + MQTTPacket_encode(&buf[1], remaining_length);
*error = MQTTPersistence_put(socket, buf, buf0len, 1,
&data, &remaining_length, header.bits.type, ((Publish *)pack)->msgId, 1);
free(buf);
}
#endif
}
}
exit:
FUNC_EXIT_RC(*error);
return pack;
}
/**
* Sends an MQTT packet in one system call write
* @param socket the socket to which to write the data
* @param header the one-byte MQTT header
* @param buffer the rest of the buffer to write (not including remaining length)
* @param buflen the length of the data in buffer to be written
* @return the completion code (TCPSOCKET_COMPLETE etc)
*/
int MQTTPacket_send(int socket, Header header, char* buffer, int buflen)
{
int rc, buf0len;
char *buf;
FUNC_ENTRY;
buf = malloc(10);
buf[0] = header.byte;
buf0len = 1 + MQTTPacket_encode(&buf[1], buflen);
#if !defined(NO_PERSISTENCE)
if (header.bits.type == PUBREL)
{
char* ptraux = buffer;
int msgId = readInt(&ptraux);
rc = MQTTPersistence_put(socket, buf, buf0len, 1, &buffer, &buflen,
header.bits.type, msgId, 0);
}
#endif
rc = Socket_putdatas(socket, buf, buf0len, 1, &buffer, &buflen);
if (rc != TCPSOCKET_INTERRUPTED)
free(buf);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Sends an MQTT packet from multiple buffers in one system call write
* @param socket the socket to which to write the data
* @param header the one-byte MQTT header
* @param count the number of buffers
* @param buffers the rest of the buffers to write (not including remaining length)
* @param buflens the lengths of the data in the array of buffers to be written
* @return the completion code (TCPSOCKET_COMPLETE etc)
*/
int MQTTPacket_sends(int socket, Header header, int count, char** buffers, int* buflens)
{
int i, rc, buf0len, total = 0;
char *buf;
FUNC_ENTRY;
buf = malloc(10);
buf[0] = header.byte;
for (i = 0; i < count; i++)
total += buflens[i];
buf0len = 1 + MQTTPacket_encode(&buf[1], total);
#if !defined(NO_PERSISTENCE)
if (header.bits.type == PUBLISH && header.bits.qos != 0)
{ /* persist PUBLISH QoS1 and Qo2 */
char *ptraux = buffers[2];
int msgId = readInt(&ptraux);
rc = MQTTPersistence_put(socket, buf, buf0len, count, buffers, buflens,
header.bits.type, msgId, 0);
}
#endif
rc = Socket_putdatas(socket, buf, buf0len, count, buffers, buflens);
if (rc != TCPSOCKET_INTERRUPTED)
free(buf);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Encodes the message length according to the MQTT algorithm
* @param buf the buffer into which the encoded data is written
* @param length the length to be encoded
* @return the number of bytes written to buffer
*/
int MQTTPacket_encode(char* buf, int length)
{
int rc = 0;
FUNC_ENTRY;
do
{
char d = length % 128;
length /= 128;
/* if there are more digits to encode, set the top bit of this digit */
if (length > 0)
d |= 0x80;
buf[rc++] = d;
} while (length > 0);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Decodes the message length according to the MQTT algorithm
* @param socket the socket from which to read the bytes
* @param value the decoded length returned
* @return the number of bytes read from the socket
*/
int MQTTPacket_decode(int socket, int* value)
{
int rc = SOCKET_ERROR;
char c;
int multiplier = 1;
int len = 0;
#define MAX_NO_OF_REMAINING_LENGTH_BYTES 4
FUNC_ENTRY;
*value = 0;
do
{
if (++len > MAX_NO_OF_REMAINING_LENGTH_BYTES)
{
rc = SOCKET_ERROR; /* bad data */
goto exit;
}
if ((rc = Socket_getch(socket, &c)) != TCPSOCKET_COMPLETE)
goto exit;
*value += (c & 127) * multiplier;
multiplier *= 128;
} while ((c & 128) != 0);
exit:
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Calculates an integer from two bytes read from the input buffer
* @param pptr pointer to the input buffer - incremented by the number of bytes used & returned
* @return the integer value calculated
*/
int readInt(char** pptr)
{
char* ptr = *pptr;
int len = 256*((unsigned char)(*ptr)) + (unsigned char)(*(ptr+1));
*pptr += 2;
return len;
}
/**
* Reads a "UTF" string from the input buffer. UTF as in the MQTT v3 spec which really means
* a length delimited string. So it reads the two byte length then the data according to
* that length. The end of the buffer is provided too, so we can prevent buffer overruns caused
* by an incorrect length.
* @param pptr pointer to the input buffer - incremented by the number of bytes used & returned
* @param enddata pointer to the end of the buffer not to be read beyond
* @param len returns the calculcated value of the length bytes read
* @return an allocated C string holding the characters read, or NULL if the length read would
* have caused an overrun.
*
*/
char* readUTFlen(char** pptr, char* enddata, int* len)
{
char* string = NULL;
FUNC_ENTRY;
if (enddata - (*pptr) > 1) /* enough length to read the integer? */
{
*len = readInt(pptr);
if (&(*pptr)[*len] <= enddata)
{
string = malloc(*len+1);
memcpy(string, *pptr, *len);
string[*len] = '\0';
*pptr += *len;
}
}
FUNC_EXIT;
return string;
}
/**
* Reads a "UTF" string from the input buffer. UTF as in the MQTT v3 spec which really means
* a length delimited string. So it reads the two byte length then the data according to
* that length. The end of the buffer is provided too, so we can prevent buffer overruns caused
* by an incorrect length.
* @param pptr pointer to the input buffer - incremented by the number of bytes used & returned
* @param enddata pointer to the end of the buffer not to be read beyond
* @return an allocated C string holding the characters read, or NULL if the length read would
* have caused an overrun.
*/
char* readUTF(char** pptr, char* enddata)
{
int len;
return readUTFlen(pptr, enddata, &len);
}
/**
* Reads one character from the input buffer.
* @param pptr pointer to the input buffer - incremented by the number of bytes used & returned
* @return the character read
*/
char readChar(char** pptr)
{
char c = **pptr;
(*pptr)++;
return c;
}
/**
* Writes one character to an output buffer.
* @param pptr pointer to the output buffer - incremented by the number of bytes used & returned
* @param c the character to write
*/
void writeChar(char** pptr, char c)
{
**pptr = c;
(*pptr)++;
}
/**
* Writes an integer as 2 bytes to an output buffer.
* @param pptr pointer to the output buffer - incremented by the number of bytes used & returned
* @param anInt the integer to write
*/
void writeInt(char** pptr, int anInt)
{
**pptr = (char)(anInt / 256);
(*pptr)++;
**pptr = (char)(anInt % 256);
(*pptr)++;
}
/**
* Writes a "UTF" string to an output buffer. Converts C string to length-delimited.
* @param pptr pointer to the output buffer - incremented by the number of bytes used & returned
* @param string the C string to write
*/
void writeUTF(char** pptr, char* string)
{
int len = strlen(string);
writeInt(pptr, len);
memcpy(*pptr, string, len);
*pptr += len;
}
/**
* Function used in the new packets table to create packets which have only a header.
* @param aHeader the MQTT header byte
* @param data the rest of the packet
* @param datalen the length of the rest of the packet
* @return pointer to the packet structure
*/
void* MQTTPacket_header_only(unsigned char aHeader, char* data, int datalen)
{
static unsigned char header = 0;
header = aHeader;
return &header;
}
/**
* Send an MQTT disconnect packet down a socket.
* @param socket the open socket to send the data to
* @return the completion code (e.g. TCPSOCKET_COMPLETE)
*/
int MQTTPacket_send_disconnect(int socket, char* clientID)
{
Header header;
int rc = 0;
FUNC_ENTRY;
header.byte = 0;
header.bits.type = DISCONNECT;
rc = MQTTPacket_send(socket, header, NULL, 0);
Log(LOG_PROTOCOL, 28, NULL, socket, clientID, rc);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Function used in the new packets table to create publish packets.
* @param aHeader the MQTT header byte
* @param data the rest of the packet
* @param datalen the length of the rest of the packet
* @return pointer to the packet structure
*/
void* MQTTPacket_publish(unsigned char aHeader, char* data, int datalen)
{
Publish* pack = malloc(sizeof(Publish));
char* curdata = data;
char* enddata = &data[datalen];
FUNC_ENTRY;
pack->header.byte = aHeader;
if ((pack->topic = readUTFlen(&curdata, enddata, &pack->topiclen)) == NULL) /* Topic name on which to publish */
{
free(pack);
pack = NULL;
goto exit;
}
if (pack->header.bits.qos > 0) /* Msgid only exists for QoS 1 or 2 */
pack->msgId = readInt(&curdata);
else
pack->msgId = 0;
pack->payload = curdata;
pack->payloadlen = datalen-(curdata-data);
exit:
FUNC_EXIT;
return pack;
}
/**
* Free allocated storage for a publish packet.
* @param pack pointer to the publish packet structure
*/
void MQTTPacket_freePublish(Publish* pack)
{
FUNC_ENTRY;
if (pack->topic != NULL)
free(pack->topic);
free(pack);
FUNC_EXIT;
}
/**
* Send an MQTT acknowledgement packet down a socket.
* @param type the MQTT packet type e.g. SUBACK
* @param msgid the MQTT message id to use
* @param dup boolean - whether to set the MQTT DUP flag
* @param socket the open socket to send the data to
* @return the completion code (e.g. TCPSOCKET_COMPLETE)
*/
int MQTTPacket_send_ack(int type, int msgid, int dup, int socket)
{
Header header;
int rc;
char *buf = malloc(2);
char *ptr = buf;
FUNC_ENTRY;
header.byte = 0;
header.bits.type = type;
header.bits.dup = dup;
writeInt(&ptr, msgid);
if ((rc = MQTTPacket_send(socket, header, buf, 2)) != TCPSOCKET_INTERRUPTED)
free(buf);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Send an MQTT PUBACK packet down a socket.
* @param msgid the MQTT message id to use
* @param socket the open socket to send the data to
* @return the completion code (e.g. TCPSOCKET_COMPLETE)
*/
int MQTTPacket_send_puback(int msgid, int socket, char* clientID)
{
int rc = 0;
FUNC_ENTRY;
rc = MQTTPacket_send_ack(PUBACK, msgid, 0, socket);
Log(LOG_PROTOCOL, 12, NULL, socket, clientID, msgid, rc);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Free allocated storage for a suback packet.
* @param pack pointer to the suback packet structure
*/
void MQTTPacket_freeSuback(Suback* pack)
{
FUNC_ENTRY;
if (pack->qoss != NULL)
ListFree(pack->qoss);
free(pack);
FUNC_EXIT;
}
/**
* Send an MQTT PUBREC packet down a socket.
* @param msgid the MQTT message id to use
* @param socket the open socket to send the data to
* @return the completion code (e.g. TCPSOCKET_COMPLETE)
*/
int MQTTPacket_send_pubrec(int msgid, int socket, char* clientID)
{
int rc = 0;
FUNC_ENTRY;
rc = MQTTPacket_send_ack(PUBREC, msgid, 0, socket);
Log(LOG_PROTOCOL, 13, NULL, socket, clientID, msgid, rc);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Send an MQTT PUBREL packet down a socket.
* @param msgid the MQTT message id to use
* @param dup the MQTT DUP flag
* @param socket the open socket to send the data to
* @return the completion code (e.g. TCPSOCKET_COMPLETE)
*/
int MQTTPacket_send_pubrel(int msgid, int dup, int socket, char* clientID)
{
int rc = 0;
FUNC_ENTRY;
rc = MQTTPacket_send_ack(PUBREL, msgid, dup, socket);
Log(LOG_PROTOCOL, 16, NULL, socket, clientID, msgid, rc);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Send an MQTT PUBCOMP packet down a socket.
* @param msgid the MQTT message id to use
* @param socket the open socket to send the data to
* @return the completion code (e.g. TCPSOCKET_COMPLETE)
*/
int MQTTPacket_send_pubcomp(int msgid, int socket, char* clientID)
{
int rc = 0;
FUNC_ENTRY;
rc = MQTTPacket_send_ack(PUBCOMP, msgid, 0, socket);
Log(LOG_PROTOCOL, 18, NULL, socket, clientID, msgid, rc);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Function used in the new packets table to create acknowledgement packets.
* @param aHeader the MQTT header byte
* @param data the rest of the packet
* @param datalen the length of the rest of the packet
* @return pointer to the packet structure
*/
void* MQTTPacket_ack(unsigned char aHeader, char* data, int datalen)
{
Ack* pack = malloc(sizeof(Ack));
char* curdata = data;
FUNC_ENTRY;
pack->header.byte = aHeader;
pack->msgId = readInt(&curdata);
FUNC_EXIT;
return pack;
}
/**
* Send an MQTT PUBLISH packet down a socket.
* @param pack a structure from which to get some values to use, e.g topic, payload
* @param dup boolean - whether to set the MQTT DUP flag
* @param qos the value to use for the MQTT QoS setting
* @param retained boolean - whether to set the MQTT retained flag
* @param socket the open socket to send the data to
* @return the completion code (e.g. TCPSOCKET_COMPLETE)
*/
int MQTTPacket_send_publish(Publish* pack, int dup, int qos, int retained, int socket, char* clientID)
{
Header header;
char *topiclen;
int rc = -1;
FUNC_ENTRY;
topiclen = malloc(2);
header.bits.type = PUBLISH;
header.bits.dup = dup;
header.bits.qos = qos;
header.bits.retain = retained;
if (qos > 0)
{
char *buf = malloc(2);
char *ptr = buf;
char* bufs[4] = {topiclen, pack->topic, buf, pack->payload};
int lens[4] = {2, strlen(pack->topic), 2, pack->payloadlen};
writeInt(&ptr, pack->msgId);
ptr = topiclen;
writeInt(&ptr, lens[1]);
rc = MQTTPacket_sends(socket, header, 4, bufs, lens);
if (rc != TCPSOCKET_INTERRUPTED)
free(buf);
}
else
{
char* ptr = topiclen;
char* bufs[3] = {topiclen, pack->topic, pack->payload};
int lens[3] = {2, strlen(pack->topic), pack->payloadlen};
writeInt(&ptr, lens[1]);
rc = MQTTPacket_sends(socket, header, 3, bufs, lens);
}
if (rc != TCPSOCKET_INTERRUPTED)
free(topiclen);
if (qos == 0)
Log(LOG_PROTOCOL, 27, NULL, socket, clientID, retained, rc);
else
Log(LOG_PROTOCOL, 10, NULL, socket, clientID, pack->msgId, qos, retained, rc,
min(20, pack->payloadlen), pack->payload);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Free allocated storage for a various packet tyoes
* @param pack pointer to the suback packet structure
*/
void MQTTPacket_free_packet(MQTTPacket* pack)
{
FUNC_ENTRY;
if (pack->header.bits.type == PUBLISH)
MQTTPacket_freePublish((Publish*)pack);
/*else if (pack->header.type == SUBSCRIBE)
MQTTPacket_freeSubscribe((Subscribe*)pack, 1);
else if (pack->header.type == UNSUBSCRIBE)
MQTTPacket_freeUnsubscribe((Unsubscribe*)pack);*/
else
free(pack);
FUNC_EXIT;
}
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#if !defined(MQTTPACKET_H)
#define MQTTPACKET_H
#include "Socket.h"
#include "LinkedList.h"
#include "Clients.h"
/*BE
include "Socket"
include "LinkedList"
include "Clients"
BE*/
typedef unsigned int bool;
typedef void* (*pf)(unsigned char, char*, int);
#define BAD_MQTT_PACKET -4
enum msgTypes
{
CONNECT = 1, CONNACK, PUBLISH, PUBACK, PUBREC, PUBREL,
PUBCOMP, SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK,
PINGREQ, PINGRESP, DISCONNECT
};
/**
* Bitfields for the MQTT header byte.
*/
typedef union
{
/*unsigned*/ char byte; /**< the whole byte */
#if defined(REVERSED)
struct
{
unsigned int type : 4; /**< message type nibble */
bool dup : 1; /**< DUP flag bit */
unsigned int qos : 2; /**< QoS value, 0, 1 or 2 */
bool retain : 1; /**< retained flag bit */
} bits;
#else
struct
{
bool retain : 1; /**< retained flag bit */
unsigned int qos : 2; /**< QoS value, 0, 1 or 2 */
bool dup : 1; /**< DUP flag bit */
unsigned int type : 4; /**< message type nibble */
} bits;
#endif
} Header;
/**
* Data for a connect packet.
*/
typedef struct
{
Header header; /**< MQTT header byte */
union
{
unsigned char all; /**< all connect flags */
#if defined(REVERSED)
struct
{
bool username : 1; /**< 3.1 user name */
bool password : 1; /**< 3.1 password */
bool willRetain : 1; /**< will retain setting */
unsigned int willQoS : 2; /**< will QoS value */
bool will : 1; /**< will flag */
bool cleanstart : 1; /**< cleansession flag */
int : 1; /**< unused */
} bits;
#else
struct
{
int : 1; /**< unused */
bool cleanstart : 1; /**< cleansession flag */
bool will : 1; /**< will flag */
unsigned int willQoS : 2; /**< will QoS value */
bool willRetain : 1; /**< will retain setting */
bool password : 1; /**< 3.1 password */
bool username : 1; /**< 3.1 user name */
} bits;
#endif
} flags; /**< connect flags byte */
char *Protocol, /**< MQTT protocol name */
*clientID, /**< string client id */
*willTopic, /**< will topic */
*willMsg; /**< will payload */
int keepAliveTimer; /**< keepalive timeout value in seconds */
unsigned char version; /**< MQTT version number */
} Connect;
/**
* Data for a connack packet.
*/
typedef struct
{
Header header; /**< MQTT header byte */
char rc; /**< connack return code */
} Connack;
/**
* Data for a packet with header only.
*/
typedef struct
{
Header header; /**< MQTT header byte */
} MQTTPacket;
/**
* Data for a subscribe packet.
*/
typedef struct
{
Header header; /**< MQTT header byte */
int msgId; /**< MQTT message id */
List* topics; /**< list of topic strings */
List* qoss; /**< list of corresponding QoSs */
int noTopics; /**< topic and qos count */
} Subscribe;
/**
* Data for a suback packet.
*/
typedef struct
{
Header header; /**< MQTT header byte */
int msgId; /**< MQTT message id */
List* qoss; /**< list of granted QoSs */
} Suback;
/**
* Data for an unsubscribe packet.
*/
typedef struct
{
Header header; /**< MQTT header byte */
int msgId; /**< MQTT message id */
List* topics; /**< list of topic strings */
int noTopics; /**< topic count */
} Unsubscribe;
/**
* Data for a publish packet.
*/
typedef struct
{
Header header; /**< MQTT header byte */
char* topic; /**< topic string */
int topiclen;
int msgId; /**< MQTT message id */
char* payload; /**< binary payload, length delimited */
int payloadlen; /**< payload length */
} Publish;
/**
* Data for one of the ack packets.
*/
typedef struct
{
Header header; /**< MQTT header byte */
int msgId; /**< MQTT message id */
} Ack;
typedef Ack Puback;
typedef Ack Pubrec;
typedef Ack Pubrel;
typedef Ack Pubcomp;
typedef Ack Unsuback;
int MQTTPacket_encode(char* buf, int length);
int MQTTPacket_decode(int socket, int* value);
int readInt(char** pptr);
char* readUTF(char** pptr, char* enddata);
char readChar(char** pptr);
void writeChar(char** pptr, char c);
void writeInt(char** pptr, int anInt);
void writeUTF(char** pptr, char* string);
char* MQTTPacket_name(int ptype);
void* MQTTPacket_Factory(int socket, int* error);
int MQTTPacket_send(int socket, Header header, char* buffer, int buflen);
int MQTTPacket_sends(int socket, Header header, int count, char** buffers, int* buflens);
void* MQTTPacket_header_only(unsigned char aHeader, char* data, int datalen);
int MQTTPacket_send_disconnect(int socket, char* clientID);
void* MQTTPacket_publish(unsigned char aHeader, char* data, int datalen);
void MQTTPacket_freePublish(Publish* pack);
int MQTTPacket_send_publish(Publish* pack, int dup, int qos, int retained, int socket, char* clientID);
int MQTTPacket_send_puback(int msgid, int socket, char* clientID);
void* MQTTPacket_ack(unsigned char aHeader, char* data, int datalen);
void MQTTPacket_freeSuback(Suback* pack);
int MQTTPacket_send_pubrec(int msgid, int socket, char* clientID);
int MQTTPacket_send_pubrel(int msgid, int dup, int socket, char* clientID);
int MQTTPacket_send_pubcomp(int msgid, int socket, char* clientID);
void MQTTPacket_free_packet(MQTTPacket* pack);
#if !defined(NO_BRIDGE)
#include "MQTTPacketOut.h"
#endif
#endif /* MQTTPACKET_H */
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
/**
* @file
* \brief functions to deal with reading and writing of MQTT packets from and to sockets
*
* Some other related functions are in the MQTTPacket module
*/
#include "MQTTPacketOut.h"
#include "Log.h"
#include "StackTrace.h"
#include <string.h>
#include <stdlib.h>
#include "Heap.h"
/**
* Send an MQTT CONNECT packet down a socket.
* @param client a structure from which to get all the required values
* @return the completion code (e.g. TCPSOCKET_COMPLETE)
*/
int MQTTPacket_send_connect(Clients* client)
{
char *buf, *ptr;
Connect packet;
int rc, len;
FUNC_ENTRY;
packet.header.byte = 0;
packet.header.bits.type = CONNECT;
packet.header.bits.qos = 1;
len = 12 + strlen(client->clientID)+2;
if (client->will)
len += strlen(client->will->topic)+2 + strlen(client->will->msg)+2;
if (client->username)
len += strlen(client->username)+2;
if (client->password)
len += strlen(client->password)+2;
ptr = buf = malloc(len);
writeUTF(&ptr, "MQIsdp");
writeChar(&ptr, (char)3);
packet.flags.all = 0;
packet.flags.bits.cleanstart = client->cleansession;
packet.flags.bits.will = (client->will) ? 1 : 0;
if (packet.flags.bits.will)
{
packet.flags.bits.willQoS = client->will->qos;
packet.flags.bits.willRetain = client->will->retained;
}
if (client->username)
packet.flags.bits.username = 1;
if (client->password)
packet.flags.bits.password = 1;
writeChar(&ptr, packet.flags.all);
writeInt(&ptr, client->keepAliveInterval);
writeUTF(&ptr, client->clientID);
if (client->will)
{
writeUTF(&ptr, client->will->topic);
writeUTF(&ptr, client->will->msg);
}
if (client->username)
writeUTF(&ptr, client->username);
if (client->password)
writeUTF(&ptr, client->password);
rc = MQTTPacket_send(client->socket, packet.header, buf, len);
Log(LOG_PROTOCOL, 0, NULL, client->socket, client->clientID, client->cleansession, rc);
free(buf);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Function used in the new packets table to create connack packets.
* @param aHeader the MQTT header byte
* @param data the rest of the packet
* @param datalen the length of the rest of the packet
* @return pointer to the packet structure
*/
void* MQTTPacket_connack(unsigned char aHeader, char* data, int datalen)
{
Connack* pack = malloc(sizeof(Connack));
char* curdata = data;
FUNC_ENTRY;
pack->header.byte = aHeader;
readChar(&curdata); /* reserved byte */
pack->rc = readChar(&curdata);
FUNC_EXIT;
return pack;
}
/**
* Send an MQTT PINGREQ packet down a socket.
* @param socket the open socket to send the data to
* @param clientID the string client identifier, only used for tracing
* @return the completion code (e.g. TCPSOCKET_COMPLETE)
*/
int MQTTPacket_send_pingreq(int socket, char* clientID)
{
Header header;
int rc = 0;
FUNC_ENTRY;
header.byte = 0;
header.bits.type = PINGREQ;
rc = MQTTPacket_send(socket, header, NULL, 0);
Log(LOG_PROTOCOL, 20, NULL, socket, clientID, rc);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Send an MQTT subscribe packet down a socket.
* @param topics list of topics
* @param qoss list of corresponding QoSs
* @param msgid the MQTT message id to use
* @param dup boolean - whether to set the MQTT DUP flag
* @param socket the open socket to send the data to
* @param clientID the string client identifier, only used for tracing
* @return the completion code (e.g. TCPSOCKET_COMPLETE)
*/
int MQTTPacket_send_subscribe(List* topics, List* qoss, int msgid, int dup, int socket, char* clientID)
{
Header header;
char *data, *ptr;
int rc = -1;
ListElement *elem = NULL, *qosElem = NULL;
int datalen;
FUNC_ENTRY;
header.bits.type = SUBSCRIBE;
header.bits.dup = dup;
header.bits.qos = 1;
header.bits.retain = 0;
datalen = 2 + topics->count * 3; // utf length + char qos == 3
while (ListNextElement(topics, &elem))
datalen += strlen((char*)(elem->content));
ptr = data = malloc(datalen);
writeInt(&ptr, msgid);
elem = NULL;
while (ListNextElement(topics, &elem))
{
ListNextElement(qoss, &qosElem);
writeUTF(&ptr, (char*)(elem->content));
writeChar(&ptr, *(int*)(qosElem->content));
}
rc = MQTTPacket_send(socket, header, data, datalen);
Log(LOG_PROTOCOL, 22, NULL, socket, clientID, msgid, rc);
free(data);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Function used in the new packets table to create suback packets.
* @param aHeader the MQTT header byte
* @param data the rest of the packet
* @param datalen the length of the rest of the packet
* @return pointer to the packet structure
*/
void* MQTTPacket_suback(unsigned char aHeader, char* data, int datalen)
{
Suback* pack = malloc(sizeof(Suback));
char* curdata = data;
FUNC_ENTRY;
pack->header.byte = aHeader;
pack->msgId = readInt(&curdata);
pack->qoss = ListInitialize();
while (curdata - data < datalen)
{
int* newint;
newint = malloc(sizeof(int));
*newint = (int)readChar(&curdata);
ListAppend(pack->qoss, newint, sizeof(int));
}
FUNC_EXIT;
return pack;
}
/**
* Send an MQTT unsubscribe packet down a socket.
* @param topics list of topics
* @param msgid the MQTT message id to use
* @param dup boolean - whether to set the MQTT DUP flag
* @param socket the open socket to send the data to
* @param clientID the string client identifier, only used for tracing
* @return the completion code (e.g. TCPSOCKET_COMPLETE)
*/
int MQTTPacket_send_unsubscribe(List* topics, int msgid, int dup, int socket, char* clientID)
{
Header header;
char *data, *ptr;
int rc = -1;
ListElement *elem = NULL;
int datalen;
FUNC_ENTRY;
header.bits.type = UNSUBSCRIBE;
header.bits.dup = dup;
header.bits.qos = 1;
header.bits.retain = 0;
datalen = 2 + topics->count * 2; // utf length == 2
while (ListNextElement(topics, &elem))
datalen += strlen((char*)(elem->content));
ptr = data = malloc(datalen);
writeInt(&ptr, msgid);
elem = NULL;
while (ListNextElement(topics, &elem))
writeUTF(&ptr, (char*)(elem->content));
rc = MQTTPacket_send(socket, header, data, datalen);
Log(LOG_PROTOCOL, 25, NULL, socket, clientID, msgid, rc);
free(data);
FUNC_EXIT_RC(rc);
return rc;
}
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#if !defined(MQTTPACKETOUT_H)
#define MQTTPACKETOUT_H
#include "MQTTPacket.h"
int MQTTPacket_send_connect(Clients* client);
void* MQTTPacket_connack(unsigned char aHeader, char* data, int datalen);
int MQTTPacket_send_pingreq(int socket, char* clientID);
int MQTTPacket_send_subscribe(List* topics, List* qoss, int msgid, int dup, int socket, char* clientID);
void* MQTTPacket_suback(unsigned char aHeader, char* data, int datalen);
int MQTTPacket_send_unsubscribe(List* topics, int msgid, int dup, int socket, char* clientID);
#endif
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
/**
* @file
* \brief Functions that apply to persistence operations.
*
*/
#include <stdio.h>
#include <string.h>
#include "MQTTPersistence.h"
#include "MQTTPersistenceDefault.h"
#include "MQTTProtocolClient.h"
#include "Heap.h"
/**
* Creates a ::MQTTClient_persistence structure representing a persistence implementation.
* @param persistence the ::MQTTClient_persistence structure.
* @param type the type of the persistence implementation. See ::MQTTClient_create.
* @param pcontext the context for this persistence implementation. See ::MQTTClient_create.
* @return 0 if success, #MQTTCLIENT_PERSISTENCE_ERROR otherwise.
*/
#include "StackTrace.h"
int MQTTPersistence_create(MQTTClient_persistence** persistence, int type, void* pcontext)
{
int rc = 0;
MQTTClient_persistence* per = NULL;
FUNC_ENTRY;
#if !defined(NO_PERSISTENCE)
switch (type)
{
case MQTTCLIENT_PERSISTENCE_NONE :
per = NULL;
break;
case MQTTCLIENT_PERSISTENCE_DEFAULT :
per = malloc(sizeof(MQTTClient_persistence));
if ( per != NULL )
{
if ( pcontext != NULL )
{
per->context = malloc(strlen(pcontext) + 1);
strcpy(per->context, pcontext);
}
else
per->context = "."; /* working directory */
/* file system functions */
per->popen = pstopen;
per->pclose = pstclose;
per->pput = pstput;
per->pget = pstget;
per->premove = pstremove;
per->pkeys = pstkeys;
per->pclear = pstclear;
per->pcontainskey = pstcontainskey;
}
else
rc = MQTTCLIENT_PERSISTENCE_ERROR;
break;
case MQTTCLIENT_PERSISTENCE_USER :
per = (MQTTClient_persistence *)pcontext;
if ( per == NULL || (per != NULL && (per->context == NULL || per->pclear == NULL ||
per->pclose == NULL || per->pcontainskey == NULL || per->pget == NULL || per->pkeys == NULL ||
per->popen == NULL || per->pput == NULL || per->premove == NULL)) )
rc = MQTTCLIENT_PERSISTENCE_ERROR;
break;
default:
rc = MQTTCLIENT_PERSISTENCE_ERROR;
break;
}
#endif
*persistence = per;
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Open persistent store and restore any persisted messages.
* @param client the client as ::Clients.
* @param serverURI the URI of the remote end.
* @return 0 if success, #MQTTCLIENT_PERSISTENCE_ERROR otherwise.
*/
int MQTTPersistence_initialize(Clients *c, char *serverURI)
{
int rc = 0;
FUNC_ENTRY;
if ( c->persistence != NULL )
{
rc = c->persistence->popen(&(c->phandle), c->clientID, serverURI, c->persistence->context);
if ( rc == 0 )
rc = MQTTPersistence_restore(c);
}
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Close persistent store.
* @param client the client as ::Clients.
* @return 0 if success, #MQTTCLIENT_PERSISTENCE_ERROR otherwise.
*/
int MQTTPersistence_close(Clients *c)
{
int rc =0;
FUNC_ENTRY;
if (c->persistence != NULL)
{
rc = c->persistence->pclose(c->phandle);
c->phandle = NULL;
#if !defined(NO_PERSISTENCE)
if ( c->persistence->popen == pstopen )
free(c->persistence);
#endif
c->persistence = NULL;
}
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Clears the persistent store.
* @param client the client as ::Clients.
* @return 0 if success, #MQTTCLIENT_PERSISTENCE_ERROR otherwise.
*/
int MQTTPersistence_clear(Clients *c)
{
int rc = 0;
FUNC_ENTRY;
if (c->persistence != NULL)
rc = c->persistence->pclear(c->phandle);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Restores the persisted records to the outbound and inbound message queues of the
* client.
* @param client the client as ::Clients.
* @return 0 if success, #MQTTCLIENT_PERSISTENCE_ERROR otherwise.
*/
int MQTTPersistence_restore(Clients *c)
{
int rc = 0;
char **msgkeys, *buffer;
int nkeys, buflen;
int i = 0;
FUNC_ENTRY;
if ( c->persistence != NULL &&
(rc = c->persistence->pkeys(c->phandle, &msgkeys, &nkeys)) == 0 )
{
while( rc == 0 && i < nkeys)
{
if ( (rc = c->persistence->pget(c->phandle, msgkeys[i], &buffer, &buflen)) == 0 )
{
MQTTPacket* pack = MQTTPersistence_restorePacket(buffer, buflen);
if ( pack != NULL )
{
if ( strstr(msgkeys[i],PERSISTENCE_PUBLISH_RECEIVED) != NULL )
{
Publish* publish = (Publish*)pack;
Messages* msg = NULL;
msg = MQTTProtocol_createMessage(publish, &msg, publish->header.bits.qos, publish->header.bits.retain);
msg->nextMessageType = PUBREL;
/* order does not matter for persisted received messages */
ListAppend(c->inboundMsgs, msg, msg->len);
publish->topic = NULL;
MQTTPacket_freePublish(publish);
}
else if ( strstr(msgkeys[i],PERSISTENCE_PUBLISH_SENT) != NULL )
{
Publish* publish = (Publish*)pack;
Messages* msg = NULL;
char *key = malloc(MESSAGE_FILENAME_LENGTH + 1);
sprintf(key, "%s%d", PERSISTENCE_PUBREL, publish->msgId);
msg = MQTTProtocol_createMessage(publish, &msg, publish->header.bits.qos, publish->header.bits.retain);
if ( c->persistence->pcontainskey(c->phandle, key) == 0 )
/* PUBLISH Qo2 and PUBREL sent */
msg->nextMessageType = PUBCOMP;
/* else: PUBLISH QoS1, or PUBLISH QoS2 and PUBREL not sent */
/* retry at the first opportunity */
msg->lastTouch = 0;
MQTTPersistence_insertInOrder(c->outboundMsgs, msg, msg->len);
publish->topic = NULL;
MQTTPacket_freePublish(publish);
free(key);
}
else if ( strstr(msgkeys[i],PERSISTENCE_PUBREL) != NULL )
{
/* orphaned PUBRELs ? */
Pubrel* pubrel = (Pubrel*)pack;
char *key = malloc(MESSAGE_FILENAME_LENGTH + 1);
sprintf(key, "%s%d", PERSISTENCE_PUBLISH_SENT, pubrel->msgId);
if ( c->persistence->pcontainskey(c->phandle, key) != 0 )
rc = c->persistence->premove(c->phandle, msgkeys[i]);
free(pubrel);
free(key);
}
}
else /* pack == NULL -> bad persisted record */
rc = c->persistence->premove(c->phandle, msgkeys[i]);
}
if ( buffer != NULL )
free(buffer);
if ( msgkeys[i] != NULL )
free(msgkeys[i]);
i++;
}
if ( msgkeys != NULL )
free(msgkeys);
}
MQTTPersistence_wrapMsgID(c);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Returns a MQTT packet restored from persisted data.
* @param buffer the persisted data.
* @param buflen the number of bytes of the data buffer.
*/
void* MQTTPersistence_restorePacket(char* buffer, int buflen)
{
void* pack = NULL;
Header header;
int fixed_header_length = 1, ptype, remaining_length = 0;
char c;
int multiplier = 1;
extern pf new_packets[];
FUNC_ENTRY;
header.byte = buffer[0];
/* decode the message length according to the MQTT algorithm */
do
{
c = *(++buffer);
remaining_length += (c & 127) * multiplier;
multiplier *= 128;
fixed_header_length++;
} while ((c & 128) != 0);
if ( (fixed_header_length + remaining_length) == buflen )
{
ptype = header.bits.type;
if (ptype >= CONNECT && ptype <= DISCONNECT && new_packets[ptype] != NULL)
pack = (*new_packets[ptype])(header.byte, ++buffer, remaining_length);
}
FUNC_EXIT;
return pack;
}
/**
* Inserts the specified message into the list, maintaining message ID order.
* @param list the list to insert the message into.
* @param content the message to add.
* @param size size of the message.
*/
void MQTTPersistence_insertInOrder(List* list, void* content, int size)
{
ListElement* index = NULL;
ListElement* current = NULL;
FUNC_ENTRY;
while(ListNextElement(list, &current) != NULL && index == NULL)
{
if ( ((Messages*)content)->msgid < ((Messages*)current->content)->msgid )
index = current;
}
ListInsert(list, content, size, index);
FUNC_EXIT;
}
/**
* Adds a record to the persistent store. This function must not be called for QoS0
* messages.
* @param socket the socket of the client.
* @param buf0 fixed header.
* @param buf0len length of the fixed header.
* @param count number of buffers representing the variable header and/or the payload.
* @param buffers the buffers representing the variable header and/or the payload.
* @param buflens length of the buffers representing the variable header and/or the payload.
* @param msgId the message ID.
* @param scr 0 indicates message in the sending direction; 1 indicates message in the
* receiving direction.
* @return 0 if success, #MQTTCLIENT_PERSISTENCE_ERROR otherwise.
*/
int MQTTPersistence_put(int socket, char* buf0, int buf0len, int count,
char** buffers, int* buflens, int htype, int msgId, int scr )
{
int rc = 0;
extern ClientStates* bstate;
int nbufs, i;
int* lens = NULL;
char** bufs = NULL;
char *key;
Clients* client = NULL;
FUNC_ENTRY;
client = (Clients*)(ListFindItem(bstate->clients, &socket, clientSocketCompare)->content);
if (client->persistence != NULL)
{
key = malloc(MESSAGE_FILENAME_LENGTH + 1);
nbufs = 1 + count;
lens = (int *) malloc(nbufs * sizeof(int));
bufs = (char **)malloc(nbufs * sizeof(char *));
lens[0] = buf0len;
bufs[0] = buf0;
for (i = 0; i < count; i++)
{
lens[i+1] = buflens[i];
bufs[i+1] = buffers[i];
}
// key
if ( scr == 0 )
{ /* sending */
if (htype == PUBLISH) /* PUBLISH QoS1 and QoS2*/
sprintf(key, "%s%d", PERSISTENCE_PUBLISH_SENT, msgId);
if (htype == PUBREL) /* PUBREL */
sprintf(key, "%s%d", PERSISTENCE_PUBREL, msgId);
}
if ( scr == 1 ) /* receiving PUBLISH QoS2 */
sprintf(key, "%s%d", PERSISTENCE_PUBLISH_RECEIVED, msgId);
rc = client->persistence->pput(client->phandle, key, nbufs, bufs, lens);
free(key);
free(lens);
free(bufs);
}
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Deletes a record from the persistent store.
* @param client the client as ::Clients.
* @param type the type of the persisted record: #PERSISTENCE_PUBLISH_SENT, #PERSISTENCE_PUBREL
* or #PERSISTENCE_PUBLISH_RECEIVED.
* @param qos the qos field of the message.
* @param msgId the message ID.
* @return 0 if success, #MQTTCLIENT_PERSISTENCE_ERROR otherwise.
*/
int MQTTPersistence_remove(Clients* c, char *type, int qos, int msgId)
{
int rc = 0;
FUNC_ENTRY;
if (c->persistence != NULL)
{
char *key = malloc(MESSAGE_FILENAME_LENGTH + 1);
if ( (strcmp(type,PERSISTENCE_PUBLISH_SENT) == 0) && qos == 2 )
{
sprintf(key, "%s%d", PERSISTENCE_PUBLISH_SENT, msgId) ;
rc = c->persistence->premove(c->phandle, key);
sprintf(key, "%s%d", PERSISTENCE_PUBREL, msgId) ;
rc = c->persistence->premove(c->phandle, key);
}
else /* PERSISTENCE_PUBLISH_SENT && qos == 1 */
{ /* or PERSISTENCE_PUBLISH_RECEIVED */
sprintf(key, "%s%d", type, msgId) ;
rc = c->persistence->premove(c->phandle, key);
}
free(key);
}
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Checks whether the message IDs wrapped by looking for the largest gap between two consecutive
* message IDs in the outboundMsgs queue.
* @param client the client as ::Clients.
*/
void MQTTPersistence_wrapMsgID(Clients *client)
{
ListElement* wrapel = NULL;
ListElement* current = NULL;
FUNC_ENTRY;
if ( client->outboundMsgs->count > 0 )
{
int firstMsgID = ((Messages*)client->outboundMsgs->first->content)->msgid;
int lastMsgID = ((Messages*)client->outboundMsgs->last->content)->msgid;
int gap = MAX_MSG_ID - lastMsgID + firstMsgID;
current = ListNextElement(client->outboundMsgs, &current);
while(ListNextElement(client->outboundMsgs, &current) != NULL)
{
int curMsgID = ((Messages*)current->content)->msgid;
int curPrevMsgID = ((Messages*)current->prev->content)->msgid;
int curgap = curMsgID - curPrevMsgID;
if ( curgap > gap )
{
gap = curgap;
wrapel = current;
}
}
}
if ( wrapel != NULL )
{
/* put wrapel at the beginning of the queue */
client->outboundMsgs->first->prev = client->outboundMsgs->last;
client->outboundMsgs->last->next = client->outboundMsgs->first;
client->outboundMsgs->first = wrapel;
client->outboundMsgs->last = wrapel->prev;
client->outboundMsgs->first->prev = NULL;
client->outboundMsgs->last->next = NULL;
}
FUNC_EXIT;
}
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#include "Clients.h"
/** Stem of the key for a sent PUBLISH QoS1 or QoS2 */
#define PERSISTENCE_PUBLISH_SENT "s-"
/** Stem of the key for a sent PUBREL */
#define PERSISTENCE_PUBREL "sc-"
/** Stem of the key for a received PUBLISH QoS2 */
#define PERSISTENCE_PUBLISH_RECEIVED "r-"
int MQTTPersistence_create(MQTTClient_persistence** per, int type, void* pcontext);
int MQTTPersistence_initialize(Clients* c, char* serverURI);
int MQTTPersistence_close(Clients* c);
int MQTTPersistence_clear(Clients* c);
int MQTTPersistence_restore(Clients* c);
void* MQTTPersistence_restorePacket(char* buffer, int buflen);
void MQTTPersistence_insertInOrder(List* list, void* content, int size);
int MQTTPersistence_put(int socket, char* buf0, int buf0len, int count,
char** buffers, int* buflens, int htype, int msgId, int scr);
int MQTTPersistence_remove(Clients* c, char* type, int qos, int msgId);
void MQTTPersistence_wrapMsgID(Clients *c);
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
/**
* @file
* \brief A file system based persistence implementation.
*
* A directory is specified when the MQTT client is created. When the persistence is then
* opened (see ::Persistence_open), a sub-directory is made beneath the base for this
* particular client ID and connection key. This allows one persistence base directory to
* be shared by multiple clients.
*
*/
#if !defined(NO_PERSISTENCE)
#include <stdio.h>
#include <string.h>
#include <errno.h>
#if defined(WIN32)
#include <windows.h>
#include <direct.h>
/* Windows doesn't have strtok_r, so remap it to strtok */
#define strtok_r( A, B, C ) strtok( A, B )
int keysWin32(char *, char ***, int *);
int clearWin32(char *);
int containskeyWin32(char *, char *);
#else
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
int keysUnix(char *, char ***, int *);
int clearUnix(char *);
int containskeyUnix(char *, char *);
#endif
#include "MQTTClientPersistence.h"
#include "MQTTPersistenceDefault.h"
#include "StackTrace.h"
#include "Heap.h"
/** Create persistence directory for the client: context/clientID-serverURI.
* See ::Persistence_open
*/
int pstopen(void **handle, char* clientID, char* serverURI, void* context)
{
int rc = 0;
char *dataDir = context;
char *clientDir;
char *pToken = NULL;
char *save_ptr = NULL;
char *pCrtDirName = NULL;
char *pTokDirName = NULL;
char *perserverURI = NULL, *ptraux;
FUNC_ENTRY;
/* Note that serverURI=address:port, but ":" not allowed in Windows directories */
perserverURI = malloc(strlen(serverURI) + 1);
strcpy(perserverURI, serverURI);
ptraux = strstr(perserverURI, ":");
*ptraux = '-' ;
/* consider '/' + '-' + '\0' */
clientDir = malloc(strlen(dataDir) + strlen(clientID) + strlen(perserverURI) + 3);
sprintf(clientDir, "%s/%s-%s", dataDir, clientID, perserverURI);
/* create clientDir directory */
/* pCrtDirName - holds the directory name we are currently trying to create. */
/* This gets built up level by level until the full path name is created.*/
/* pTokDirName - holds the directory name that gets used by strtok. */
pCrtDirName = (char*)malloc( strlen(clientDir) + 1 );
pTokDirName = (char*)malloc( strlen(clientDir) + 1 );
strcpy( pTokDirName, clientDir );
pToken = strtok_r( pTokDirName, "\\/", &save_ptr );
strcpy( pCrtDirName, pToken );
rc = pstmkdir( pCrtDirName );
pToken = strtok_r( NULL, "\\/", &save_ptr );
while ( (pToken != NULL) && (rc == 0) )
{
/* Append the next directory level and try to create it */
sprintf( pCrtDirName, "%s/%s", pCrtDirName, pToken );
rc = pstmkdir( pCrtDirName );
pToken = strtok_r( NULL, "\\/", &save_ptr );
}
*handle = clientDir;
free(perserverURI);
free(pTokDirName);
free(pCrtDirName);
FUNC_EXIT_RC(rc);
return rc;
}
/** Function to create a directory.
* Returns 0 on success or if the directory already exists.
*/
int pstmkdir( char *pPathname )
{
int rc = 0;
FUNC_ENTRY;
#if defined(WIN32)
if ( _mkdir( pPathname ) != 0 )
{
#else
/* Create a directory with read, write and execute access for the owner and read access for the group */
if ( mkdir( pPathname, S_IRWXU | S_IRGRP ) != 0 )
{
#endif
if ( errno != EEXIST )
rc = MQTTCLIENT_PERSISTENCE_ERROR;
}
FUNC_EXIT_RC(rc);
return rc;
}
/** Write wire message to the client persistence directory.
* See ::Persistence_put
*/
int pstput(void* handle, char* key, int bufcount, char* buffers[], int buflens[])
{
int rc = 0;
char *clientDir = handle;
char *file;
FILE *fp;
int bytesWritten = 0;
int bytesTotal = 0;
int i;
FUNC_ENTRY;
if (clientDir == NULL)
{
rc = MQTTCLIENT_PERSISTENCE_ERROR;
goto exit;
}
/* consider '/' + '\0' */
file = malloc(strlen(clientDir) + strlen(key) + strlen(MESSAGE_FILENAME_EXTENSION) + 2 );
sprintf(file, "%s/%s%s", clientDir, key, MESSAGE_FILENAME_EXTENSION);
fp = fopen(file, "wb");
if ( fp != NULL )
{
for(i=0; i<bufcount; i++)
{
bytesTotal += buflens[i];
bytesWritten += fwrite( buffers[i], sizeof(char), buflens[i], fp );
}
fclose(fp);
fp = NULL;
} else
rc = MQTTCLIENT_PERSISTENCE_ERROR;
if ( bytesWritten != bytesTotal )
{
pstremove(handle, key);
rc = MQTTCLIENT_PERSISTENCE_ERROR;
}
free(file);
exit:
FUNC_EXIT_RC(rc);
return rc;
};
/** Retrieve a wire message from the client persistence directory.
* See ::Persistence_get
*/
int pstget(void* handle, char* key, char** buffer, int* buflen)
{
int rc = 0;
FILE *fp;
char *clientDir = handle;
char *file;
char *buf;
unsigned long fileLen = 0;
unsigned long bytesRead = 0;
FUNC_ENTRY;
if (clientDir == NULL)
{
rc = MQTTCLIENT_PERSISTENCE_ERROR;
goto exit;
}
/* consider '/' + '\0' */
file = malloc(strlen(clientDir) + strlen(key) + strlen(MESSAGE_FILENAME_EXTENSION) + 2);
sprintf(file, "%s/%s%s", clientDir, key, MESSAGE_FILENAME_EXTENSION);
fp = fopen(file, "rb");
if ( fp != NULL )
{
fseek(fp, 0, SEEK_END);
fileLen = ftell(fp);
fseek(fp, 0, SEEK_SET);
buf=(char *)malloc(fileLen);
bytesRead = fread(buf, sizeof(char), fileLen, fp);
*buffer = buf;
*buflen = bytesRead;
if ( bytesRead != fileLen )
rc = MQTTCLIENT_PERSISTENCE_ERROR;
fclose(fp);
fp = NULL;
} else
rc = MQTTCLIENT_PERSISTENCE_ERROR;
free(file);
/* the caller must free buf */
exit:
FUNC_EXIT_RC(rc);
return rc;
}
/** Delete a persisted message from the client persistence directory.
* See ::Persistence_remove
*/
int pstremove(void* handle, char* key)
{
int rc = 0;
char *clientDir = handle;
char *file;
FUNC_ENTRY;
if (clientDir == NULL)
{
return rc = MQTTCLIENT_PERSISTENCE_ERROR;
goto exit;
}
/* consider '/' + '\0' */
file = malloc(strlen(clientDir) + strlen(key) + strlen(MESSAGE_FILENAME_EXTENSION) + 2);
sprintf(file, "%s/%s%s", clientDir, key, MESSAGE_FILENAME_EXTENSION);
#if defined(WIN32)
if ( _unlink(file) != 0 )
{
#else
if ( unlink(file) != 0 )
{
#endif
if ( errno != ENOENT )
rc = MQTTCLIENT_PERSISTENCE_ERROR;
}
free(file);
exit:
FUNC_EXIT_RC(rc);
return rc;
}
/** Delete client persistence directory (if empty).
* See ::Persistence_close
*/
int pstclose(void* handle)
{
int rc = 0;
char *clientDir = handle;
FUNC_ENTRY;
if (clientDir == NULL)
{
rc = MQTTCLIENT_PERSISTENCE_ERROR;
goto exit;
}
#if defined (WIN32)
if ( _rmdir(clientDir) != 0 )
{
#else
if ( rmdir(clientDir) != 0 )
{
#endif
if ( errno != ENOENT && errno != ENOTEMPTY )
rc = MQTTCLIENT_PERSISTENCE_ERROR;
}
free(clientDir);
exit:
FUNC_EXIT_RC(rc);
return rc;
}
/** Returns whether if a wire message is persisted in the client persistence directory.
* See ::Persistence_containskey
*/
int pstcontainskey(void *handle, char *key)
{
int rc = 0;
char *clientDir = handle;
FUNC_ENTRY;
if (clientDir == NULL)
{
rc = MQTTCLIENT_PERSISTENCE_ERROR;
goto exit;
}
#if defined (WIN32)
rc = containskeyWin32(clientDir, key);
#else
rc = containskeyUnix(clientDir, key);
#endif
exit:
FUNC_EXIT_RC(rc);
return rc;
}
#if defined(WIN32)
int containskeyWin32(char *dirname, char *key)
{
int notFound = MQTTCLIENT_PERSISTENCE_ERROR;
int fFinished = 0;
char *filekey, *ptraux;
char dir[MAX_PATH+1];
WIN32_FIND_DATAA FileData;
HANDLE hDir;
FUNC_ENTRY;
sprintf(dir, "%s/*", dirname);
hDir = FindFirstFileA(dir, &FileData);
if (hDir != INVALID_HANDLE_VALUE)
{
while (!fFinished)
{
if (FileData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
{
filekey = malloc(strlen(FileData.cFileName) + 1);
strcpy(filekey, FileData.cFileName);
ptraux = strstr(filekey, MESSAGE_FILENAME_EXTENSION);
if ( ptraux != NULL )
*ptraux = '\0' ;
if(strcmp(filekey, key) == 0)
{
notFound = 0;
fFinished = 1;
}
free(filekey);
}
if (!FindNextFileA(hDir, &FileData))
{
if (GetLastError() == ERROR_NO_MORE_FILES)
fFinished = 1;
}
}
FindClose(hDir);
}
FUNC_EXIT_RC(notFound);
return notFound;
}
#else
int containskeyUnix(char *dirname, char *key)
{
int notFound = MQTTCLIENT_PERSISTENCE_ERROR;
char *filekey, *ptraux;
DIR *dp;
struct dirent *dir_entry;
struct stat stat_info;
FUNC_ENTRY;
if((dp = opendir(dirname)) != NULL)
{
while((dir_entry = readdir(dp)) != NULL && notFound)
{
lstat(dir_entry->d_name, &stat_info);
if(S_ISREG(stat_info.st_mode))
{
filekey = malloc(strlen(dir_entry->d_name) + 1);
strcpy(filekey, dir_entry->d_name);
ptraux = strstr(filekey, MESSAGE_FILENAME_EXTENSION);
if ( ptraux != NULL )
*ptraux = '\0' ;
if(strcmp(filekey, key) == 0)
notFound = 0;
free(filekey);
}
}
closedir(dp);
}
FUNC_EXIT_RC(notFound);
return notFound;
}
#endif
/** Delete all the persisted message in the client persistence directory.
* See ::Persistence_clear
*/
int pstclear(void *handle)
{
int rc = 0;
char *clientDir = handle;
FUNC_ENTRY;
if (clientDir == NULL)
{
rc = MQTTCLIENT_PERSISTENCE_ERROR;
goto exit;
}
#if defined (WIN32)
rc = clearWin32(clientDir);
#else
rc = clearUnix(clientDir);
#endif
exit:
FUNC_EXIT_RC(rc);
return rc;
}
#if defined(WIN32)
int clearWin32(char *dirname)
{
int rc = 0;
int fFinished = 0;
char *file;
char dir[MAX_PATH+1];
WIN32_FIND_DATAA FileData;
HANDLE hDir;
FUNC_ENTRY;
sprintf(dir, "%s/*", dirname);
hDir = FindFirstFileA(dir, &FileData);
if (hDir != INVALID_HANDLE_VALUE)
{
while (!fFinished)
{
if (FileData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
{
file = malloc(strlen(dirname) + strlen(FileData.cFileName) + 2);
sprintf(file, "%s/%s", dirname, FileData.cFileName);
rc = remove(file);
free(file);
if ( rc != 0 )
{
rc = MQTTCLIENT_PERSISTENCE_ERROR;
break;
}
}
if (!FindNextFileA(hDir, &FileData))
{
if (GetLastError() == ERROR_NO_MORE_FILES)
fFinished = 1;
}
}
FindClose(hDir);
} else
rc = MQTTCLIENT_PERSISTENCE_ERROR;
FUNC_EXIT_RC(rc);
return rc;
}
#else
int clearUnix(char *dirname)
{
int rc = 0;
DIR *dp;
struct dirent *dir_entry;
struct stat stat_info;
FUNC_ENTRY;
if((dp = opendir(dirname)) != NULL)
{
while((dir_entry = readdir(dp)) != NULL && rc == 0)
{
lstat(dir_entry->d_name, &stat_info);
if(S_ISREG(stat_info.st_mode))
{
if ( remove(dir_entry->d_name) != 0 )
rc = MQTTCLIENT_PERSISTENCE_ERROR;
}
}
closedir(dp);
} else
rc = MQTTCLIENT_PERSISTENCE_ERROR;
FUNC_EXIT_RC(rc);
return rc;
}
#endif
/** Returns the keys (file names w/o the extension) in the client persistence directory.
* See ::Persistence_keys
*/
int pstkeys(void *handle, char ***keys, int *nkeys)
{
int rc = 0;
char *clientDir = handle;
FUNC_ENTRY;
if (clientDir == NULL)
{
rc = MQTTCLIENT_PERSISTENCE_ERROR;
goto exit;
}
#if defined (WIN32)
rc = keysWin32(clientDir, keys, nkeys);
#else
rc = keysUnix(clientDir, keys, nkeys);
#endif
exit:
FUNC_EXIT_RC(rc);
return rc;
}
#if defined(WIN32)
int keysWin32(char *dirname, char ***keys, int *nkeys)
{
int rc = 0;
char **fkeys = NULL;
int nfkeys = 0;
char dir[MAX_PATH+1];
WIN32_FIND_DATAA FileData;
HANDLE hDir;
int fFinished = 0;
char *ptraux;
int i;
FUNC_ENTRY;
sprintf(dir, "%s/*", dirname);
/* get number of keys */
hDir = FindFirstFileA(dir, &FileData);
if (hDir != INVALID_HANDLE_VALUE)
{
while (!fFinished)
{
if (FileData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
nfkeys++;
if (!FindNextFileA(hDir, &FileData))
{
if (GetLastError() == ERROR_NO_MORE_FILES)
fFinished = 1;
}
}
FindClose(hDir);
} else
{
rc = MQTTCLIENT_PERSISTENCE_ERROR;
goto exit;
}
if (nfkeys != 0 )
fkeys = (char **)malloc(nfkeys * sizeof(char *));
/* copy the keys */
hDir = FindFirstFileA(dir, &FileData);
if (hDir != INVALID_HANDLE_VALUE)
{
fFinished = 0;
i = 0;
while (!fFinished)
{
if (FileData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE)
{
fkeys[i] = malloc(strlen(FileData.cFileName) + 1);
strcpy(fkeys[i], FileData.cFileName);
ptraux = strstr(fkeys[i], MESSAGE_FILENAME_EXTENSION);
if ( ptraux != NULL )
*ptraux = '\0' ;
i++;
}
if (!FindNextFileA(hDir, &FileData))
{
if (GetLastError() == ERROR_NO_MORE_FILES)
fFinished = 1;
}
}
FindClose(hDir);
} else
{
rc = MQTTCLIENT_PERSISTENCE_ERROR;
goto exit;
}
*nkeys = nfkeys;
*keys = fkeys;
/* the caller must free keys */
exit:
FUNC_EXIT_RC(rc);
return rc;
}
#else
int keysUnix(char *dirname, char ***keys, int *nkeys)
{
int rc = 0;
char **fkeys = NULL;
int nfkeys = 0;
char *ptraux;
int i;
DIR *dp;
struct dirent *dir_entry;
struct stat stat_info;
FUNC_ENTRY;
/* get number of keys */
if((dp = opendir(dirname)) != NULL)
{
while((dir_entry = readdir(dp)) != NULL)
{
char* temp = malloc(strlen(dirname)+strlen(dir_entry->d_name)+2);
sprintf(temp, "%s/%s", dirname, dir_entry->d_name);
if (lstat(temp, &stat_info) == 0 && S_ISREG(stat_info.st_mode))
nfkeys++;
free(temp);
}
closedir(dp);
} else
{
rc = MQTTCLIENT_PERSISTENCE_ERROR;
goto exit;
}
if (nfkeys != 0 )
fkeys = (char **)malloc(nfkeys * sizeof(char *));
/* copy the keys */
if((dp = opendir(dirname)) != NULL)
{
i = 0;
while((dir_entry = readdir(dp)) != NULL)
{
char* temp = malloc(strlen(dirname)+strlen(dir_entry->d_name)+2);
sprintf(temp, "%s/%s", dirname, dir_entry->d_name);
if (lstat(temp, &stat_info) == 0 && S_ISREG(stat_info.st_mode))
{
fkeys[i] = malloc(strlen(dir_entry->d_name) + 1);
strcpy(fkeys[i], dir_entry->d_name);
ptraux = strstr(fkeys[i], MESSAGE_FILENAME_EXTENSION);
if ( ptraux != NULL )
*ptraux = '\0' ;
i++;
}
free(temp);
}
closedir(dp);
} else
{
rc = MQTTCLIENT_PERSISTENCE_ERROR;
goto exit;
}
*nkeys = nfkeys;
*keys = fkeys;
/* the caller must free keys */
exit:
FUNC_EXIT_RC(rc);
return rc;
}
#endif
#if defined(UNIT_TESTS)
int main (int argc, char *argv[])
{
#define MSTEM "m-"
#define NMSGS 10
#define NBUFS 4
#define NDEL 2
#define RC !rc ? "(Success)" : "(Failed) "
int rc;
char *handle;
char *perdir = ".";
char *clientID = "TheUTClient";
char *serverURI = "127.0.0.1:1883";
char *stem = MSTEM;
int msgId, i;
int nm[NDEL] = {5 , 8}; /* msgIds to get and remove */
char *key;
char **keys;
int nkeys;
char *buffer, *buff;
int buflen;
int nbufs = NBUFS;
char *bufs[NBUFS] = {"m0", "mm1", "mmm2" , "mmmm3"}; /* message content */
int buflens[NBUFS];
for(i=0;i<nbufs;i++)
buflens[i]=strlen(bufs[i]);
/* open */
//printf("Persistence directory : %s\n", perdir);
rc = pstopen((void**)&handle, clientID, serverURI, perdir);
printf("%s Persistence directory for client %s : %s\n", RC, clientID, handle);
/* put */
for(msgId=0;msgId<NMSGS;msgId++)
{
key = malloc(MESSAGE_FILENAME_LENGTH + 1);
sprintf(key, "%s%d", stem, msgId);
rc = pstput(handle, key, nbufs, bufs, buflens);
printf("%s Adding message %s\n", RC, key);
free(key);
}
/* keys ,ie, list keys added */
rc = pstkeys(handle, &keys, &nkeys);
printf("%s Found %d messages persisted in %s\n", RC, nkeys, handle);
for(i=0;i<nkeys;i++)
printf("%13s\n", keys[i]);
if (keys !=NULL)
free(keys);
/* containskey */
for(i=0;i<NDEL;i++)
{
key = malloc(MESSAGE_FILENAME_LENGTH + 1);
sprintf(key, "%s%d", stem, nm[i]);
rc = pstcontainskey(handle, key);
printf("%s Message %s is persisted ?\n", RC, key);
free(key);
}
/* get && remove*/
for(i=0;i<NDEL;i++)
{
key = malloc(MESSAGE_FILENAME_LENGTH + 1);
sprintf(key, "%s%d", stem, nm[i]);
rc = pstget(handle, key, &buffer, &buflen);
buff = malloc(buflen+1);
memcpy(buff, buffer, buflen);
buff[buflen] = '\0';
printf("%s Retrieving message %s : %s\n", RC, key, buff);
rc = pstremove(handle, key);
printf("%s Removing message %s\n", RC, key);
free(key);
free(buff);
free(buffer);
}
/* containskey */
for(i=0;i<NDEL;i++)
{
key = malloc(MESSAGE_FILENAME_LENGTH + 1);
sprintf(key, "%s%d", stem, nm[i]);
rc = pstcontainskey(handle, key);
printf("%s Message %s is persisted ?\n", RC, key);
free(key);
}
/* keys ,ie, list keys added */
rc = pstkeys(handle, &keys, &nkeys);
printf("%s Found %d messages persisted in %s\n", RC, nkeys, handle);
for(i=0;i<nkeys;i++)
printf("%13s\n", keys[i]);
if (keys != NULL)
free(keys);
/* close -> it will fail, since client persistence directory is not empty */
rc = pstclose(&handle);
printf("%s Closing client persistence directory for client %s\n", RC, clientID);
/* clear */
rc = pstclear(handle);
printf("%s Deleting all persisted messages in %s\n", RC, handle);
/* keys ,ie, list keys added */
rc = pstkeys(handle, &keys, &nkeys);
printf("%s Found %d messages persisted in %s\n", RC, nkeys, handle);
for(i=0;i<nkeys;i++)
printf("%13s\n", keys[i]);
if ( keys != NULL )
free(keys);
/* close */
rc = pstclose(&handle);
printf("%s Closing client persistence directory for client %s\n", RC, clientID);
}
#endif
#endif /* NO_PERSISTENCE */
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
/** 8.3 filesystem */
#define MESSAGE_FILENAME_LENGTH 8
/** Extension of the filename */
#define MESSAGE_FILENAME_EXTENSION ".msg"
/* prototypes of the functions for the default file system persistence */
int pstopen(void** handle, char* clientID, char* serverURI, void* context);
int pstclose(void* handle);
int pstput(void* handle, char* key, int bufcount, char* buffers[], int buflens[]);
int pstget(void* handle, char* key, char** buffer, int* buflen);
int pstremove(void* handle, char* key);
int pstkeys(void* handle, char*** keys, int* nkeys);
int pstclear(void* handle);
int pstcontainskey(void* handle, char* key);
int pstmkdir(char *pPathname);
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#if !defined(MQTTPROTOCOL_H)
#define MQTTPROTOCOL_H
#include "LinkedList.h"
#include "MQTTPacket.h"
#include "Clients.h"
#define MAX_MSG_ID 65535
#define MAX_CLIENTID_LEN 23
typedef struct
{
int socket;
Publications* p;
} pending_write;
typedef struct
{
List publications;
unsigned int msgs_received;
unsigned int msgs_sent;
List pending_writes; /* for qos 0 writes not complete */
} MQTTProtocol;
#include "MQTTProtocolOut.h"
#endif
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
/**
* @file
* \brief Functions dealing with the MQTT protocol exchanges
*
* Some other related functions are in the MQTTProtocolOut module
* */
#include <stdlib.h>
#include "MQTTProtocolClient.h"
#if !defined(NO_PERSISTENCE)
#include "MQTTPersistence.h"
#endif
#include "SocketBuffer.h"
#include "StackTrace.h"
#include "Heap.h"
#if !defined(min)
#define min(A,B) ( (A) < (B) ? (A):(B))
#endif
void Protocol_processPublication(Publish* publish, Clients* client);
void MQTTProtocol_closeSession(Clients* client, int sendwill);
extern MQTTProtocol state;
extern ClientStates* bstate;
/**
* List callback function for comparing Message structures by message id
* @param a first integer value
* @param b second integer value
* @return boolean indicating whether a and b are equal
*/
int messageIDCompare(void* a, void* b)
{
Messages* msg = (Messages*)a;
return msg->msgid == *(int*)b;
}
/**
* Assign a new message id for a client. Make sure it isn't already being used and does
* not exceed the maximum.
* @param client a client structure
* @return the next message id to use
*/
int MQTTProtocol_assignMsgId(Clients* client)
{
FUNC_ENTRY;
++(client->msgID);
while (ListFindItem(client->outboundMsgs, &(client->msgID), messageIDCompare) != NULL)
++(client->msgID);
if (client->msgID == MAX_MSG_ID + 1)
client->msgID = 1;
FUNC_EXIT_RC(client->msgID);
return client->msgID;
}
void MQTTProtocol_storeQoS0(Clients* pubclient, Publish* publish)
{
int len;
pending_write* pw = NULL;
FUNC_ENTRY;
/* store the publication until the write is finished */
pw = malloc(sizeof(pending_write));
Log(TRACE_MIN, 12, NULL);
pw->p = MQTTProtocol_storePublication(publish, &len);
pw->socket = pubclient->socket;
ListAppend(&(state.pending_writes), pw, sizeof(pending_write)+len);
/* we don't copy QoS 0 messages unless we have to, so now we have to tell the socket buffer where
the saved copy is */
if (SocketBuffer_updateWrite(pw->socket, pw->p->topic, pw->p->payload) == NULL)
Log(LOG_SEVERE, 0, "Error updating write");
FUNC_EXIT;
}
/**
* Utility function to start a new publish exchange.
* @param pubclient the client to send the publication to
* @param publish the publication data
* @param qos the MQTT QoS to use
* @param retained boolean - whether to set the MQTT retained flag
* @return the completion code
*/
int MQTTProtocol_startPublishCommon(Clients* pubclient, Publish* publish, int qos, int retained)
{
int rc = TCPSOCKET_COMPLETE;
FUNC_ENTRY;
rc = MQTTPacket_send_publish(publish, 0, qos, retained, pubclient->socket, pubclient->clientID);
if (qos == 0 && rc == TCPSOCKET_INTERRUPTED)
MQTTProtocol_storeQoS0(pubclient, publish);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Start a new publish exchange. Store any state necessary and try to send the packet
* @param pubclient the client to send the publication to
* @param publish the publication data
* @param qos the MQTT QoS to use
* @param retained boolean - whether to set the MQTT retained flag
* @param mm - pointer to the message to send
* @return the completion code
*/
int MQTTProtocol_startPublish(Clients* pubclient, Publish* publish, int qos, int retained, Messages** mm)
{
Publish p = *publish;
int rc = 0;
FUNC_ENTRY;
if (qos > 0)
{
p.msgId = publish->msgId = MQTTProtocol_assignMsgId(pubclient);
*mm = MQTTProtocol_createMessage(publish, mm, qos, retained);
ListAppend(pubclient->outboundMsgs, *mm, (*mm)->len);
/* we change these pointers to the saved message location just in case the packet could not be written
entirely; the socket buffer will use these locations to finish writing the packet */
p.payload = (*mm)->publish->payload;
p.topic = (*mm)->publish->topic;
}
rc = MQTTProtocol_startPublishCommon(pubclient, &p, qos, retained);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Copy and store message data for retries
* @param publish the publication data
* @param mm - pointer to the message data to store
* @param qos the MQTT QoS to use
* @param retained boolean - whether to set the MQTT retained flag
* @return pointer to the message data stored
*/
Messages* MQTTProtocol_createMessage(Publish* publish, Messages **mm, int qos, int retained)
{
Messages* m = malloc(sizeof(Messages));
FUNC_ENTRY;
m->len = sizeof(Messages);
if (*mm == NULL || (*mm)->publish == NULL)
{
int len1;
*mm = m;
m->publish = MQTTProtocol_storePublication(publish, &len1);
m->len += len1;
}
else
{
++(((*mm)->publish)->refcount);
m->publish = (*mm)->publish;
}
m->msgid = publish->msgId;
m->qos = qos;
m->retain = retained;
time(&(m->lastTouch));
if (qos == 2)
m->nextMessageType = PUBREC;
FUNC_EXIT;
return m;
}
/**
* Store message data for possible retry
* @param publish the publication data
* @param len returned length of the data stored
* @return the publication stored
*/
Publications* MQTTProtocol_storePublication(Publish* publish, int* len)
{
Publications* p = malloc(sizeof(Publications));
FUNC_ENTRY;
p->refcount = 1;
*len = strlen(publish->topic)+1;
if (Heap_findItem(publish->topic))
p->topic = publish->topic;
else
{
p->topic = malloc(*len);
strcpy(p->topic, publish->topic);
}
*len += sizeof(Publications);
p->topiclen = publish->topiclen;
p->payloadlen = publish->payloadlen;
p->payload = malloc(publish->payloadlen);
memcpy(p->payload, publish->payload, p->payloadlen);
*len += publish->payloadlen;
ListAppend(&(state.publications), p, *len);
FUNC_EXIT;
return p;
}
/**
* Remove stored message data. Opposite of storePublication
* @param p stored publication to remove
*/
void MQTTProtocol_removePublication(Publications* p)
{
FUNC_ENTRY;
if (--(p->refcount) == 0)
{
free(p->payload);
free(p->topic);
ListRemove(&(state.publications), p);
}
FUNC_EXIT;
}
/**
* Process an incoming publish packet for a socket
* @param pack pointer to the publish packet
* @param sock the socket on which the packet was received
* @return completion code
*/
int MQTTProtocol_handlePublishes(void* pack, int sock)
{
Publish* publish = (Publish*)pack;
Clients* client = NULL;
char* clientid = NULL;
int rc = TCPSOCKET_COMPLETE;
FUNC_ENTRY;
client = (Clients*)(ListFindItem(bstate->clients, &sock, clientSocketCompare)->content);
clientid = client->clientID;
Log(LOG_PROTOCOL, 11, NULL, sock, clientid, publish->msgId, publish->header.bits.qos,
publish->header.bits.retain, min(20, publish->payloadlen), publish->payload);
if (publish->header.bits.qos == 0)
Protocol_processPublication(publish, client);
else if (publish->header.bits.qos == 1)
{
/* send puback before processing the publications because a lot of return publications could fill up the socket buffer */
rc = MQTTPacket_send_puback(publish->msgId, sock, client->clientID);
/* if we get a socket error from sending the puback, should we ignore the publication? */
Protocol_processPublication(publish, client);
}
else if (publish->header.bits.qos == 2)
{
/* store publication in inbound list */
int len;
ListElement* listElem = NULL;
Messages* m = malloc(sizeof(Messages));
Publications* p = MQTTProtocol_storePublication(publish, &len);
m->publish = p;
m->msgid = publish->msgId;
m->qos = publish->header.bits.qos;
m->retain = publish->header.bits.retain;
m->nextMessageType = PUBREL;
if ( ( listElem = ListFindItem(client->inboundMsgs, &(m->msgid), messageIDCompare) ) != NULL )
{ /* discard queued publication with same msgID that the current incoming message */
Messages* msg = (Messages*)(listElem->content);
MQTTProtocol_removePublication(msg->publish);
ListInsert(client->inboundMsgs, m, sizeof(Messages) + len, listElem);
ListRemove(client->inboundMsgs, msg);
} else
ListAppend(client->inboundMsgs, m, sizeof(Messages) + len);
rc = MQTTPacket_send_pubrec(publish->msgId, sock, client->clientID);
publish->topic = NULL;
}
MQTTPacket_freePublish(publish);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Process an incoming puback packet for a socket
* @param pack pointer to the publish packet
* @param sock the socket on which the packet was received
* @return completion code
*/
int MQTTProtocol_handlePubacks(void* pack, int sock)
{
Puback* puback = (Puback*)pack;
Clients* client = NULL;
int rc = TCPSOCKET_COMPLETE;
FUNC_ENTRY;
client = (Clients*)(ListFindItem(bstate->clients, &sock, clientSocketCompare)->content);
Log(LOG_PROTOCOL, 14, NULL, sock, client->clientID, puback->msgId);
/* look for the message by message id in the records of outbound messages for this client */
if (ListFindItem(client->outboundMsgs, &(puback->msgId), messageIDCompare) == NULL)
Log(TRACE_MIN, 3, NULL, "PUBACK", client->clientID, puback->msgId);
else
{
Messages* m = (Messages*)(client->outboundMsgs->current->content);
if (m->qos != 1)
Log(TRACE_MIN, 4, NULL, "PUBACK", client->clientID, puback->msgId, m->qos);
else
{
Log(TRACE_MIN, 6, NULL, "PUBACK", client->clientID, puback->msgId);
#if !defined(NO_PERSISTENCE)
rc = MQTTPersistence_remove(client, PERSISTENCE_PUBLISH_SENT, m->qos, puback->msgId);
#endif
MQTTProtocol_removePublication(m->publish);
ListRemove(client->outboundMsgs, m);
}
}
free(pack);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Process an incoming pubrec packet for a socket
* @param pack pointer to the publish packet
* @param sock the socket on which the packet was received
* @return completion code
*/
int MQTTProtocol_handlePubrecs(void* pack, int sock)
{
Pubrec* pubrec = (Pubrec*)pack;
Clients* client = NULL;
int rc = TCPSOCKET_COMPLETE;
FUNC_ENTRY;
client = (Clients*)(ListFindItem(bstate->clients, &sock, clientSocketCompare)->content);
Log(LOG_PROTOCOL, 15, NULL, sock, client->clientID, pubrec->msgId);
/* look for the message by message id in the records of outbound messages for this client */
client->outboundMsgs->current = NULL;
if (ListFindItem(client->outboundMsgs, &(pubrec->msgId), messageIDCompare) == NULL)
{
if (pubrec->header.bits.dup == 0)
Log(TRACE_MIN, 3, NULL, "PUBREC", client->clientID, pubrec->msgId);
}
else
{
Messages* m = (Messages*)(client->outboundMsgs->current->content);
if (m->qos != 2)
{
if (pubrec->header.bits.dup == 0)
Log(TRACE_MIN, 4, NULL, "PUBREC", client->clientID, pubrec->msgId, m->qos);
}
else if (m->nextMessageType != PUBREC)
{
if (pubrec->header.bits.dup == 0)
Log(TRACE_MIN, 5, NULL, "PUBREC", client->clientID, pubrec->msgId);
}
else
{
rc = MQTTPacket_send_pubrel(pubrec->msgId, 0, sock, client->clientID);
m->nextMessageType = PUBCOMP;
time(&(m->lastTouch));
}
}
free(pack);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Process an incoming pubrel packet for a socket
* @param pack pointer to the publish packet
* @param sock the socket on which the packet was received
* @return completion code
*/
int MQTTProtocol_handlePubrels(void* pack, int sock)
{
Pubrel* pubrel = (Pubrel*)pack;
Clients* client = NULL;
int rc = TCPSOCKET_COMPLETE;
FUNC_ENTRY;
client = (Clients*)(ListFindItem(bstate->clients, &sock, clientSocketCompare)->content);
Log(LOG_PROTOCOL, 17, NULL, sock, client->clientID, pubrel->msgId);
/* look for the message by message id in the records of inbound messages for this client */
if (ListFindItem(client->inboundMsgs, &(pubrel->msgId), messageIDCompare) == NULL)
{
if (pubrel->header.bits.dup == 0)
Log(TRACE_MIN, 3, NULL, "PUBREL", client->clientID, pubrel->msgId);
else
/* Apparently this is "normal" behaviour, so we don't need to issue a warning */
rc = MQTTPacket_send_pubcomp(pubrel->msgId, sock, client->clientID);
}
else
{
Messages* m = (Messages*)(client->inboundMsgs->current->content);
if (m->qos != 2)
Log(TRACE_MIN, 4, NULL, "PUBREL", client->clientID, pubrel->msgId, m->qos);
else if (m->nextMessageType != PUBREL)
Log(TRACE_MIN, 5, NULL, "PUBREL", client->clientID, pubrel->msgId);
else
{
Publish publish;
/* send pubcomp before processing the publications because a lot of return publications could fill up the socket buffer */
rc = MQTTPacket_send_pubcomp(pubrel->msgId, sock, client->clientID);
publish.header.bits.qos = m->qos;
publish.header.bits.retain = m->retain;
publish.msgId = m->msgid;
publish.topic = m->publish->topic;
publish.topiclen = m->publish->topiclen;
publish.payload = m->publish->payload;
publish.payloadlen = m->publish->payloadlen;
Protocol_processPublication(&publish, client);
#if !defined(NO_PERSISTENCE)
rc = MQTTPersistence_remove(client, PERSISTENCE_PUBLISH_RECEIVED, m->qos, pubrel->msgId);
#endif
ListRemove(&(state.publications), m->publish);
ListRemove(client->inboundMsgs, m);
++(state.msgs_received);
}
}
free(pack);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Process an incoming pubcomp packet for a socket
* @param pack pointer to the publish packet
* @param sock the socket on which the packet was received
* @return completion code
*/
int MQTTProtocol_handlePubcomps(void* pack, int sock)
{
Pubcomp* pubcomp = (Pubcomp*)pack;
Clients* client = NULL;
int rc = TCPSOCKET_COMPLETE;
FUNC_ENTRY;
client = (Clients*)(ListFindItem(bstate->clients, &sock, clientSocketCompare)->content);
Log(LOG_PROTOCOL, 19, NULL, sock, client->clientID, pubcomp->msgId);
/* look for the message by message id in the records of outbound messages for this client */
if (ListFindItem(client->outboundMsgs, &(pubcomp->msgId), messageIDCompare) == NULL)
{
if (pubcomp->header.bits.dup == 0)
Log(TRACE_MIN, 3, NULL, "PUBCOMP", client->clientID, pubcomp->msgId);
}
else
{
Messages* m = (Messages*)(client->outboundMsgs->current->content);
if (m->qos != 2)
Log(TRACE_MIN, 4, NULL, "PUBCOMP", client->clientID, pubcomp->msgId, m->qos);
else
{
if (m->nextMessageType != PUBCOMP)
Log(TRACE_MIN, 5, NULL, "PUBCOMP", client->clientID, pubcomp->msgId);
else
{
Log(TRACE_MIN, 6, NULL, "PUBCOMP", client->clientID, pubcomp->msgId);
#if !defined(NO_PERSISTENCE)
rc = MQTTPersistence_remove(client, PERSISTENCE_PUBLISH_SENT, m->qos, pubcomp->msgId);
#endif
MQTTProtocol_removePublication(m->publish);
ListRemove(client->outboundMsgs, m);
(++state.msgs_sent);
}
}
}
free(pack);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* MQTT protocol keepAlive processing. Sends PINGREQ packets as required.
* @param now current time
*/
void MQTTProtocol_keepalive(time_t now)
{
ListElement* current = NULL;
FUNC_ENTRY;
ListNextElement(bstate->clients, &current);
while (current)
{
Clients* client = (Clients*)(current->content);
ListNextElement(bstate->clients, &current);
if (client->connected && client->keepAliveInterval > 0
&& (difftime(now, client->lastContact) >= client->keepAliveInterval))
{
MQTTPacket_send_pingreq(client->socket, client->clientID);
client->lastContact = now;
client->ping_outstanding = 1;
}
}
FUNC_EXIT;
}
/**
* MQTT retry processing per client
* @param now current time
* @param client - the client to which to apply the retry processing
*/
void MQTTProtocol_retries(time_t now, Clients* client)
{
ListElement* outcurrent = NULL;
FUNC_ENTRY;
if (client->retryInterval <= 0) /* 0 or -ive retryInterval turns off retry */
goto exit;
while (client && ListNextElement(client->outboundMsgs, &outcurrent))
{
Messages* m = (Messages*)(outcurrent->content);
if (difftime(now, m->lastTouch) > max(client->retryInterval, 10))
{
if (m->qos == 1 || (m->qos == 2 && m->nextMessageType == PUBREC))
{
Publish publish;
int rc;
Log(TRACE_MIN, 7, NULL, "PUBLISH", client->clientID, client->socket, m->msgid);
publish.msgId = m->msgid;
publish.topic = m->publish->topic;
publish.payload = m->publish->payload;
publish.payloadlen = m->publish->payloadlen;
rc = MQTTPacket_send_publish(&publish, 1, m->qos, m->retain, client->socket, client->clientID);
if (rc == SOCKET_ERROR)
{
client->good = 0;
Log(TRACE_MIN, 8, NULL, client->clientID, client->socket,
Socket_getpeer(client->socket));
MQTTProtocol_closeSession(client, 1);
client = NULL;
}
else
{
if (m->qos == 0 && rc == TCPSOCKET_INTERRUPTED)
MQTTProtocol_storeQoS0(client, &publish);
time(&(m->lastTouch));
}
}
else if (m->qos && m->nextMessageType == PUBCOMP)
{
Log(TRACE_MIN, 7, NULL, "PUBREL", client->clientID, client->socket, m->msgid);
if (MQTTPacket_send_pubrel(m->msgid, 1, client->socket, client->clientID) != TCPSOCKET_COMPLETE)
{
client->good = 0;
Log(TRACE_MIN, 8, NULL, client->clientID, client->socket,
Socket_getpeer(client->socket));
MQTTProtocol_closeSession(client, 1);
client = NULL;
}
else
time(&(m->lastTouch));
}
/* break; why not do all retries at once? */
}
}
exit:
FUNC_EXIT;
}
/**
* MQTT retry protocol and socket pending writes processing.
* @param now current time
* @param doRetry boolean - retries as well as pending writes?
*/
void MQTTProtocol_retry(time_t now, int doRetry)
{
ListElement* current = NULL;
FUNC_ENTRY;
ListNextElement(bstate->clients, &current);
/* look through the outbound message list of each client, checking to see if a retry is necessary */
while (current)
{
Clients* client = (Clients*)(current->content);
ListNextElement(bstate->clients, &current);
if (client->connected == 0)
continue;
if (client->good == 0)
{
MQTTProtocol_closeSession(client, 1);
continue;
}
if (Socket_noPendingWrites(client->socket) == 0)
continue;
if (doRetry)
MQTTProtocol_retries(now, client);
}
FUNC_EXIT;
}
/**
* Free a client structure
* @param client the client data to free
*/
void MQTTProtocol_freeClient(Clients* client)
{
FUNC_ENTRY;
/* free up pending message lists here, and any other allocated data */
MQTTProtocol_freeMessageList(client->outboundMsgs);
MQTTProtocol_freeMessageList(client->inboundMsgs);
ListFree(client->messageQueue);
free(client->clientID);
/*if (client->will != NULL)
{
willMessages* w = client->will;
free(w->msg);
free(w->topic);
free(client->will);
}*/
/* don't free the client structure itself... this is done elsewhere */
FUNC_EXIT;
}
/**
* Empty a message list, leaving it able to accept new messages
* @param msgList the message list to empty
*/
void MQTTProtocol_emptyMessageList(List* msgList)
{
ListElement* current = NULL;
FUNC_ENTRY;
while (ListNextElement(msgList, &current))
{
Messages* m = (Messages*)(current->content);
MQTTProtocol_removePublication(m->publish);
}
ListEmpty(msgList);
FUNC_EXIT;
}
/**
* Empty and free up all storage used by a message list
* @param msgList the message list to empty and free
*/
void MQTTProtocol_freeMessageList(List* msgList)
{
FUNC_ENTRY;
MQTTProtocol_emptyMessageList(msgList);
ListFree(msgList);
FUNC_EXIT;
}
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#if !defined(MQTTPROTOCOLCLIENT_H)
#define MQTTPROTOCOLCLIENT_H
#include "LinkedList.h"
#include "MQTTPacket.h"
#include "Log.h"
#include "MQTTProtocol.h"
#include "Messages.h"
#define MAX_MSG_ID 65535
#define MAX_CLIENTID_LEN 23
int MQTTProtocol_assignMsgId(Clients* client);
int MQTTProtocol_startPublish(Clients* pubclient, Publish* publish, int qos, int retained, Messages** m);
Messages* MQTTProtocol_createMessage(Publish* publish, Messages** mm, int qos, int retained);
Publications* MQTTProtocol_storePublication(Publish* publish, int* len);
int messageIDCompare(void* a, void* b);
int MQTTProtocol_assignMsgId(Clients* client);
int MQTTProtocol_handlePublishes(void* pack, int sock);
int MQTTProtocol_handlePubacks(void* pack, int sock);
int MQTTProtocol_handlePubrecs(void* pack, int sock);
int MQTTProtocol_handlePubrels(void* pack, int sock);
int MQTTProtocol_handlePubcomps(void* pack, int sock);
void MQTTProtocol_keepalive(time_t);
void MQTTProtocol_retry(time_t, int);
void MQTTProtocol_freeClient(Clients* client);
void MQTTProtocol_emptyMessageList(List* msgList);
void MQTTProtocol_freeMessageList(List* msgList);
#endif
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
/**
* @file
* \brief Functions dealing with the MQTT protocol exchanges
*
* Some other related functions are in the MQTTProtocolClient module
*/
#include <stdlib.h>
#include "MQTTProtocolOut.h"
#include "StackTrace.h"
#include "Heap.h"
extern MQTTProtocol state;
extern ClientStates* bstate;
/**
* Separates an address:port into two separate values
* @param ip_address the input string
* @param port the returned port integer
* @return the address string
*/
char* MQTTProtocol_addressPort(char* ip_address, int* port)
{
static char buf[INET6_ADDRSTRLEN+1];
char* pos = strrchr(ip_address, ':'); /* reverse find to allow for ':' in IPv6 addresses */
int len;
FUNC_ENTRY;
if (ip_address[0] == '[')
{ /* ip v6 */
if (pos < strrchr(ip_address, ']'))
pos = NULL; /* means it was an IPv6 separator, not for host:port */
}
if (pos)
{
int len = pos - ip_address;
*port = atoi(pos+1);
strncpy(buf, ip_address, len);
buf[len] = '\0';
pos = buf;
}
else
{
*port = DEFAULT_PORT;
pos = ip_address;
}
len = strlen(buf);
if (buf[len - 1] == ']')
buf[len - 1] = '\0';
FUNC_EXIT;
return pos;
}
/**
* MQTT outgoing connect processing for a client
* @param ip_address the TCP address:port to connect to
* @param clientID the MQTT client id to use
* @param cleansession MQTT cleansession flag
* @param keepalive MQTT keepalive timeout in seconds
* @param willMessage pointer to the will message to be used, if any
* @param username MQTT 3.1 username, or NULL
* @param password MQTT 3.1 password, or NULL
* @return the new client structure
*/
int MQTTProtocol_connect(char* ip_address, Clients* aClient)
{
int rc, port;
char* addr;
FUNC_ENTRY;
aClient->good = 1;
time(&(aClient->lastContact));
addr = MQTTProtocol_addressPort(ip_address, &port);
rc = Socket_new(addr, port, &(aClient->socket));
if (rc == EINPROGRESS || rc == EWOULDBLOCK)
aClient->connect_state = 1; /* TCP connect called */
else if (rc == 0)
{
if ((rc = MQTTPacket_send_connect(aClient)) == 0)
aClient->connect_state = 2; /* TCP connect completed, in which case send the MQTT connect packet */
else
aClient->connect_state = 0;
}
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Process an incoming pingresp packet for a socket
* @param pack pointer to the publish packet
* @param sock the socket on which the packet was received
* @return completion code
*/
int MQTTProtocol_handlePingresps(void* pack, int sock)
{
Clients* client = NULL;
int rc = TCPSOCKET_COMPLETE;
FUNC_ENTRY;
client = (Clients*)(ListFindItem(bstate->clients, &sock, clientSocketCompare)->content);
Log(LOG_PROTOCOL, 21, NULL, sock, client->clientID);
client->ping_outstanding = 0;
FUNC_EXIT_RC(rc);
return rc;
}
/**
* MQTT outgoing subscribe processing for a client
* @param client the client structure
* @param topics list of topics
* @param qoss corresponding list of QoSs
* @return completion code
*/
int MQTTProtocol_subscribe(Clients* client, List* topics, List* qoss)
{
int rc = 0;
FUNC_ENTRY;
/* we should stack this up for retry processing too */
rc = MQTTPacket_send_subscribe(topics, qoss, MQTTProtocol_assignMsgId(client), 0, client->socket, client->clientID);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Process an incoming suback packet for a socket
* @param pack pointer to the publish packet
* @param sock the socket on which the packet was received
* @return completion code
*/
int MQTTProtocol_handleSubacks(void* pack, int sock)
{
Suback* suback = (Suback*)pack;
Clients* client = NULL;
int rc = TCPSOCKET_COMPLETE;
FUNC_ENTRY;
client = (Clients*)(ListFindItem(bstate->clients, &sock, clientSocketCompare)->content);
Log(LOG_PROTOCOL, 23, NULL, sock, client->clientID, suback->msgId);
MQTTPacket_freeSuback(suback);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* MQTT outgoing unsubscribe processing for a client
* @param client the client structure
* @param topics list of topics
* @return completion code
*/
int MQTTProtocol_unsubscribe(Clients* client, List* topics)
{
int rc = 0;
FUNC_ENTRY;
/* we should stack this up for retry processing too? */
rc = MQTTPacket_send_unsubscribe(topics, MQTTProtocol_assignMsgId(client), 0, client->socket, client->clientID);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Process an incoming unsuback packet for a socket
* @param pack pointer to the publish packet
* @param sock the socket on which the packet was received
* @return completion code
*/
int MQTTProtocol_handleUnsubacks(void* pack, int sock)
{
Unsuback* unsuback = (Unsuback*)pack;
Clients* client = NULL;
int rc = TCPSOCKET_COMPLETE;
FUNC_ENTRY;
client = (Clients*)(ListFindItem(bstate->clients, &sock, clientSocketCompare)->content);
Log(LOG_PROTOCOL, 24, NULL, sock, client->clientID, unsuback->msgId);
free(unsuback);
FUNC_EXIT_RC(rc);
return rc;
}
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#if !defined(MQTTPROTOCOLOUT_H)
#define MQTTPROTOCOLOUT_H
#include "LinkedList.h"
#include "MQTTPacket.h"
#include "Clients.h"
#include "Log.h"
#include "Messages.h"
#include "MQTTProtocol.h"
#include "MQTTProtocolClient.h"
#define DEFAULT_PORT 1883
void MQTTProtocol_reconnect(char* ip_address, Clients* client);
int MQTTProtocol_connect(char* ip_address, Clients* acClients);
int MQTTProtocol_handlePingresps(void* pack, int sock);
int MQTTProtocol_subscribe(Clients* client, List* topics, List* qoss);
int MQTTProtocol_handleSubacks(void* pack, int sock);
int MQTTProtocol_unsubscribe(Clients* client, List* topics);
int MQTTProtocol_handleUnsubacks(void* pack, int sock);
#endif
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
/**
* @file
* \brief Trace messages
*
*/
#include "Messages.h"
#include "Log.h"
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include "Heap.h"
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#define max_msg_len 120
static char* protocol_message_list[] =
{
"%d %s -> CONNECT cleansession: %d (%d)", /* 0, was 131, 68 and 69 */
"%d %s <- CONNACK rc: %d", /* 1, was 132 */
"%d %s -> CONNACK rc: %d (%d)", /* 2, was 138 */
"%d %s <- PINGREQ", /* 3, was 35 */
"%d %s -> PINGRESP (%d)", /* 4 */
"%d %s <- DISCONNECT", /* 5 */
"%d %s <- SUBSCRIBE msgid: %d", /* 6, was 39 */
"%d %s -> SUBACK msgid: %d (%d)", /* 7, was 40 */
"%d %s <- UNSUBSCRIBE msgid: %d", /* 8, was 41 */
"%d %s -> UNSUBACK msgid: %d (%d)", /* 9 */
"%d %s -> PUBLISH msgid: %d qos: %d retained: %d (%d) payload: %.*s", /* 10, was 42 */
"%d %s <- PUBLISH msgid: %d qos: %d retained: %d payload: %.*s", /* 11, was 46 */
"%d %s -> PUBACK msgid: %d (%d)", /* 12, was 47 */
"%d %s -> PUBREC msgid: %d (%d)", /* 13, was 48 */
"%d %s <- PUBACK msgid: %d", /* 14, was 49 */
"%d %s <- PUBREC msgid: %d", /* 15, was 53 */
"%d %s -> PUBREL msgid: %d (%d)", /* 16, was 57 */
"%d %s <- PUBREL msgid %d", /* 17, was 58 */
"%d %s -> PUBCOMP msgid %d (%d)", /* 18, was 62 */
"%d %s <- PUBCOMP msgid:%d", /* 19, was 63 */
"%d %s -> PINGREQ (%d)", /* 20, was 137 */
"%d %s <- PINGRESP", /* 21, was 70 */
"%d %s -> SUBSCRIBE msgid: %d (%d)", /* 22, was 72 */
"%d %s <- SUBACK msgid: %d", /* 23, was 73 */
"%d %s <- UNSUBACK msgid: %d", /* 24, was 74 */
"%d %s -> UNSUBSCRIBE msgid: %d (%d)", /* 25, was 106 */
"%d %s <- CONNECT", /* 26 */
"%d %s -> PUBLISH qos: 0 retained: %d (%d)", /* 27 */
"%d %s -> DISCONNECT (%d)", /* 28 */
};
static char* trace_message_list[] =
{
"Failed to remove client from bstate->clients", /* 0 */
"Removed client %s from bstate->clients, socket %d", /* 1 */
"Packet_Factory: unhandled packet type %d", /* 2 */
"Packet %s received from client %s for message identifier %d, but no record of that message identifier found", /* 3 */
"Packet %s received from client %s for message identifier %d, but message is wrong QoS, %d", /* 4 */
"Packet %s received from client %s for message identifier %d, but message is in wrong state", /* 5 */
"%s received from client %s for message id %d - removing publication", /* 6 */
"Trying %s again for client %s, socket %d, message identifier %d", /* 7 */
"Socket error for client identifier %s, socket %d, peer address %s; ending connection", /* 8 */
"(%lu) %*s(%d)> %s:%d", /* 9 */
"(%lu) %*s(%d)< %s:%d", /* 10 */
"(%lu) %*s(%d)< %s:%d (%d)", /* 11 */
"Storing unsent QoS 0 message", /* 12 */
};
/**
* Get a log message by its index
* @param index the integer index
* @param log_level the log level, used to determine which message list to use
* @return the message format string
*/
char* Messages_get(int index, int log_level)
{
char* msg = NULL;
if (log_level == TRACE_PROTOCOL)
msg = (index >= 0 && index < ARRAY_SIZE(protocol_message_list)) ? protocol_message_list[index] : NULL;
else
msg = (index >= 0 && index < ARRAY_SIZE(trace_message_list)) ? trace_message_list[index] : NULL;
return msg;
}
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#if !defined(MESSAGES_H)
#define MESSAGES_H
char* Messages_get(int, int);
#endif
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
/**
* @file
* \brief Socket related functions
*
* Some other related functions are in the SocketBuffer module
*/
#include "Socket.h"
#include "Log.h"
#include "SocketBuffer.h"
#include "Messages.h"
#include "StackTrace.h"
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <ctype.h>
#include "Heap.h"
int Socket_close_only(int socket);
int Socket_continueWrites(fd_set* pwset);
#if defined(WIN32)
#define iov_len len
#define iov_base buf
#else
#include <sys/uio.h>
#endif
/**
* Structure to hold all socket data for the module
*/
static Sockets s;
static fd_set wset;
/**
* Set a socket non-blocking, OS independently
* @param sock the socket to set non-blocking
* @return TCP call error code
*/
int Socket_setnonblocking(int sock)
{
int rc;
#if defined(WIN32)
u_long flag = 1L;
FUNC_ENTRY;
rc = ioctl(sock, FIONBIO, &flag);
#else
int flags;
FUNC_ENTRY;
if ((flags = fcntl(sock, F_GETFL, 0)))
flags = 0;
rc = fcntl(sock, F_SETFL, flags | O_NONBLOCK);
#endif
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Gets the specific error corresponding to SOCKET_ERROR
* @param aString the function that was being used when the error occurred
* @param sock the socket on which the error occurred
* @return the specific TCP error code
*/
int Socket_error(char* aString, int sock)
{
#if defined(WIN32)
int errno;
#endif
FUNC_ENTRY;
#if defined(WIN32)
errno = WSAGetLastError();
#endif
if (errno != EINTR && errno != EAGAIN && errno != EINPROGRESS && errno != EWOULDBLOCK)
{
if (strcmp(aString, "shutdown") != 0 || (errno != ENOTCONN && errno != ECONNRESET))
Log(TRACE_MIN, -1, "Socket error %d in %s for socket %d", errno, aString, sock);
}
FUNC_EXIT_RC(errno);
return errno;
}
/**
* Initialize the socket module
*/
void Socket_outInitialize()
{
#if defined(WIN32)
WORD winsockVer = 0x0202;
WSADATA wsd;
FUNC_ENTRY;
WSAStartup(winsockVer, &wsd);
#else
FUNC_ENTRY;
signal(SIGPIPE, SIG_IGN);
#endif
SocketBuffer_initialize();
s.clientsds = ListInitialize();
s.connect_pending = ListInitialize();
s.write_pending = ListInitialize();
s.cur_clientsds = NULL;
FD_ZERO(&(s.rset)); /* Initialize the descriptor set */
FD_ZERO(&(s.pending_wset));
s.maxfdp1 = 0;
memcpy((void*)&(s.rset_saved), (void*)&(s.rset), sizeof(s.rset_saved));
FUNC_EXIT;
}
/**
* Terminate the socket module
*/
void Socket_outTerminate()
{
FUNC_ENTRY;
ListFree(s.connect_pending);
ListFree(s.write_pending);
ListFree(s.clientsds);
SocketBuffer_terminate();
#if defined(WIN32)
WSACleanup();
#endif
FUNC_EXIT;
}
/**
* Add a socket to the list of socket to check with select
* @param newSd the new socket to add
*/
int Socket_addSocket(int newSd)
{
int rc = 0;
FUNC_ENTRY;
if (ListFindItem(s.clientsds, &newSd, intcompare) == NULL) /* make sure we don't add the same socket twice */
{
int* pnewSd = (int*)malloc(sizeof(newSd));
*pnewSd = newSd;
ListAppend(s.clientsds, pnewSd, sizeof(newSd));
FD_SET(newSd, &(s.rset_saved));
s.maxfdp1 = max(s.maxfdp1, newSd + 1);
rc = Socket_setnonblocking(newSd);
}
else
Log(TRACE_MIN, -1, "addSocket: socket %d already in the list", newSd);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Don't accept work from a client unless it is accepting work back, i.e. its socket is writeable
* this seems like a reasonable form of flow control, and practically, seems to work.
* @param socket the socket to check
* @param read_set the socket read set (see select doc)
* @param write_set the socket write set (see select doc)
* @return boolean - is the socket ready to go?
*/
int isReady(int socket, fd_set* read_set, fd_set* write_set)
{
int rc = 1;
FUNC_ENTRY;
if (ListFindItem(s.connect_pending, &socket, intcompare) && FD_ISSET(socket, write_set))
ListRemoveItem(s.connect_pending, &socket, intcompare);
else
rc = FD_ISSET(socket, read_set) && FD_ISSET(socket, write_set) && Socket_noPendingWrites(socket);
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Returns the next socket ready for communications as indicated by select
* @param more_work flag to indicate more work is waiting, and thus a timeout value of 0 should
* be used for the select
* @param tp the timeout to be used for the select, unless overridden
* @return the socket next ready, or 0 if none is ready
*/
int Socket_getReadySocket(int more_work, struct timeval *tp)
{
int rc = 0;
static struct timeval zero = {0L, 0L}; /* 0 seconds */
static struct timeval one = {1L, 0L}; /* 1 second */
struct timeval timeout = one;
FUNC_ENTRY;
if (s.clientsds->count == 0)
goto exit;
if (more_work)
timeout = zero;
else if (tp)
timeout = *tp;
while (s.cur_clientsds != NULL)
{
if (isReady(*((int*)(s.cur_clientsds->content)), &(s.rset), &wset))
break;
ListNextElement(s.clientsds, &s.cur_clientsds);
}
if (s.cur_clientsds == NULL)
{
int rc1;
fd_set pwset;
memcpy((void*)&(s.rset), (void*)&(s.rset_saved), sizeof(s.rset));
memcpy((void*)&(pwset), (void*)&(s.pending_wset), sizeof(pwset));
if ((rc = select(s.maxfdp1, &(s.rset), &pwset, NULL, &timeout)) == SOCKET_ERROR)
{
Socket_error("read select", 0);
goto exit;
}
Log(TRACE_MAX, -1, "Return code %d from read select", rc);
if (Socket_continueWrites(&pwset) == SOCKET_ERROR)
{
rc = 0;
goto exit;
}
memcpy((void*)&wset, (void*)&(s.rset_saved), sizeof(wset));
if ((rc1 = select(s.maxfdp1, NULL, &(wset), NULL, &zero)) == SOCKET_ERROR)
{
Socket_error("write select", 0);
rc = rc1;
goto exit;
}
Log(TRACE_MAX, -1, "Return code %d from write select", rc1);
if (rc == 0 && rc1 == 0)
goto exit; /* no work to do */
s.cur_clientsds = s.clientsds->first;
while (s.cur_clientsds != NULL)
{
int cursock = *((int*)(s.cur_clientsds->content));
if (isReady(cursock, &(s.rset), &wset))
break;
ListNextElement(s.clientsds, &s.cur_clientsds);
}
}
if (s.cur_clientsds == NULL)
rc = 0;
else
{
rc = *((int*)(s.cur_clientsds->content));
ListNextElement(s.clientsds, &s.cur_clientsds);
}
exit:
FUNC_EXIT_RC(rc);
return rc;
} /* end getReadySocket */
/**
* Reads one byte from a socket
* @param socket the socket to read from
* @param c the character read, returned
* @return completion code
*/
int Socket_getch(int socket, char* c)
{
int rc = SOCKET_ERROR;
FUNC_ENTRY;
if ((rc = SocketBuffer_getQueuedChar(socket, c)) != SOCKETBUFFER_INTERRUPTED)
goto exit;
if ((rc = recv(socket, c, (size_t)1, 0)) == SOCKET_ERROR)
{
int err = Socket_error("recv - getch", socket);
if (err == EWOULDBLOCK || err == EAGAIN)
{
rc = TCPSOCKET_INTERRUPTED;
SocketBuffer_interrupted(socket, 0);
}
}
else if (rc == 0)
rc = SOCKET_ERROR; /* The return value from recv is 0 when the peer has performed an orderly shutdown. */
else if (rc == 1)
{
SocketBuffer_queueChar(socket, *c);
rc = TCPSOCKET_COMPLETE;
}
exit:
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Attempts to read a number of bytes from a socket, non-blocking. If a previous read did not
* finish, then retrieve that data.
* @param socket the socket to read from
* @param bytes the number of bytes to read
* @param actual_len the actual number of bytes read
* @return completion code
*/
char *Socket_getdata(int socket, int bytes, int* actual_len)
{
int rc;
char* buf;
FUNC_ENTRY;
if (bytes == 0)
{
buf = SocketBuffer_complete(socket);
goto exit;
}
buf = SocketBuffer_getQueuedData(socket, bytes, actual_len);
if ((rc = recv(socket, buf + (*actual_len), (size_t)(bytes - (*actual_len)), 0)) == SOCKET_ERROR)
{
rc = Socket_error("recv - getdata", socket);
if (rc != EAGAIN && rc != EWOULDBLOCK)
{
buf = NULL;
goto exit;
}
}
else if (rc == 0) /* rc 0 means the other end closed the socket, albeit "gracefully" */
{
buf = NULL;
goto exit;
}
else
*actual_len += rc;
if (*actual_len == bytes)
SocketBuffer_complete(socket);
else /* we didn't read the whole packet */
{
SocketBuffer_interrupted(socket, *actual_len);
Log(TRACE_MAX, -1, "%d bytes expected but %d bytes now received", bytes, *actual_len);
}
exit:
FUNC_EXIT;
return buf;
}
/**
* Indicate whether any data is pending outbound for a socket.
* @return boolean - true == data pending.
*/
int Socket_noPendingWrites(int socket)
{
int cursock = socket;
return ListFindItem(s.write_pending, &cursock, intcompare) == NULL;
}
/**
* Attempts to write a series of iovec buffers to a socket in *one* system call so that
* they are sent as one packet.
* @param socket the socket to write to
* @param iovecs an array of buffers to write
* @param count number of buffers in iovecs
* @param bytes number of bytes actually written returned
* @return completion code, especially TCPSOCKET_INTERRUPTED
*/
int Socket_writev(int socket, iobuf* iovecs, int count, unsigned long* bytes)
{
int rc;
FUNC_ENTRY;
#if defined(WIN32)
rc = WSASend(socket, iovecs, count, (LPDWORD)bytes, 0, NULL, NULL);
if (rc == SOCKET_ERROR)
{
int err = Socket_error("WSASend - putdatas", socket);
if (err == EWOULDBLOCK || err == EAGAIN)
rc = TCPSOCKET_INTERRUPTED;
}
#else
*bytes = 0L;
rc = writev(socket, iovecs, count);
if (rc == SOCKET_ERROR)
{
int err = Socket_error("writev - putdatas", socket);
if (err == EWOULDBLOCK || err == EAGAIN)
rc = TCPSOCKET_INTERRUPTED;
}
else
*bytes = rc;
#endif
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Attempts to write a series of buffers to a socket in *one* system call so that they are
* sent as one packet.
* @param socket the socket to write to
* @param buf0 the first buffer
* @param buf0len the length of data in the first buffer
* @param count number of buffers
* @param buffers an array of buffers to write
* @param buflens an array of corresponding buffer lengths
* @return completion code, especially TCPSOCKET_INTERRUPTED
*/
int Socket_putdatas(int socket, char* buf0, int buf0len, int count, char** buffers, int* buflens)
{
unsigned long bytes = 0L;
iobuf iovecs[5];
int rc = TCPSOCKET_INTERRUPTED, i, total = buf0len;
FUNC_ENTRY;
if (!Socket_noPendingWrites(socket))
{
Log(LOG_SEVERE, -1, "Trying to write to socket %d for which there is already pending output", socket);
rc = SOCKET_ERROR;
goto exit;
}
for (i = 0; i < count; i++)
total += buflens[i];
iovecs[0].iov_base = buf0;
iovecs[0].iov_len = buf0len;
for (i = 0; i < count; i++)
{
iovecs[i+1].iov_base = buffers[i];
iovecs[i+1].iov_len = buflens[i];
}
if ((rc = Socket_writev(socket, iovecs, count+1, &bytes)) != SOCKET_ERROR)
{
if (bytes == total)
rc = TCPSOCKET_COMPLETE;
else
{
int* sockmem = (int*)malloc(sizeof(int));
Log(TRACE_MIN, -1, "Partial write: %ld bytes of %d actually written on socket %d",
bytes, total, socket);
SocketBuffer_pendingWrite(socket, count+1, iovecs, total, bytes);
*sockmem = socket;
ListAppend(s.write_pending, sockmem, sizeof(int));
FD_SET(socket, &(s.pending_wset));
rc = TCPSOCKET_INTERRUPTED;
}
}
exit:
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Close a socket without removing it from the select list.
* @param socket the socket to close
* @return completion code
*/
int Socket_close_only(int socket)
{
int rc;
FUNC_ENTRY;
#if defined(WIN32)
if (shutdown(socket, SD_BOTH) == SOCKET_ERROR)
Socket_error("shutdown", socket);
if ((rc = closesocket(socket)) == SOCKET_ERROR)
Socket_error("close", socket);
#else
if (shutdown(socket, SHUT_RDWR) == SOCKET_ERROR)
Socket_error("shutdown", socket);
if ((rc = close(socket)) == SOCKET_ERROR)
Socket_error("close", socket);
#endif
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Close a socket and remove it from the select list.
* @param socket the socket to close
* @return completion code
*/
void Socket_close(int socket)
{
FUNC_ENTRY;
Socket_close_only(socket);
FD_CLR(socket, &(s.rset_saved));
if (FD_ISSET(socket, &(s.pending_wset)))
FD_CLR(socket, &(s.pending_wset));
if (s.cur_clientsds != NULL && *(int*)(s.cur_clientsds->content) == socket)
s.cur_clientsds = s.cur_clientsds->next;
ListRemoveItem(s.connect_pending, &socket, intcompare);
ListRemoveItem(s.write_pending, &socket, intcompare);
SocketBuffer_cleanup(socket);
if (ListRemoveItem(s.clientsds, &socket, intcompare))
Log(TRACE_MIN, -1, "Removed socket %d", socket);
else
Log(TRACE_MIN, -1, "Failed to remove socket %d", socket);
if (socket + 1 >= s.maxfdp1)
{
/* now we have to reset s.maxfdp1 */
ListElement* cur_clientsds = NULL;
s.maxfdp1 = 0;
while (ListNextElement(s.clientsds, &cur_clientsds))
s.maxfdp1 = max(*((int*)(cur_clientsds->content)), s.maxfdp1);
++(s.maxfdp1);
Log(TRACE_MAX, -1, "Reset max fdp1 to %d", s.maxfdp1);
}
FUNC_EXIT;
}
/**
* Create a new socket and TCP connect to an address/port
* @param addr the address string
* @param port the TCP port
* @param sock returns the new socket
* @return completion code
*/
int Socket_new(char* addr, int port, int* sock)
{
int type = SOCK_STREAM;
struct sockaddr_in address;
#if defined(AF_INET6)
struct sockaddr_in6 address6;
#endif
int rc = SOCKET_ERROR;
#if defined(WIN32)
short family;
#else
sa_family_t family = AF_INET;
#endif
struct addrinfo *result = NULL;
struct addrinfo hints = {0, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, 0, NULL, NULL, NULL};
FUNC_ENTRY;
*sock = -1;
if (addr[0] == '[')
++addr;
if ((rc = getaddrinfo(addr, NULL, &hints, &result)) == 0)
{
struct addrinfo* res = result;
/* prefer ip4 addresses */
while (res)
{
if (res->ai_family == AF_INET)
{
result = res;
break;
}
res = res->ai_next;
}
#if defined(AF_INET6)
if (result->ai_family == AF_INET6)
{
address6.sin6_port = htons(port);
address6.sin6_family = family = AF_INET6;
address6.sin6_addr = ((struct sockaddr_in6*)(result->ai_addr))->sin6_addr;
}
else
#endif
if (result->ai_family == AF_INET)
{
address.sin_port = htons(port);
address.sin_family = family = AF_INET;
address.sin_addr = ((struct sockaddr_in*)(result->ai_addr))->sin_addr;
}
else
rc = -1;
freeaddrinfo(result);
}
else
Log(TRACE_MIN, -1, "getaddrinfo failed for addr %s with rc %d", addr, rc);
if (rc != 0)
Log(LOG_ERROR, -1, "%s is not a valid IP address", addr);
else
{
*sock = socket(family, type, 0);
if (*sock == INVALID_SOCKET)
rc = Socket_error("socket", *sock);
else
{
Log(TRACE_MIN, -1, "New socket %d for %s, port %d", *sock, addr, port);
if (Socket_addSocket(*sock) == SOCKET_ERROR)
rc = Socket_error("setnonblocking", *sock);
else
{
/* this could complete immmediately, even though we are non-blocking */
if (family == AF_INET)
rc = connect(*sock, (struct sockaddr*)&address, sizeof(address));
#if defined(AF_INET6)
else
rc = connect(*sock, (struct sockaddr*)&address6, sizeof(address6));
#endif
if (rc == SOCKET_ERROR)
rc = Socket_error("connect", *sock);
if (rc == EINPROGRESS || rc == EWOULDBLOCK)
{
int* pnewSd = (int*)malloc(sizeof(int));
*pnewSd = *sock;
ListAppend(s.connect_pending, pnewSd, sizeof(int));
Log(TRACE_MIN, 15, "Connect pending");
}
}
}
}
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Continue an outstanding write for a particular socket
* @param socket that socket
* @return completion code
*/
int Socket_continueWrite(int socket)
{
int rc = 0;
pending_writes* pw;
unsigned long curbuflen = 0L, /* cumulative total of buffer lengths */
bytes;
int curbuf = -1, i;
iobuf iovecs1[5];
FUNC_ENTRY;
pw = SocketBuffer_getWrite(socket);
for (i = 0; i < pw->count; ++i)
{
if (pw->bytes <= curbuflen)
{ /* if previously written length is less than the buffer we are currently looking at,
add the whole buffer */
iovecs1[++curbuf].iov_len = pw->iovecs[i].iov_len;
iovecs1[curbuf].iov_base = pw->iovecs[i].iov_base;
}
else if (pw->bytes < curbuflen + pw->iovecs[i].iov_len)
{ /* if previously written length is in the middle of the buffer we are currently looking at,
add some of the buffer */
int offset = pw->bytes - curbuflen;
iovecs1[++curbuf].iov_len = pw->iovecs[i].iov_len - offset;
iovecs1[curbuf].iov_base = pw->iovecs[i].iov_base + offset;
break;
}
curbuflen += pw->iovecs[i].iov_len;
}
if ((rc = Socket_writev(socket, iovecs1, curbuf+1, &bytes)) != SOCKET_ERROR)
{
pw->bytes += bytes;
if ((rc = (pw->bytes == pw->total)))
{ /* topic and payload buffers are freed elsewhere, when all references to them have been removed */
free(pw->iovecs[0].iov_base);
free(pw->iovecs[1].iov_base);
if (pw->count == 5)
free(pw->iovecs[3].iov_base);
Log(TRACE_MIN, -1, "ContinueWrite: partial write now complete for socket %d", socket);
}
else
Log(TRACE_MIN, -1, "ContinueWrite wrote +%lu bytes on socket %d", bytes, socket);
}
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Continue any outstanding writes for a socket set
* @param pwset the set of sockets
* @return completion code
*/
int Socket_continueWrites(fd_set* pwset)
{
int rc1 = 0;
ListElement* curpending = s.write_pending->first;
FUNC_ENTRY;
while (curpending)
{
int socket = *(int*)(curpending->content);
if (FD_ISSET(socket, pwset) && Socket_continueWrite(socket))
{
if (!SocketBuffer_writeComplete(socket))
Log(LOG_SEVERE, -1, "Failed to remove pending write from socket buffer list");
FD_CLR(socket, &(s.pending_wset));
if (!ListRemove(s.write_pending, curpending->content))
{
Log(LOG_SEVERE, -1, "Failed to remove pending write from list");
ListNextElement(s.write_pending, &curpending);
}
curpending = s.write_pending->current;
}
else
ListNextElement(s.write_pending, &curpending);
}
FUNC_EXIT_RC(rc1);
return rc1;
}
/**
* Convert a numeric address to character string
* @param sa socket numerical address
* @param sock socket
* @return the peer information
*/
char* Socket_getaddrname(struct sockaddr* sa, int sock)
{
/**
* maximum length of the address string
*/
#define ADDRLEN INET6_ADDRSTRLEN+1
/**
* maximum length of the port string
*/
#define PORTLEN 10
static char addr_string[ADDRLEN + PORTLEN];
#if defined(WIN32)
int buflen = ADDRLEN*2;
wchar_t buf[ADDRLEN*2];
if (WSAAddressToString(sa, sizeof(struct sockaddr_in6), NULL, buf, (LPDWORD)&buflen) == SOCKET_ERROR)
Socket_error("WSAAddressToString", sock);
else
wcstombs(addr_string, buf, sizeof(addr_string));
/* TODO: append the port information - format: [00:00:00::]:port */
/* strcpy(&addr_string[strlen(addr_string)], "what?"); */
#else
struct sockaddr_in *sin = (struct sockaddr_in *)sa;
inet_ntop(sin->sin_family, &sin->sin_addr, addr_string, ADDRLEN);
sprintf(&addr_string[strlen(addr_string)], ":%d", ntohs(sin->sin_port));
#endif
return addr_string;
}
/**
* Get information about the other end connected to a socket
* @param sock the socket to inquire on
* @return the peer information
*/
char* Socket_getpeer(int sock)
{
struct sockaddr_in6 sa;
socklen_t sal = sizeof(sa);
int rc;
if ((rc = getpeername(sock, (struct sockaddr*)&sa, &sal)) == SOCKET_ERROR)
{
Socket_error("getpeername", sock);
return "unknown";
}
return Socket_getaddrname((struct sockaddr*)&sa, sock);
}
#if defined(Socket_TEST)
int main(int argc, char *argv[])
{
Socket_connect("127.0.0.1", 1883);
Socket_connect("localhost", 1883);
Socket_connect("loadsadsacalhost", 1883);
}
#endif
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#if !defined(SOCKET_H)
#define SOCKET_H
#include <sys/types.h>
#if defined(WIN32)
#include <winsock2.h>
#include <ws2tcpip.h>
#define MAXHOSTNAMELEN 256
#define EAGAIN WSAEWOULDBLOCK
#define EINTR WSAEINTR
#define EINPROGRESS WSAEINPROGRESS
#define EWOULDBLOCK WSAEWOULDBLOCK
#define ENOTCONN WSAENOTCONN
#define ECONNRESET WSAECONNRESET
#define ioctl ioctlsocket
#define socklen_t int
#else
#define INVALID_SOCKET SOCKET_ERROR
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#endif
/** socket operation completed successfully */
#define TCPSOCKET_COMPLETE 0
#if !defined(SOCKET_ERROR)
/** error in socket operation */
#define SOCKET_ERROR -1
#endif
/** must be the same as SOCKETBUFFER_INTERRUPTED */
#define TCPSOCKET_INTERRUPTED -2
#if !defined(INET6_ADDRSTRLEN)
#define INET6_ADDRSTRLEN 46 /** only needed for gcc/cygwin on windows */
#endif
#if !defined(max)
#define max(A,B) ( (A) > (B) ? (A):(B))
#endif
#include "LinkedList.h"
/*BE
def FD_SET
{
128 n8 "data"
}
def SOCKETS
{
FD_SET "rset"
FD_SET "rset_saved"
n32 dec "maxfdp1"
n32 ptr INTList "clientsds"
n32 ptr INTItem "cur_clientsds"
n32 ptr INTList "connect_pending"
n32 ptr INTList "write_pending"
FD_SET "pending_wset"
}
BE*/
/**
* Structure to hold all socket data for the module
*/
typedef struct
{
fd_set rset, /**< socket read set (see select doc) */
rset_saved; /**< saved socket read set */
int maxfdp1; /**< max descriptor used +1 (again see select doc) */
List* clientsds; /**< list of client socket descriptors */
ListElement* cur_clientsds; /**< current client socket descriptor (iterator) */
List* connect_pending; /**< list of sockets for which a connect is pending */
List* write_pending; /**< list of sockets for which a write is pending */
fd_set pending_wset; /**< socket pending write set for select */
} Sockets;
void Socket_outInitialize(void);
void Socket_outTerminate(void);
int Socket_getReadySocket(int more_work, struct timeval *tp);
int Socket_getch(int socket, char* c);
char *Socket_getdata(int socket, int bytes, int* actual_len);
int Socket_putdatas(int socket, char* buf0, int buf0len, int count, char** buffers, int* buflens);
void Socket_close(int socket);
int Socket_new(char* addr, int port, int* socket);
int Socket_noPendingWrites(int socket);
char* Socket_getpeer(int sock);
#endif /* SOCKET_H */
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
/**
* @file
* \brief Socket buffering related functions
*
* Some other related functions are in the Socket module
*/
#include "SocketBuffer.h"
#include "LinkedList.h"
#include "Log.h"
#include "Messages.h"
#include "StackTrace.h"
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include "Heap.h"
#if defined(WIN32)
#define iov_len len
#define iov_base buf
#endif
/**
* Default input queue buffer
*/
static socket_queue* def_queue;
/**
* List of queued input buffers
*/
static List* queues;
/**
* List of queued write buffers
*/
static List writes;
/**
* List callback function for comparing socket_queues by socket
* @param a first integer value
* @param b second integer value
* @return boolean indicating whether a and b are equal
*/
int socketcompare(void* a, void* b)
{
return ((socket_queue*)a)->socket == *(int*)b;
}
/**
* Create a new default queue when one has just been used.
*/
void SocketBuffer_newDefQ(void)
{
def_queue = malloc(sizeof(socket_queue));
def_queue->buflen = 1000;
def_queue->buf = malloc(def_queue->buflen);
def_queue->socket = def_queue->index = def_queue->buflen = def_queue->datalen = 0;
}
/**
* Initialize the socketBuffer module
*/
void SocketBuffer_initialize(void)
{
FUNC_ENTRY;
SocketBuffer_newDefQ();
queues = ListInitialize();
ListZero(&writes);
FUNC_EXIT;
}
/**
* Free the default queue memory
*/
void SocketBuffer_freeDefQ(void)
{
free(def_queue->buf);
free(def_queue);
}
/**
* Terminate the socketBuffer module
*/
void SocketBuffer_terminate(void)
{
ListElement* cur = NULL;
ListEmpty(&writes);
FUNC_ENTRY;
while (ListNextElement(queues, &cur))
free(((socket_queue*)(cur->content))->buf);
ListFree(queues);
SocketBuffer_freeDefQ();
FUNC_EXIT;
}
/**
* Cleanup any buffers for a specific socket
* @param socket the socket to clean up
*/
void SocketBuffer_cleanup(int socket)
{
FUNC_ENTRY;
if (ListFindItem(queues, &socket, socketcompare))
{
free(((socket_queue*)(queues->current->content))->buf);
ListRemove(queues, queues->current->content);
}
if (def_queue->socket == socket)
def_queue->socket = def_queue->index = def_queue->headerlen = def_queue->datalen = 0;
FUNC_EXIT;
}
/**
* Get any queued data for a specific socket
* @param socket the socket to get queued data for
* @param bytes the number of bytes of data to retrieve
* @param actual_len the actual length returned
* @return the actual data
*/
char* SocketBuffer_getQueuedData(int socket, int bytes, int* actual_len)
{
socket_queue* queue = NULL;
FUNC_ENTRY;
if (ListFindItem(queues, &socket, socketcompare))
{ /* if there is queued data for this socket, add any data read to it */
queue = (socket_queue*)(queues->current->content);
*actual_len = queue->datalen;
}
else
{
*actual_len = 0;
queue = def_queue;
}
if (bytes > queue->buflen)
{
if (queue->datalen > 0)
{
void* newmem = malloc(bytes);
memcpy(newmem, queue->buf, queue->datalen);
free(queue->buf);
queue->buf = newmem;
}
else
queue->buf = realloc(queue->buf, bytes);
queue->buflen = bytes;
}
FUNC_EXIT;
return queue->buf;
}
/**
* Get any queued character for a specific socket
* @param socket the socket to get queued data for
* @param c the character returned if any
* @return completion code
*/
int SocketBuffer_getQueuedChar(int socket, char* c)
{
int rc = SOCKETBUFFER_INTERRUPTED;
FUNC_ENTRY;
if (ListFindItem(queues, &socket, socketcompare))
{ /* if there is queued data for this socket, read that first */
socket_queue* queue = (socket_queue*)(queues->current->content);
if (queue->index < queue->headerlen)
{
*c = queue->fixed_header[(queue->index)++];
Log(TRACE_MAX, -1, "index is now %d, headerlen %d", queue->index, queue->headerlen);
rc = SOCKETBUFFER_COMPLETE;
goto exit;
}
else if (queue->index > 4)
{
Log(LOG_FATAL, -1, "header is already at full length");
rc = SOCKET_ERROR;
goto exit;
}
}
exit:
FUNC_EXIT_RC(rc);
return rc; /* there was no queued char if rc is SOCKETBUFFER_INTERRUPTED*/
}
/**
* A socket read was interrupted so we need to queue data
* @param socket the socket to get queued data for
* @param actual_len the actual length of data that was read
*/
void SocketBuffer_interrupted(int socket, int actual_len)
{
socket_queue* queue = NULL;
FUNC_ENTRY;
if (ListFindItem(queues, &socket, socketcompare))
queue = (socket_queue*)(queues->current->content);
else /* new saved queue */
{
queue = def_queue;
ListAppend(queues, def_queue, sizeof(socket_queue)+def_queue->buflen);
SocketBuffer_newDefQ();
}
queue->index = 0;
queue->datalen = actual_len;
FUNC_EXIT;
}
/**
* A socket read has now completed so we can get rid of the queue
* @param socket the socket for which the operation is now complete
* @return pointer to the default queue data
*/
char* SocketBuffer_complete(int socket)
{
FUNC_ENTRY;
if (ListFindItem(queues, &socket, socketcompare))
{
socket_queue* queue = (socket_queue*)(queues->current->content);
SocketBuffer_freeDefQ();
def_queue = queue;
ListDetach(queues, queue);
}
def_queue->socket = def_queue->index = def_queue->headerlen = def_queue->datalen = 0;
FUNC_EXIT;
return def_queue->buf;
}
/**
* A socket operation had now completed so we can get rid of the queue
* @param socket the socket for which the operation is now complete
* @param c the character to queue
*/
void SocketBuffer_queueChar(int socket, char c)
{
int error = 0;
socket_queue* curq = def_queue;
FUNC_ENTRY;
if (ListFindItem(queues, &socket, socketcompare))
curq = (socket_queue*)(queues->current->content);
else if (def_queue->socket == 0)
{
def_queue->socket = socket;
def_queue->index = def_queue->datalen = 0;
}
else if (def_queue->socket != socket)
{
Log(LOG_FATAL, -1, "attempt to reuse socket queue");
error = 1;
}
if (curq->index > 4)
{
Log(LOG_FATAL, -1, "socket queue fixed_header field full");
error = 1;
}
if (!error)
{
curq->fixed_header[(curq->index)++] = c;
curq->headerlen = curq->index;
}
Log(TRACE_MAX, -1, "queueChar: index is now %d, headerlen %d", curq->index, curq->headerlen);
FUNC_EXIT;
}
/**
* A socket write was interrupted so store the remaining data
* @param socket the socket for which the write was interrupted
* @param count the number of iovec buffers
* @param iovecs buffer array
* @param total total data length to be written
* @param bytes actual data length that was written
*/
void SocketBuffer_pendingWrite(int socket, int count, iobuf* iovecs, int total, int bytes)
{
int i = 0;
pending_writes* pw = NULL;
FUNC_ENTRY;
/* store the buffers until the whole packet is written */
pw = malloc(sizeof(pending_writes));
pw->socket = socket;
pw->bytes = bytes;
pw->total = total;
pw->count = count;
for (i = 0; i < count; i++)
pw->iovecs[i] = iovecs[i];
ListAppend(&writes, pw, sizeof(pw) + total);
FUNC_EXIT;
}
/**
* List callback function for comparing pending_writes by socket
* @param a first integer value
* @param b second integer value
* @return boolean indicating whether a and b are equal
*/
int pending_socketcompare(void* a, void* b)
{
return ((pending_writes*)a)->socket == *(int*)b;
}
/**
* Get any queued write data for a specific socket
* @param socket the socket to get queued data for
* @return pointer to the queued data or NULL
*/
pending_writes* SocketBuffer_getWrite(int socket)
{
ListElement* le = ListFindItem(&writes, &socket, pending_socketcompare);
return (le) ? (pending_writes*)(le->content) : NULL;
}
/**
* A socket write has now completed so we can get rid of the queue
* @param socket the socket for which the operation is now complete
* @return completion code, boolean - was the queue removed?
*/
int SocketBuffer_writeComplete(int socket)
{
return ListRemoveItem(&writes, &socket, pending_socketcompare);
}
/**
* Update the queued write data for a socket in the case of QoS 0 messages.
* @param socket the socket for which the operation is now complete
* @param topic the topic of the QoS 0 write
* @param payload the payload of the QoS 0 write
* @return pointer to the updated queued data structure, or NULL
*/
pending_writes* SocketBuffer_updateWrite(int socket, char* topic, char* payload)
{
pending_writes* pw = NULL;
ListElement* le = NULL;
FUNC_ENTRY;
if ((le = ListFindItem(&writes, &socket, pending_socketcompare)) != NULL)
{
pw = (pending_writes*)(le->content);
if (pw->count == 4)
{
pw->iovecs[2].iov_base = topic;
pw->iovecs[3].iov_base = payload;
}
}
FUNC_EXIT;
return pw;
}
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#if !defined(SOCKETBUFFER_H)
#define SOCKETBUFFER_H
#if defined(WIN32)
#include "winsock2.h"
#else
#include <sys/socket.h>
#endif
#if defined(WIN32)
typedef WSABUF iobuf;
#else
typedef struct iovec iobuf;
#endif
typedef struct
{
int socket;
int index, headerlen;
char fixed_header[5]; /**< header plus up to 4 length bytes */
int buflen, /**< total length of the buffer */
datalen; /**< current length of data in buf */
char* buf;
} socket_queue;
typedef struct
{
int socket, total, count;
unsigned long bytes;
iobuf iovecs[5];
} pending_writes;
#define SOCKETBUFFER_COMPLETE 0
#if !defined(SOCKET_ERROR)
#define SOCKET_ERROR -1
#endif
#define SOCKETBUFFER_INTERRUPTED -2 /* must be the same value as TCPSOCKET_INTERRUPTED */
void SocketBuffer_initialize(void);
void SocketBuffer_terminate(void);
void SocketBuffer_cleanup(int socket);
char* SocketBuffer_getQueuedData(int socket, int bytes, int* actual_len);
int SocketBuffer_getQueuedChar(int socket, char* c);
void SocketBuffer_interrupted(int socket, int actual_len);
char* SocketBuffer_complete(int socket);
void SocketBuffer_queueChar(int socket, char c);
void SocketBuffer_pendingWrite(int socket, int count, iobuf* iovecs, int total, int bytes);
pending_writes* SocketBuffer_getWrite(int socket);
int SocketBuffer_writeComplete(int socket);
pending_writes* SocketBuffer_updateWrite(int socket, char* topic, char* payload);
#endif
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#include "StackTrace.h"
#include "Log.h"
#include "LinkedList.h"
#include "Clients.h"
#include "Thread.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#if defined(WIN32)
#define snprintf _snprintf
#endif
/*BE
def STACKENTRY
{
n32 ptr STRING open "name"
n32 dec "line"
}
defList(STACKENTRY)
BE*/
#define MAX_STACK_DEPTH 50
#define MAX_FUNCTION_NAME_LENGTH 30
#define MAX_THREADS 255
typedef struct
{
unsigned long threadid;
char name[MAX_FUNCTION_NAME_LENGTH];
int line;
} stackEntry;
typedef struct
{
unsigned long id;
int maxdepth;
int current_depth;
stackEntry callstack[MAX_STACK_DEPTH];
} threadEntry;
#include "StackTrace.h"
static int thread_count = 0;
static threadEntry threads[MAX_THREADS];
static threadEntry *cur_thread = NULL;
#if defined(WIN32)
mutex_type stack_mutex;
#else
static pthread_mutex_t stack_mutex_store = PTHREAD_MUTEX_INITIALIZER;
static mutex_type stack_mutex = &stack_mutex_store;
#endif
int setStack(int create)
{
int i = -1;
thread_id_type curid = Thread_getid();
cur_thread = NULL;
for (i = 0; i < MAX_THREADS && i < thread_count; ++i)
{
if (threads[i].id == curid)
{
cur_thread = &threads[i];
break;
}
}
if (cur_thread == NULL && create && thread_count < MAX_THREADS)
{
cur_thread = &threads[thread_count];
cur_thread->id = curid;
cur_thread->maxdepth = 0;
cur_thread->current_depth = 0;
++thread_count;
}
return cur_thread != NULL; /* good == 1 */
}
void StackTrace_entry(const char* name, int line, int trace_level)
{
Thread_lock_mutex(stack_mutex);
if (!setStack(1))
goto exit;
if (trace_level != -1)
Log_stackTrace(trace_level, 9, cur_thread->id, cur_thread->current_depth, name, line, NULL);
strncpy(cur_thread->callstack[cur_thread->current_depth].name, name, sizeof(cur_thread->callstack[0].name)-1);
cur_thread->callstack[(cur_thread->current_depth)++].line = line;
if (cur_thread->current_depth > cur_thread->maxdepth)
cur_thread->maxdepth = cur_thread->current_depth;
if (cur_thread->current_depth >= MAX_STACK_DEPTH)
Log(LOG_FATAL, -1, "Max stack depth exceeded");
exit:
Thread_unlock_mutex(stack_mutex);
}
void StackTrace_exit(const char* name, int line, void* rc, int trace_level)
{
Thread_lock_mutex(stack_mutex);
if (!setStack(0))
goto exit;
if (--(cur_thread->current_depth) < 0)
Log(LOG_FATAL, -1, "Minimum stack depth exceeded for thread %lu", cur_thread->id);
if (strncmp(cur_thread->callstack[cur_thread->current_depth].name, name, sizeof(cur_thread->callstack[0].name)-1) != 0)
Log(LOG_FATAL, -1, "Stack mismatch. Entry:%s Exit:%s\n", cur_thread->callstack[cur_thread->current_depth].name, name);
if (trace_level != -1)
{
if (rc == NULL)
Log_stackTrace(trace_level, 10, cur_thread->id, cur_thread->current_depth, name, line, NULL);
else
Log_stackTrace(trace_level, 11, cur_thread->id, cur_thread->current_depth, name, line, (int*)rc);
}
exit:
Thread_unlock_mutex(stack_mutex);
}
void StackTrace_printStack(char* dest)
{
FILE* file = stdout;
int t = 0;
for (t = 0; t < thread_count; ++t)
{
threadEntry *cur_thread = &threads[t];
if (cur_thread->id > 0)
{
int i = cur_thread->current_depth - 1;
fprintf(file, "=========== Start of stack trace for thread %lu ==========\n", cur_thread->id);
if (i >= 0)
{
fprintf(file, "%s (%d)\n", cur_thread->callstack[i].name, cur_thread->callstack[i].line);
while (--i >= 0)
fprintf(file, " at %s (%d)\n", cur_thread->callstack[i].name, cur_thread->callstack[i].line);
}
fprintf(file, "=========== End of stack trace for thread %lu ==========\n\n", cur_thread->id);
}
}
if (file != stdout && file != stderr && file != NULL)
fclose(file);
}
char* StackTrace_get(unsigned long threadid)
{
int bufsize = 256;
char* buf = NULL;
int t = 0;
if ((buf = malloc(bufsize)) == NULL)
goto exit;
buf[0] = '\0';
for (t = 0; t < thread_count; ++t)
{
threadEntry *cur_thread = &threads[t];
if (cur_thread->id == threadid)
{
int i = cur_thread->current_depth - 1;
int curpos = 0;
if (i >= 0)
{
curpos += snprintf(&buf[curpos], bufsize - curpos -1,
"%s (%d)\n", cur_thread->callstack[i].name, cur_thread->callstack[i].line);
while (--i >= 0)
curpos += snprintf(&buf[curpos], bufsize - curpos -1,
" at %s (%d)\n", cur_thread->callstack[i].name, cur_thread->callstack[i].line);
if (buf[--curpos] == '\n')
buf[curpos] = '\0';
}
break;
}
}
exit:
return buf;
}
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#ifndef STACKTRACE_H_
#define STACKTRACE_H_
#include "Log.h"
#if defined(NOSTACKTRACE)
#define FUNC_ENTRY
#define FUNC_ENTRY_NOLOG
#define FUNC_ENTRY_MED
#define FUNC_ENTRY_MAX
#define FUNC_EXIT
#define FUNC_EXIT_NOLOG
#define FUNC_EXIT_MED
#define FUNC_EXIT_MAX
#define FUNC_EXIT_RC(x)
#define FUNC_EXIT_MED_RC(x)
#define FUNC_EXIT_MAX_RC(x)
#else
#if defined(WIN32)
#define inline __inline
#define FUNC_ENTRY StackTrace_entry(__FUNCTION__, __LINE__, TRACE_MINIMUM)
#define FUNC_ENTRY_NOLOG StackTrace_entry(__FUNCTION__, __LINE__, -1)
#define FUNC_ENTRY_MED StackTrace_entry(__FUNCTION__, __LINE__, TRACE_MEDIUM)
#define FUNC_ENTRY_MAX StackTrace_entry(__FUNCTION__, __LINE__, TRACE_MAXIMUM)
#define FUNC_EXIT StackTrace_exit(__FUNCTION__, __LINE__, NULL, TRACE_MINIMUM)
#define FUNC_EXIT_NOLOG StackTrace_exit(__FUNCTION__, __LINE__, -1)
#define FUNC_EXIT_MED StackTrace_exit(__FUNCTION__, __LINE__, NULL, TRACE_MEDIUM)
#define FUNC_EXIT_MAX StackTrace_exit(__FUNCTION__, __LINE__, NULL, TRACE_MAXIMUM)
#define FUNC_EXIT_RC(x) StackTrace_exit(__FUNCTION__, __LINE__, &x, TRACE_MINIMUM)
#define FUNC_EXIT_MED_RC(x) StackTrace_exit(__FUNCTION__, __LINE__, &x, TRACE_MEDIUM)
#define FUNC_EXIT_MAX_RC(x) StackTrace_exit(__FUNCTION__, __LINE__, &x, TRACE_MAXIMUM)
#else
#define FUNC_ENTRY StackTrace_entry(__func__, __LINE__, TRACE_MINIMUM)
#define FUNC_ENTRY_NOLOG StackTrace_entry(__func__, __LINE__, -1)
#define FUNC_ENTRY_MED StackTrace_entry(__func__, __LINE__, TRACE_MEDIUM)
#define FUNC_ENTRY_MAX StackTrace_entry(__func__, __LINE__, TRACE_MAXIMUM)
#define FUNC_EXIT StackTrace_exit(__func__, __LINE__, NULL, TRACE_MINIMUM)
#define FUNC_EXIT_NOLOG StackTrace_exit(__func__, __LINE__, NULL, -1)
#define FUNC_EXIT_MED StackTrace_exit(__func__, __LINE__, NULL, TRACE_MEDIUM)
#define FUNC_EXIT_MAX StackTrace_exit(__func__, __LINE__, NULL, TRACE_MAXIMUM)
#define FUNC_EXIT_RC(x) StackTrace_exit(__func__, __LINE__, &x, TRACE_MINIMUM)
#define FUNC_EXIT_MED_RC(x) StackTrace_exit(__func__, __LINE__, &x, TRACE_MEDIUM)
#define FUNC_EXIT_MAX_RC(x) StackTrace_exit(__func__, __LINE__, &x, TRACE_MAXIMUM)
#endif
#endif
void StackTrace_entry(const char* name, int line, int trace);
void StackTrace_exit(const char* name, int line, void* return_value, int trace);
void StackTrace_dumpStack(char* dest);
char* StackTrace_get(unsigned long);
#endif /* STACKTRACE_H_ */
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
/**
* @file
* \brief Threading related functions
*
* Used to create platform independent threading functions
*/
#include "Thread.h"
#if defined(THREAD_UNIT_TESTS)
#define NOSTACKTRACE
#endif
#include "StackTrace.h"
#undef malloc
#undef realloc
#undef free
#if !defined(WIN32)
#include <errno.h>
#include <unistd.h>
#include <sys/time.h>
#endif
#include <memory.h>
#include <stdlib.h>
/**
* Start a new thread
* @param fn the function to run, must be of the correct signature
* @param parameter pointer to the function parameter, can be NULL
* @return the new thread
*/
thread_type Thread_start(thread_fn fn, void* parameter)
{
#if defined(WIN32)
thread_type thread = NULL;
#else
thread_type thread = 0;
#endif
FUNC_ENTRY;
#if defined(WIN32)
thread = CreateThread(NULL, 0, fn, parameter, 0, NULL);
#else
if (pthread_create(&thread, NULL, fn, parameter) != 0)
thread = 0;
#endif
FUNC_EXIT;
return thread;
}
/**
* Create a new mutex
* @return the new mutex
*/
mutex_type Thread_create_mutex()
{
mutex_type mutex = NULL;
int rc = 0;
FUNC_ENTRY;
#if defined(WIN32)
mutex = CreateMutex(NULL, 0, NULL);
#else
mutex = malloc(sizeof(pthread_mutex_t));
rc = pthread_mutex_init(mutex, NULL);
#endif
FUNC_EXIT_RC(rc);
return mutex;
}
/**
* Lock a mutex which has already been created, block until ready
* @param mutex the mutex
* @return completion code
*/
int Thread_lock_mutex(mutex_type mutex)
{
int rc = -1;
/* don't add entry/exit trace points as the stack log uses mutexes - recursion beckons */
#if defined(WIN32)
if (WaitForSingleObject(mutex, INFINITE) != WAIT_FAILED)
#else
if ((rc = pthread_mutex_lock(mutex)) == 0)
#endif
rc = 0;
return rc;
}
/**
* Unlock a mutex which has already been locked
* @param mutex the mutex
* @return completion code
*/
int Thread_unlock_mutex(mutex_type mutex)
{
int rc = -1;
/* don't add entry/exit trace points as the stack log uses mutexes - recursion beckons */
#if defined(WIN32)
if (ReleaseMutex(mutex) != 0)
#else
if ((rc = pthread_mutex_unlock(mutex)) == 0)
#endif
rc = 0;
return rc;
}
/**
* Destroy a mutex which has already been created
* @param mutex the mutex
*/
void Thread_destroy_mutex(mutex_type mutex)
{
int rc = 0;
FUNC_ENTRY;
#if defined(WIN32)
rc = CloseHandle(mutex);
#else
rc = pthread_mutex_destroy(mutex);
free(mutex);
#endif
FUNC_EXIT_RC(rc);
}
/**
* Get the thread id of the thread from which this function is called
* @return thread id, type varying according to OS
*/
thread_id_type Thread_getid()
{
#if defined(WIN32)
return GetCurrentThreadId();
#else
return pthread_self();
#endif
}
/**
* Create a new semaphore
* @return the new condition variable
*/
sem_type Thread_create_sem()
{
sem_type sem = NULL;
int rc = 0;
FUNC_ENTRY;
#if defined(WIN32)
sem = CreateEvent(
NULL, // default security attributes
FALSE, // manual-reset event?
FALSE, // initial state is nonsignaled
NULL // object name
);
#else
sem = malloc(sizeof(sem_t));
rc = sem_init(sem, 0, 0);
#endif
FUNC_EXIT_RC(rc);
return sem;
}
/**
* Lock a mutex which has already been created, block until ready
* @param mutex the mutex
* @return completion code
*/
int Thread_wait_sem(sem_type sem)
{
/* sem_timedwait is the obvious call to use, but seemed not to work on the Viper,
* so I've used trywait in a loop instead. Ian Craggs 23/7/2010
*/
int rc = -1;
int timeout = 10; /* seconds */
#define USE_TRYWAIT
#if defined(USE_TRYWAIT)
int i = 0;
int interval = 10000;
int count = (1000000 / interval) * timeout;
#elif !defined(WIN32)
struct timespec ts;
#endif
FUNC_ENTRY;
#if defined(WIN32)
rc = WaitForSingleObject(sem, timeout*1000L);
#elif defined(USE_TRYWAIT)
while (++i < count && (rc = sem_trywait(sem)) != 0)
{
if (rc == -1 && ((rc = errno) != EAGAIN))
break;
usleep(interval); /* microseconds - .1 of a second */
}
#else
if (clock_gettime(CLOCK_REALTIME, &ts) != -1)
{
ts.tv_sec += timeout;
rc = sem_timedwait(sem, &ts);
}
#endif
FUNC_EXIT_RC(rc);
return rc;
}
int Thread_check_sem(sem_type sem)
{
#if defined(WIN32)
return WaitForSingleObject(sem, 0) == WAIT_OBJECT_0;
#else
int semval = -1;
sem_getvalue(sem, &semval);
return semval > 0;
#endif
}
/**
* Lock a mutex which has already been created, block until ready
* @param mutex the mutex
* @return completion code
*/
int Thread_post_sem(sem_type sem)
{
int rc = 0;
FUNC_ENTRY;
#if defined(WIN32)
if (SetEvent(sem) == 0)
rc = GetLastError();
#else
if (sem_post(sem) == -1)
rc = errno;
#endif
FUNC_EXIT_RC(rc);
return rc;
}
/**
* Destroy a semaphore which has already been created
* @param sem the semaphore
*/
int Thread_destroy_sem(sem_type sem)
{
int rc = 0;
FUNC_ENTRY;
#if defined(WIN32)
rc = CloseHandle(sem);
#else
rc = sem_destroy(sem);
free(sem);
#endif
FUNC_EXIT_RC(rc);
return rc;
}
#if defined(THREAD_UNIT_TESTS)
#include <stdio.h>
thread_return_type secondary(void* n)
{
int rc = 0;
/*
cond_type cond = n;
printf("Secondary thread about to wait\n");
rc = Thread_wait_cond(cond);
printf("Secondary thread returned from wait %d\n", rc);*/
sem_type sem = n;
printf("Secondary thread about to wait\n");
rc = Thread_wait_sem(sem);
printf("Secondary thread returned from wait %d\n", rc);
printf("Secondary thread about to wait\n");
rc = Thread_wait_sem(sem);
printf("Secondary thread returned from wait %d\n", rc);
printf("Secondary check sem %d\n", Thread_check_sem(sem));
return 0;
}
int main(int argc, char *argv[])
{
int rc = 0;
sem_type sem = Thread_create_sem();
printf("check sem %d\n", Thread_check_sem(sem));
printf("post secondary\n");
rc = Thread_post_sem(sem);
printf("posted secondary %d\n", rc);
printf("check sem %d\n", Thread_check_sem(sem));
printf("Starting secondary thread\n");
Thread_start(secondary, (void*)sem);
sleep(3);
printf("check sem %d\n", Thread_check_sem(sem));
printf("post secondary\n");
rc = Thread_post_sem(sem);
printf("posted secondary %d\n", rc);
sleep(3);
printf("Main thread ending\n");
}
#endif
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#if !defined(THREAD_H)
#define THREAD_H
#if defined(WIN32)
#include <Windows.h>
#define thread_type HANDLE
#define thread_id_type DWORD
#define thread_return_type DWORD
#define thread_fn LPTHREAD_START_ROUTINE
#define mutex_type HANDLE
#define cond_type HANDLE
#define sem_type HANDLE
#else
#include <pthread.h>
#include <semaphore.h>
#define thread_type pthread_t
#define thread_id_type pthread_t
#define thread_return_type void*
typedef thread_return_type (*thread_fn)(void*);
#define mutex_type pthread_mutex_t*
typedef struct { pthread_cond_t cond; pthread_mutex_t mutex; } cond_type_struct;
typedef cond_type_struct *cond_type;
typedef sem_t *sem_type;
#endif
thread_type Thread_start(thread_fn, void*);
mutex_type Thread_create_mutex();
int Thread_lock_mutex(mutex_type);
int Thread_unlock_mutex(mutex_type);
void Thread_destroy_mutex(mutex_type);
thread_id_type Thread_getid();
sem_type Thread_create_sem();
int Thread_wait_sem(sem_type sem);
int Thread_check_sem(sem_type sem);
int Thread_post_sem(sem_type sem);
int Thread_destroy_sem(sem_type sem);
#endif
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#include <stdlib.h>
#include <string.h>
#include "StackTrace.h"
#if !defined(ARRAY_SIZE)
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#endif
struct
{
int len;
struct
{
char lower;
char upper;
} bytes[4];
}
valid_ranges[] =
{
{1, { {00, 0x7F} } },
{2, { {0xC2, 0xDF}, {0x80, 0xBF} } },
{3, { {0xE0, 0xE0}, {0xA0, 0xBF}, {0x80, 0xBF} } },
{3, { {0xE1, 0xEC}, {0x80, 0xBF}, {0x80, 0xBF} } },
{3, { {0xED, 0xED}, {0x80, 0x9F}, {0x80, 0xBF} } },
{3, { {0xEE, 0xEF}, {0x80, 0xBF}, {0x80, 0xBF} } },
{4, { {0xF0, 0xF0}, {0x90, 0xBF}, {0x80, 0xBF}, {0x80, 0xBF} } },
{4, { {0xF1, 0xF3}, {0x80, 0xBF}, {0x80, 0xBF}, {0x80, 0xBF} } },
{4, { {0xF4, 0xF4}, {0x80, 0x8F}, {0x80, 0xBF}, {0x80, 0xBF} } },
};
char* UTF8_char_validate(int len, char* data)
{
int good = 0;
int charlen = 2;
int i, j;
char *rc = NULL;
FUNC_ENTRY;
/* first work out how many bytes this char is encoded in */
if ((data[0] & 128) == 0)
charlen = 1;
else if ((data[0] & 0xF0) == 0xF0)
charlen = 4;
else if ((data[0] & 0xE0) == 0xE0)
charlen = 3;
if (charlen > len)
goto exit; /* not enough characters in the string we were given */
for (i = 0; i < ARRAY_SIZE(valid_ranges); ++i)
{ /* just has to match one of these rows */
if (valid_ranges[i].len == charlen)
{
good = 1;
for (j = 0; j < charlen; ++j)
{
if (data[j] < valid_ranges[i].bytes[j].lower ||
data[j] > valid_ranges[i].bytes[j].upper)
{
good = 0; /* failed the check */
break;
}
}
if (good)
break;
}
}
if (good)
rc = data + charlen;
exit:
FUNC_EXIT;
return rc;
}
int UTF8_validate(int len, char* data)
{
char* curdata = NULL;
int rc = 0;
FUNC_ENTRY;
curdata = UTF8_char_validate(len, data);
while (curdata && (curdata < data + len))
curdata = UTF8_char_validate(len, curdata);
rc = curdata != NULL;
FUNC_EXIT_RC(rc);
return rc;
}
int UTF8_validateString(char* string)
{
int rc = 0;
FUNC_ENTRY;
rc = UTF8_validate(strlen(string), string);
FUNC_EXIT_RC(rc);
return rc;
}
#if defined(UNIT_TESTS)
#include <stdio.h>
typedef struct
{
int len;
char data[20];
} tests;
tests valid_strings[] =
{
{3, "hjk" },
{7, {0x41, 0xE2, 0x89, 0xA2, 0xCE, 0x91, 0x2E} },
{3, {'f', 0xC9, 0xB1 } },
{9, {0xED, 0x95, 0x9C, 0xEA, 0xB5, 0xAD, 0xEC, 0x96, 0xB4} },
{9, {0xE6, 0x97, 0xA5, 0xE6, 0x9C, 0xAC, 0xE8, 0xAA, 0x9E} },
{4, {0x2F, 0x2E, 0x2E, 0x2F} },
{7, {0xEF, 0xBB, 0xBF, 0xF0, 0xA3, 0x8E, 0xB4} },
};
tests invalid_strings[] =
{
{2, {0xC0, 0x80} },
{5, {0x2F, 0xC0, 0xAE, 0x2E, 0x2F} },
{6, {0xED, 0xA1, 0x8C, 0xED, 0xBE, 0xB4} },
{1, {0xF4} },
};
int main (int argc, char *argv[])
{
int i, failed = 0;
for (i = 0; i < ARRAY_SIZE(valid_strings); ++i)
{
if (!UTF8_validate(valid_strings[i].len, valid_strings[i].data))
{
printf("valid test %d failed\n", i);
failed = 1;
}
else
printf("valid test %d passed\n", i);
}
for (i = 0; i < ARRAY_SIZE(invalid_strings); ++i)
{
if (UTF8_validate(invalid_strings[i].len, invalid_strings[i].data))
{
printf("invalid test %d failed\n", i);
failed = 1;
}
else
printf("invalid test %d passed\n", i);
}
if (failed)
printf("Failed\n");
else
printf("Passed\n");
return 0;
} /* End of main function*/
#endif
/*******************************************************************************
* Copyright (c) 2009, 2012 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ian Craggs - initial API and implementation and/or initial documentation
*******************************************************************************/
#if !defined(UTF8_H)
#define UTF8_H
int UTF8_validate(int len, char* data);
int UTF8_validateString(char* string);
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment