summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore34
-rw-r--r--AUTHORS1
-rw-r--r--Doxyfile2426
-rw-r--r--LICENSE21
-rw-r--r--Makefile.am5
-rw-r--r--README109
-rwxr-xr-xautogen.sh3
-rw-r--r--configure.ac23
-rw-r--r--examples/.gitignore1
-rw-r--r--examples/Makefile.am11
-rw-r--r--examples/sandbox.c33
-rw-r--r--include/config.h46
-rw-r--r--include/ice.h129
-rw-r--r--include/peerconn.h140
-rw-r--r--include/runloop.h111
-rw-r--r--include/str.h34
-rw-r--r--include/stun.h134
-rw-r--r--include/urtc.h34
-rw-r--r--include/urtc/opts.h22
-rw-r--r--m4/.gitignore5
-rw-r--r--m4/ax_pthread.m4507
-rw-r--r--src/Makefile.am9
-rw-r--r--src/err.h58
-rw-r--r--src/g711.c40
-rw-r--r--src/g711.h68
-rw-r--r--src/g711_tables.c745
-rw-r--r--src/log.h83
-rw-r--r--src/mdns.c607
-rw-r--r--src/mdns.h43
-rw-r--r--src/prng.c47
-rw-r--r--src/prng.h31
-rw-r--r--src/sdp.c699
-rw-r--r--src/sdp.h150
-rw-r--r--src/urtc.c441
-rw-r--r--src/urtc.h223
-rw-r--r--src/uuid.c76
-rw-r--r--src/uuid.h54
-rw-r--r--tests/Makefile.am32
-rw-r--r--tests/g711_test.c53
-rw-r--r--tests/mdns_test.c66
-rw-r--r--tests/sdp_test.c328
-rw-r--r--tests/str_test.c31
-rw-r--r--tests/uuid_test.c45
43 files changed, 7758 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c16e3b2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,34 @@
+.deps/
+.libs/
+autom4te.cache/
+doxygen/
+*.dirstamp
+*.la
+*.lo
+*.o
+*.swp
+Makefile
+Makefile.in
+aclocal.m4
+ar-lib
+compile
+config.guess
+config.h.in
+config.h.in~
+config.h
+config.log
+config.status
+config.sub
+configure
+depcomp
+install-sh
+libtool
+ltmain.sh
+missing
+stamp-h1
+
+tests/*_test
+tests/*.trs
+tests/*.log
+test-driver
+test-suite.log
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..62e02f6
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1 @@
+liburtc is written and maintained by Chris Hiszpanski <chris@hiszpanski.name>.
diff --git a/Doxyfile b/Doxyfile
new file mode 100644
index 0000000..e43743f
--- /dev/null
+++ b/Doxyfile
@@ -0,0 +1,2426 @@
+# Doxyfile 1.8.12
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
+# for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME = "liburtc"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF = "Small footprint WebRTC stack for embedded devices"
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = doxygen
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF = "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.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH = src
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines.
+
+ALIASES =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
+# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
+# Fortran. In the later case the parser tries to guess whether the code is fixed
+# or free formatted code, this is the default for Fortran type files), VHDL. For
+# instance to make doxygen treat .inc files as Fortran files (default is PHP),
+# and .f files as C (default is Fortran), use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT = YES
+
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 0.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS = 0
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT = YES
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES = NO
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO, these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES, upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET = YES
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong or incomplete
+# parameter documentation, but not about the absence of documentation.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC = NO
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered.
+# The default value is: NO.
+
+WARN_AS_ERROR = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT = src/urtc.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. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: http://www.gnu.org/software/libiconv) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
+# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
+# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.
+
+FILE_PATTERNS = *.c \
+ *.h \
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH = examples
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see http://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX = YES
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE = 30
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT = 150
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP = 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.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: http://developer.apple.com/tools/xcode/), introduced with
+# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID = org.liburtc
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID = org.liburtc
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME = liburtc.org
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the master .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# http://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from http://www.mathjax.org before deployment.
+# The default value is: http://cdn.mathjax.org/mathjax/latest.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX = 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.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
+# string, for the replacement values of the other commands the user is referred
+# to HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES, to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP = 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 too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE =
+
+# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
+# with syntax highlighting in the RTF output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_SOURCE_CODE = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sf.net) file that captures the
+# structure of the code including all documentation. Note that this feature is
+# still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have 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. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
+
+HAVE_DOT = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+
+PLANTUML_JAR_PATH =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP = YES
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..eff0eb0
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright 2019-2021 Chris Hiszpanski
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..e935715
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,5 @@
+# custom m4 macros within m4/ subdirectory
+ACLOCAL_AMFLAGS = -I m4 --install
+
+# library source, examples, and unit tests
+SUBDIRS = src examples tests
diff --git a/README b/README
new file mode 100644
index 0000000..3a0833e
--- /dev/null
+++ b/README
@@ -0,0 +1,109 @@
+liburtc
+=======
+
+Real-time communication for microcontrollers
+
+The WebRTC reference implementation was designed for web browsers, namely
+Chromium. It requires specialized build tools, over 10GB of disk space for
+source code, and produces a binary with a large footprint. liburtc, on the
+other hand, is designed with the following goals in mind:
+
+* Small footprint. Under 100KB.
+* Portable. Implemented in C (C99) and works on nearly any architecture and
+ POSIX.1-2001 compliant operating system.
+* Easy to use. Straight-forward to compile and integrate into your code.
+* Standards compliant. Adheres to IETF specs to ensure interoperability.
+* Minimal. Only implements subset of protocols required for basic functionality
+ for common embedded applications, such as smart cameras.
+* Minimal dependencies. Only dependency is OpenSSL.
+
+
+Bootstrapping
+-------------
+
+If checking out this code via `git`, run `autoconf` to generate a `configure`
+script.
+
+
+Quickstart
+----------
+
+To compile the library and usage example from source, you will need autoconf,
+automake, and libtool. Use `autogen.sh` to create a `configure` script for
+configuring and making the library:
+
+ ./autogen.sh && ./configure && make
+
+To test the library:
+
+ make check
+
+Next, see `example/README.md` for running a demo.
+
+
+Features
+--------
+
+- Autotools based build process
+- POSIX.1-2001 compliant implementation
+
+/ sdp
+/ stun client
+- ice agent (state machine)
+ - aes cbc encipher / decipher (openssl)
+- v4l2
+- libx264
+- libopus
+- dtls (openssl?)
+- srtp
+
+- ANSI C compliant implementation (C99)
+- BSD sockets (winsock not supported)
+
+- Session Description Protocol (SDP)
+ - SHA-256 certificate fingerprint
+
+- Interactive Connectivity Establishment (ICE) agent
+ - STUN: RFC 5389 compliant client (no server capability)
+ - TURN: RFC 5766 compliant client (no server capability)
+
+- DTLS-SRTP transport
+ - RFC 5764 compliant implementation (using OpenSSL)
+
+- Video codecs:
+ - H.264
+ - Profiles: baseline, constrained baseline, main, high
+ - Levels: 3.1 and up
+ - H.265 not supported (few, if any, browsers support this)
+ - VP8/9 not supported (few embedded devices support this)
+
+- Video sources:
+ - Video4Linux2
+ - USB Video Class
+
+- Audio codecs:
+ - G.711
+ - Opus
+
+- Audio sources:
+ - ALSA
+ - PulseAudio
+
+
+Possible Future Improvements
+----------------------------
+
+- seamless handoff between network interfaces (e.g. cellular and wifi)
+
+Notes
+-----
+
+- A thread is used per peer connection. Each thread asynchronously handles
+ i/o events.
+
+
+Credits
+-------
+
+- Diego Elio Petteno (https://autotools.io) for the wonderful resource on
+ autoconf, automake, libtool, and other tools in the autotools suite.
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..9b6b39f
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+autoreconf -fiv
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..c6a74a3
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,23 @@
+AC_INIT([liburtc], [1.0.0], [chris@hiszpanski.name])
+
+# custom m4 macros within m4/ subdirectory
+AC_CONFIG_MACRO_DIR([m4])
+
+AM_INIT_AUTOMAKE([-Wall -Werror foreign no-dependencies subdir-objects])
+AC_PROG_CC
+AC_CONFIG_HEADERS([config.h])
+AC_CONFIG_LIBOBJ_DIR([src])
+
+# for pthread support on linux
+AX_PTHREAD(,[AC_MSG_ERROR([Could not configure pthreads support])])
+
+AM_PROG_AR
+LT_INIT
+
+AC_CONFIG_FILES([
+ Makefile
+ examples/Makefile
+ src/Makefile
+ tests/Makefile
+])
+AC_OUTPUT
diff --git a/examples/.gitignore b/examples/.gitignore
new file mode 100644
index 0000000..b04600a
--- /dev/null
+++ b/examples/.gitignore
@@ -0,0 +1 @@
+sandbox
diff --git a/examples/Makefile.am b/examples/Makefile.am
new file mode 100644
index 0000000..f2e28b1
--- /dev/null
+++ b/examples/Makefile.am
@@ -0,0 +1,11 @@
+# Builds a minimal example program
+#bin_PROGRAMS = example
+#example_CFLAGS = -I$(top_srcdir)/src/include $(LWS_CFLAGS)
+#example_LDADD = $(top_builddir)/src/liburtc.la $(LWS_LIBS)
+#example_SOURCES = server.c
+
+# Builds a minimal example program
+bin_PROGRAMS = sandbox
+sandbox_CFLAGS = -I$(top_srcdir)/src
+sandbox_LDADD = $(top_builddir)/src/liburtc.la
+sandbox_SOURCES = sandbox.c
diff --git a/examples/sandbox.c b/examples/sandbox.c
new file mode 100644
index 0000000..2f3728d
--- /dev/null
+++ b/examples/sandbox.c
@@ -0,0 +1,33 @@
+#include <signal.h>
+#include <unistd.h>
+
+#include "mdns.h"
+#include "urtc.h"
+
+
+int main() {
+ sigset_t ss;
+ int signal;
+
+ const char *stun[] = {
+ "stun.l.google.com:19302",
+ "stun2.l.google.com:19302",
+ NULL
+ };
+
+ sigemptyset(&ss);
+ sigaddset(&ss, SIGINT);
+ sigaddset(&ss, SIGTERM);
+ sigaddset(&ss, SIGQUIT);
+
+ urtc_peerconn_t *pc = urtc_peerconn_create(stun);
+
+ // query for raspberry.local
+ mdns_query("raspberry");
+
+ sigwait(&ss, &signal);
+
+ urtc_peerconn_destroy(pc);
+
+ return 0;
+}
diff --git a/include/config.h b/include/config.h
new file mode 100644
index 0000000..a003218
--- /dev/null
+++ b/include/config.h
@@ -0,0 +1,46 @@
+/*
+ * liburtc
+ * Copyright (C) 2019 Chris Hiszpanski
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef URTC_CONFIG_H
+#define URTC_CONFIG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "ice.h"
+
+/* Peer connection configuration */
+typedef struct urtc_config {
+ urtc_ice_server_t ice_servers[ICE_MAX_SERVERS];
+
+ // TODO video_source_t *vsrc;
+ // TODO video_sink_t *vsnk;
+
+ // TODO audio_source_t *asrc;
+ // TODO audio_sink_t *asnk;
+
+ // TODO data_source_t *dsrc;
+ // TODO data_sink_t *dsnk;
+} urtc_config_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* URTC_CONFIG_H */
diff --git a/include/ice.h b/include/ice.h
new file mode 100644
index 0000000..8b7251f
--- /dev/null
+++ b/include/ice.h
@@ -0,0 +1,129 @@
+/*
+ * liburtc
+ * Copyright (C) 2019 Chris Hiszpanski
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef URTC_ICE_H
+#define URTC_ICE_H
+
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <sys/select.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Maximum number of ICE bases. Each network interface address is a base. */
+#define ICE_MAX_BASES 16
+
+/* Maximum number of supported ICE (i.e. STUN or TURN) servers */
+#define ICE_MAX_SERVERS 16
+
+
+///////////////////////////// TYPE DEFINITIONS /////////////////////////////
+
+// STUN or TURN server
+typedef struct {
+ char *url;
+ char *username; /* ignored for stun urls */
+ char *credential; /* ignored for stun urls */
+} urtc_ice_server_t;
+
+// ICE base
+typedef struct {
+} ice_base_t;
+
+// ICE component
+typedef enum {
+ ICE_COMPONENT_NULL = 0,
+ ICE_COMPONENT_RTP,
+ ICE_COMPONENT_RTCP
+} ice_component_t;
+
+// ICE candidate
+typedef struct {
+ ice_component_t component;
+ uint16_t port;
+ uint32_t priority;
+} ice_candidate_t;
+
+typedef struct {
+ ice_candidate_t local;
+ ice_candidate_t remote;
+} ice_candidate_pair_t;
+
+// See https://tools.ietf.org/html/rfc8445#section-6.1.2.1
+typedef enum {
+ CHECKLIST_STATE_RUNNING = 0,
+ CHECKLIST_STATE_COMPLETED,
+ CHECKLIST_STATE_FAILED
+} ice_checklist_state_t;
+
+typedef struct {
+ ice_checklist_state_t state;
+} ice_checklist_t;
+
+// ICE agent object
+typedef struct {
+ pthread_t thread;
+ ice_checklist_t checklist;
+ ice_base_t bases[ICE_MAX_BASES];
+
+ int nfds;
+ fd_set readfds;
+ fd_set writefds;
+ fd_set errorfds;
+
+ struct timeval ta;
+
+ bool cancelled;
+} ice_agent_t;
+
+
+/////////////////////////// FORWARD DECLARATIONS ///////////////////////////
+
+/**
+ * Create new ICE agent
+ *
+ * Each agent creates a separate thread for processing network i/o events.
+ *
+ * \param agent[out] Pointer to agent object pointer
+ *
+ * \return 0 on success. Negative on error.
+ */
+int ice_agent_create(ice_agent_t **agent);
+
+/**
+ * Destroy ICE agent
+ *
+ * Stops agent thread (if running) and frees memory.
+ *
+ * \param agent[in] Pointer to agent object
+ *
+ * \return 0 on success. Negative on error.
+ */
+int ice_agent_destroy(ice_agent_t *agent);
+
+int ice_gather_host_candidates();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* URTC_ICE_H */
diff --git a/include/peerconn.h b/include/peerconn.h
new file mode 100644
index 0000000..feecc0e
--- /dev/null
+++ b/include/peerconn.h
@@ -0,0 +1,140 @@
+/*
+ * liburtc
+ * Copyright (C) 2019 Chris Hiszpanski
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef URTC_PEERCONN_H
+#define URTC_PEERCONN_H
+
+#include "config.h"
+#include "ice.h"
+#include "sdp.h"
+#include "runloop.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**************************** TYPE DEFINITIONS ****************************/
+
+// Event callback function type. Takes and returns void pointers.
+typedef void *(*event_callback_t)(void *arg);
+
+// Peer connection
+typedef struct urtc_peerconn {
+ uint8_t n;
+
+ // Interactive connectivity establishment agent
+ ice_agent_t *agent;
+
+ // Local and remote session descriptions
+ urtc_sdp_t *ldesc;
+ urtc_sdp_t *rdesc;
+
+ // Event callbacks
+ void (*onIceCandidate)(const char *cand, void *arg);
+ void (*onIceGatheringStateChange)(int state);
+ void (*onIceConnectionStateChange)(int state);
+ void (*onSignalingStateChange)(int state);
+ void (*onTrack)(int track);
+
+ // Run loop for event handling
+ runloop_t runloop;
+} urtc_peerconn_t;
+
+
+/************************** FORWARD DECLARATIONS **************************/
+
+/**
+ * Create a new peer connection.
+ *
+ * \param pc[out] New peer connection.
+ * \param cfg[in] Peer connection configuration.
+ *
+ * \return 0 on success. Negative on error.
+ */
+int urtc_peerconn_create(urtc_peerconn_t **pc, const urtc_config_t *cfg);
+
+/**
+ * Adds received ICE candidate to ICE agent.
+ *
+ * If `cand` is a NULL pointer or empty string, it signals that all remote
+ * candidates have been delivered (i.e. end-of-candidates).
+ *
+ * \param pc[in] Peer connection.
+ * \param cand[in] ICE candidate.
+ *
+ * \return 0 on success. Negative on error.
+ */
+int urtc_peerconn_add_ice_candidate(urtc_peerconn_t *pc, const char *cand);
+
+/**
+ * Set remote description.
+ *
+ * \param pc[in] Peer connection.
+ * \param desc[in] Session description protocol string.
+ *
+ * \return 0 on success. Negative on error.
+ */
+int urtc_peerconn_set_remote_description(urtc_peerconn_t *pc, const char *desc);
+
+/**
+ * Set local description.
+ *
+ * \param pc[in] Peer connection.
+ * \param desc[in] Session description protocol string.
+ *
+ * \return 0 on success. Negative on error.
+ */
+int urtc_peerconn_set_local_description(urtc_peerconn_t *pc, const char *desc);
+
+/**
+ * Create offer.
+ *
+ * \param pc[in] Peer connection.
+ * \param offer[out] Session description to send to remote peer. Caller must
+ * free.
+ *
+ * \return 0 on success. Negative on error.
+ */
+int urtc_peerconn_create_offer(urtc_peerconn_t *pc, char **offer);
+
+/**
+ * Create answer.
+ *
+ * \param pc[in] Peer connection
+ * \param answer[out] Session description to send to remote peer. Caller must
+ * free.
+ *
+ * \return 0 on success. Negative on error.
+ */
+int urtc_peerconn_create_answer(urtc_peerconn_t *pc, char **answer);
+
+/**
+ * Teardown a peer connection.
+ *
+ * \param pc[in] Peer connection.
+ *
+ * \return 0 on success. Negative on error.
+ */
+int urtc_peerconn_destroy(urtc_peerconn_t *pc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* URTC_PEERCONN_H */
diff --git a/include/runloop.h b/include/runloop.h
new file mode 100644
index 0000000..ea840e7
--- /dev/null
+++ b/include/runloop.h
@@ -0,0 +1,111 @@
+/*
+ * liburtc
+ * Copyright (C) 2019 Chris Hiszpanski
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file runloop.h
+ * Event loop
+ * A more elaborate file description.
+ */
+
+#ifndef URTC_RUNLOOP_H
+#define URTC_RUNLOOP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <poll.h>
+#include <pthread.h>
+#include <stdbool.h>
+
+typedef void *(*callback_t)(int fd, void *arg);
+
+typedef struct {
+ // File descriptor sets for poll().
+ struct pollfd *fds;
+ nfds_t nfds;
+
+ // Callback array. Index into array is file descriptor.
+ callback_t *callbacks;
+ void **args;
+
+ // Pipe for aborting blocked poll(). Allows dynamic adds/removes.
+ int abortpipe[2];
+ bool done;
+
+ // Run loop thread ID
+ pthread_t tid;
+} runloop_t;
+
+/**
+ * Create a new run loop
+ *
+ * Each run loop creates a separate thread which then repeatedly poll()s
+ * for events. Upon receiving an event, the run loop calls the respective
+ * file descriptor's callback function.
+ *
+ * @param[out] rl Pointer to run loop struct
+ */
+int urtc__runloop_create(runloop_t *rl);
+
+/**
+ * Add file descriptor to run loop
+ *
+ * @param rl Run loop
+ * @param fd File descriptor
+ * @param events Events to monitor (see struct pollfd)
+ * @param cb Callback to run on activity
+ * @param arg User-specified argument to pass to callback
+ *
+ * @return Zero on success. Negative on error.
+ */
+int urtc__runloop_add(
+ runloop_t *rl,
+ int fd,
+ short events,
+ void *(*cb)(int fd, void *arg),
+ void *arg
+);
+
+/**
+ * Remove a file descriptor from the run loop. No more events will be
+ * processed for this file descriptor.
+ *
+ * @param rl Run loop.
+ * @param fd File descriptor.
+ *
+ * @return Zero on success. Negative on error.
+ */
+int urtc__runloop_remove(runloop_t *rl, int fd);
+
+int urtc__runloop_join(runloop_t *rl);
+
+/**
+ * Destroy a run loop
+ *
+ * @param rl Pointer to run loop object
+ *
+ * @return Zero on success; negative on error. On error, errno is set.
+ */
+int urtc__runloop_destroy(runloop_t *rl);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* URTC_RUNLOOP_H */
diff --git a/include/str.h b/include/str.h
new file mode 100644
index 0000000..f0164d3
--- /dev/null
+++ b/include/str.h
@@ -0,0 +1,34 @@
+/*
+ * liburtc
+ * Copyright (C) 2019 Chris Hiszpanski
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef URTC_STR_H
+#define URTC_STR_H
+
+#include <stddef.h>
+
+typedef struct {
+ char *string;
+ size_t length;
+ size_t capacity;
+} str_t;
+
+str_t str_create_empty(size_t capacity);
+str_t str_append(const str_t str, const char *c_str);
+void str_destroy(str_t s);
+
+#endif
diff --git a/include/stun.h b/include/stun.h
new file mode 100644
index 0000000..126e7f5
--- /dev/null
+++ b/include/stun.h
@@ -0,0 +1,134 @@
+/*
+ * liburtc
+ * Copyright (C) 2019 Chris Hiszpanski
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef URTC_STUN_H
+#define URTC_STUN_H
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SIZEOF_STUN_HDR 20
+
+// STUN Classes
+#define STUN_CLASS_REQUEST 0
+#define STUN_CLASS_INDICTATION 1
+#define STUN_CLASS_SUCCESS 2
+#define STUN_CLASS_ERROR 3
+
+// STUN Methods
+#define STUN_METHOD_RESERVED 0
+#define STUN_METHOD_BINDING 1
+
+
+///////////////////////////// TYPE DEFINITIONS /////////////////////////////
+
+typedef struct {
+ int rc; // Retransmission count
+ int rto; // Retransmission timeout (in ms)
+ int sockfd; // Socket file descriptor
+} stun_client_t;
+
+typedef struct {
+ char *server;
+} stun_client_config_t;
+
+// STUN Message Header
+typedef struct __attribute__((__packed__)) {
+ uint16_t reserved:2; // Upper two bits are 0
+ uint16_t type:14; // 14-bit type
+ uint16_t msglen; // Message length
+ uint32_t cookie; // Magic cookie
+ uint8_t txid[12]; // Transaction ID
+} stun_msg_hdr_t;
+
+// STUN Attribute
+typedef struct {
+ uint16_t type; // Attribute type
+ uint16_t len; // Value length, prior to padding
+ uint8_t *val; // Pointer to attribute value
+} stun_attr_t;
+
+// STUN Attribute: MAPPED-ADDRESS
+typedef struct __attribute__((__packed__)) {
+ uint8_t reserved;
+ uint8_t family;
+ uint16_t port;
+ union {
+ uint8_t ipv4[4]; // IPv4 address is 32-bits
+ uint8_t ipv6[16]; // IPv6 address is 128-bits
+ };
+} stun_attr_mapped_address_t;
+
+// STUN Attribute: XOR-MAPPED-ADDRESS
+typedef stun_attr_mapped_address_t stun_attr_xor_mapped_address_t;
+
+// STUN Attribute: USERNAME
+
+
+////////////////////////////////// MACROS //////////////////////////////////
+
+// Get class from message header type
+#define CLASS(typ) (((typ >> 7) & 0x2) | ((typ >> 4) & 0x1))
+
+// Get method from message header type
+#define METHOD(typ) (((typ >> 2) & 0xF80) | ((typ >> 1) & 0x70) | (typ & 0xF))
+
+#define TYPE(cls, mthd) ( \
+ ((mthd & 0xF80) << 2) | \
+ ((cls & 0x2) << 7) | \
+ ((mthd & 0x70) << 1) | \
+ ((cls & 0x1) << 4) | \
+ (mthd & 0xF) \
+)
+
+
+/////////////////////////// FORWARD DECLARATIONS ///////////////////////////
+
+/**
+ * (callback) Binding request response handler
+ *
+ * @param[in] name
+ * @param[in] arg User argument
+ */
+typedef void (*stun_client_binding_response_callback)(
+ const char *name,
+ void *arg
+);
+
+/**
+ * Sends a STUN binding request to the specified server
+ *
+ * @param[in] srv Server (hostname or IP address)
+ * @param[in] cb Handler to call when a response is received
+ * @param[in] arg Optional user argument to pass to callback
+ */
+err_t stun_client_binding_request(
+ runloop_t *rl,
+ const char *srv,
+ stun_client_binding_response_callback cb,
+ void *arg
+);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* URTC_STUN_H */
diff --git a/include/urtc.h b/include/urtc.h
new file mode 100644
index 0000000..3eabba0
--- /dev/null
+++ b/include/urtc.h
@@ -0,0 +1,34 @@
+/*
+ * liburtc
+ * Copyright (C) 2019 Chris Hiszpanski
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef URTC_H
+#define URTC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "config.h"
+#include "err.h"
+#include "peerconn.h"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* URTC_H */
diff --git a/include/urtc/opts.h b/include/urtc/opts.h
new file mode 100644
index 0000000..7e5200b
--- /dev/null
+++ b/include/urtc/opts.h
@@ -0,0 +1,22 @@
+/*
+ * liburtc
+ * Copyright (C) 2019 Chris Hiszpanski
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef URTC_OPTS_H
+#define URTC_OPTS_H
+
+#endif /* URTC_OPTS_H */
diff --git a/m4/.gitignore b/m4/.gitignore
new file mode 100644
index 0000000..38066dd
--- /dev/null
+++ b/m4/.gitignore
@@ -0,0 +1,5 @@
+libtool.m4
+ltoptions.m4
+ltsugar.m4
+ltversion.m4
+lt~obsolete.m4
diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4
new file mode 100644
index 0000000..1598d07
--- /dev/null
+++ b/m4/ax_pthread.m4
@@ -0,0 +1,507 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_pthread.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]])
+#
+# DESCRIPTION
+#
+# This macro figures out how to build C programs using POSIX threads. It
+# sets the PTHREAD_LIBS output variable to the threads library and linker
+# flags, and the PTHREAD_CFLAGS output variable to any special C compiler
+# flags that are needed. (The user can also force certain compiler
+# flags/libs to be tested by setting these environment variables.)
+#
+# Also sets PTHREAD_CC to any special C compiler that is needed for
+# multi-threaded programs (defaults to the value of CC otherwise). (This
+# is necessary on AIX to use the special cc_r compiler alias.)
+#
+# NOTE: You are assumed to not only compile your program with these flags,
+# but also to link with them as well. For example, you might link with
+# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS
+#
+# If you are only building threaded programs, you may wish to use these
+# variables in your default LIBS, CFLAGS, and CC:
+#
+# LIBS="$PTHREAD_LIBS $LIBS"
+# CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+# CC="$PTHREAD_CC"
+#
+# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant
+# has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to
+# that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX).
+#
+# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the
+# PTHREAD_PRIO_INHERIT symbol is defined when compiling with
+# PTHREAD_CFLAGS.
+#
+# ACTION-IF-FOUND is a list of shell commands to run if a threads library
+# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it
+# is not found. If ACTION-IF-FOUND is not specified, the default action
+# will define HAVE_PTHREAD.
+#
+# Please let the authors know if this macro fails on any platform, or if
+# you have any other suggestions or comments. This macro was based on work
+# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help
+# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by
+# Alejandro Forero Cuervo to the autoconf macro repository. We are also
+# grateful for the helpful feedback of numerous users.
+#
+# Updated for Autoconf 2.68 by Daniel Richard G.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu>
+# Copyright (c) 2011 Daniel Richard G. <skunk@iSKUNK.ORG>
+# Copyright (c) 2019 Marc Stevens <marc.stevens@cwi.nl>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+# As a special exception, the respective Autoconf Macro's copyright owner
+# gives unlimited permission to copy, distribute and modify the configure
+# scripts that are the output of Autoconf when processing the Macro. You
+# need not follow the terms of the GNU General Public License when using
+# or distributing such scripts, even though portions of the text of the
+# Macro appear in them. The GNU General Public License (GPL) does govern
+# all other use of the material that constitutes the Autoconf Macro.
+#
+# This special exception to the GPL applies to versions of the Autoconf
+# Macro released by the Autoconf Archive. When you make and distribute a
+# modified version of the Autoconf Macro, you may extend this special
+# exception to the GPL to apply to your modified version as well.
+
+#serial 27
+
+AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD])
+AC_DEFUN([AX_PTHREAD], [
+AC_REQUIRE([AC_CANONICAL_HOST])
+AC_REQUIRE([AC_PROG_CC])
+AC_REQUIRE([AC_PROG_SED])
+AC_LANG_PUSH([C])
+ax_pthread_ok=no
+
+# We used to check for pthread.h first, but this fails if pthread.h
+# requires special compiler flags (e.g. on Tru64 or Sequent).
+# It gets checked for in the link test anyway.
+
+# First of all, check if the user has set any of the PTHREAD_LIBS,
+# etcetera environment variables, and if threads linking works using
+# them:
+if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then
+ ax_pthread_save_CC="$CC"
+ ax_pthread_save_CFLAGS="$CFLAGS"
+ ax_pthread_save_LIBS="$LIBS"
+ AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"])
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+ AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS])
+ AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes])
+ AC_MSG_RESULT([$ax_pthread_ok])
+ if test "x$ax_pthread_ok" = "xno"; then
+ PTHREAD_LIBS=""
+ PTHREAD_CFLAGS=""
+ fi
+ CC="$ax_pthread_save_CC"
+ CFLAGS="$ax_pthread_save_CFLAGS"
+ LIBS="$ax_pthread_save_LIBS"
+fi
+
+# We must check for the threads library under a number of different
+# names; the ordering is very important because some systems
+# (e.g. DEC) have both -lpthread and -lpthreads, where one of the
+# libraries is broken (non-POSIX).
+
+# Create a list of thread flags to try. Items with a "," contain both
+# C compiler flags (before ",") and linker flags (after ","). Other items
+# starting with a "-" are C compiler flags, and remaining items are
+# library names, except for "none" which indicates that we try without
+# any flags at all, and "pthread-config" which is a program returning
+# the flags for the Pth emulation library.
+
+ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config"
+
+# The ordering *is* (sometimes) important. Some notes on the
+# individual items follow:
+
+# pthreads: AIX (must check this before -lpthread)
+# none: in case threads are in libc; should be tried before -Kthread and
+# other compiler flags to prevent continual compiler warnings
+# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h)
+# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64
+# (Note: HP C rejects this with "bad form for `-t' option")
+# -pthreads: Solaris/gcc (Note: HP C also rejects)
+# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it
+# doesn't hurt to check since this sometimes defines pthreads and
+# -D_REENTRANT too), HP C (must be checked before -lpthread, which
+# is present but should not be used directly; and before -mthreads,
+# because the compiler interprets this as "-mt" + "-hreads")
+# -mthreads: Mingw32/gcc, Lynx/gcc
+# pthread: Linux, etcetera
+# --thread-safe: KAI C++
+# pthread-config: use pthread-config program (for GNU Pth library)
+
+case $host_os in
+
+ freebsd*)
+
+ # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able)
+ # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread)
+
+ ax_pthread_flags="-kthread lthread $ax_pthread_flags"
+ ;;
+
+ hpux*)
+
+ # From the cc(1) man page: "[-mt] Sets various -D flags to enable
+ # multi-threading and also sets -lpthread."
+
+ ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags"
+ ;;
+
+ openedition*)
+
+ # IBM z/OS requires a feature-test macro to be defined in order to
+ # enable POSIX threads at all, so give the user a hint if this is
+ # not set. (We don't define these ourselves, as they can affect
+ # other portions of the system API in unpredictable ways.)
+
+ AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING],
+ [
+# if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS)
+ AX_PTHREAD_ZOS_MISSING
+# endif
+ ],
+ [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])])
+ ;;
+
+ solaris*)
+
+ # On Solaris (at least, for some versions), libc contains stubbed
+ # (non-functional) versions of the pthreads routines, so link-based
+ # tests will erroneously succeed. (N.B.: The stubs are missing
+ # pthread_cleanup_push, or rather a function called by this macro,
+ # so we could check for that, but who knows whether they'll stub
+ # that too in a future libc.) So we'll check first for the
+ # standard Solaris way of linking pthreads (-mt -lpthread).
+
+ ax_pthread_flags="-mt,-lpthread pthread $ax_pthread_flags"
+ ;;
+esac
+
+# Are we compiling with Clang?
+
+AC_CACHE_CHECK([whether $CC is Clang],
+ [ax_cv_PTHREAD_CLANG],
+ [ax_cv_PTHREAD_CLANG=no
+ # Note that Autoconf sets GCC=yes for Clang as well as GCC
+ if test "x$GCC" = "xyes"; then
+ AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG],
+ [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */
+# if defined(__clang__) && defined(__llvm__)
+ AX_PTHREAD_CC_IS_CLANG
+# endif
+ ],
+ [ax_cv_PTHREAD_CLANG=yes])
+ fi
+ ])
+ax_pthread_clang="$ax_cv_PTHREAD_CLANG"
+
+
+# GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC)
+
+# Note that for GCC and Clang -pthread generally implies -lpthread,
+# except when -nostdlib is passed.
+# This is problematic using libtool to build C++ shared libraries with pthread:
+# [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25460
+# [2] https://bugzilla.redhat.com/show_bug.cgi?id=661333
+# [3] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=468555
+# To solve this, first try -pthread together with -lpthread for GCC
+
+AS_IF([test "x$GCC" = "xyes"],
+ [ax_pthread_flags="-pthread,-lpthread -pthread -pthreads $ax_pthread_flags"])
+
+# Clang takes -pthread (never supported any other flag), but we'll try with -lpthread first
+
+AS_IF([test "x$ax_pthread_clang" = "xyes"],
+ [ax_pthread_flags="-pthread,-lpthread -pthread"])
+
+
+# The presence of a feature test macro requesting re-entrant function
+# definitions is, on some systems, a strong hint that pthreads support is
+# correctly enabled
+
+case $host_os in
+ darwin* | hpux* | linux* | osf* | solaris*)
+ ax_pthread_check_macro="_REENTRANT"
+ ;;
+
+ aix*)
+ ax_pthread_check_macro="_THREAD_SAFE"
+ ;;
+
+ *)
+ ax_pthread_check_macro="--"
+ ;;
+esac
+AS_IF([test "x$ax_pthread_check_macro" = "x--"],
+ [ax_pthread_check_cond=0],
+ [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"])
+
+
+if test "x$ax_pthread_ok" = "xno"; then
+for ax_pthread_try_flag in $ax_pthread_flags; do
+
+ case $ax_pthread_try_flag in
+ none)
+ AC_MSG_CHECKING([whether pthreads work without any flags])
+ ;;
+
+ *,*)
+ PTHREAD_CFLAGS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\1/"`
+ PTHREAD_LIBS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\2/"`
+ AC_MSG_CHECKING([whether pthreads work with "$PTHREAD_CFLAGS" and "$PTHREAD_LIBS"])
+ ;;
+
+ -*)
+ AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag])
+ PTHREAD_CFLAGS="$ax_pthread_try_flag"
+ ;;
+
+ pthread-config)
+ AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no])
+ AS_IF([test "x$ax_pthread_config" = "xno"], [continue])
+ PTHREAD_CFLAGS="`pthread-config --cflags`"
+ PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`"
+ ;;
+
+ *)
+ AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag])
+ PTHREAD_LIBS="-l$ax_pthread_try_flag"
+ ;;
+ esac
+
+ ax_pthread_save_CFLAGS="$CFLAGS"
+ ax_pthread_save_LIBS="$LIBS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+
+ # Check for various functions. We must include pthread.h,
+ # since some functions may be macros. (On the Sequent, we
+ # need a special flag -Kthread to make this header compile.)
+ # We check for pthread_join because it is in -lpthread on IRIX
+ # while pthread_create is in libc. We check for pthread_attr_init
+ # due to DEC craziness with -lpthreads. We check for
+ # pthread_cleanup_push because it is one of the few pthread
+ # functions on Solaris that doesn't have a non-functional libc stub.
+ # We try pthread_create on general principles.
+
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>
+# if $ax_pthread_check_cond
+# error "$ax_pthread_check_macro must be defined"
+# endif
+ static void *some_global = NULL;
+ static void routine(void *a)
+ {
+ /* To avoid any unused-parameter or
+ unused-but-set-parameter warning. */
+ some_global = a;
+ }
+ static void *start_routine(void *a) { return a; }],
+ [pthread_t th; pthread_attr_t attr;
+ pthread_create(&th, 0, start_routine, 0);
+ pthread_join(th, 0);
+ pthread_attr_init(&attr);
+ pthread_cleanup_push(routine, 0);
+ pthread_cleanup_pop(0) /* ; */])],
+ [ax_pthread_ok=yes],
+ [])
+
+ CFLAGS="$ax_pthread_save_CFLAGS"
+ LIBS="$ax_pthread_save_LIBS"
+
+ AC_MSG_RESULT([$ax_pthread_ok])
+ AS_IF([test "x$ax_pthread_ok" = "xyes"], [break])
+
+ PTHREAD_LIBS=""
+ PTHREAD_CFLAGS=""
+done
+fi
+
+
+# Clang needs special handling, because older versions handle the -pthread
+# option in a rather... idiosyncratic way
+
+if test "x$ax_pthread_clang" = "xyes"; then
+
+ # Clang takes -pthread; it has never supported any other flag
+
+ # (Note 1: This will need to be revisited if a system that Clang
+ # supports has POSIX threads in a separate library. This tends not
+ # to be the way of modern systems, but it's conceivable.)
+
+ # (Note 2: On some systems, notably Darwin, -pthread is not needed
+ # to get POSIX threads support; the API is always present and
+ # active. We could reasonably leave PTHREAD_CFLAGS empty. But
+ # -pthread does define _REENTRANT, and while the Darwin headers
+ # ignore this macro, third-party headers might not.)
+
+ # However, older versions of Clang make a point of warning the user
+ # that, in an invocation where only linking and no compilation is
+ # taking place, the -pthread option has no effect ("argument unused
+ # during compilation"). They expect -pthread to be passed in only
+ # when source code is being compiled.
+ #
+ # Problem is, this is at odds with the way Automake and most other
+ # C build frameworks function, which is that the same flags used in
+ # compilation (CFLAGS) are also used in linking. Many systems
+ # supported by AX_PTHREAD require exactly this for POSIX threads
+ # support, and in fact it is often not straightforward to specify a
+ # flag that is used only in the compilation phase and not in
+ # linking. Such a scenario is extremely rare in practice.
+ #
+ # Even though use of the -pthread flag in linking would only print
+ # a warning, this can be a nuisance for well-run software projects
+ # that build with -Werror. So if the active version of Clang has
+ # this misfeature, we search for an option to squash it.
+
+ AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread],
+ [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG],
+ [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown
+ # Create an alternate version of $ac_link that compiles and
+ # links in two steps (.c -> .o, .o -> exe) instead of one
+ # (.c -> exe), because the warning occurs only in the second
+ # step
+ ax_pthread_save_ac_link="$ac_link"
+ ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g'
+ ax_pthread_link_step=`$as_echo "$ac_link" | sed "$ax_pthread_sed"`
+ ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)"
+ ax_pthread_save_CFLAGS="$CFLAGS"
+ for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do
+ AS_IF([test "x$ax_pthread_try" = "xunknown"], [break])
+ CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS"
+ ac_link="$ax_pthread_save_ac_link"
+ AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])],
+ [ac_link="$ax_pthread_2step_ac_link"
+ AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])],
+ [break])
+ ])
+ done
+ ac_link="$ax_pthread_save_ac_link"
+ CFLAGS="$ax_pthread_save_CFLAGS"
+ AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no])
+ ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try"
+ ])
+
+ case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in
+ no | unknown) ;;
+ *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;;
+ esac
+
+fi # $ax_pthread_clang = yes
+
+
+
+# Various other checks:
+if test "x$ax_pthread_ok" = "xyes"; then
+ ax_pthread_save_CFLAGS="$CFLAGS"
+ ax_pthread_save_LIBS="$LIBS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+
+ # Detect AIX lossage: JOINABLE attribute is called UNDETACHED.
+ AC_CACHE_CHECK([for joinable pthread attribute],
+ [ax_cv_PTHREAD_JOINABLE_ATTR],
+ [ax_cv_PTHREAD_JOINABLE_ATTR=unknown
+ for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>],
+ [int attr = $ax_pthread_attr; return attr /* ; */])],
+ [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break],
+ [])
+ done
+ ])
+ AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \
+ test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \
+ test "x$ax_pthread_joinable_attr_defined" != "xyes"],
+ [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE],
+ [$ax_cv_PTHREAD_JOINABLE_ATTR],
+ [Define to necessary symbol if this constant
+ uses a non-standard name on your system.])
+ ax_pthread_joinable_attr_defined=yes
+ ])
+
+ AC_CACHE_CHECK([whether more special flags are required for pthreads],
+ [ax_cv_PTHREAD_SPECIAL_FLAGS],
+ [ax_cv_PTHREAD_SPECIAL_FLAGS=no
+ case $host_os in
+ solaris*)
+ ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS"
+ ;;
+ esac
+ ])
+ AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \
+ test "x$ax_pthread_special_flags_added" != "xyes"],
+ [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS"
+ ax_pthread_special_flags_added=yes])
+
+ AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT],
+ [ax_cv_PTHREAD_PRIO_INHERIT],
+ [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <pthread.h>]],
+ [[int i = PTHREAD_PRIO_INHERIT;
+ return i;]])],
+ [ax_cv_PTHREAD_PRIO_INHERIT=yes],
+ [ax_cv_PTHREAD_PRIO_INHERIT=no])
+ ])
+ AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \
+ test "x$ax_pthread_prio_inherit_defined" != "xyes"],
+ [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.])
+ ax_pthread_prio_inherit_defined=yes
+ ])
+
+ CFLAGS="$ax_pthread_save_CFLAGS"
+ LIBS="$ax_pthread_save_LIBS"
+
+ # More AIX lossage: compile with *_r variant
+ if test "x$GCC" != "xyes"; then
+ case $host_os in
+ aix*)
+ AS_CASE(["x/$CC"],
+ [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6],
+ [#handle absolute path differently from PATH based program lookup
+ AS_CASE(["x$CC"],
+ [x/*],
+ [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])],
+ [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])])
+ ;;
+ esac
+ fi
+fi
+
+test -n "$PTHREAD_CC" || PTHREAD_CC="$CC"
+
+AC_SUBST([PTHREAD_LIBS])
+AC_SUBST([PTHREAD_CFLAGS])
+AC_SUBST([PTHREAD_CC])
+
+# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND:
+if test "x$ax_pthread_ok" = "xyes"; then
+ ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1])
+ :
+else
+ ax_pthread_ok=no
+ $2
+fi
+AC_LANG_POP
+])dnl AX_PTHREAD
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..5855857
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,9 @@
+lib_LTLIBRARIES = liburtc.la
+liburtc_la_SOURCES = g711.c g711_tables.c mdns.c prng.c sdp.c urtc.c \
+ uuid.c
+include_HEADERS = err.h g711.h mdns.h prng.h sdp.h urtc.h uuid.h
+
+# for pthreads support on linux
+liburtc_la_CFLAGS = $(PTHREAD_CFLAGS)
+liburtc_la_LDFLAGS = $(PTHREAD_LDFLAGS)
+liburtc_la_LIBADD = $(PTHREAD_LIBS)
diff --git a/src/err.h b/src/err.h
new file mode 100644
index 0000000..cd7fd19
--- /dev/null
+++ b/src/err.h
@@ -0,0 +1,58 @@
+/*
+ * liburtc
+ * Copyright 2020 Chris Hiszpanski
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef _URTC_ERR_H
+#define _URTC_ERR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum err {
+ URTC_SUCCESS = 0,
+
+ URTC_ERR,
+
+ URTC_ERR_BAD_ARGUMENT,
+ URTC_ERR_INSUFFICIENT_MEMORY,
+ URTC_ERR_MALFORMED,
+ URTC_ERR_NOT_IMPLEMENTED,
+
+ URTC_ERR_PEERCONN_MISSING_REMOTE_DESC,
+
+ URTC_ERR_SDP_MALFORMED,
+ URTC_ERR_SDP_MALFORMED_VERSION,
+ URTC_ERR_SDP_MALFORMED_ORIGIN,
+ URTC_ERR_SDP_MALFORMED_TIMING,
+ URTC_ERR_SDP_MALFORMED_MEDIA,
+ URTC_ERR_SDP_MALFORMED_ATTRIBUTE,
+ URTC_ERR_SDP_UNSUPPORTED_FINGERPRINT_ALGO,
+ URTC_ERR_SDP_UNSUPPORTED_MEDIA_PROTOCOL,
+ URTC_ERR_SDP_UNSUPPORTED_MEDIA_TYPE
+} err_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _URTC_ERR_H */
diff --git a/src/g711.c b/src/g711.c
new file mode 100644
index 0000000..47114d6
--- /dev/null
+++ b/src/g711.c
@@ -0,0 +1,40 @@
+/*
+ * liburtc
+ * Copyright (C) 2019 Chris Hiszpanski
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+
+#include "g711.h"
+
+void g711_encode(uint8_t *dst, const int16_t *src, size_t n) {
+ int i;
+
+ for (i = 0; i < n; i++) {
+ dst[i] = pcm2ulaw[0x1FFF & (src[i] >> 3)];
+ }
+}
+
+void g711_decode(int16_t *dst, const uint8_t *src, size_t n) {
+ int i;
+
+ for (i = 0; i < n; i++) {
+ fprintf(stderr, "i = %i, src[i] = %i\n", i, src[i]);
+ fprintf(stderr, "ulaw2pcm[i] = %i\n", ulaw2pcm[0]);
+// dst[i] = ulaw2pcm[src[i]] << 3;
+ dst[i] = 0;
+ }
+}
diff --git a/src/g711.h b/src/g711.h
new file mode 100644
index 0000000..b607734
--- /dev/null
+++ b/src/g711.h
@@ -0,0 +1,68 @@
+/*
+ * liburtc
+ * Copyright (C) 2019 Chris Hiszpanski
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef URTC_G711_H
+#define URTC_G711_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stddef.h>
+#include <stdint.h>
+
+extern const uint8_t pcm2ulaw[];
+extern const int16_t ulaw2pcm[];
+
+/**
+ * ITU-T G.711 compand a 16-bit signed PCM audio buffer. Buffer may contain
+ * multiple channels, in which case \a n must be the number of frames times
+ * the number of channels.
+ *
+ * Only the upper 13-bits are companded. The lowest 3-bits are discarded.
+ *
+ * \a dst must be allocated and point to at least (\a n / 2) bytes. \a dst
+ * and \a src may overlap.
+ *
+ * @param dst Destination buffer
+ * @param src Signed 16-bit sample source buffer
+ * @param n Number of samples (frames times channels)
+ */
+void g711_encode(uint8_t *dst, const int16_t *src, size_t n);
+
+/**
+ * ITU-T G.711 decompand to a 16-bit signed PCM audio buffer. Buffer may
+ * contain multiple channels, in which case \a n must be the number of frames
+ * times the number of channels.
+ *
+ * The decompanded samples are normalized to 16-bits (the lowest 3-bits will be
+ * zero).
+ *
+ * \a dst must NOT overlap with \a src.
+ *
+ * @param dst Destination buffer
+ * @param src Signed 16-bit sample source buffer
+ * @param n Number of samples (frames times channels)
+ */
+void g711_decode(int16_t *dst, const uint8_t *src, size_t n);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* URTC_G711_H */
diff --git a/src/g711_tables.c b/src/g711_tables.c
new file mode 100644
index 0000000..0690d1a
--- /dev/null
+++ b/src/g711_tables.c
@@ -0,0 +1,745 @@
+/*
+ * liburtc
+ * Copyright 2019 Chris Hiszpanski
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+
+#include "g711.h"
+
+/* Table for ITU-T G.711 mu-law companding from 16-bit signed PCM */
+const uint8_t pcm2ulaw[] = {
+ 0xff, 0xfe, 0xfe, 0xfd, 0xfd, 0xfc, 0xfc, 0xfb, 0xfb, 0xfa, 0xfa, 0xf9,
+ 0xf9, 0xf8, 0xf8, 0xf7, 0xf7, 0xf6, 0xf6, 0xf5, 0xf5, 0xf4, 0xf4, 0xf3,
+ 0xf3, 0xf2, 0xf2, 0xf1, 0xf1, 0xf0, 0xf0, 0xef, 0xef, 0xef, 0xef, 0xee,
+ 0xee, 0xee, 0xee, 0xed, 0xed, 0xed, 0xed, 0xec, 0xec, 0xec, 0xec, 0xeb,
+ 0xeb, 0xeb, 0xeb, 0xea, 0xea, 0xea, 0xea, 0xe9, 0xe9, 0xe9, 0xe9, 0xe8,
+ 0xe8, 0xe8, 0xe8, 0xe7, 0xe7, 0xe7, 0xe7, 0xe6, 0xe6, 0xe6, 0xe6, 0xe5,
+ 0xe5, 0xe5, 0xe5, 0xe4, 0xe4, 0xe4, 0xe4, 0xe3, 0xe3, 0xe3, 0xe3, 0xe2,
+ 0xe2, 0xe2, 0xe2, 0xe1, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf,
+ 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xde, 0xde, 0xde, 0xde, 0xde,
+ 0xde, 0xde, 0xde, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdc,
+ 0xdc, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb,
+ 0xdb, 0xdb, 0xdb, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xd9,
+ 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8,
+ 0xd8, 0xd8, 0xd8, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd6,
+ 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
+ 0xd5, 0xd5, 0xd5, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd3,
+ 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2,
+ 0xd2, 0xd2, 0xd2, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd0,
+ 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf,
+ 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xce,
+ 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce,
+ 0xce, 0xce, 0xce, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd,
+ 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc,
+ 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcb,
+ 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb,
+ 0xcb, 0xcb, 0xcb, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca,
+ 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9,
+ 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc8,
+ 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8,
+ 0xc8, 0xc8, 0xc8, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7,
+ 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+ 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc5,
+ 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5,
+ 0xc5, 0xc5, 0xc5, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4,
+ 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3,
+ 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc2,
+ 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2,
+ 0xc2, 0xc2, 0xc2, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1,
+ 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xbf,
+ 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf,
+ 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf,
+ 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe,
+ 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe,
+ 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe,
+ 0xbe, 0xbe, 0xbe, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd,
+ 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd,
+ 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbc,
+ 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc,
+ 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc,
+ 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
+ 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
+ 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
+ 0xbb, 0xbb, 0xbb, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba,
+ 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba,
+ 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xb9,
+ 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9,
+ 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9,
+ 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8,
+ 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8,
+ 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8,
+ 0xb8, 0xb8, 0xb8, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7,
+ 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7,
+ 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb6,
+ 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6,
+ 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6,
+ 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5,
+ 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5,
+ 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5,
+ 0xb5, 0xb5, 0xb5, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4,
+ 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4,
+ 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb3,
+ 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3,
+ 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3,
+ 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2,
+ 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2,
+ 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2,
+ 0xb2, 0xb2, 0xb2, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1,
+ 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1,
+ 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb0,
+ 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0,
+ 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0,
+ 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
+ 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
+ 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
+ 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
+ 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
+ 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xae,
+ 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae,
+ 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae,
+ 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae,
+ 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae,
+ 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae,
+ 0xae, 0xae, 0xae, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+ 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+ 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+ 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+ 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+ 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xac, 0xac, 0xac, 0xac, 0xac,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab,
+ 0xab, 0xab, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9,
+ 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9,
+ 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9,
+ 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9,
+ 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9,
+ 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8,
+ 0xa8, 0xa8, 0xa8, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7,
+ 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7,
+ 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7,
+ 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7,
+ 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7,
+ 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6,
+ 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6,
+ 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6,
+ 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6,
+ 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6,
+ 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+ 0xa5, 0xa5, 0xa5, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
+ 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3,
+ 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3,
+ 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3,
+ 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3,
+ 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3,
+ 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2,
+ 0xa2, 0xa2, 0xa2, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1,
+ 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
+ 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f,
+ 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e,
+ 0x9e, 0x9e, 0x9e, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d,
+ 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c,
+ 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+ 0x9b, 0x9b, 0x9b, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a,
+ 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
+ 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+ 0x98, 0x98, 0x98, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97,
+ 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96,
+ 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95,
+ 0x95, 0x95, 0x95, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93,
+ 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92,
+ 0x92, 0x92, 0x92, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91,
+ 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
+ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f,
+ 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+ 0x8e, 0x8e, 0x8e, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d,
+ 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c,
+ 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b,
+ 0x8b, 0x8b, 0x8b, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a,
+ 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89,
+ 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
+ 0x88, 0x88, 0x88, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87,
+ 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86,
+ 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+ 0x85, 0x85, 0x85, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,
+ 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83,
+ 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82,
+ 0x82, 0x82, 0x82, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
+ 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80
+};
+
+/* Table for ITU-T G.711 mu-law decompanding to 16-bit signed PCM */
+const int16_t ulaw2pcm[] = {
+ -32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956,
+ -23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764,
+ -15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412,
+ -11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316,
+ -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
+ -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
+ -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
+ -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
+ -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
+ -1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
+ -876, -844, -812, -780, -748, -716, -684, -652,
+ -620, -588, -556, -524, -492, -460, -428, -396,
+ -372, -356, -340, -324, -308, -292, -276, -260,
+ -244, -228, -212, -196, -180, -164, -148, -132,
+ -120, -112, -104, -96, -88, -80, -72, -64,
+ -56, -48, -40, -32, -24, -16, -8, 0,
+
+ 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
+ 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
+ 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
+ 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316,
+ 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140,
+ 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092,
+ 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004,
+ 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980,
+ 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436,
+ 1372, 1308, 1244, 1180, 1116, 1052, 988, 924,
+ 876, 844, 812, 780, 748, 716, 684, 652,
+ 620, 588, 556, 524, 492, 460, 428, 396,
+ 372, 356, 340, 324, 308, 292, 276, 260,
+ 244, 228, 212, 196, 180, 164, 148, 132,
+ 120, 112, 104, 96, 88, 80, 72, 64,
+ 56, 48, 40, 32, 24, 16, 8, 0
+};
diff --git a/src/log.h b/src/log.h
new file mode 100644
index 0000000..0798200
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,83 @@
+/**
+ * liburtc
+ * Copyright 2020 Chris Hiszpanski
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef _URTC_LOG_H
+#define _URTC_LOG_H
+
+#include <stdio.h>
+
+#ifdef _cplusplus
+extern "C" {
+#endif
+
+enum level {
+ TRACE = 0,
+ DEBUG,
+ INFO,
+ WARN,
+ ERROR,
+ FATAL,
+ NUM_LEVELS // must be last
+};
+
+static const char *logl[NUM_LEVELS] = {
+ "\033[0;37m", // trace (gray)
+ "\033[0;32m", // debug (green)
+ "", // info (white)
+ "\033[0;33m", // warn (yellow)
+ "\033[0;35m", // error (magenta)
+ "\033[0;31m" // fatal (red)
+};
+
+static const char *logn[NUM_LEVELS] = {
+ "[trace] ", // trace (gray)
+ "[debug] ", // debug (green)
+ "[info] ", // info (white)
+ "[warn] ", // warn (yellow)
+ "[error] ", // error (magenta)
+ "[fatal] " // fatal (red)
+};
+
+static const char *logr[NUM_LEVELS] = {
+ "\033[0m\n", // trace
+ "\033[0m\n", // debug
+ "\n", // info
+ "\033[0m\n", // warn
+ "\033[0m\n", // error
+ "\033[0m\n" // fatal
+};
+
+#define STRINGIFY(x) #x
+#define TOSTRING(x) STRINGIFY(x)
+#define AT __FILE__ ":" TOSTRING(__LINE__)
+
+#define log(lvl, format, ...) \
+ do { \
+ fprintf(stderr, "%s" AT " %s" format "%s", logl[lvl], logn[lvl], ##__VA_ARGS__, logr[lvl]); \
+ } while (0);
+
+#ifdef _cplusplus
+}
+#endif
+
+#endif // _URTC_LOG_H
diff --git a/src/mdns.c b/src/mdns.c
new file mode 100644
index 0000000..fa782e4
--- /dev/null
+++ b/src/mdns.c
@@ -0,0 +1,607 @@
+/**
+ * liburtc
+ * Copyright 2020 Chris Hiszpanski
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <net/if.h> // IFF_LOOPBACK (include pre ifaddrs.h)
+#include <ifaddrs.h> // getifaddrs
+#include <limits.h> // HOST_NAME_MAX
+#include <stdbool.h>
+#include <string.h> // memcpy
+#include <unistd.h> // sleep
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#ifdef WITH_IPV6
+ #include <netinet6/in6.h>
+#endif
+#include <sys/socket.h> // sendto, setsocketopt, socket
+
+#include "err.h"
+#include "log.h"
+#include "mdns.h"
+#include "uuid.h"
+
+#define MDNS_ADDR "224.0.0.251"
+#define MDNS_PORT 5353
+
+#define DEFAULT_TTL 120 // time-to-live recommended by RFC 6762
+
+#define CLASS_INTERNET 1
+#define CACHE_FLUSH (1 << 15)
+
+#define FLAG_RESPONSE (1 << 15)
+#define FLAG_OPCODE (0xF << 11)
+
+#ifndef HOST_NAME_MAX
+ #define HOST_NAME_MAX 255
+#endif
+
+struct __attribute__((__packed__)) header {
+ uint16_t id;
+ uint16_t flags;
+ uint16_t n_question; // number of question records
+ uint16_t n_answer; // number of answer records
+ uint16_t n_authority; // number of authority records
+ uint16_t n_additional; // number of additional records
+};
+
+struct __attribute__((__packed__)) qname {
+ uint8_t size;
+ uint8_t name[];
+};
+
+struct __attribute__((__packed__)) answer {
+ // variable length name precedes answer
+ uint16_t type;
+ uint16_t class;
+ uint32_t ttl;
+ uint16_t size;
+ uint8_t data[];
+};
+
+/**
+ * Subscribe to mDNS queries
+ *
+ * Opens new multicast socket on the mDNS port. This socket receives and can
+ * be used to send mDNS datagrams. Close with mdns_unsubscribe().
+ *
+ * \return Socket file descriptor on success, negative on error.
+ */
+int mdns_subscribe() {
+ // create socket
+ int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (-1 == sockfd) {
+ log(ERROR, "%s", strerror(errno));
+ goto _fail_create_socket;
+ }
+
+ // enable address/port reuse
+ {
+ const unsigned int opt = 1;
+ if (-1 == setsockopt(
+ sockfd,
+ SOL_SOCKET,
+#if defined(__APPLE__) || defined(__FreeBSD__)
+ SO_REUSEPORT,
+#else
+ SO_REUSEADDR,
+#endif
+ &opt,
+ sizeof(opt)
+ )) {
+ log(ERROR, "%s", strerror(errno));
+ goto _fail_enable_addr_reuse;
+ }
+ }
+
+ // bind socket to mDNS query port
+ {
+ const struct sockaddr_in mgroup = {
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = htonl(INADDR_ANY),
+ .sin_port = htons(MDNS_PORT)
+ };
+ if (-1 == bind(sockfd, (struct sockaddr *)(&mgroup), sizeof(mgroup))) {
+ log(ERROR, "%s", strerror(errno));
+ goto _fail_bind;
+ }
+ }
+
+ // disable loopback
+ {
+ const int opt = 0;
+ if (-1 == setsockopt(
+ sockfd,
+ IPPROTO_IP,
+ IP_MULTICAST_LOOP,
+ &opt,
+ sizeof(opt)
+ )) {
+ log(ERROR, "setsockopt: %s", strerror(errno));
+ goto _fail_disable_loopback;
+ }
+ }
+
+ // join multicast group
+ {
+ const struct ip_mreqn imr = {
+ .imr_multiaddr.s_addr = inet_addr(MDNS_ADDR),
+ .imr_address.s_addr = htonl(INADDR_ANY)
+ };
+ if (-1 == setsockopt(
+ sockfd,
+ IPPROTO_IP,
+ IP_ADD_MEMBERSHIP,
+ &imr,
+ sizeof(imr)
+ )) {
+ log(ERROR, "setsockopt: %s", strerror(errno));
+ goto _fail_join_multicast_group;
+ }
+ }
+
+ return sockfd;
+
+_fail_join_multicast_group:
+_fail_disable_loopback:
+_fail_bind:
+_fail_enable_addr_reuse:
+ close(sockfd);
+_fail_create_socket:
+
+ return -URTC_ERR;
+}
+
+/**
+ * Unsubscribe from mDNS queries
+ *
+ * Close multicast socket previously opened with mdns_subscribe().
+ *
+ * \param sockfd Socket file descriptor returned by mdns_subscribe().
+ *
+ * \return 0 on success, negative on error.
+ */
+int mdns_unsubscribe(int sockfd) {
+ if (-1 == close(sockfd)) {
+ log(ERROR, "close: %s", strerror(errno));
+ return -URTC_ERR;
+ }
+
+ return 0;
+}
+
+/**
+ * Send a multicast DNS query
+ *
+ * \param name Hostname to query. Must end with ".local".
+ *
+ * \return 0 on success, negative on error.
+ */
+int mdns_query(const char *name) {
+ // create socket
+ int msockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (-1 == msockfd) {
+ log(FATAL, "socket: %s", strerror(errno));
+ }
+
+ uint8_t q[512];
+ size_t qlen = 0;
+
+ // header
+ struct header hdr = {
+ .n_question = htons(1)
+ };
+ memcpy(&q[qlen], &hdr, sizeof(hdr));
+ qlen += sizeof(hdr);
+
+ // qname
+ q[qlen] = strlen(name);
+ memcpy(&q[1 + qlen], name, strlen(name));
+ qlen += 1 + q[qlen];
+
+ q[qlen] = strlen("local");
+ memcpy(&q[1 + qlen], "local", strlen("local"));
+ qlen += 1 + q[qlen];
+
+ q[qlen++] = 0; // root record
+
+ // qtype: 255 = any
+ q[qlen++] = 0;
+ q[qlen++] = 255;
+
+ // qclass: 1 = ipv4
+ q[qlen++] = 0x80; // unicast response please
+ q[qlen++] = 1;
+
+ // send query
+ struct sockaddr_in mgroup = {
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = inet_addr(MDNS_ADDR),
+ .sin_port = htons(MDNS_PORT)
+ };
+ if (-1 == sendto(msockfd, q, qlen, 0, (struct sockaddr *)(&mgroup), sizeof(mgroup))) {
+ log(FATAL, "sendto: %s", strerror(errno));
+ }
+
+ return 0;
+}
+
+/**
+ * Simple mDNS response parser (single record only)
+ *
+ * \param response mDNS response packet
+ * \param len Length of response, in bytes
+ *
+ * \return 0 on success, negative on error.
+ */
+int mdns_parse_response(const void *response, size_t len)
+{
+ // sanity checks
+ if (!response) return -URTC_ERR_BAD_ARGUMENT;
+ if (len < sizeof(struct header)) return -URTC_ERR_MALFORMED;
+
+ struct header *hdr = (struct header *)(response);
+ response += sizeof(struct header);
+ len -= sizeof(struct header);
+
+ // transaction id must be zero
+ if (0 != hdr->id) return -URTC_ERR_MALFORMED;
+
+ // flags must indicate a response
+ if (!(ntohs(hdr->flags) & FLAG_RESPONSE)) return -URTC_ERR_MALFORMED;
+
+ // sent single question, expecting reponse for single question
+ if (1 != ntohs(hdr->n_question)) return -URTC_ERR_MALFORMED;
+
+ // parse query names
+ struct qname *qname;
+ do {
+ if (len < 1) return -URTC_ERR_MALFORMED;
+ qname = (struct qname *)(response);
+ if (len < 1 + qname->size) return -URTC_ERR_MALFORMED;
+ response += 1 + qname->size;
+ len -= 1 + qname->size;
+ } while (qname->size);
+
+ // parse query type (ignore)
+ if (len < sizeof(uint16_t)) return -URTC_ERR_MALFORMED;
+ response += sizeof(uint16_t);
+ len -= sizeof(uint16_t);
+
+ // parse query class (ignore)
+ if (len < sizeof(uint16_t)) return -URTC_ERR_MALFORMED;
+ response += sizeof(uint16_t);
+ len -= sizeof(uint16_t);
+
+ // answers
+ for (int i = 0; i < ntohs(hdr->n_answer); i++) {
+ uint16_t size;
+ uint8_t namelen;
+
+ // parse name (assume matches)
+ if (len < 2) return -URTC_ERR_MALFORMED;
+ namelen = *(uint8_t *)response;
+ switch (namelen) {
+ // name compression
+ case 0xc0:
+ response += 2;
+ len -= 2;
+ break;
+ // no name compression
+ default:
+ response += 1 + namelen;
+ len -= 1 + namelen;
+ break;
+ }
+
+ if (len < sizeof(struct answer)) return -URTC_ERR_MALFORMED;
+ struct answer *answer = (struct answer *)response;
+ response += sizeof(struct answer);
+ len -= sizeof(struct answer);
+
+ size = ntohs(answer->size);
+ if (len < size) return -URTC_ERR_MALFORMED;
+ response += size;
+ len -= size;
+ }
+
+ return 0;
+}
+
+/**
+ * Check if run-length encoded query name resource records match hostname
+ *
+ * \param expected Expected hostname record.
+ * \param q Length-encoded query names.
+ * \param len Pointer to minimum length of q. Overwritten with actual length.
+ *
+ * \return Positive on match, 0 on no match, negative on malformed query.
+ */
+static int match(const char *expected, const uint8_t *q, int *len) {
+ struct qname *qname;
+ bool matches;
+ int n_records;
+ int n;
+
+ if (!q) return -URTC_ERR_BAD_ARGUMENT;
+ if (!expected) return -URTC_ERR_BAD_ARGUMENT;
+ if (!len) return -URTC_ERR_BAD_ARGUMENT;
+
+ n = *len;
+ matches = true;
+
+ n_records = 0;
+ do {
+ // minimum size check: a single root record (1 byte)
+ if (n < 1) return -URTC_ERR_MALFORMED;
+ qname = (struct qname *)(q);
+
+ // minimum size check: length byte plus stated length
+ if (n < 1 + qname->size) return -URTC_ERR_MALFORMED;
+
+ // match records
+ switch (n_records) {
+ // match hostname record
+ case 0:
+ if (qname->size == strlen(expected)) {
+ if (0 == memcmp(qname->name, expected, qname->size)) {
+ break; // matched
+ }
+ }
+ matches = false;
+ break;
+ // match "local" record
+ case 1:
+ if (qname->size == sizeof("local")-1) {
+ if (0 == memcmp(qname->name, "local", sizeof("local")-1)) {
+ break; //matched
+ }
+ }
+ matches = false;
+ break;
+ // match root record
+ case 2:
+ if (qname->size == 0) {
+ break;
+ }
+ matches = false;
+ break;
+ // match ancillary records
+ default:
+ matches = false;
+ break;
+ }
+
+ q += 1 + qname->size;
+ n -= 1 + qname->size;
+ n_records++;
+ } while(qname->size);
+
+ // update with actual length of run-length encoding
+ *len -= n;
+
+ return matches;
+}
+
+/**
+ * Validate whether mDNS packet is a query for the specified hostname
+ *
+ * Only standard, single question queries are supported. All others
+ * return an error.
+ *
+ * \param q Query buffer (from network)
+ * \param n Size of query (in bytes)
+ * \param hostname Queried hostname
+ *
+ * \return 0 on success, negative on error
+ */
+int mdns_validate_query(const uint8_t *q, size_t n, const char *hostname)
+{
+ const struct header *hdr = (struct header *)q;
+ const uint8_t *qi = q;
+ const size_t ni = n;
+ int found;
+
+ found = 0;
+
+ // sanity checks
+ if (!q) return -URTC_ERR_BAD_ARGUMENT;
+ if (n < sizeof(struct header)) return -URTC_ERR_MALFORMED;
+
+ q += sizeof(struct header);
+ n -= sizeof(struct header);
+
+ // transaction id must be zero
+ // note: hdr in host byte order (0 is byte order agnostic)
+ if (0 != hdr->id) return -URTC_ERR_MALFORMED;
+
+ // only support standard, non-truncated, non-recursive queries
+ // note: hdr in host byte order (non-0 is byte order agnostic)
+ if (hdr->flags) return -URTC_ERR_NOT_IMPLEMENTED;
+
+ // parse questions
+ for (int j = 0; j < ntohs(hdr->n_question); j++) {
+ bool matched;
+ int ret;
+ int len;
+
+ // upper two bits set? "compressed" records
+ matched = false;
+ if (n > 2 && q[0] >> 6 == 3) {
+ // compressed (i.e. offset to previous record)
+ const uint16_t offset = ((q[0] & 0x3F) << 8) | q[1];
+ if (offset > ni) return -URTC_ERR_MALFORMED;
+ len = ni - offset;
+ ret = match(hostname, qi + offset, &len);
+ q += 2;
+ n -= 2;
+ } else {
+ // uncompressed
+ len = n;
+ ret = match(hostname, q, &len);
+ q += len;
+ n -= len;
+ }
+ if (ret < 0) return ret;
+ if (ret > 0) matched = true;
+
+ // parse query type for A and AAAA records
+ if (n < sizeof(uint16_t)) return -URTC_ERR_MALFORMED;
+ if (matched) {
+ uint16_t type = ntohs(*(uint16_t *)(q));
+ if (TYPE_A == type || TYPE_AAAA == type) {
+ found |= type;
+ }
+ }
+ q += sizeof(uint16_t);
+ n -= sizeof(uint16_t);
+
+ // parse query class (ignore)
+ if (n < sizeof(uint16_t)) return -URTC_ERR_MALFORMED;
+ q += sizeof(uint16_t);
+ n -= sizeof(uint16_t);
+ }
+
+ return found;
+}
+
+/**
+ * Send mDNS response
+ *
+ * \param hostname NULL-terminated hostname (sans ".local" suffix)
+ *
+ * \return 0 on success, negative on error
+ */
+int mdns_send_response(int sockfd, const char *hostname) {
+ struct ifaddrs *ifaddrs;
+ struct header *hdr;
+ uint8_t *wr; // writer (convenience pointer)
+ uint8_t response[
+ sizeof(struct header) +
+ 1 + HOST_NAME_MAX + 1 + // max size of length and host record
+ 1 + sizeof(".local") + // size of length and local record
+ 1 + // size of root record
+ sizeof(struct answer) +
+ 16 // size of AAAA record (largest record)
+ ];
+ hdr = (struct header *)(response);
+ wr = response;
+
+ // write header
+ *hdr = (struct header){
+ .id = 0,
+ .flags = htons(FLAG_RESPONSE),
+ .n_question = 0,
+ .n_answer = 0,
+ .n_authority = 0,
+ .n_additional = 0
+ };
+ wr += sizeof(struct header);
+
+ // get linked list of network interfaces
+ if (-1 == getifaddrs(&ifaddrs)) {
+ log(ERROR, "%s", strerror(errno));
+ goto _fail_getifaddrs;
+ }
+
+ // for each network interface...
+ for (struct ifaddrs *ifaddr = ifaddrs; ifaddr; ifaddr = ifaddr->ifa_next) {
+ // skip loopback interfaces
+ if (ifaddr->ifa_flags & IFF_LOOPBACK) continue;
+
+ // for an AF_INET* interface address, display the address
+ if (ifaddr->ifa_addr && ifaddr->ifa_addr->sa_family == AF_INET) {
+
+ // write host record
+ {
+ strncpy((char *)(wr + 1), hostname, HOST_NAME_MAX);
+ wr[1 + HOST_NAME_MAX - 1] = '\0';
+ *wr = strlen((char *)(wr + 1));
+ wr += 1 + strlen((char *)(wr + 1));
+ }
+
+ // write local record
+ {
+ const char local[] = "local";
+ *wr++ = sizeof(local) - 1;
+ memcpy(wr, local, sizeof(local) - 1);
+ wr += sizeof(local) - 1;
+ }
+
+ // write root record
+ *wr++ = 0;
+
+ // write answer
+ {
+ struct answer ans = {
+ .type = htons(TYPE_A),
+ .class = htons(CACHE_FLUSH | CLASS_INTERNET),
+ .ttl = htonl(DEFAULT_TTL),
+ .size = htons(sizeof(struct in_addr)),
+ };
+ memcpy(wr, &ans, sizeof(ans));
+ wr += sizeof(ans);
+
+ memcpy(wr,
+ &(((struct sockaddr_in *)(ifaddr->ifa_addr))->sin_addr),
+ sizeof(struct in_addr));
+ wr += sizeof(struct in_addr);
+ }
+
+ // increment number of answers
+ hdr->n_answer++;
+
+ if (hdr->n_answer == 1) {
+ break;
+ }
+ }
+ }
+
+ // free linked list of network interfaces
+ freeifaddrs(ifaddrs);
+
+ // convert to network byte order
+ hdr->n_answer = htons(hdr->n_answer);
+
+ // send response
+ struct sockaddr_in mgroup = {
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = inet_addr(MDNS_ADDR),
+ .sin_port = htons(MDNS_PORT)
+ };
+ if (-1 == sendto(
+ sockfd,
+ response,
+ wr - response,
+ 0,
+ (struct sockaddr *)(&mgroup),
+ sizeof(mgroup)
+ )) {
+ log(FATAL, "sendto: %s", strerror(errno));
+ }
+
+ return 0;
+
+_fail_getifaddrs:
+ return -URTC_ERR;
+}
diff --git a/src/mdns.h b/src/mdns.h
new file mode 100644
index 0000000..7e73ba6
--- /dev/null
+++ b/src/mdns.h
@@ -0,0 +1,43 @@
+/**
+ * liburtc
+ * Copyright 2020 Chris Hiszpanski
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef _URTC_MDNS_H
+#define _URTC_MDNS_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#define TYPE_A 1 // IPv4 record
+#define TYPE_AAAA 28 // IPv6 record
+
+// server: for processing queries and generating responses
+int mdns_subscribe();
+int mdns_unsubscribe(int sockfd);
+int mdns_validate_query(const uint8_t *q, size_t n, const char *hostname);
+int mdns_send_response(int sockfd, const char *hostname);
+
+// client: for sending queries and processing responses
+int mdns_parse_response(const void *response, size_t len);
+int mdns_query(const char *name);
+
+#endif // _URTC_MDNS_H
diff --git a/src/prng.c b/src/prng.c
new file mode 100644
index 0000000..147304b
--- /dev/null
+++ b/src/prng.c
@@ -0,0 +1,47 @@
+/**
+ * liburtc
+ * Copyright 2020 Chris Hiszpanski
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "prng.h"
+
+/**
+ * Initialize pseudo-random number generator
+ */
+void prng_init() {
+ srand(time(NULL));
+}
+
+/**
+ * Fill specified memory with pseudo-random data
+ *
+ * \param dst Destination memory
+ * \param sz Size (in bytes) of destination memory
+ */
+void prng(void *dst, size_t sz) {
+ for (; sz; sz--) {
+ *(uint8_t *)dst++ = rand() & 0xFF;
+ }
+}
diff --git a/src/prng.h b/src/prng.h
new file mode 100644
index 0000000..b1ad9b4
--- /dev/null
+++ b/src/prng.h
@@ -0,0 +1,31 @@
+/**
+ * liburtc
+ * Copyright 2020 Chris Hiszpanski
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef _URTC_PRNG_H
+#define _URTC_PRNG_H
+
+void prng_init();
+
+void prng(void *dst, size_t sz);
+
+#endif // _URTC_PRNG_H
diff --git a/src/sdp.c b/src/sdp.c
new file mode 100644
index 0000000..baa1ece
--- /dev/null
+++ b/src/sdp.c
@@ -0,0 +1,699 @@
+/**
+ * liburtc
+ * Copyright 2020 Chris Hiszpanski
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "err.h"
+#include "log.h"
+#include "sdp.h"
+
+#define STRINGIFY(x) #x
+#define TOSTRING(x) STRINGIFY(x)
+
+/**
+ * Parse group attribute
+ *
+ * Format is:
+ *
+ * a=group:BUNDLE <mid> <mid> ...
+ *
+ * Only the BUNDLE semantics value is supported.
+ *
+ * \param sdp[out] SDP structure updated with bundle id tags.
+ * \param value[in] NULL-terminated string containing SDP attribute line value
+ *
+ * \return 0 on success. Negative on error.
+ */
+static int sdp_parse_attr_group(struct sdp *sdp, char *val) {
+ char *id = val;
+
+ val = strsep(&id, " ");
+
+ if (0 == strcmp("BUNDLE", val)) {
+ for (int i = 0; id && i < SDP_MAX_BUNDLE_IDS; i++) {
+ strncpy(sdp->mid[i], strsep(&id, " "), SDP_MAX_BUNDLE_ID_SIZE);
+ }
+ }
+ return 0;
+}
+
+static int sdp_parse_attr_msid_semantic(struct sdp *sdp, const char *val) {
+ /* not implemented */
+ return 0;
+}
+
+static int sdp_parse_attr_rtcp(struct sdp *sdp, const char *val) {
+ /* not implemented */
+ return 0;
+}
+
+static int sdp_parse_attr_ice_ufrag(struct sdp *sdp, const char *val) {
+ if (sdp->ufrag != strncpy(sdp->ufrag, val, sizeof(sdp->ufrag))) {
+ return -1;
+ }
+ return 0;
+}
+
+static int sdp_parse_attr_ice_password(struct sdp *sdp, const char *val) {
+ if (sdp->pwd != strncpy(sdp->pwd, val, sizeof(sdp->pwd))) {
+ return -1;
+ }
+ return 0;
+}
+
+static int sdp_parse_attr_ice_options(struct sdp *sdp, const char *val) {
+ if (0 == strcmp("trickle", val)) {
+ sdp->ice_options.trickle = true;
+ }
+ return 0;
+}
+
+/**
+ * Parse fingerprint
+ *
+ * Only SHA-256 fingerprint is supported.
+ *
+ * \param[out] sdp SDP structure updated with parsed content.
+ * \param[in] val NULL-terminated string.
+ *
+ * \return 0 on success. Negative on error.
+ */
+static int sdp_parse_attr_fingerprint(struct sdp *sdp, char *val) {
+ char *fingerprint = val;
+
+ val = strsep(&fingerprint, " ");
+
+ if (fingerprint) {
+ if (0 == strcmp("sha-256", val)) {
+ if (32 != sscanf(fingerprint,
+ "%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:"
+ "%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:"
+ "%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:"
+ "%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX",
+ &sdp->fingerprint.sha256[0], &sdp->fingerprint.sha256[1],
+ &sdp->fingerprint.sha256[2], &sdp->fingerprint.sha256[3],
+ &sdp->fingerprint.sha256[4], &sdp->fingerprint.sha256[5],
+ &sdp->fingerprint.sha256[6], &sdp->fingerprint.sha256[7],
+ &sdp->fingerprint.sha256[8], &sdp->fingerprint.sha256[9],
+ &sdp->fingerprint.sha256[10], &sdp->fingerprint.sha256[11],
+ &sdp->fingerprint.sha256[12], &sdp->fingerprint.sha256[13],
+ &sdp->fingerprint.sha256[14], &sdp->fingerprint.sha256[15],
+ &sdp->fingerprint.sha256[16], &sdp->fingerprint.sha256[17],
+ &sdp->fingerprint.sha256[18], &sdp->fingerprint.sha256[19],
+ &sdp->fingerprint.sha256[20], &sdp->fingerprint.sha256[21],
+ &sdp->fingerprint.sha256[22], &sdp->fingerprint.sha256[23],
+ &sdp->fingerprint.sha256[24], &sdp->fingerprint.sha256[25],
+ &sdp->fingerprint.sha256[26], &sdp->fingerprint.sha256[27],
+ &sdp->fingerprint.sha256[28], &sdp->fingerprint.sha256[29],
+ &sdp->fingerprint.sha256[30], &sdp->fingerprint.sha256[31]
+ )) {
+ return -URTC_ERR_SDP_MALFORMED;
+ }
+ } else {
+ return -URTC_ERR_SDP_UNSUPPORTED_FINGERPRINT_ALGO;
+ }
+ } else {
+ return -URTC_ERR_SDP_MALFORMED;
+ }
+
+ return 0;
+}
+
+static int sdp_parse_attr_setup(struct sdp *sdp, const char *val) {
+ /* not implemented */
+ return 0;
+}
+
+static int sdp_parse_attr_mid(struct sdp *sdp, const char *val) {
+ /* not implemented */
+ return 0;
+}
+
+static int sdp_parse_attr_extmap(struct sdp *sdp, const char *val) {
+ /* not implemented */
+ return 0;
+}
+
+/**
+ * Parse RTP map
+ *
+ * Expected format is:
+ *
+ * <payload type> <encoding name>/<clock rate> [/<encoding parameters>]
+ *
+ * \param[out] sdp SDP structure updated with parsed content.
+ * \param[in] val NULL-terminated string.
+ *
+ * \return 0 on success. Negative on error.
+ */
+static int sdp_parse_attr_rtpmap(struct sdp *sdp, char *val) {
+ if (!val) return -URTC_ERR_SDP_MALFORMED;
+
+ unsigned int pt; // payload type
+ char en[7]; // encoding name
+ unsigned int cr; // clock rate
+
+ if (3 != sscanf(val, "%3d %6[a-zA-Z0-9]%*c%5d", &pt, en, &cr)) {
+ return -URTC_ERR_SDP_MALFORMED_ATTRIBUTE;
+ }
+
+ if (0 == strcmp("H264", en)) {
+ for (int i = 0; i < sdp->video.count; i++) {
+ if (sdp->video.params[i].type == pt) {
+ sdp->video.params[i].clock = cr;
+ sdp->video.params[i].codec = SDP_CODEC_H264;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int sdp_parse_attr_rtcp_feedback(struct sdp *sdp, const char *val) {
+ /* not implemented */
+ return 0;
+}
+
+static int sdp_parse_attr_fmtp(struct sdp *sdp, const char *val) {
+ /* not implemented */
+ return 0;
+}
+
+/**
+ * Parse SDP attribute line
+ *
+ * \param sdp[out] SDP structure updated with parsed content
+ * \param attr[in] NULL-terminated SDP attribute string (after "a=")
+ *
+ * \return 0 on success. Negative on error.
+ */
+static int sdp_parse_attribute(struct sdp *sdp, char *attr) {
+ char *value = attr;
+
+ // Find colon separating attribute from value
+ attr = strsep(&value, ":");
+
+ if (value) {
+ if (0 == strcmp("group", attr)) {
+ return sdp_parse_attr_group(sdp, value);
+ } else if (0 == strcmp("msid-semantic", attr)) {
+ return sdp_parse_attr_msid_semantic(sdp, value);
+ } else if (0 == strcmp("rtcp", attr)) {
+ return sdp_parse_attr_rtcp(sdp, value);
+ } else if (0 == strcmp("ice-ufrag", attr)) {
+ return sdp_parse_attr_ice_ufrag(sdp, value);
+ } else if (0 == strcmp("ice-pwd", attr)) {
+ return sdp_parse_attr_ice_password(sdp, value);
+ } else if (0 == strcmp("ice-options", attr)) {
+ return sdp_parse_attr_ice_options(sdp, value);
+ } else if (0 == strcmp("fingerprint", attr)) {
+ return sdp_parse_attr_fingerprint(sdp, value);
+ } else if (0 == strcmp("setup", attr)) {
+ return sdp_parse_attr_setup(sdp, value);
+ } else if (0 == strcmp("mid", attr)) {
+ return sdp_parse_attr_mid(sdp, value);
+ } else if (0 == strcmp("extmap", attr)) {
+ return sdp_parse_attr_extmap(sdp, value);
+ } else if (0 == strcmp("rtpmap", attr)) {
+ return sdp_parse_attr_rtpmap(sdp, value);
+ } else if (0 == strcmp("rtcp-fb", attr)) {
+ return sdp_parse_attr_rtcp_feedback(sdp, value);
+ } else if (0 == strcmp("fmtp", attr)) {
+ return sdp_parse_attr_fmtp(sdp, value);
+ }
+ } else /* flag */ {
+ if (0 == strcmp("recvonly", attr)) {
+ sdp->mode = SDP_MODE_RECEIVE_ONLY;
+ } else if (0 == strcmp("sendonly", attr)) {
+ sdp->mode = SDP_MODE_SEND_ONLY;
+ } else if (0 == strcmp("sendrecv", attr)) {
+ sdp->mode = SDP_MODE_SEND_AND_RECEIVE;
+ } else if (0 == strcmp("rtcp-mux", attr)) {
+ sdp->rtcp_mux = true;
+ } else if (0 == strcmp("rtcp-rsize", attr)) {
+ sdp->rtcp_rsize = true;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * Parse media description line
+ *
+ * Format is:
+ *
+ * m=<audio|video> <port> UDP/TLS/RTP/SAVPF <n> ...
+ *
+ * Only the UDP/TLS/RTP/SAVPF transport protocol is supported.
+ *
+ * \param sdp[out] SDP structure updated with username, session id and
+ * session version.
+ * \param value[in] NULL-terminated string containing SDP line value
+ *
+ * \return 0 on success. Negative on error.
+ */
+static int sdp_parse_media_description(struct sdp *sdp, char *value) {
+ char *next = value;
+
+ value = strsep(&next, " ");
+
+ if (0 == strcmp("audio", value)) {
+ /* not implemented */
+ } else if (0 == strcmp("video", value)) {
+ if (!next) return -URTC_ERR_SDP_MALFORMED_MEDIA;
+
+ /* get port */
+ value = strsep(&next, " ");
+ if (1 != sscanf(value, "%5hd", &sdp->video.port)) {
+ return -URTC_ERR_SDP_MALFORMED_MEDIA;
+ }
+ if (!next) return -URTC_ERR_SDP_MALFORMED_MEDIA;
+
+ /* get protocol: only "UDP/TLS/RTP/SAVPF" supported */
+ value = strsep(&next, " ");
+ if (0 != strcmp("UDP/TLS/RTP/SAVPF", value)) {
+ return -URTC_ERR_SDP_UNSUPPORTED_MEDIA_PROTOCOL;
+ }
+ if (!next) return -URTC_ERR_SDP_MALFORMED_MEDIA;
+
+ /* get RTP payload types */
+ sdp->video.count = 0;
+ for (
+ value = strsep(&next, " ");
+ value && sdp->video.count < SDP_MAX_RTP_PAYLOAD_TYPES;
+ value = strsep(&next, " ")
+ ) {
+ unsigned int *v = &sdp->video.params[sdp->video.count].type;
+ if (1 != sscanf(value, "%3d", v)) {
+ return -URTC_ERR_SDP_MALFORMED_MEDIA;
+ }
+ sdp->video.count++;
+ }
+
+ } else if (0 == strcmp("text", value)) {
+ /* ignore */
+ } else if (0 == strcmp("message", value)) {
+ /* ignore */
+ } else if (0 == strcmp("application", value)) {
+ /* ignore */
+ } else {
+ return -URTC_ERR_SDP_UNSUPPORTED_MEDIA_TYPE;
+ }
+
+ return 0;
+}
+
+static int sdp_parse_connection_data(struct sdp *sdp, const char *value) {
+ return 0;
+}
+
+/**
+ * Parse version line
+ *
+ * Format is:
+ *
+ * v=0
+ *
+ * Only version zero is supported. Anything else is an error.
+ *
+ * \param value[in] NULL-terminated string containing SDP line value
+ *
+ * \return 0 on success. Negative on error.
+ */
+static int sdp_parse_version(struct sdp *sdp, const char *value) {
+ if (0 != strcmp("0", value)) {
+ return -URTC_ERR_SDP_MALFORMED_VERSION;
+ }
+ sdp->version = 0;
+
+ return 0;
+}
+
+/**
+ * Parse origin line
+ *
+ * Format is:
+ *
+ * o=<username> <sess-id> <sess-version> <nettype> <addrtype>
+ * <unicast-address>
+ *
+ * <nettype> <addrtype> and <unicast-address> are ignored.
+ *
+ * \param sdp[out] SDP structure updated with username, session id and
+ * session version.
+ * \param value[in] NULL-terminated string containing SDP line value
+ *
+ * \return 0 on success. Negative on error.
+ */
+static int sdp_parse_origin(struct sdp *sdp, const char *value) {
+ if (3 != sscanf(value,
+ "%" TOSTRING(SDP_MAX_USERNAME_SIZE) "s "
+ "%" TOSTRING(SDP_MAX_SESSION_ID_SIZE) "[0-9] "
+ "%" TOSTRING(SDP_MAX_SESSION_VERSION_SIZE) "[0-9] "
+ "IN IP4 127.0.0.1",
+ sdp->username,
+ sdp->session_id,
+ sdp->session_version
+ )) {
+ return -URTC_ERR_SDP_MALFORMED_ORIGIN;
+ }
+
+ return 0;
+}
+
+static int sdp_parse_session_name(struct sdp *sdp, const char *value) {
+ return 0;
+}
+
+/**
+ * Parse timing line
+ *
+ * Format is:
+ *
+ * t=<start-time> <stop-time>
+ *
+ * \param sdp[out] SDP structure updated with start and stop time
+ * \param value[in] NULL-terminated string containing SDP line value
+ *
+ * \return 0 on success. Negative on error.
+ */
+static int sdp_parse_timing(struct sdp *sdp, const char *value) {
+ if (2 != sscanf(value, "%10lli %10lli",
+ (unsigned long long *)&sdp->start_time,
+ (unsigned long long *)&sdp->stop_time
+ )) {
+ return -URTC_ERR_SDP_MALFORMED_TIMING;
+ }
+
+ return 0;
+}
+
+/**
+ * Parse a line of SDP
+ *
+ * \param sdp[out] SDP structure to be updated with parsed information
+ * \param line[in] NULL-terminated string containing one line of SDP
+ *
+ * \return 0 on success, negative on error.
+ */
+static int sdp_parse_line(struct sdp *sdp, char *line) {
+ if (!line) return -URTC_ERR_BAD_ARGUMENT;
+ if (0 == strlen(line)) return 0;
+ if (strlen(line) < 3) return -URTC_ERR_SDP_MALFORMED;
+ if ('=' != line[1]) return -URTC_ERR_SDP_MALFORMED;
+
+ char type = line[0];
+ char *value = line + 2;
+
+ switch (type) {
+ case 'a':
+ return sdp_parse_attribute(sdp, value);
+ case 'c':
+ return sdp_parse_connection_data(sdp, value);
+ case 'o':
+ return sdp_parse_origin(sdp, value);
+ case 'm':
+ return sdp_parse_media_description(sdp, value);
+ case 's':
+ return sdp_parse_session_name(sdp, value);
+ case 't':
+ return sdp_parse_timing(sdp, value);
+ case 'v':
+ return sdp_parse_version(sdp, value);
+ default:
+ /* ignore unsupported types */
+ return 0;
+ }
+}
+
+
+int sdp_parse(struct sdp *dst, const char *str) {
+ if (!str) return -URTC_ERR_BAD_ARGUMENT;
+ if (!dst) return -URTC_ERR_BAD_ARGUMENT;
+
+ err_t retval = 0;
+
+ /* make a malleable copy of string for parser */
+ char *copy = malloc(strlen(str));
+ if (copy) {
+ strcpy(copy, str);
+
+ for (char *in = copy, *line = strsep(&in, "\r\n"); in; line = strsep(&in, "\r\n")) {
+ retval = sdp_parse_line(dst, line);
+ if (retval != 0) {
+ log(ERROR, "sdp_parse_line returned %i for %s", retval, line);
+ break;
+ }
+ }
+
+ free(copy);
+ } else {
+ return -URTC_ERR_INSUFFICIENT_MEMORY;
+ }
+
+ return retval;
+}
+
+int sdp_serialize(char *dst, size_t len, const struct sdp *src) {
+ int n;
+
+ // sanity check
+ if (!dst) return -URTC_ERR_BAD_ARGUMENT;
+ if (!src) return -URTC_ERR_BAD_ARGUMENT;
+
+ // write version
+ n = snprintf(dst, len, "v=%u\n", src->version);
+ if (n < 0) return -URTC_ERR_SDP_MALFORMED;
+ if (n >= len) return -URTC_ERR_SDP_MALFORMED;
+ dst += n;
+ len -= n;
+
+ // write originator and session identifier
+ n = snprintf(dst, len,
+ "o=liburtc/0.0.0 %s %s IN IP4 127.0.0.1\n",
+ src->session_id,
+ src->session_version
+ );
+ if (n < 0) return -URTC_ERR_SDP_MALFORMED;
+ if (n >= len) return -URTC_ERR_SDP_MALFORMED;
+ dst += n;
+ len -= n;
+
+ // write session name
+ n = snprintf(dst, len, "s=%s\n",
+ 0 == strlen(src->session_name) ? " " : src->session_name);
+ if (n < 0) return -URTC_ERR_SDP_MALFORMED;
+ if (n >= len) return -URTC_ERR_SDP_MALFORMED;
+ dst += n;
+ len -= n;
+
+ // write uri
+ n = snprintf(dst, len, "u=http://www.liburtc.org\n");
+ if (n < 0) return -URTC_ERR_SDP_MALFORMED;
+ if (n >= len) return -URTC_ERR_SDP_MALFORMED;
+ dst += n;
+ len -= n;
+
+ // write time descriptor
+ n = snprintf(dst, len, "t=%" PRIu64 " %" PRIu64 "\n",
+ src->start_time, src->stop_time);
+ if (n < 0) return -URTC_ERR_SDP_MALFORMED;
+ if (n >= len) return -URTC_ERR_SDP_MALFORMED;
+ dst += n;
+ len -= n;
+
+ // write session attribute
+ n = snprintf(dst, len, "a=group:BUNDLE");
+ if (n < 0) return -URTC_ERR_SDP_MALFORMED;
+ if (n >= len) return -URTC_ERR_SDP_MALFORMED;
+ dst += n;
+ len -= n;
+ for (int i = 0; i < SDP_MAX_BUNDLE_IDS; i++) {
+ if (strlen(src->mid[i]) > 0) {
+ n = snprintf(dst, len, " %s", src->mid[i]);
+ if (n < 0) return -URTC_ERR_SDP_MALFORMED;
+ if (n >= len) return -URTC_ERR_SDP_MALFORMED;
+ dst += n;
+ len -= n;
+ }
+ }
+ n = snprintf(dst, len, "\n");
+ if (n < 0) return -URTC_ERR_SDP_MALFORMED;
+ if (n >= len) return -URTC_ERR_SDP_MALFORMED;
+ dst += n;
+ len -= n;
+
+ // write video media description
+ if (src->video.count > 0) {
+ n = snprintf(
+ dst, len, "m=video %hd UDP/TLS/RTP/SAVPF", src->video.port
+ );
+ if (n < 0) return -URTC_ERR_SDP_MALFORMED;
+ if (n >= len) return -URTC_ERR_SDP_MALFORMED;
+ dst += n;
+ len -= n;
+ for (int i = 0; i < src->video.count; i++) {
+ if (src->video.params[i].codec == SDP_CODEC_H264) {
+ n = snprintf(dst, len, " %d", src->video.params[i].type);
+ if (n < 0) return -URTC_ERR_SDP_MALFORMED;
+ if (n > len) return -URTC_ERR_SDP_MALFORMED;
+ dst += n;
+ len -= n;
+ }
+ }
+ n = snprintf(dst, len, "\n");
+ if (n < 0) return -URTC_ERR_SDP_MALFORMED;
+ if (n >= len) return -URTC_ERR_SDP_MALFORMED;
+ dst += n;
+ len -= n;
+ }
+
+ // TODO write rtcp attribute
+
+ // write connection information (TODO IPv6)
+ n = snprintf(dst, len, "c=IN IP4 0.0.0.0\n");
+ if (n < 0) return -URTC_ERR_SDP_MALFORMED;
+ if (n >= len) return -URTC_ERR_SDP_MALFORMED;
+ dst += n;
+ len -= n;
+
+ // write media attribute: ice ufrag
+ n = snprintf(dst, len, "a=ice-ufrag:%s\n", src->ufrag);
+ if (n < 0) return -URTC_ERR_SDP_MALFORMED;
+ if (n >= len) return -URTC_ERR_SDP_MALFORMED;
+ dst += n;
+ len -= n;
+
+ // write media attribute: ice pwd
+ n = snprintf(dst, len, "a=ice-pwd:%s\n", src->pwd);
+ if (n < 0) return -URTC_ERR_SDP_MALFORMED;
+ if (n >= len) return -URTC_ERR_SDP_MALFORMED;
+ dst += n;
+ len -= n;
+
+ // write media attribute: trickle ICE
+ if (src->ice_options.trickle) {
+ n = snprintf(dst, len, "a=ice-options:trickle\n");
+ if (n < 0) return -URTC_ERR_SDP_MALFORMED;
+ if (n >= len) return -URTC_ERR_SDP_MALFORMED;
+ dst += n;
+ len -= n;
+ }
+
+ // write media attribute: fingerprint
+ n = snprintf(dst, len,
+ "a=fingerprint:sha-256 "
+ "%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:"
+ "%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:"
+ "%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:"
+ "%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX\n",
+ src->fingerprint.sha256[0], src->fingerprint.sha256[1],
+ src->fingerprint.sha256[2], src->fingerprint.sha256[3],
+ src->fingerprint.sha256[4], src->fingerprint.sha256[5],
+ src->fingerprint.sha256[6], src->fingerprint.sha256[7],
+ src->fingerprint.sha256[8], src->fingerprint.sha256[9],
+ src->fingerprint.sha256[10], src->fingerprint.sha256[11],
+ src->fingerprint.sha256[12], src->fingerprint.sha256[13],
+ src->fingerprint.sha256[14], src->fingerprint.sha256[15],
+ src->fingerprint.sha256[16], src->fingerprint.sha256[17],
+ src->fingerprint.sha256[18], src->fingerprint.sha256[19],
+ src->fingerprint.sha256[20], src->fingerprint.sha256[21],
+ src->fingerprint.sha256[22], src->fingerprint.sha256[23],
+ src->fingerprint.sha256[24], src->fingerprint.sha256[25],
+ src->fingerprint.sha256[26], src->fingerprint.sha256[27],
+ src->fingerprint.sha256[28], src->fingerprint.sha256[29],
+ src->fingerprint.sha256[30], src->fingerprint.sha256[31]
+ );
+ if (n < 0) return -URTC_ERR_SDP_MALFORMED;
+ if (n >= len) return -URTC_ERR_SDP_MALFORMED;
+ dst += n;
+ len -= n;
+
+ // TODO write setup, mid
+
+ // write media attribute: mode
+ switch (src->mode) {
+ case SDP_MODE_RECEIVE_ONLY:
+ n = snprintf(dst, len, "a=recvonly\n");
+ break;
+ case SDP_MODE_SEND_ONLY:
+ n = snprintf(dst, len, "a=sendonly\n");
+ break;
+ case SDP_MODE_SEND_AND_RECEIVE:
+ n = snprintf(dst, len, "a=sendrecv\n");
+ break;
+ default:
+ return -URTC_ERR_SDP_MALFORMED;
+ break;
+ }
+ if (n < 0) return -URTC_ERR_SDP_MALFORMED;
+ if (n >= len) return -URTC_ERR_SDP_MALFORMED;
+ dst += n;
+ len -= n;
+
+ // write media attribute: rtcp mux
+ if (src->rtcp_mux) {
+ n = snprintf(dst, len, "a=rtcp-mux\n");
+ if (n < 0) return -URTC_ERR_SDP_MALFORMED;
+ if (n >= len) return -URTC_ERR_SDP_MALFORMED;
+ dst += n;
+ len -= n;
+ }
+
+ // write media attribute: rtcp rsize
+ if (src->rtcp_rsize) {
+ n = snprintf(dst, len, "a=rtcp-rsize\n");
+ if (n < 0) return -URTC_ERR_SDP_MALFORMED;
+ if (n >= len) return -URTC_ERR_SDP_MALFORMED;
+ dst += n;
+ n -= n;
+ }
+
+ // write dynamic profiles
+ for (int i = 0; i < src->video.count; i++) {
+ if (src->video.params[i].codec == SDP_CODEC_H264) {
+ n = snprintf(dst, len, "a=rtpmap:%d H264/%d\n",
+ src->video.params[i].type,
+ src->video.params[i].clock
+ );
+ if (n < 0) return -URTC_ERR_SDP_MALFORMED;
+ if (n >= len) return -URTC_ERR_SDP_MALFORMED;
+ dst += n;
+ len -= n;
+ }
+ }
+
+ // write media attribute: media id
+ // TODO fix hardcoded media id
+ n = snprintf(dst, len, "a=mid:0\n");
+ if (n < 0) return -URTC_ERR_SDP_MALFORMED;
+ if (n >= len) return -URTC_ERR_SDP_MALFORMED;
+ dst += n;
+ len -= n;
+
+ return 0;
+}
diff --git a/src/sdp.h b/src/sdp.h
new file mode 100644
index 0000000..b65b5e5
--- /dev/null
+++ b/src/sdp.h
@@ -0,0 +1,150 @@
+/**
+ * liburtc
+ * Copyright 2020 Chris Hiszpanski
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef _URTC_SDP_H
+#define _URTC_SDP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdbool.h>
+#include <stdint.h>
+
+
+// Maximum size of SDP string (in bytes)
+#define SDP_MAX_SIZE 2048
+
+#define SDP_MAX_USERNAME_SIZE 32
+#define SDP_MAX_SESSION_ID_SIZE 32
+#define SDP_MAX_SESSION_VERSION_SIZE 32
+#define SDP_MAX_SESSION_NAME_SIZE 32
+
+#define SDP_MAX_BUNDLE_IDS 5
+#define SDP_MAX_BUNDLE_ID_SIZE 32
+#define SDP_MAX_RTP_PAYLOAD_TYPES 32
+
+
+typedef enum sdp_mode {
+ SDP_MODE_SEND_AND_RECEIVE = 0,
+ SDP_MODE_RECEIVE_ONLY,
+ SDP_MODE_SEND_ONLY
+} sdp_mode_t;
+
+typedef enum sdp_media {
+ SDP_MEDIA_TYPE_NULL = 0,
+ SDP_MEDIA_TYPE_AUDIO,
+ SDP_MEDIA_TYPE_VIDEO,
+ SDP_MEDIA_TYPE_TEXT,
+ SDP_MEDIA_TYPE_APPLICATION,
+ SDP_MEDIA_TYPE_MESSAGE
+} sdp_media_type_t;
+
+// Codecs recognized by SDP parser
+typedef enum sdp_codec {
+ SDP_CODEC_NULL = 0,
+ SDP_CODEC_H264,
+ SDP_CODEC_VP9
+} sdp_codec_t;
+
+// Dynamic payload type
+typedef struct sdp_rtpmap {
+ unsigned int type; // Dynamic RTP payload type (e.g. 96)
+ enum sdp_codec codec; // H264, VP9, etc.
+ unsigned int clock; // Typically 90kHz for video
+ unsigned int flags;
+} sdp_rtpmap_t;
+
+typedef struct sdp {
+ // protocol version
+ uint8_t version;
+
+ // originator and session identifier
+ char username[SDP_MAX_USERNAME_SIZE+1];
+ char session_id[SDP_MAX_SESSION_ID_SIZE+1];
+ char session_version[SDP_MAX_SESSION_VERSION_SIZE+1];
+
+ // session name
+ char session_name[SDP_MAX_SESSION_NAME_SIZE+1];
+
+ // time description (mandatory)
+ uint64_t start_time;
+ uint64_t stop_time;
+
+ // session attributes
+
+ // bundle media identification tags
+ char mid[SDP_MAX_BUNDLE_IDS][SDP_MAX_BUNDLE_ID_SIZE+1];
+
+ // ice
+ char ufrag[4*256]; // ice-ufrag (256 unicode chars max.)
+ char pwd[4*256]; // ice-pwd (256 unicode chars max.)
+ struct {
+ bool trickle;
+ } ice_options;
+
+ // video media
+ struct {
+ uint16_t port;
+ struct sdp_rtpmap params[SDP_MAX_RTP_PAYLOAD_TYPES];
+ int count;
+ } video;
+
+ sdp_mode_t mode; // send, receive, and send-and-receive
+
+ bool rtcp_mux; // is rtcp multiplexed on same socket
+ bool rtcp_rsize;
+
+ union {
+ uint8_t sha256[32]; // certificate fingerprint
+ } fingerprint;
+
+} sdp_t;
+
+
+/**
+ * Parse SDP string into structure
+ *
+ * \param dst[out] SDP structure
+ * \param src[in] NULL-terminated SDP string
+ *
+ * \return 0 on success, negative on error.
+ */
+int sdp_parse(struct sdp *dst, const char *src);
+
+/**
+ * Serialize SDP structure into string
+ *
+ * \param dst[out] NULL-terminated SDP string
+ * \param len[in] Capacity of dst
+ * \param src[in] SDP structure
+ *
+ * \return 0 on success, negative on error.
+ */
+int sdp_serialize(char *dst, size_t len, const struct sdp *src);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _URTC_SDP_H */
diff --git a/src/urtc.c b/src/urtc.c
new file mode 100644
index 0000000..892b632
--- /dev/null
+++ b/src/urtc.c
@@ -0,0 +1,441 @@
+/**
+ * liburtc
+ * Copyright 2020 Chris Hiszpanski
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <assert.h> // assert
+#include <errno.h> // errno
+#include <poll.h> // poll
+#include <pthread.h> // pthread_create
+#include <stdbool.h> // true
+#include <stdio.h> // fprintf
+#include <stdlib.h> // calloc, free
+#include <string.h> // strerror
+#include <unistd.h> // close
+
+#include <arpa/inet.h> // inet_ntoa
+#include <netinet/in.h> // IPPROTO_UDP
+#include <sys/socket.h> // socket
+#include <sys/types.h>
+
+#include "err.h"
+#include "log.h"
+#include "mdns.h" // mdns_subscribe, mdns_unsubscribe
+#include "prng.h" // prng_init
+#include "sdp.h"
+#include "urtc.h"
+#include "uuid.h" // uuid_create_str
+
+#define RX_BUF_CAP 2048 // receive buffer capacity
+
+#define POLL_TIMEOUT_MS 50 // poll() timeout (in milliseconds)
+
+enum signaling_state {
+ SIGNALING_STATE_STABLE = 0,
+ SIGNALING_STATE_HAVE_LOCAL_OFFER,
+ SIGNALING_STATE_HAVE_REMOTE_OFFER,
+ SIGNALING_STATE_HAVE_LOCAL_PRANSWER,
+ SIGNALING_STATE_HAVE_REMOTE_PRANSWER,
+};
+
+enum rtc_state {
+ STATE_NEW = 0,
+
+ STATE_ICE_NEW,
+ STATE_ICE_GATHERING,
+ STATE_ICE_COMPLETE,
+
+ STATE_CONNECTING,
+
+ STATE_CONNECTED,
+
+ STATE_DISCONNECTED,
+
+ STATE_FAILED,
+
+ STATE_CLOSED,
+
+ NUM_STATES // must be last
+};
+
+enum rtc_event {
+ EVENT_SOCKET = 0,
+ EVENT_TIMER,
+ EVENT_MDNS,
+ NUM_EVENTS, // must be last
+};
+
+struct peerconn {
+ // socket file descriptor
+ int sockfd;
+
+ // execution thread
+ pthread_t thread;
+
+ // poll file descriptors
+ struct pollfd fds[NUM_EVENTS];
+
+ // callbacks
+ urtc_on_ice_candidate *on_ice_candidate;
+ urtc_force_idr *force_idr;
+
+ // stun servers
+ const char **stun;
+
+ // state machine
+ enum signaling_state ss;
+
+ // local and remote descriptions
+ struct sdp ldesc, rdesc;
+
+ // mDNS related state
+ struct {
+ char hostname[UUID_STR_LEN]; // .local hostname
+ int sockfd; // port 5353 listener
+ } mdns;
+};
+
+
+typedef int (*event_handler)(struct peerconn *pc);
+
+const enum rtc_state state_table[NUM_STATES][NUM_EVENTS] = {
+ { STATE_NEW, STATE_NEW },
+ { STATE_NEW, STATE_NEW },
+ { STATE_NEW, STATE_NEW },
+ { STATE_NEW, STATE_NEW },
+ { STATE_NEW, STATE_NEW },
+ { STATE_NEW, STATE_NEW },
+ { STATE_NEW, STATE_NEW },
+ { STATE_NEW, STATE_NEW },
+ { STATE_NEW, STATE_NEW }
+};
+
+/**
+ * Handle incoming STUN packet
+ *
+ * \param pc Peer connection.
+ *
+ * \return 0 on success, negative on error.
+ */
+static int stun_handler(
+ struct peerconn *pc,
+ const uint8_t *pkt,
+ size_t n
+) {
+ return 0;
+}
+
+/**
+ * Handle incoming DTLS packet
+ *
+ * \param pc Peer connection.
+ *
+ * \return 0 on success, negative on error.
+ */
+static int dtls_handler(
+ struct peerconn *pc,
+ const uint8_t *pkt,
+ size_t n
+) {
+ return 0;
+}
+
+/**
+ * Handle incoming SRTP (or SRTCP) packet
+ *
+ * \param pc Peer connection.
+ *
+ * \return 0 on success, negative on error.
+ */
+static int rtp_handler(
+ struct peerconn *pc,
+ const uint8_t *pkt,
+ size_t n
+) {
+ return 0;
+}
+
+/**
+ * Handle incoming packet
+ *
+ * Packet may be a DTLS, SRTP, SRTCP, or STUN packet. Other packet types
+ * are discarded.
+ *
+ * \param pc Peer connection.
+ *
+ * \return 0 on success, negative on error.
+ */
+static int socket_event_handler(struct peerconn *pc) {
+ uint8_t buffer[RX_BUF_CAP];
+ struct sockaddr_in ra;
+ socklen_t ralen;
+ ssize_t n;
+
+ // read packet
+ ralen = sizeof(ra);
+ n = recvfrom(
+ pc->sockfd,
+ buffer,
+ sizeof(buffer),
+ 0,
+ (struct sockaddr *)(&ra),
+ &ralen
+ );
+
+ // rtp
+ if ((127 < buffer[0]) && (buffer[0] < 192)) {
+ log(INFO, "[rtp] %s", inet_ntoa(ra.sin_addr));
+ rtp_handler(pc, buffer, n);
+ } else
+ // dtls
+ if ((19 < buffer[0]) && (buffer[0] < 64)) {
+ log(INFO, "[dtls] %s", inet_ntoa(ra.sin_addr));
+ dtls_handler(pc, buffer, n);
+ } else
+ // stun
+ if (buffer[0] < 2) {
+ log(INFO, "[stun] %s", inet_ntoa(ra.sin_addr));
+ stun_handler(pc, buffer, n);
+ }
+
+ return 0;
+}
+
+/**
+ * Handle timer event
+ *
+ * May need to:
+ * - resend STUN packet
+ * - resend DTLS packet
+ * - expire ICE candidate?
+ *
+ * \param pc Peer connection.
+ *
+ * \return 0 on success, negative on error.
+ */
+static int timer_event_handler(struct peerconn *pc) {
+ return 0;
+}
+
+/**
+ * Handle incoming mDNS query
+ *
+ * \param pc Peer connection.
+ *
+ * \return 0 on success, negative on error.
+ */
+static int mdns_handler(struct peerconn *pc) {
+ uint8_t buffer[RX_BUF_CAP];
+ ssize_t n;
+
+ // read packet
+ if (n = recvfrom(
+ pc->mdns.sockfd,
+ buffer,
+ sizeof(buffer),
+ 0,
+ NULL,
+ NULL
+ ), -1 == n) {
+ log(ERROR, "%s", strerror(errno));
+ goto _fail_recvfrom;
+ }
+
+ // if valid query for peer connection's ephemeral hostname...
+ int type = mdns_validate_query(buffer, n, pc->mdns.hostname);
+ if (type > 0) {
+ if (type | TYPE_A) {
+ log(DEBUG, "[mdns] received A query");
+ // ...respond to query with interface addresses
+ mdns_send_response(pc->mdns.sockfd, pc->mdns.hostname);
+ }
+ if (type | TYPE_AAAA) {
+ log(DEBUG, "[mdns] received AAAA query");
+ }
+ }
+
+ return 0;
+
+_fail_recvfrom:
+ return -URTC_ERR;
+}
+
+const event_handler action_table[NUM_STATES][NUM_EVENTS] = {
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL },
+ { NULL, NULL, NULL }
+};
+
+/**
+ * Runloop finite state machine (mealy) for handling timer and receive events
+ */
+
+// send stun request
+// wait for stun reply
+// timeout -> resend request
+
+// called dir caller
+// ------ --- ------
+// setRemoteDescription(offer) <-- offer = createOffer()
+// setLocalDescription(offer)
+// answer = createAnswer() --> setRemoteDescription(answer)
+// setLocalDescription(answer)
+//
+// cand = onIceCandidate --> addIceCandidate(cand)
+// addIceCandidate(cand) <-- cand = onIceCandidate
+// ...
+//
+// (connection established)
+//
+// (dtls handshake)
+//
+// srtp -->
+// <-- rtcp, nack
+
+/**
+ * Execution thread per peer connection
+ *
+ * \param arg Peer connection
+ *
+ * \return Unused.
+ */
+static void * runloop(void *arg) {
+ struct peerconn *pc;
+ enum rtc_event event;
+ int n;
+
+ pc = (struct peerconn *)arg;
+ assert(pc);
+
+_loop:
+ // block indefinitely (-1 timeout) until i/o event
+ n = poll(pc->fds, NUM_EVENTS, -1);
+ assert(n > 0);
+
+ // which event(s) occurred?
+ if (pc->fds[EVENT_SOCKET].revents & POLLIN) {
+ event = EVENT_SOCKET;
+ } else
+ if (pc->fds[EVENT_MDNS].revents & POLLIN) {
+ mdns_handler(pc);
+ } else
+ if (pc->fds[EVENT_TIMER].revents & POLLIN) {
+ event = EVENT_TIMER;
+ }
+
+ goto _loop;
+
+
+ return NULL;
+};
+
+urtc_peerconn_t * urtc_peerconn_create(const char *stun[]) {
+ // seend pseudorandom number generator
+ prng_init();
+
+ // allocate peer connection
+ struct peerconn *pc = (struct peerconn *)calloc(1, sizeof(struct peerconn));
+ if (!pc) return pc;
+
+ // copy pointer to stun servers
+ pc->stun = stun;
+
+ // open udp socket (for all dtls/srtp/srtcp/stun/turn communication)
+ pc->sockfd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (-1 == pc->sockfd) goto _fail_socket;
+ pc->fds[EVENT_SOCKET] = (struct pollfd){ pc->sockfd, POLLIN };
+
+ // generate a unique local mDNS hostname
+ uuid_create_str(pc->mdns.hostname);
+ log(INFO, "mDNS hostname is %s.local", pc->mdns.hostname);
+
+ // open multicast udp socket for replying to mDNS queries
+ pc->mdns.sockfd = mdns_subscribe();
+ if (-1 == pc->mdns.sockfd) goto _fail_mdns_subscribe;
+ pc->fds[EVENT_MDNS] = (struct pollfd){
+ pc->mdns.sockfd,
+ POLLIN
+ };
+
+ // start new thread per peer connection
+ if (0 != pthread_create(
+ &(pc->thread),
+ NULL,
+ runloop,
+ pc
+ )) goto _fail_pthread_create;
+
+ return pc;
+
+_fail_pthread_create:
+ mdns_unsubscribe(pc->mdns.sockfd);
+_fail_mdns_subscribe:
+ close(pc->sockfd);
+_fail_socket:
+ free(pc);
+
+ return NULL;
+}
+
+int urtc_set_on_ice_candidate(struct peerconn *pc, urtc_on_ice_candidate *cb) {
+ if (!pc) return -1;
+ if (!cb) return -1;
+ pc->on_ice_candidate = cb;
+
+ return -URTC_ERR_NOT_IMPLEMENTED;
+}
+
+int urtc_add_ice_candidate(struct peerconn *pc, const char *cand) {
+ return -URTC_ERR_NOT_IMPLEMENTED;
+}
+
+int urtc_create_answer(struct peerconn *pc, char **answer) {
+ return -URTC_ERR_NOT_IMPLEMENTED;
+}
+
+int urtc_create_offer(struct peerconn *pc, char **offer) {
+ return -URTC_ERR_NOT_IMPLEMENTED;
+}
+
+int urtc_set_remote_description(struct peerconn *pc, const char *desc) {
+ return -URTC_ERR_NOT_IMPLEMENTED;
+}
+
+int urtc_set_local_description(struct peerconn *pc, const char *desc) {
+ return -URTC_ERR_NOT_IMPLEMENTED;
+}
+
+void urtc_peerconn_destroy(struct peerconn *pc) {
+ if (pc) {
+ pthread_cancel(pc->thread);
+ pthread_join(pc->thread, NULL);
+ mdns_unsubscribe(pc->mdns.sockfd);
+ shutdown(pc->sockfd, SHUT_RDWR);
+ close(pc->sockfd);
+ free(pc);
+ }
+}
diff --git a/src/urtc.h b/src/urtc.h
new file mode 100644
index 0000000..3da0947
--- /dev/null
+++ b/src/urtc.h
@@ -0,0 +1,223 @@
+/**
+ * \file urtc.h
+ *
+ * \brief liburtc header file.
+ *
+ * Copyright 2020 Chris Hiszpanski
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/**
+ * \mainpage notitle
+ *
+ * The reference WebRTC implementation, libwebrtc, was developed by Google
+ * for the Chrome web browser. It's codebase is large (over 10GB of source),
+ * uses uncommon build tools (Bazel), and produces large executables not
+ * well suited for constrained embedded devices.
+ *
+ * liburtc is designed with embedded devices in mind. Only functionality
+ * commonly found on embedded devices is implemented. For example, only
+ * the H.264 video codec is supported (there is no VP8/VP9 support) as
+ * most embedded devices with hardware-accelerated video encoders only
+ * support H.264.
+ *
+ * \section features_sec Features
+ * * Small footprint (< 100KB binary)
+ * * H.264 support only (no VP8/9 support)
+ * * ANSI C99
+ *
+ */
+#ifndef _URTC_H
+#define _URTC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Opque peer connection structure.
+ *
+ * Instantiate via urtc_peerconn_create(). Each peer connection creates its
+ * own single thread for processing events in a runloop.
+ */
+typedef struct peerconn urtc_peerconn_t;
+
+/**
+ * (callback) Called for each new local ICE candidate discovered
+ *
+ * The local candidate should be relayed to the remote peer. Note that callback
+ * executes on peer connection event loop -- do not block or do other expensive
+ * work within the callback.
+ *
+ * Akin to the `onicecandidate` event handler in `RTCPeerConnection`.
+ *
+ * \param cand Discovered local candidate string.
+ * \param arg User specified argument, see urtc_set_on_ice_candidate().
+ *
+ * \return 0 on success, negative on error.
+ */
+typedef int (urtc_on_ice_candidate)(const char *cand, void *arg);
+
+/**
+ * (callback) Called to request that video encoder immediately generate an IDR
+ *
+ * H.264 video encoders produce I-frames and P-frames. Only I-frames can be
+ * decoded independently. To minimize the time-to-first-frame (TTFF), an
+ * I-frame should be sent as soon as a peer connection is established. In the
+ * event of picture loss, an I-frame should be sent as soon as possible to
+ * re-establish picture.
+ *
+ * This is an optional callback. If set, it improves the TTFF and picture
+ * recovery time from loss of picture.
+ */
+typedef void (urtc_force_idr)();
+
+
+/**
+ * Create a new peer connection
+ *
+ * Note that the new peer connection is not yet connected to a peer.
+ *
+ * Akin to `RTCPeerConnection()` in the WebRTC JS API.
+ *
+ * \param stun Array of STUN servers to use for discovery of server-reflexive
+ * ICE candidates. Each entry should be a string of the form
+ * `hostname[:port]` (e.g. "stun.l.google.com:19302"). The port
+ * is optional -- if omitted, 3478 is assumed. The final entry in
+ * the array must be NULL. If no STUN servers are provided, only
+ * local area network peers will be discoverable.
+ *
+ * \return On success, a pointer to new peer connection object is returned.
+ * The peer connection must be destroyed with urtc_peerconn_destroy()
+ * when no longer needed. On error, NULL is returned.
+ */
+urtc_peerconn_t * urtc_peerconn_create(const char *stun[]);
+
+/**
+ * Sets onIceCandidate callback function
+ *
+ * Callback function will be called when a new local ICE candidate is
+ * discovered.
+ */
+int urtc_set_on_ice_candidate(urtc_peerconn_t *pc, urtc_on_ice_candidate *cb);
+
+/**
+ * Adds received remote ICE candidate to peer connection
+ *
+ * For each ICE candidate received from the remote peer via some external
+ * signaling channel, use this function to add the candidate to the peer
+ * connection. Candidates are asynchronously used to check for direct
+ * peer-to-peer connectivity with the remote peer.
+ *
+ * Akin to `addIceCandidate` method of `RTCPeerConnection` in the WebRTC JS API.
+ *
+ * \param pc Peer connection.
+ * \param cand An ICE candidate string from remote peer.
+ *
+ * \return 0 on success, negative on error.
+ */
+int urtc_add_ice_candidate(urtc_peerconn_t *pc, const char *cand);
+
+/**
+ * Creates local description for an answering peer connection
+ *
+ * When a peer connection is the answering party, generates a local
+ * description (in Session Description Protocol). This description
+ * should be both sent to remote peer connection via some external
+ * signaling channel as well as added to the local peer connection via
+ * urtc_set_local_description().
+ *
+ * Akin to `createAnswer` method of `RTCPeerConnection` in WebRTC JS API.
+ *
+ * \param pc Peer connection
+ * \param answer Pointer to string pointer pointing to generated answer.
+ * Memory internally managed (caller need not and must not free).
+ *
+ * \return 0 on success, negative on error.
+ */
+int urtc_create_answer(urtc_peerconn_t *pc, char **answer);
+
+/**
+ * Creates a local description for an offering peer connection
+ *
+ * When a peer connection is the offering party, this function generates a
+ * local description (in Session Description Protocol). This description
+ * should be both sent to the remote peer connection via some external
+ * signaling channel as well as added to the local peer connection via
+ * urtc_set_local_description().
+ *
+ * Akin to `createOffer` method of `RTCPeerConnection` in WebRTC JS API.
+ *
+ * \param pc Peer connection
+ * \param offer Pointer to string pointer pointing to generated offer.
+ * Memory interally managed (caller need no and must not free).
+ *
+ * \return 0 on success, negative on error.
+ */
+int urtc_create_offer(urtc_peerconn_t *pc, char **offer);
+
+/**
+ * Sets local description for peer connection
+ *
+ * The local description is a Session Description Protocol string, typically
+ * generated by urtc_create_offer() or urtc_create_answer(), depending on
+ * whether the local peer connection is the offering or answering party,
+ * respectively. Setting the local description begins local ICE candidate
+ * discovery.
+ *
+ * Akin to `setLocalDescription` method of `RTCPeerConnection` in WebRTC JS API.
+ *
+ * \param pc Peer connection previously created via urtc_peerconn_create().
+ * \param desc Local description in Session Description Protocol (SDP)
+ *
+ * \return 0 on success, negative on error.
+ */
+int urtc_set_local_description(urtc_peerconn_t *pc, const char *desc);
+
+/**
+ * Sets remote description
+ *
+ * The remote description is a Session Description Protocol string,
+ * received from the remote peer via some external signaling channel.
+ * Amongst other things, it defines the set of media codecs the remote
+ * peer supports.
+ *
+ * Akin to `setRemoteDescription` method of `RTCPeerConnection` in WebRTC JS
+ * API.
+ *
+ * \param pc Peer connection created by urtc_peerconn_create().
+ * \param desc Session description protocol.
+ *
+ * \return 0 on success, negative on error.
+ */
+int urtc_set_remote_description(urtc_peerconn_t *pc, const char *desc);
+
+/**
+ * Tears down peer connection, closing and freeing all resources
+ *
+ * \param pc Peer connection.
+ */
+void urtc_peerconn_destroy(urtc_peerconn_t *pc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _URTC_H */
diff --git a/src/uuid.c b/src/uuid.c
new file mode 100644
index 0000000..6dfa4c1
--- /dev/null
+++ b/src/uuid.c
@@ -0,0 +1,76 @@
+/**
+ * liburtc
+ * Copyright 2020 Chris Hiszpanski
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/**
+ * Pseudo-random univerally-unique identifier generator [^RFC4122].
+ *
+ * [^RFC4122]: https://tools.ietf.org/html/rfc4122#section-4.4
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "prng.h"
+#include "uuid.h"
+
+/**
+ * Generate a pseudo-random UUID.
+ *
+ * \return Populated UUID.
+ */
+struct uuid uuid_create() {
+ struct uuid u;
+
+ // fill with randomness
+ prng(&u, sizeof(u));
+
+ // set reserved and version bits
+ u.clk_seq_hi_and_reserved = (u.clk_seq_hi_and_reserved & 0x3F) | 0x80;
+ u.time_hi_and_version = (u.time_hi_and_version & 0x0FFF) | 0x4000;
+
+ return u;
+}
+
+/**
+ * Generate pseudorandom UUID (as a string)
+ *
+ * \param s Destination string memory.
+ *
+ */
+void uuid_create_str(char s[UUID_STR_LEN]) {
+ struct uuid u = uuid_create();
+
+ sprintf(s, "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ u.time_low,
+ u.time_mid,
+ u.time_hi_and_version,
+ u.clk_seq_hi_and_reserved,
+ u.clk_seq_low,
+ u.node[0],
+ u.node[1],
+ u.node[2],
+ u.node[3],
+ u.node[4],
+ u.node[5]
+ );
+}
diff --git a/src/uuid.h b/src/uuid.h
new file mode 100644
index 0000000..464f33e
--- /dev/null
+++ b/src/uuid.h
@@ -0,0 +1,54 @@
+/**
+ * liburtc
+ * Copyright 2020 Chris Hiszpanski
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef _URTC_UUID_H
+#define _URTC_UUID_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+
+#define UUID_SIZE sizeof(struct uuid)
+
+#define UUID_STR_LEN (32 + 4 + 1) // 16 hex octets, 4 dashes, 1 null terminator
+
+struct __attribute__((__packed__)) uuid {
+ uint32_t time_low;
+ uint16_t time_mid;
+ uint16_t time_hi_and_version;
+ uint8_t clk_seq_hi_and_reserved;
+ uint8_t clk_seq_low;
+ uint8_t node[6];
+};
+
+struct uuid uuid_create();
+
+void uuid_create_str(char uuid[UUID_STR_LEN]);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _URTC_UUID_H
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..27ebf83
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,32 @@
+# Build test programs (run with 'make check')
+TESTS = $(check_PROGRAMS)
+check_PROGRAMS = \
+ g711_test \
+ mdns_test \
+ sdp_test \
+ uuid_test
+
+g711_test_CFLAGS = -I$(top_srcdir)/src
+g711_test_SOURCES = \
+ g711_test.c \
+ $(top_srcdir)/src/g711.c \
+ $(top_srcdir)/src/g711_tables.c
+g711_test_LDADD = $(top_builddir)/src/liburtc.la
+
+mdns_test_CFLAGS = -I$(top_srcdir)/src
+mdns_test_SOURCES = \
+ mdns_test.c \
+ $(top_srcdir)/src/mdns.c
+mdns_test_LDADD = $(top_builddir)/src/liburtc.la
+
+sdp_test_CFLAGS = -I$(top_srcdir)/src
+sdp_test_SOURCES = \
+ sdp_test.c \
+ $(top_srcdir)/src/sdp.c
+sdp_test_LDADD = $(top_builddir)/src/liburtc.la
+
+uuid_test_CFLAGS = -I$(top_srcdir)/src
+uuid_test_SOURCES = \
+ uuid_test.c \
+ $(top_srcdir)/src/uuid.c
+uuid_test_LDADD = $(top_builddir)/src/liburtc.la
diff --git a/tests/g711_test.c b/tests/g711_test.c
new file mode 100644
index 0000000..2e6aa53
--- /dev/null
+++ b/tests/g711_test.c
@@ -0,0 +1,53 @@
+/*
+ * liburtc
+ * Copyright (C) 2019 Chris Hiszpanski
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "g711.h"
+
+
+int main(int argc, char **argv) {
+
+ {
+ const int16_t src[] = {
+ 0,
+ 4096,
+ 8192,
+ -8192,
+ -4096
+ };
+ uint8_t dst[sizeof(src) / sizeof(int16_t)];
+ g711_encode(dst, src, sizeof(src) / sizeof(int16_t));
+ }
+
+ {
+ const uint8_t src[] = {
+ 0x00,
+ 0x7f,
+ 0x80,
+ 0xff,
+ };
+ int16_t dst[sizeof(src) / sizeof(uint8_t)];
+ g711_decode(dst, src, sizeof(src) / sizeof(uint8_t));
+ }
+
+ return 0;
+}
diff --git a/tests/mdns_test.c b/tests/mdns_test.c
new file mode 100644
index 0000000..8604696
--- /dev/null
+++ b/tests/mdns_test.c
@@ -0,0 +1,66 @@
+/**
+ * liburtc
+ * Copyright 2020 Chris Hiszpanski
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <assert.h>
+#include <stdint.h>
+
+#include "mdns.h"
+
+const uint8_t response[] = {
+ // header
+ 0x00, 0x00, // transaction id
+ 0x84, 0x00, // flags
+ 0x00, 0x01, // questions
+ 0x00, 0x02, // answer RRs
+ 0x00, 0x00, // authority RRs
+ 0x00, 0x00, // additional RRs
+
+ // query
+ 0x07, 0x6c, 0x69, 0x62, 0x75, 0x72, 0x74, 0x63, // "liburtc"
+ 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, // "local"
+ 0x00, // root record
+ 0x00, 0xff, // type: any
+ 0x80, 0x01, // class: unicast | in(ternet)
+
+ // answer
+ 0xc0, 0x0c, // name
+ 0x00, 0x1c, // type: AAAA
+ 0x00, 0x01, // class: in(ternet)
+ 0x00, 0x00, 0x00, 0x0a, // ttl
+ 0x00, 0x10, // size
+ 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // IPv6
+ 0x72, 0x85, 0xc2, 0xff, 0xfe, 0x07, 0x1f, 0x03, // IPv6 (continued)
+
+ // answer
+ 0xc0, 0x0c, // name
+ 0x00, 0x01, // type: A
+ 0x00, 0x01, // class: in(ternet)
+ 0x00, 0x00, 0x00, 0x0a, // ttl
+ 0x00, 0x04, // size
+ 0xc0, 0xa8, 0x01, 0x64 // IPv4
+};
+
+int main(int argc, char **argv) {
+ assert(0 == mdns_parse_response(response, sizeof(response)));
+ return 0;
+}
diff --git a/tests/sdp_test.c b/tests/sdp_test.c
new file mode 100644
index 0000000..c5c210c
--- /dev/null
+++ b/tests/sdp_test.c
@@ -0,0 +1,328 @@
+/**
+ * liburtc
+ * Copyright 2020 Chris Hiszpanski
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "err.h"
+#include "sdp.h"
+
+// Chrome 73.0.3683.103 (Official Build) (64-bit)
+const char *chrome =
+ "v=0\n"
+ "o=- 2136573259711410686 2 IN IP4 127.0.0.1\n"
+ "s=-\n"
+ "t=0 0\n"
+ "a=group:BUNDLE 0\n"
+ "a=msid-semantic: WMS\n"
+ "m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 122 127 121 125 107 108 109 124 120 123 119 114 115 116\n"
+ "c=IN IP4 0.0.0.0\n"
+ "a=rtcp:9 IN IP4 0.0.0.0\n"
+ "a=ice-ufrag:DPkQ\n"
+ "a=ice-pwd:23oU5vsiyBKLHbND/Ql8f7gZ\n"
+ "a=ice-options:trickle\n"
+ "a=fingerprint:sha-256 D0:44:DF:68:71:39:56:0B:D3:61:7A:F2:42:5B:1B:0A:CD:B2:72:84:3A:DE:0F:22:CA:8C:B0:06:0A:8D:A2:00\n"
+ "a=setup:actpass\n"
+ "a=mid:0\n"
+ "a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\n"
+ "a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\n"
+ "a=extmap:4 urn:3gpp:video-orientation\n"
+ "a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\n"
+ "a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\n"
+ "a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\n"
+ "a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\n"
+ "a=extmap:10 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07\n"
+ "a=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/color-space\n"
+ "a=extmap:9 urn:ietf:params:rtp-hdrext:sdes:mid\n"
+ "a=extmap:13 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\n"
+ "a=extmap:14 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\n"
+ "a=recvonly\n"
+ "a=rtcp-mux\n"
+ "a=rtcp-rsize\n"
+ "a=rtpmap:96 VP8/90000\n"
+ "a=rtcp-fb:96 goog-remb\n"
+ "a=rtcp-fb:96 transport-cc\n"
+ "a=rtcp-fb:96 ccm fir\n"
+ "a=rtcp-fb:96 nack\n"
+ "a=rtcp-fb:96 nack pli\n"
+ "a=rtpmap:97 rtx/90000\n"
+ "a=fmtp:97 apt=96\n"
+ "a=rtpmap:98 VP9/90000\n"
+ "a=rtcp-fb:98 goog-remb\n"
+ "a=rtcp-fb:98 transport-cc\n"
+ "a=rtcp-fb:98 ccm fir\n"
+ "a=rtcp-fb:98 nack\n"
+ "a=rtcp-fb:98 nack pli\n"
+ "a=fmtp:98 profile-id=0\n"
+ "a=rtpmap:99 rtx/90000\n"
+ "a=fmtp:99 apt=98\n"
+ "a=rtpmap:100 VP9/90000\n"
+ "a=rtcp-fb:100 goog-remb\n"
+ "a=rtcp-fb:100 transport-cc\n"
+ "a=rtcp-fb:100 ccm fir\n"
+ "a=rtcp-fb:100 nack\n"
+ "a=rtcp-fb:100 nack pli\n"
+ "a=fmtp:100 profile-id=2\n"
+ "a=rtpmap:101 rtx/90000\n"
+ "a=fmtp:101 apt=100\n"
+ "a=rtpmap:102 H264/90000\n"
+ "a=rtcp-fb:102 goog-remb\n"
+ "a=rtcp-fb:102 transport-cc\n"
+ "a=rtcp-fb:102 ccm fir\n"
+ "a=rtcp-fb:102 nack\n"
+ "a=rtcp-fb:102 nack pli\n"
+ "a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f\n"
+ "a=rtpmap:122 rtx/90000\n"
+ "a=fmtp:122 apt=102\n"
+ "a=rtpmap:127 H264/90000\n"
+ "a=rtcp-fb:127 goog-remb\n"
+ "a=rtcp-fb:127 transport-cc\n"
+ "a=rtcp-fb:127 ccm fir\n"
+ "a=rtcp-fb:127 nack\n"
+ "a=rtcp-fb:127 nack pli\n"
+ "a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f\n"
+ "a=rtpmap:121 rtx/90000\n"
+ "a=fmtp:121 apt=127\n"
+ "a=rtpmap:125 H264/90000\n"
+ "a=rtcp-fb:125 goog-remb\n"
+ "a=rtcp-fb:125 transport-cc\n"
+ "a=rtcp-fb:125 ccm fir\n"
+ "a=rtcp-fb:125 nack\n"
+ "a=rtcp-fb:125 nack pli\n"
+ "a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\n"
+ "a=rtpmap:107 rtx/90000\n"
+ "a=fmtp:107 apt=125\n"
+ "a=rtpmap:108 H264/90000\n"
+ "a=rtcp-fb:108 goog-remb\n"
+ "a=rtcp-fb:108 transport-cc\n"
+ "a=rtcp-fb:108 ccm fir\n"
+ "a=rtcp-fb:108 nack\n"
+ "a=rtcp-fb:108 nack pli\n"
+ "a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f\n"
+ "a=rtpmap:109 rtx/90000\n"
+ "a=fmtp:109 apt=108\n"
+ "a=rtpmap:124 H264/90000\n"
+ "a=rtcp-fb:124 goog-remb\n"
+ "a=rtcp-fb:124 transport-cc\n"
+ "a=rtcp-fb:124 ccm fir\n"
+ "a=rtcp-fb:124 nack\n"
+ "a=rtcp-fb:124 nack pli\n"
+ "a=fmtp:124 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d0032\n"
+ "a=rtpmap:120 rtx/90000\n"
+ "a=fmtp:120 apt=124\n"
+ "a=rtpmap:123 H264/90000\n"
+ "a=rtcp-fb:123 goog-remb\n"
+ "a=rtcp-fb:123 transport-cc\n"
+ "a=rtcp-fb:123 ccm fir\n"
+ "a=rtcp-fb:123 nack\n"
+ "a=rtcp-fb:123 nack pli\n"
+ "a=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640032\n"
+ "a=rtpmap:119 rtx/90000\n"
+ "a=fmtp:119 apt=123\n"
+ "a=rtpmap:114 red/90000\n"
+ "a=rtpmap:115 rtx/90000\n"
+ "a=fmtp:115 apt=114\n"
+ "a=rtpmap:116 ulpfec/90000\n";
+
+// Safari 13.1 (14609.1.20.111.8)
+const char *safari =
+ "v=0\n"
+ "o=- 3389190485417077944 2 IN IP4 127.0.0.1\n"
+ "s=-\n"
+ "t=0 0\n"
+ "a=group:BUNDLE 0\n"
+ "a=msid-semantic: WMS\n"
+ "m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127 125 104\n"
+ "c=IN IP4 0.0.0.0\n"
+ "a=rtcp:9 IN IP4 0.0.0.0\n"
+ "a=ice-ufrag:yMtQ\n"
+ "a=ice-pwd:92GWQlqPVFfVjlxV2qSlQxEq\n"
+ "a=ice-options:trickle\n"
+ "a=fingerprint:sha-256 D7:41:A3:34:FC:54:27:FD:D1:2A:58:1D:9E:01:8A:C8:A9:F3:E0:BE:66:B3:D9:58:FC:7D:59:A7:BA:D6:99:F3\n"
+ "a=setup:actpass\n"
+ "a=mid:0\n"
+ "a=extmap:14 urn:ietf:params:rtp-hdrext:toffset\n"
+ "a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\n"
+ "a=extmap:13 urn:3gpp:video-orientation\n"
+ "a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\n"
+ "a=extmap:12 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay\n"
+ "a=extmap:11 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type\n"
+ "a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing\n"
+ "a=extmap:8 http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07\n"
+ "a=extmap:9 http://www.webrtc.org/experiments/rtp-hdrext/color-space\n"
+ "a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\n"
+ "a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\n"
+ "a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\n"
+ "a=sendrecv\n"
+ "a=msid:- d770ebc2-b725-4de0-8314-a76a8a67695e\n"
+ "a=rtcp-mux\n"
+ "a=rtcp-rsize\n"
+ "a=rtpmap:96 H264/90000\n"
+ "a=rtcp-fb:96 goog-remb\n"
+ "a=rtcp-fb:96 transport-cc\n"
+ "a=rtcp-fb:96 ccm fir\n"
+ "a=rtcp-fb:96 nack\n"
+ "a=rtcp-fb:96 nack pli\n"
+ "a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640c1f\n"
+ "a=rtpmap:97 rtx/90000\n"
+ "a=fmtp:97 apt=96\n"
+ "a=rtpmap:98 H264/90000\n"
+ "a=rtcp-fb:98 goog-remb\n"
+ "a=rtcp-fb:98 transport-cc\n"
+ "a=rtcp-fb:98 ccm fir\n"
+ "a=rtcp-fb:98 nack\n"
+ "a=rtcp-fb:98 nack pli\n"
+ "a=fmtp:98 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\n"
+ "a=rtpmap:99 rtx/90000\n"
+ "a=fmtp:99 apt=98\n"
+ "a=rtpmap:100 VP8/90000\n"
+ "a=rtcp-fb:100 goog-remb\n"
+ "a=rtcp-fb:100 transport-cc\n"
+ "a=rtcp-fb:100 ccm fir\n"
+ "a=rtcp-fb:100 nack\n"
+ "a=rtcp-fb:100 nack pli\n"
+ "a=rtpmap:101 rtx/90000\n"
+ "a=fmtp:101 apt=100\n"
+ "a=rtpmap:127 red/90000\n"
+ "a=rtpmap:125 rtx/90000\n"
+ "a=fmtp:125 apt=127\n"
+ "a=rtpmap:104 ulpfec/90000\n"
+ "a=ssrc-group:FID 737915040 1139142965\n"
+ "a=ssrc:737915040 cname:uGvMyunFXUYJjCuf\n"
+ "a=ssrc:737915040 msid:- d770ebc2-b725-4de0-8314-a76a8a67695e\n"
+ "a=ssrc:737915040 mslabel:-\n"
+ "a=ssrc:737915040 label:d770ebc2-b725-4de0-8314-a76a8a67695e\n"
+ "a=ssrc:1139142965 cname:uGvMyunFXUYJjCuf\n"
+ "a=ssrc:1139142965 msid:- d770ebc2-b725-4de0-8314-a76a8a67695e\n"
+ "a=ssrc:1139142965 mslabel:-\n"
+ "a=ssrc:1139142965 label:d770ebc2-b725-4de0-8314-a76a8a67695e\n";
+
+int main(int argc, char **argv) {
+
+ // Test SDP deserialize
+ {
+ sdp_t sdp;
+ assert(-URTC_ERR_BAD_ARGUMENT == sdp_parse(&sdp, NULL));
+ assert(0 == sdp_parse(&sdp, ""));
+ }
+
+ {
+ sdp_t sdp;
+ assert(0 == sdp_parse(&sdp, chrome));
+ assert(0 == sdp.version);
+ assert(0 == sdp.start_time);
+ assert(0 == sdp.stop_time);
+
+ }
+
+ // Test parser against Chrome offer
+ {
+ sdp_t sdp;
+ assert(0 == sdp_parse(&sdp, chrome));
+ assert(0 == sdp.version);
+ assert(0 == strcmp("-", sdp.username));
+ assert(0 == strcmp("2136573259711410686", sdp.session_id));
+ assert(0 == strcmp("2", sdp.session_version));
+ assert(0 == sdp.start_time);
+ assert(0 == sdp.stop_time);
+ {
+ assert(21 == sdp.video.count);
+ assert(9 == sdp.video.port);
+ unsigned int expected_type[] = {
+ 96, 97, 98, 99, 100, 101, 102, 122, 127, 121,
+ 125, 107, 108, 109, 124, 120, 123, 119, 114, 115,
+ 116
+ };
+ for (int i = 0; i < sdp.video.count; i++) {
+ assert(expected_type[i] == sdp.video.params[i].type);
+ }
+ }
+ assert(0 == strcmp("DPkQ", sdp.ufrag));
+ assert(0 == strcmp("23oU5vsiyBKLHbND/Ql8f7gZ", sdp.pwd));
+ assert(true == sdp.ice_options.trickle);
+ {
+ uint8_t expected_fingerprint[] = {
+ 0xD0, 0x44, 0xDF, 0x68, 0x71, 0x39, 0x56, 0x0B,
+ 0xD3, 0x61, 0x7A, 0xF2, 0x42, 0x5B, 0x1B, 0x0A,
+ 0xCD, 0xB2, 0x72, 0x84, 0x3A, 0xDE, 0x0F, 0x22,
+ 0xCA, 0x8C, 0xB0, 0x06, 0x0A, 0x8D, 0xA2, 0x00
+ };
+ assert(0 == memcmp(sdp.fingerprint.sha256, expected_fingerprint, 32));
+ }
+ assert(SDP_MODE_RECEIVE_ONLY == sdp.mode);
+ assert(true == sdp.rtcp_mux);
+ assert(true == sdp.rtcp_rsize);
+ }
+
+ // Test parser against Safari offer
+ {
+ sdp_t sdp;
+ assert(0 == sdp_parse(&sdp, safari));
+ assert(0 == sdp.version);
+ assert(0 == strcmp("-", sdp.username));
+ assert(0 == strcmp("3389190485417077944", sdp.session_id));
+ assert(0 == strcmp("2", sdp.session_version));
+ assert(0 == sdp.start_time);
+ assert(0 == sdp.stop_time);
+ {
+ assert(9 == sdp.video.count);
+ assert(9 == sdp.video.port);
+ unsigned int expected_type[] = {
+ 96, 97, 98, 99, 100, 101, 127, 125, 104
+ };
+ for (int i = 0; i < sdp.video.count; i++) {
+ assert(expected_type[i] == sdp.video.params[i].type);
+ }
+ }
+ assert(0 == strcmp("yMtQ", sdp.ufrag));
+ assert(0 == strcmp("92GWQlqPVFfVjlxV2qSlQxEq", sdp.pwd));
+ assert(true == sdp.ice_options.trickle);
+ {
+ uint8_t expected_fingerprint[] = {
+ 0xD7, 0x41, 0xA3, 0x34, 0xFC, 0x54, 0x27, 0xFD,
+ 0xD1, 0x2A, 0x58, 0x1D, 0x9E, 0x01, 0x8A, 0xC8,
+ 0xA9, 0xF3, 0xE0, 0xBE, 0x66, 0xB3, 0xD9, 0x58,
+ 0xFC, 0x7D, 0x59, 0xA7, 0xBA, 0xD6, 0x99, 0xF3
+ };
+ assert(0 == memcmp(sdp.fingerprint.sha256, expected_fingerprint, 32));
+ }
+ assert(SDP_MODE_SEND_AND_RECEIVE == sdp.mode);
+ assert(true == sdp.rtcp_mux);
+ assert(true == sdp.rtcp_rsize);
+ }
+
+ // Test SDP serialize
+ {
+ char str[2048];
+ sdp_t sdp = {
+ .ice_options.trickle = true,
+ .rtcp_mux = true
+ };
+ assert(0 == sdp_serialize(str, sizeof(str), &sdp));
+ fprintf(stderr, "%s", str);
+ }
+
+ return 0;
+}
diff --git a/tests/str_test.c b/tests/str_test.c
new file mode 100644
index 0000000..58e6a20
--- /dev/null
+++ b/tests/str_test.c
@@ -0,0 +1,31 @@
+/*
+ * liburtc
+ * Copyright (C) 2019 Chris Hiszpanski
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "string.h"
+
+int main(int argc, char **argv) {
+ {
+ string_t *s = string_create_empty(3);
+ string_append(s, "one");
+ string_append(s, "two");
+ string_append(s, "three");
+ string_destroy(s);
+ }
+
+ return 0;
+}
diff --git a/tests/uuid_test.c b/tests/uuid_test.c
new file mode 100644
index 0000000..a515005
--- /dev/null
+++ b/tests/uuid_test.c
@@ -0,0 +1,45 @@
+/**
+ * liburtc
+ * Copyright 2020 Chris Hiszpanski
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+
+#include "uuid.h"
+
+int main(int argc, char **argv) {
+ {
+ char sa[UUID_STR_LEN];
+
+ uuid_create_str(sa);
+
+ assert(sa[ 8] == '-');
+ assert(sa[13] == '-');
+ assert(sa[14] == '4');
+ assert(sa[18] == '-');
+ assert(sa[19] == '8' || sa[19] == '9' ||
+ sa[19] == 'a' || sa[19] == 'b');
+ assert(sa[23] == '-');
+ }
+
+ return 0;
+}