Added SILC XML API
authorPekka Riikonen <priikone@silcnet.org>
Sat, 21 Jun 2008 12:08:54 +0000 (15:08 +0300)
committerPekka Riikonen <priikone@silcnet.org>
Sat, 21 Jun 2008 12:08:54 +0000 (15:08 +0300)
The SILC XML API provides (or will provide) a simple stream based
XML parsing interface.  It is wrapper to Expat XML library.

Added --with-expat configuration option too in case the automatic
detection of expat doesn't find it in the system.

TODO
configure.ad
lib/silcutil/Makefile.ad
lib/silcutil/silcruntime.h.in
lib/silcutil/silcxml.c [new file with mode: 0644]
lib/silcutil/silcxml.h [new file with mode: 0644]
lib/silcutil/tests/Makefile.am
lib/silcutil/tests/test_silcxml.c [new file with mode: 0644]

diff --git a/TODO b/TODO
index 7ebae882fdd9d13af89fedfae44d8a81d5ab7202..0cb16895e7d934f9829daa23427408b0d5cf720a 100644 (file)
--- a/TODO
+++ b/TODO
@@ -44,6 +44,10 @@ Runtime library, lib/silcutil/
    SILC currently supports SOCKS4 and SOCKS5 but it needs to be compiled
    in separately.
 
+ o Add silc_xml_parse_stream to parse SilcStream XML stream.
+
+ o SILC XML API (wrapper to expat). (***DONE)
+
  o Bring silchttp HTTP server library to SRT.  (***DONE)
 
  o Simple SILC Rand API for pseudo-random numbers.  (***DONE)
@@ -139,19 +143,6 @@ Runtime library, lib/silcutil/
    rwlock implementation using atomic operations.) not for now.
 
 
-SILC XML Library, lib/silcxml/
-==============================
-
- o SILC XML API (wrapper to expat).  Look at the expat API and simplify
-   it.  The SILC XML API should have at most 8-10 API functions.  It should
-   be possible to create full XML parser with only one function.  And, it
-   should be possible to have a function that is able to parse an entire
-   XML document.  It should also have a parser function to be able to
-   parse a stream of XML data (SilcStream).  It MUST NOT have operations
-   that require multiple function calls to be able to execute that one
-   operation (like creating parser).
-
-
 Windows Support
 ===============
 
index cf3373582f0a6df19d83bc82ae671c7bbfb5817c..1fe945ea7c33a5d22345b3cf764deeb0a6eced70 100644 (file)
@@ -1193,6 +1193,30 @@ if test x$has_threads = xtrue; then
 fi
 
 
+# Check for Expat
+AC_ARG_WITH(expat,
+  [[  --with-expat[=DIR]      use Expat XML [search in DIR/include and DIR/lib]]],
+  [
+    case "${withval}" in
+      no)
+        ;;
+      *)
+        if test -d $withval/include; then
+          CPPFLAGS="$CPPFLAGS -I$withval/include"
+          CFLAGS="$CFLAGS -I$withval/include"
+        fi
+        if test -d $withval/lib; then
+          LDFLAGS="$LDFLAGS -L$withval/lib"
+        fi
+        ;;
+    esac
+  ])
+
+AC_CHECK_HEADERS(expat.h,
+  [ LIBS="$LIBS -lexpat" ],
+  [ AC_MSG_ERROR(Expat XML Library is required to compile SRT) ])
+
+
 ##
 ## Native WIN32 compilation under cygwin
 ##
index 75df24b23722da997fc8dd777dfa5c5dc64f3f08..972b83d68c29c8e5812b97011b20c8bc4602d586 100644 (file)
@@ -68,7 +68,8 @@ libsilcutil_la_SOURCES = \
        silcthreadqueue.c \
        silcrand.c      \
        silcglobal.c    \
-       silcbufferstream.c
+       silcbufferstream.c \
+       silcxml.c
 
 include_HEADERS =      \
        $(SILC_DIST_HEADER) \
@@ -124,7 +125,8 @@ include_HEADERS =   \
        silcglobal.h    \
        silcruntime.h   \
        silcdir.h       \
-       silcbufferstream.h
+       silcbufferstream.h \
+       silcxml.h
 
 SILC_EXTRA_DIST =
 
index 503b3924a8125e7f9a99ae5063974f3222082455..2ad645e8d46e484c2e28a05c4d97b8f785d07d6d 100644 (file)
@@ -198,8 +198,8 @@ extern "C" {
 #endif /* __SILC_ENABLE_STACKTRACE */
 
 /* SILC Runtime Toolkit includes */
-#include <silcerrno.h>
 #include <silctypes.h>
+#include <silcerrno.h>
 #include <silcbitops.h>
 #include <silcmutex.h>
 #include <silcatomic.h>
@@ -240,6 +240,7 @@ extern "C" {
 #include <silcmime.h>
 #include <silcrand.h>
 #include <silcbufferstream.h>
+#include <silcxml.h>
 #include <silchttpserver.h>
 #include <silchttpphp.h>
 
diff --git a/lib/silcutil/silcxml.c b/lib/silcutil/silcxml.c
new file mode 100644 (file)
index 0000000..decadb6
--- /dev/null
@@ -0,0 +1,282 @@
+/*
+
+  silcxml.c
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  Copyright (C) 2008 Pekka Riikonen
+
+  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; version 2 of the License.
+
+  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.
+
+*/
+
+#include <silcruntime.h>
+
+/* XML parser context */
+struct SilcXMLParserStruct {
+  void *parser;                               /* Parser implementation */
+  SilcXMLParserHandlerStruct handler;  /* Handler */
+  void *context;                      /* User context */
+  SilcXMLParamsStruct params;         /* Parser parameters */
+};
+
+#ifdef HAVE_EXPAT_H
+
+#include <expat.h>
+
+/* Map expat error to silc_errno */
+
+static SilcResult silc_xml_expat_error(XML_Parser parser)
+{
+  enum XML_Error error = XML_GetErrorCode(parser);
+
+  switch (error) {
+  case XML_ERROR_NONE:
+    return SILC_OK;
+  case XML_ERROR_NO_MEMORY:
+    return SILC_ERR_OUT_OF_MEMORY;
+  case XML_ERROR_UNKNOWN_ENCODING:
+  case XML_ERROR_INCORRECT_ENCODING:
+    return SILC_ERR_BAD_ENCODING;
+  case XML_ERROR_ABORTED:
+    return SILC_ERR_ABORTED;
+  default:
+    return SILC_ERR_SYNTAX;
+  }
+}
+
+/* Return error string */
+
+static const char *silc_xml_get_error(SilcXMLParser parser)
+{
+  return XML_ErrorString(XML_GetErrorCode(parser->parser));
+}
+
+/* Start element */
+
+static void silc_xml_expat_start_element(void *userData,
+                                        const XML_Char *name,
+                                        const XML_Char **atts)
+{
+  SilcXMLParser parser = userData;
+  SilcHashTable t = NULL;
+  int i;
+
+  if (atts && atts[0]) {
+    t = silc_hash_table_alloc(NULL, 0, silc_hash_utf8_string, NULL,
+                             silc_hash_utf8_compare, NULL,
+                             NULL, NULL, TRUE);
+    if (!t) {
+      silc_set_errno(SILC_ERR_OUT_OF_MEMORY);
+      silc_set_errno_location(NULL,
+                             XML_GetCurrentLineNumber(parser->parser),
+                             XML_GetCurrentColumnNumber(parser->parser));
+      XML_StopParser(parser->parser, FALSE);
+      return;
+    }
+
+    for (i = 0; atts[i]; i += 2)
+      silc_hash_table_add(t, (void *)atts[i], (void *)atts[i + 1]);
+  }
+
+  if (parser->handler.start_element)
+    parser->handler.start_element(parser, name, t, parser->context);
+
+  if (t)
+    silc_hash_table_free(t);
+}
+
+/* End element */
+
+static void silc_xml_expat_end_element(void *userData,
+                                      const XML_Char *name)
+{
+  SilcXMLParser parser = userData;
+
+  if (parser->handler.end_element)
+    parser->handler.end_element(parser, name, parser->context);
+}
+
+/* Characters */
+
+static void silc_xml_expat_data(void *userData,
+                               const XML_Char *s,
+                               int len)
+
+{
+  SilcXMLParser parser = userData;
+
+  if (parser->handler.data)
+    parser->handler.data(parser, (const unsigned char *)s,
+                        (SilcUInt32)len, parser->context);
+}
+
+/* Processing instruction */
+
+static void silc_xml_expat_pi(void *userData,
+                             const XML_Char *target,
+                             const XML_Char *data)
+{
+  SilcXMLParser parser = userData;
+
+  if (parser->handler.pi)
+    parser->handler.pi(parser, target, data, parser->context);
+}
+
+/* Create parser */
+
+SilcXMLParser silc_xml_parser_create(SilcXMLParams params,
+                                    SilcXMLParserHandler handler,
+                                    void *context)
+{
+  SilcXMLParser parser;
+  XML_Parser ep;
+
+  parser = silc_calloc(1, sizeof(*parser));
+  if (!parser)
+    return NULL;
+
+  SILC_LOG_DEBUG(("Allcoated XML parser %p", parser));
+
+  if (params)
+    parser->params = *params;
+  if (handler)
+    parser->handler = *handler;
+  parser->context = context;
+
+  /* Allocate expat parser */
+  if (parser->params.no_namespace)
+    ep = XML_ParserCreate("UTF-8");
+  else
+    ep = XML_ParserCreateNS("UTF-8", '\0');
+
+  if (!ep) {
+    silc_set_errno(SILC_ERR_OUT_OF_MEMORY);
+    silc_free(ep);
+    return NULL;
+  }
+
+  parser->parser = ep;
+
+  /* Set callbacks */
+  XML_SetUserData(ep, parser);
+  XML_SetElementHandler(ep, silc_xml_expat_start_element,
+                       silc_xml_expat_end_element);
+  XML_SetCharacterDataHandler(ep, silc_xml_expat_data);
+  XML_SetProcessingInstructionHandler(ep, silc_xml_expat_pi);
+
+  return parser;
+}
+
+/* Free parser */
+
+void silc_xml_parser_free(SilcXMLParser parser)
+{
+  if (!parser)
+    return;
+
+  SILC_LOG_DEBUG(("Free XML parser %p", parser));
+
+  if (parser->parser)
+    XML_ParserFree(parser->parser);
+  silc_free(parser);
+}
+
+/* Parse */
+
+SilcBool silc_xml_parse(SilcXMLParser parser,
+                       const unsigned char *data,
+                       SilcUInt32 data_len)
+{
+  int ret;
+
+  SILC_LOG_DEBUG(("Parse XML data with parser %p", parser));
+
+  if (!parser || !data) {
+    silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
+    return FALSE;
+  }
+
+  /* Parse */
+  ret = XML_Parse(parser->parser, (const char *)data, (int)data_len, 1);
+  if (!ret) {
+    silc_set_errno_reason(silc_xml_expat_error(parser->parser),
+                         silc_xml_get_error(parser));
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+/* Parse file */
+
+SilcBool silc_xml_parse_file(SilcXMLParser parser,
+                            const char *filename)
+{
+  unsigned char *data;
+  SilcUInt32 data_len;
+  SilcBool ret;
+
+  if (!filename) {
+    silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
+    return FALSE;
+  }
+
+  SILC_LOG_DEBUG(("Parse XML file '%s' with parser %p", filename, parser));
+
+  data = silc_file_readfile(filename, &data_len, NULL);
+  if (!data)
+    return FALSE;
+
+  ret = silc_xml_parse(parser, data, data_len);
+  if (!ret) {
+    silc_set_errno_reason(silc_xml_expat_error(parser->parser),
+                         silc_xml_get_error(parser));
+    silc_set_errno_location(filename,
+                           XML_GetCurrentLineNumber(parser->parser),
+                           XML_GetCurrentColumnNumber(parser->parser));
+    return FALSE;
+  }
+
+  silc_free(data);
+
+  return ret;
+}
+
+/* Get attribute */
+
+const char *silc_xml_get_attribute(SilcXMLParser parser,
+                                  SilcHashTable attributes,
+                                  const char *name)
+{
+  char *val;
+
+  if (!attributes)
+    return NULL;
+
+  if (!silc_hash_table_find(attributes, (void *)name, NULL, (void *)&val))
+    return NULL;
+
+  return val;
+}
+
+/* Return current location */
+
+void silc_xml_current_location(SilcXMLParser parser,
+                              SilcUInt32 *current_line,
+                              SilcUInt32 *current_column)
+{
+  if (current_line)
+    *current_line = XML_GetCurrentLineNumber(parser->parser);
+  if (current_column)
+    *current_column = XML_GetCurrentColumnNumber(parser->parser);
+}
+
+#endif /* HAVE_EXPAT_H */
diff --git a/lib/silcutil/silcxml.h b/lib/silcutil/silcxml.h
new file mode 100644 (file)
index 0000000..e6ccce8
--- /dev/null
@@ -0,0 +1,244 @@
+/*
+
+  silcxml.h
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  Copyright (C) 2008 Pekka Riikonen
+
+  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; version 2 of the License.
+
+  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.
+
+*/
+
+/****h* silcutil/XML Interface
+ *
+ * DESCRIPTION
+ *
+ * XML parser interface provides simple stream based interface for parsing
+ * XML data and files.
+ *
+ * EXAMPLE
+ *
+ * SilcXMLParser parser;
+ *
+ * // Create XML parser
+ * parser = silc_xml_parser_create(NULL, &handler, ctx);
+ *
+ * // Parse XML file
+ * if (!silc_xml_parse_file(parser, filename)) {
+ *   silc_errno_location(NULL, &cur_line, NULL);
+ *   fatal("Error %s:%d: %s", filename, cur_line, silc_errno_reason());
+ * }
+ *
+ * // Free parser
+ * silc_xml_parser_free(parser);
+ *
+ ***/
+
+#ifndef SILCXML_H
+#define SILCXML_H
+
+/****s* silcutil/SilcXMLParser
+ *
+ * NAME
+ *
+ *    typedef struct SilcXMLParserStruct *SilcXMLParser;
+ *
+ * DESCRIPTION
+ *
+ *    The XLM parser context allocated by silc_xml_parser_create.  It is
+ *    freed by calling silc_xml_parser_free.
+ *
+ ***/
+typedef struct SilcXMLParserStruct *SilcXMLParser;
+
+/****s* silcutil/SilcXMLParserHandler
+ *
+ * NAME
+ *
+ *    typedef struct SilcXMLParserHandlerObject { ... }
+ *                   SilcXMLParserHandler, SilcXMLParserHandlerStruct;
+ *
+ * DESCRIPTION
+ *
+ *    The XML parser handler function callbacks are declared in this
+ *    structure.  The structure is given as argument to the
+ *    silc_xml_parser_create.
+ *
+ * SOURCE
+ */
+typedef struct SilcXMLParserHandlerObject {
+  /* Called at the start of an XML element.  The `name' is the element name.
+     The `attributes' is the element attributes or NULL if there were no
+     attributes.  The `attributes' may be enumerated using the SilcHashTable
+     API.  The silc_xml_get_attribute can be used to retrieve the attribute
+     values from the `attributes' by their name. */
+  void (*start_element)(SilcXMLParser parser,
+                       const char *name,
+                       SilcHashTable attributes,
+                       void *context);
+
+  /* Called and the end of an XML element.  The `name' is the element name. */
+  void (*end_element)(SilcXMLParser parser,
+                     const char *name,
+                     void *context);
+
+  /* Called to deliver the characters or whatever data is in the element. */
+  void (*data)(SilcXMLParser parser,
+              const unsigned char *data,
+              SilcUInt32 data_len,
+              void *context);
+
+  /* Called to deliver a processing instruction.  The `target' is the first
+     word in the processing instruction.  The `data' is the rest of the
+     characters in it skipping all whitespace after the initial word.  This
+     callback may be NULL if it is not needed. */
+  void (*pi)(SilcXMLParser parser,
+            const char *target,
+            const char *data,
+            void *context);
+} *SilcXMLParserHandler, SilcXMLParserHandlerStruct;
+/***/
+
+/****s* silcutil/SilcXMLParams
+ *
+ * NAME
+ *
+ *    typedef struct SilcXMLParamsObject { ... }
+ *                                *SilcXMLParams, SilcXMLParamsStruct;
+ *
+ * DESCRIPTION
+ *
+ *    The XML parser parameters that can be give as argument to the
+ *    silc_xml_parser_create.
+ *
+ * SOURCE
+ */
+typedef struct SilcXMLParamsObject {
+  /* Do not process XML namespaces. */
+  SilcBool no_namespace;
+} *SilcXMLParams, SilcXMLParamsStruct;
+
+/****f* silcutil/silc_xml_parser_create
+ *
+ * SYNOPSIS
+ *
+ *    SilcXMLParser silc_xml_parser_create(SilcXMLParams params,
+ *                                         SilcXMLParserHandler handler,
+ *                                         void *context);
+ *
+ * DESCRIPTION
+ *
+ *    Create XML parser and return in.  The `handler' contains the callback
+ *    functions to be called while parsing XML data.  The `context' is
+ *    delivered to each callback function.  The `params' define parser
+ *    parameters, and may be NULL.  The parser parses XML data with UTF-8
+ *    encoding.  All characters delivered to callbacks are in UTF-8 encoding.
+ *
+ ***/
+SilcXMLParser silc_xml_parser_create(SilcXMLParams params,
+                                    SilcXMLParserHandler handler,
+                                    void *context);
+
+/****f* silcutil/silc_xml_parser_free
+ *
+ * SYNOPSIS
+ *
+ *    void silc_xml_parser_free(SilcXMLParser parser);
+ *
+ * DESCRIPTION
+ *
+ *    Free's XML parser.
+ *
+ ***/
+void silc_xml_parser_free(SilcXMLParser parser);
+
+/****f* silcutil/silc_xml_parse
+ *
+ * SYNOPSIS
+ *
+ *    SilcBool silc_xml_parse(SilcXMLParser parser,
+ *                            const unsigned char *data,
+ *                            SilcUInt32 data_len);
+ *
+ * DESCRIPTION
+ *
+ *    Parse XML data `data' of length of `data_len' bytes.  Returns TRUE
+ *    after the data has been parsed.  The handler callback functions set for
+ *    `parser' will be called while parsing the XML data.
+ *
+ *    Returns FALSE and set silc_errno and silc_errno_reason if error
+ *    occurs.
+ *
+ ***/
+SilcBool silc_xml_parse(SilcXMLParser parser,
+                       const unsigned char *data,
+                       SilcUInt32 data_len);
+
+/****f* silcutil/silc_xml_parse_file
+ *
+ * SYNOPSIS
+ *
+ *    SilcBool silc_xml_parse_file(SilcXMLParser parser,
+ *                                 const char *filename);
+ *
+ * DESCRIPTION
+ *
+ *    Parse XML file indicated by `filename'.  Returns TRUE after the file
+ *    has been parsed.  The handler callback functions set for `parser' will
+ *    be called while parsing the XML file.
+ *
+ *    Returns FALSE and set silc_errno and silc_errno_reason if error
+ *    occurs.  The silc_errno_location can be used to retrieve the exact
+ *    location in the file where the error occurred.
+ *
+ ***/
+SilcBool silc_xml_parse_file(SilcXMLParser parser,
+                            const char *filename);
+
+/****f* silcutil/silc_xml_get_attribute
+ *
+ * SYNOPSIS
+ *
+ *    const char *silc_xml_get_attribute(SilcXMLParser parser,
+ *                                       SilcHashTable attributes,
+ *                                       const char *name);
+ *
+ * DESCRIPTION
+ *
+ *    Returns the value of the attributes namaed `name' or NULL if no such
+ *    attribute exist in the hash table of `attributes'.
+ *
+ ***/
+const char *silc_xml_get_attribute(SilcXMLParser parser,
+                                  SilcHashTable attributes,
+                                  const char *name);
+
+/****f* silcutil/silc_xml_get_attribute
+ *
+ * SYNOPSIS
+ *
+ *    void silc_xml_current_location(SilcXMLParser parser,
+ *                                   SilcUInt32 *current_line,
+ *                                   SilcUInt32 *current_column);
+ *
+ * DESCRIPTION
+ *
+ *    Return the current location of the parsed XML data.  The current line
+ *    number and columns can be returned.  This may be used also when an
+ *    error occurs but it is preferred to use silc_errno_location in case
+ *    of error.
+ *
+ ***/
+void silc_xml_current_location(SilcXMLParser parser,
+                              SilcUInt32 *current_line,
+                              SilcUInt32 *current_column);
+
+#endif /* SILCXML_H */
index ceade7d458d5f6b2fb5e6d130be76b55ded0150f..bc6937041bbff27ee0f82b1cd8313533f53c19c6 100644 (file)
@@ -24,7 +24,7 @@ check_PROGRAMS = \
        test_silcatomic test_silcmutex test_silctime test_silcthread \
        test_silcdll test_silcenv test_silctimer test_silcbitops \
        test_silcregex test_silcbuffmt test_silcdir test_silcthreadqueue \
-       test_silcrand test_silcglobal test_silcbufferstream
+       test_silcrand test_silcglobal test_silcbufferstream test_silcxml
 
 TESTS = test_silcstrutil test_silcstringprep test_silchashtable \
        test_silclist test_silcfsm test_silcasync test_silcschedule \
diff --git a/lib/silcutil/tests/test_silcxml.c b/lib/silcutil/tests/test_silcxml.c
new file mode 100644 (file)
index 0000000..d24445d
--- /dev/null
@@ -0,0 +1,94 @@
+/* SILC XML tests */
+
+#include "silcruntime.h"
+
+SilcXMLParser parser;
+SilcBool success = FALSE;
+
+static void xml_start_element(SilcXMLParser parser,
+                             const char *name,
+                             SilcHashTable attributes,
+                             void *context)
+{
+  fprintf(stderr, "<%s", name);
+
+  if (attributes) {
+    SilcHashTableList htl;
+    char *att, *val;
+
+    silc_hash_table_list(attributes, &htl);
+    while (silc_hash_table_get(&htl, (void *)&att, (void *)&val))
+      fprintf(stderr, " %s='%s'", att, val);
+
+    silc_hash_table_list_reset(&htl);
+  }
+  fprintf(stderr, ">");
+}
+
+static void xml_end_element(SilcXMLParser parser,
+                           const char *name,
+                           void *context)
+{
+  fprintf(stderr, "</%s>", name);
+}
+
+static void xml_data(SilcXMLParser parser,
+                    const unsigned char *data,
+                    SilcUInt32 data_len,
+                    void *context)
+{
+  silc_file_write(2, data, data_len);
+}
+
+static void xml_pi(SilcXMLParser parser,
+                  const char *target,
+                  const char *data,
+                  void *context)
+{
+  fprintf(stderr, "%s %s", target, data);
+}
+
+static SilcXMLParserHandlerStruct handler =
+{
+  xml_start_element,
+  xml_end_element,
+  xml_data,
+  xml_pi
+};
+
+int main(int argc, char **argv)
+{
+  SilcXMLParamsStruct params;
+  SilcUInt32 cur_line;
+  char *file;
+
+  silc_runtime_init();
+
+  if (argc != 2) {
+    fprintf(stderr, "Usage: test_silcxml <filename>\n");
+    goto err;
+  }
+
+  memset(&params, 0, sizeof(params));
+  parser = silc_xml_parser_create(&params, &handler, NULL);
+  if (!parser)
+    goto err;
+
+  if (!silc_xml_parse_file(parser, argv[1])) {
+    silc_errno_location(&file, &cur_line, NULL);
+    fprintf(stderr, "%s:%d: %s\n", file, cur_line, silc_errno_reason());
+    goto err;
+  }
+
+  silc_xml_parser_free(parser);
+
+  success = TRUE;
+
+ err:
+  SILC_LOG_DEBUG(("Testing was %s", success ? "SUCCESS" : "FAILURE"));
+  fprintf(stderr, "Testing was %s\n", success ? "SUCCESS" : "FAILURE");
+
+  silc_runtime_uninit();
+
+  return !success;
+}