changeset 0:1b3f35f9f37f

Initial version, copied from dtmfx tree
author Norman Gray <norman@astro.gla.ac.uk>
date Wed, 04 Sep 2013 22:00:24 +0100
parents
children 8834a6154573
files .hgignore Makefile.am README.md RELEASE_NOTES bootstrap configure.ac src/Makefile.am src/jason.c src/jason.h src/json-parse.y src/json2der.1 src/json2der.c src/json_lex.lex src/test/Makefile.am src/test/lcut/README src/test/lcut/apr_ring.h src/test/lcut/lcut.c src/test/lcut/lcut.h src/test/unit_tests.c src/util.c src/util.h
diffstat 21 files changed, 4144 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Wed Sep 04 22:00:24 2013 +0100
@@ -0,0 +1,33 @@
+syntax: glob
+
+Makefile
+Makefile.in
+.deps
+autom4te.cache
+aclocal.m4
+config.*
+configure
+depcomp
+install-sh
+missing
+stamp-h1
+m4
+libtool
+TAGS
+
+# libtool stuff
+src/libjason.la
+.libs
+*.o
+*.lo
+
+src/json2der
+src/json-parse.tab.c
+src/json-parse.tab.h
+src/json_lex.c
+
+# testing
+src/test/unit_tests
+
+# OS X junk
+.DS_Store
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Makefile.am	Wed Sep 04 22:00:24 2013 +0100
@@ -0,0 +1,4 @@
+ACLOCAL_AMFLAGS = -I m4
+
+SUBDIRS=src
+EXTRA_DIST=README.md RELEASE_NOTES
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README.md	Wed Sep 04 22:00:24 2013 +0100
@@ -0,0 +1,129 @@
+Jason
+=====
+
+A JSON to ASN.1/DER encoder/decoder, in C.
+
+The `libjason` library is able to parse JSON, and encode it to ASN.1
+using the Distinguished Encoding Rules (DER) of ITU-T X.690.
+
+The library parses JSON as specified in [RFC 4627][], with no
+extensions.  However the string parser cannot at present handle
+Unicode strings (which also means it doesn't respect the JSON `\uXXXX`
+escape); all strings must be ASCII. 
+
+The DER *de*coder can handle some BER-encoded objects, but not all,
+and it doesn't attempt to handle ASN.1 types other than the subset
+which it generates.
+
+In that encoding, JSON integers, floats, booleans, and null, are
+encoded to the DER analogues.  A JSON array is encoded to a DER
+`SEQUENCE`, an object to a `SET OF` two-element sequences, and strings
+to DER `ia5string` objects.
+
+DER is specified in ITU-T Rec. X.690, as part of the ASN/1 suite of
+standards.  See the [ITU-T recommendation][] and [Wikipedia][], and
+Burton S. Kaliski Jr., *A Layman's Guide to a Subset of ASN.1, BER,
+and DER* (1993).
+
+
+Building
+--------
+
+Building from a distribution:
+
+    ./configure
+    make
+    make check
+
+If building from the repository, then this build sequence must be
+preceded by `./bootstrap`, and the GNU autotools must be installed.
+
+The build depends on `flex` and `bison` being in the path.
+There are no other dependencies.
+
+
+Utility program
+---------------
+
+As well as a library (documented below), this kit includes a program
+`json2der` which encodes and decodes JSON to DER on the command-line.
+See the man-page `json2der.1` for details.
+
+
+Interface
+---------
+
+The interface is still somewhat preliminary, and not documented very
+elaborately.  However...
+
+    #include <jason.h>
+
+    JsonObject parse_json_string(const char* json_string);
+
+Parse the JSON object given in the string.  If the string cannot be
+parsed, then this returns `NULL`, and an explanation is available from
+the function `const char* unsupported_message()`.
+
+    void free_json_object(JsonObject o, int free_contents);
+
+Free the object.  If the argument `free_contents` is true, then any
+contents (such as a string, for example) are freed also.
+
+    const char* print_json_object(JsonObject obj);
+
+Serialise the object to a string.  The string must be subsequently
+freed by the caller.
+
+    byte* get_der_encoding(JsonObject obj, size_t *len);
+
+Get the DER-encoding of the object.  The object should not be freed by
+the caller.
+
+    char* bytes_to_string(byte* b, size_t blen);
+
+Utility method to serialise a byte sequence to a printable string of
+hex digits.
+
+    JsonObject parse_der_bytes(byte*, size_t, size_t*);
+
+Decode a sequence of bytes which are the DER encoding of a JSON
+object, and return the object.  The result should be freed by
+`free_json_object` when no longer required.  If the byte sequence
+cannot be parsed, then this returns `NULL`, and an explanation is
+available from the function `const char* unsupported_message()`.
+
+Though this library is not intended as a general JSON parser, the
+contents of the `JsonObject` structure should be fairly stable.  See
+the `jason.h` header for the details.
+
+
+Name
+----
+
+The name *Jason* is a fairly obvious amalgam of JSON and ASN.  JASN
+would be a better name, but someone's had that idea before me, and
+[that name's already taken][jasn] for a Java ASN parser.
+
+
+Source code
+-----------
+
+The source code is available at <https://bitbucket.org/nxg/jason>.
+
+The software is Copyright 2013 Norman Gray, and is released under the
+terms of the [GNU General Public Licence, v3.0][]
+
+The distribution includes the [`lcut` unit-testing framework][lcut],
+which is Copyright 2005-2010 Tony Bai, and released under the terms of
+the Apache Licence, Version 2.0.
+
+Norman Gray  
+http://nxg.me.uk
+
+
+[ITU-T recommendation]: http://www.itu.int/rec/T-REC-X.690-200811-I/en
+[Wikipedia]: http://en.wikipedia.org/wiki/X.690
+[RFC 4627]: http://www.ietf.org/rfc/rfc4627.txt
+[GNU General Public Licence, v3.0]: http://www.gnu.org/licenses/gpl-3.0.html
+[jasn]: https://code.google.com/p/jasn/
+[lcut]: https://github.com/bigwhite/lcut
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/RELEASE_NOTES	Wed Sep 04 22:00:24 2013 +0100
@@ -0,0 +1,3 @@
+Release notes for version 0.1, XXX XXXX 2013
+
+  * Initial release
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bootstrap	Wed Sep 04 22:00:24 2013 +0100
@@ -0,0 +1,9 @@
+#! /bin/sh -
+
+# test -L install.sh || automake --foreign --add-missing
+AUTOMAKE='automake --foreign --add-missing'
+export AUTOMAKE
+test -d m4 || mkdir m4
+libtoolize
+autoheader
+autoreconf
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/configure.ac	Wed Sep 04 22:00:24 2013 +0100
@@ -0,0 +1,38 @@
+dnl  Make release version numbers match /^[0-9.]*$/
+dnl  Experimental ones are ...x, and betas are ...bn
+AC_INIT(jason, 0.1x, norman@astro.gla.ac.uk)
+AC_DEFINE(RELEASEDATE, "2013 February 8", [Release date of the current version])
+
+dnl  Embed a snapshot identifier in the binary, if it's not a release version
+if expr $PACKAGE_VERSION : ['[0-9.]*$'] >/dev/null; then
+    # release version: empty string identifies release version
+    SNAPSHOTID=""
+else
+    SNAPSHOTID="`hg id`"
+fi
+AC_DEFINE_UNQUOTED([SNAPSHOTID],
+                   ["$SNAPSHOTID"],
+                   [Description of snapshot, or blank if release version])
+
+AC_CONFIG_AUX_DIR([m4])  dnl tidy these away
+
+AM_INIT_AUTOMAKE
+LT_INIT
+AC_CONFIG_MACRO_DIR([m4])
+AM_SILENT_RULES([yes])  dnl add V=1 to make to get chatter back
+
+AC_CONFIG_SRCDIR(README.md)
+
+AC_C_BIGENDIAN
+
+AC_PROG_CC
+AC_CONFIG_HEADERS(config.h)
+
+AC_PROG_LEX
+AC_PATH_PROG(BISON, bison)
+dnl  AC_PROG_YACC
+
+dnl  Output
+AC_CONFIG_FILES([Makefile src/Makefile src/test/Makefile])
+
+AC_OUTPUT
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Makefile.am	Wed Sep 04 22:00:24 2013 +0100
@@ -0,0 +1,24 @@
+bin_PROGRAMS=json2der
+
+json2der_SOURCES = json2der.c
+json2der.$(OBJEXT): jason.h
+json2der_LDADD = libjason.la
+
+SUBDIRS=. test
+
+EXTRA_DIST=json_lex.lex json-parse.y
+
+lib_LTLIBRARIES = libjason.la
+libjason_la_SOURCES = jason.c jason.h \
+	util.c util.h \
+	json_lex.c \
+	json-parse.tab.c json-parse.tab.h
+
+json_lex.c: json-parse.tab.h
+json_lex.c: json_lex.lex
+	$(LEX) -o $@ json_lex.lex
+json-parse.tab.h json-parse.tab.c: json-parse.y
+	$(BISON) -d json-parse.y
+
+dist_man1_MANS = json2der.1
+CLEANFILES = json-parse.c json_lex.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jason.c	Wed Sep 04 22:00:24 2013 +0100
@@ -0,0 +1,1282 @@
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <setjmp.h>
+#include <math.h>
+
+#include "jason.h"
+#include "util.h"
+
+#define DBG_INTEGER 1
+#define DBG_REAL 2
+#define DBG_SEQUENCES 4
+/* #define DEBUG DBG_INTEGER|DBG_REAL|DBG_SEQUENCES */
+#define DEBUG 0
+
+static jmp_buf unsupported_jmp;
+#define UNSUPPORTED_MSG_LENGTH 128
+static char unsupported_msg[UNSUPPORTED_MSG_LENGTH];
+static void unsupported_operation(const char *msg, ...)
+{
+    va_list ap;
+
+    va_start(ap, msg);
+    vsnprintf(unsupported_msg, UNSUPPORTED_MSG_LENGTH, msg, ap);
+    va_end(ap);
+    longjmp(unsupported_jmp, 1);
+}
+// If get_der_encoding fails because of unsupported behaviour,
+// then it returns NULL and sets a message which can be retrieved
+// here.  The message will be NULL unless this is the cause of the failure.
+const char* unsupported_message() 
+{
+    if (unsupported_msg[0] == '\0') {
+        return NULL;
+    } else {
+        return unsupported_msg;
+    }
+}
+
+JsonObject initialise_json_object(JsonObject r, JsonType type)
+{
+    r->type = type;
+    r->der_encoding = NULL;
+    r->der_encoding_length = 0;
+    return r;
+}
+
+void free_json_object(JsonObject o, int free_contents)
+{
+    if (o == NULL)
+        return;
+
+    if (free_contents) {
+        switch (o->type) {
+          case JSON_STRING:
+            free((void*)o->x.s);
+            break;
+          case JSON_KVPAIR:
+            free((void*)o->x.kv.k);
+            free_json_object(o->x.kv.v, 1);
+            break;
+          case JSON_OBJECT:
+          case JSON_ARRAY:
+            {
+                int i;
+                JsonObject* p = o->x.a.p;
+                size_t np = o->x.a.len;
+                
+                for (i=0; i<np; i++) {
+                    free_json_object(p[i], 1);
+                }
+            }
+            break;
+          default:
+            // nothing to do
+            break;
+        }
+    }
+    
+    free((void*)o);
+}
+
+
+// JSON object to string
+//
+// We make some efforts to support round-tripping, but this is
+// slightly complicated for reals, enough that it's not really worth
+// bothing about getting it perfect.
+static void print_json_array(JsonObject arr, StringBuilder sb);
+static void print_json_oneobject(JsonObject obj, StringBuilder sb);
+
+void print_json_object_to_buffer(JsonObject obj, StringBuilder sb)
+{
+#define BUFLEN 64          /* can any integer or float be longer than this? */
+    char printbuf[BUFLEN];
+    
+    switch (obj->type) {
+      case JSON_STRING:
+        {
+            const char* sp = obj->x.s;
+            const char* ep;           /* character to be escaped */
+            append_to_buffer_c(sb, '\"');
+            while (sp != NULL) {
+                ep = strpbrk(sp, "\"\\/\b\f\n\r\t");
+                if (ep == NULL) {
+                    append_to_buffer_s(sb, sp);
+                    sp = NULL;
+                } else {
+                    char esc;
+                    switch (*ep) {
+                      case '\"': esc = '\"'; break;
+                      case '\\': esc = '\\'; break;
+                      case '/':  esc = '/'; break;
+                      case '\b': esc = 'b'; break;
+                      case '\f': esc = 'f'; break;
+                      case '\n': esc = 'n'; break;
+                      case '\r': esc = 'r'; break;
+                      case '\t': esc = 't'; break;
+                      default:
+                        assert(0);
+                    }
+                    append_to_buffer_n(sb, (const byte*)sp, ep-sp);
+                    append_to_buffer_c(sb, '\\');
+                    append_to_buffer_c(sb, esc);
+                    sp = ep+1;
+                }
+            }
+            append_to_buffer_c(sb, '\"');
+        }
+        break;
+      case JSON_INTEGER:
+        snprintf(printbuf, BUFLEN, "%" JSONFMT, obj->x.i);
+        append_to_buffer_s(sb, printbuf);
+        break;
+      case JSON_REAL:
+        if (nearbyint(obj->x.f) == obj->x.f) {
+            // this is an integer value, so force there to be a
+            // trailing ".0" so that this will be reparsed as a real
+            // rather than an integer.
+            snprintf(printbuf, BUFLEN, "%.0f.0", obj->x.f);
+        } else {
+            snprintf(printbuf, BUFLEN, "%g", obj->x.f);
+        }
+        append_to_buffer_s(sb, printbuf);
+        break;
+      case JSON_ARRAY:
+        print_json_array(obj, sb);
+        break;
+      case JSON_OBJECT:
+        print_json_oneobject(obj, sb);
+        break;
+      case JSON_BOOLEAN:
+        append_to_buffer_s(sb, (obj->x.b ? "true" : "false"));
+        break;
+      case JSON_NULL:
+        append_to_buffer_s(sb, "null");
+        break;
+      default:
+        error_exit("Unexpected object of type %d\n", obj->type);
+    }
+#undef BUFLEN
+}
+
+static void print_json_array(JsonObject arr, StringBuilder sb)
+{
+    JsonObject* p = arr->x.a.p;
+    size_t np = arr->x.a.len;
+    int i;
+    int first_time = 1;
+    
+    append_to_buffer_s(sb, "[ ");
+    for (i=0; i<np; i++) {
+        if (first_time)
+            first_time = 0;
+        else
+            append_to_buffer_s(sb, ", ");
+        
+        print_json_object_to_buffer(p[i], sb);
+    }
+    append_to_buffer_s(sb, " ]");
+}
+
+static void print_json_oneobject(JsonObject obj, StringBuilder sb)
+{
+    JsonObject* p = obj->x.a.p;
+    size_t np = obj->x.a.len;
+    int i;
+    int first_time = 1;
+
+    append_to_buffer_s(sb, "{ ");
+    for (i=0; i<np; i++) {
+        if (first_time)
+            first_time = 0;
+        else
+            append_to_buffer_s(sb, ", ");
+
+        append_to_buffer_c(sb, '\"');
+        append_to_buffer_s(sb, p[i]->x.kv.k);
+        append_to_buffer_s(sb, "\": ");
+        print_json_object_to_buffer(p[i]->x.kv.v, sb);
+    }
+    append_to_buffer_s(sb, " }");
+}
+
+const char* print_json_object(JsonObject obj)
+{
+    const char* rval;
+
+    if (obj == NULL) {
+        return NULL;
+    }
+    
+    StringBuilder sb = make_append_buffer();
+    print_json_object_to_buffer(obj, sb);
+    append_to_buffer_c(sb, '\0');
+    rval = (const char*)sb->buf;
+    free(sb);                   /* doesn't free sb->buf */
+    return rval;
+}
+
+// creating JSON objects
+
+static JsonObject new_object(JsonType type)
+{
+    static JsonObject r;
+    if ((r = (JsonObject)malloc(sizeof(struct json_object))) == NULL) {
+        error_exit("Unable to allocate memory for JSON object!");
+    }
+    return initialise_json_object(r, type);
+}
+
+// argument 's' should me malloc-ed, and not a constant string
+JsonObject make_string(const char* s)
+{
+    JsonObject r = new_object(JSON_STRING);
+    r->x.s = s;
+    return r;
+}
+
+JsonObject make_integer(jsonint i)
+{
+    JsonObject r = new_object(JSON_INTEGER);
+    r->x.i = i;
+    return r;
+}
+
+JsonObject make_real(double f)
+{
+    JsonObject r = new_object(JSON_REAL);
+    r->x.f = f;
+    return r;
+}
+
+JsonObject make_boolean(int b)
+{
+    JsonObject r = new_object(JSON_BOOLEAN);
+    r->x.b = b;
+    return r;
+}
+
+JsonObject make_null(void)
+{
+    JsonObject r = new_object(JSON_NULL);
+    return r;
+}
+
+// Create an array object which contains (only) the given value.  This
+// is used for both JSON 'array' objects (in which case the initial
+// object can be anything) and for JSON 'object' objects (in which
+// case the initial object will be a k-v pair).
+//
+// The 'initial' object is now 'owned' by this module.
+static JsonObject make_initial_array(JsonType type, JsonObject initial)
+{
+    JsonObject r = new_object(type);
+    if (initial == NULL) {
+        // create an object which is, and will remain, empty
+        r->x.a.p = NULL;
+        r->x.a.alloc = 0;
+        r->x.a.len = 0;
+    } else {
+        size_t alloc = 4;
+        JsonObject* arr = (JsonObject*)malloc(alloc * sizeof(JsonObject));
+        if (arr == NULL) {
+            error_exit("Can't allocate space for make_array");
+        }
+        arr[0] = initial;
+        r->x.a.p = arr;
+        r->x.a.alloc = alloc;
+        r->x.a.len = 1;
+    }
+    return r;
+}
+
+JsonObject make_array(JsonObject obj)
+{
+    return make_initial_array(JSON_ARRAY, obj);
+}
+
+JsonObject make_object(JsonObject obj)
+{
+    assert(obj == NULL || obj->type == JSON_KVPAIR);
+    return make_initial_array(JSON_OBJECT, obj);
+}
+
+// argument 'k' should me malloc-ed, and not a constant string
+JsonObject make_kv(const char* k, JsonObject o)
+{
+    JsonObject r = new_object(JSON_KVPAIR);
+    r->x.kv.k = k;
+    r->x.kv.v = o;
+    return r;
+}
+
+// the newobj object is now 'owned' by this module
+JsonObject append_object(JsonObject o, JsonObject newobj)
+{
+    assert(o != NULL);
+    assert(o->type == JSON_ARRAY
+           || (o->type == JSON_OBJECT && newobj->type == JSON_KVPAIR));
+
+    // the object/array must have been initialised as non-empty:
+    // if this is false, deem it to be a programming error
+    assert(o->x.a.alloc > 0);
+    if (o->x.a.len == o->x.a.alloc) {
+        size_t alloc = 3 * o->x.a.alloc / 2;
+        JsonObject* arr = (JsonObject*)realloc((void*)(o->x.a.p),
+                                               alloc*sizeof(JsonObject));
+        if (arr == NULL) {
+            error_exit("Can't realloc memory for append_object");
+        }
+        o->x.a.p = arr;
+        o->x.a.alloc = alloc;
+    }
+    assert(o->x.a.len < o->x.a.alloc);
+    o->x.a.p[o->x.a.len] = newobj;
+    o->x.a.len++;
+
+    return o;
+}
+
+// DER-encoding
+
+static byte* get_der_encoding_internal(JsonObject obj, size_t *len);
+static byte* get_der_encoding_charp(const char* str, size_t *len);
+
+/*
+ * Allocate bytes, calling error_exit on any errors
+ */
+static byte* allocb(size_t len)
+{
+    byte* b;
+    if ((b = (byte*)malloc(len)) == NULL) {
+        error_exit("allocb: unable to allocate %ld\n", len);
+    }
+    return b;
+}
+
+/*
+ * Allocate a DER object with the given identifier and content length,
+ * and fill in its length octet(s).
+ */
+static byte* alloc_der_object(byte identifier,
+                              size_t content_length,
+                              size_t* headlen)
+{
+    byte* b;
+    int i;
+    // lenbytes is the number of extra length bytes
+    // which are required for the length specification
+    size_t lenbytes;
+    size_t totallen;
+
+    // this module does not include any high-tag-number encodings
+    // (a high-tag-number identifier has bits 5-1 all 1).
+    assert((identifier & 0x1f) < 0x1f);
+
+    if (content_length <= 127) {
+        lenbytes = 0;
+    } else {
+        lenbytes = 1;
+        while (content_length >= (1<<(8*lenbytes))) {
+            lenbytes++;
+            // as a sanity check, assume that the string length is at
+            // most eight bytes (the size of a long integer /
+            // size_t).  If this assertion fails, then we've
+            // presumably made some signed/unsigned error somewhere.
+            assert(lenbytes <= 8);
+        }
+    }
+
+    totallen = 2 + lenbytes + content_length;
+    
+    b = allocb(totallen);
+    b[0] = identifier;
+    if (lenbytes == 0) {
+        b[1] = content_length;
+    } else {
+        size_t l = content_length;
+        
+        b[1] = (lenbytes | 0x80);
+        for (i=2+lenbytes-1; i>=2; i--) {
+            b[i] = (l & 0xff);
+            l >>= 8;
+        }
+    }
+
+    if (headlen != NULL) {
+        *headlen = 2 + lenbytes;
+    }
+
+    return b;
+}
+
+byte* get_der_encoding(JsonObject obj, size_t *len) 
+{
+    if (obj == NULL) {
+        return NULL;
+    }
+
+    if (setjmp(unsupported_jmp) != 0) {
+        // something has called unsupported_operation()
+        return NULL;
+    }
+    // normal behaviour
+    unsupported_msg[0] = '\0';
+
+    return get_der_encoding_internal(obj, len);
+}
+
+char* bytes_to_string(byte* b, size_t blen)
+{
+    static char* buf = NULL;
+    static size_t buflen = 0;
+    int i;
+
+    if (b == NULL) {
+        return NULL;
+    }
+    
+    if (buf == NULL) {
+        buflen = (2*blen)+1;
+        if ((buf = (char*)malloc(buflen)) == NULL) {
+            error_exit("bytes_to_string: can't malloc %ld bytes\n", buflen);
+        }
+    } else if (buflen <= (2*blen)) {
+        buflen = 2*blen+1;
+        if ((buf = (char*)realloc((void*)buf, buflen)) == NULL) {
+            error_exit( "bytes_to_string: can't realloc %ld bytes\n", buflen);
+        }
+    }
+    assert(buflen > 2*blen);
+    for (i=0; i<blen; i++) {
+        sprintf(&buf[2*i], "%02x", b[i]);
+    }
+    buf[2*i] = '\0';
+    return buf;
+}
+
+static byte* get_der_encoding_charp(const char* str, size_t *len)
+{
+    // 0x16 <len> bytes...
+    byte* b;
+    size_t slen = strlen(str);
+    size_t headlen;
+    int i;
+
+    b = alloc_der_object(0x16, slen, &headlen);
+
+    for (i=0; i<slen; i++) {
+        b[headlen+i] = (byte)(str[i]);
+    }
+
+    if (len != NULL) {
+        *len = headlen + slen;
+    }
+    return b;
+}
+
+static void set_der_encoding_string(JsonObject obj)
+{
+    // ASCII only at the moment
+    // 0x16 <len> bytes...
+
+    size_t slen;
+
+    assert(obj->type == JSON_STRING);
+    obj->der_encoding = get_der_encoding_charp(obj->x.s, &slen);
+    obj->der_encoding_length = slen;
+}
+
+static byte* encode_integer(jsonint num, size_t *nbyte)
+{
+    static byte b[8];
+    int ndigs;
+    int i;
+    jsonint one = 1;
+    uintmax_t unum;
+
+#if DEBUG & DBG_INTEGER
+    printf("encode_integer: num=%" JSONFMT, num);
+#endif
+    if (num > 0) {
+        for (ndigs=1; (one<<(8*ndigs-1)) - num <= 0; ndigs++) {
+            // nothing
+        }
+        assert(ndigs <= sizeof(jsonint));
+        unum = num;
+    } else {
+        for (ndigs=1; (one<<(8*ndigs-1)) + num < 0; ndigs++) {
+            // nothing
+        }
+        assert(ndigs <= sizeof(jsonint));
+        if (ndigs == sizeof(jsonint)) {
+            // exploit the machines' architecture being
+            // twos-complement, and simply copy the bits
+            // (attempting the 'else' calculation would overflow)
+            uintmax_t* p = (uintmax_t*)&num;
+            unum = *p;
+        } else {
+            unum = (one<<(8*ndigs)) + num;
+        }
+        /* now positive, and twos-complement with ndigs digits */
+    }
+#if DEBUG & DBG_INTEGER
+    printf(" --> %" PRIuMAX "/%" PRIxMAX " (%d) -->", unum, unum, ndigs);
+#endif
+   
+    *nbyte = ndigs;
+    
+    for (i=ndigs-1; i>=0; i--) {
+        b[i] = (unum & 0xff);
+        unum >>= 8;
+    }
+#if DEBUG & DBG_INTEGER
+    for (i=0; i<ndigs; i++) {
+        printf(" %02x", b[i]);
+    }
+    printf("\n");
+#endif
+
+    return b;
+}
+
+static void set_der_encoding_integer(JsonObject obj)
+{
+    // 0x02 <len> bytes...
+    // BER encoding. Primitive. Contents octets give the value of the
+    // integer, base 256, in two's complement form, most significant
+    // digit first, with the minimum number of octets. The value 0 is
+    // encoded as a single 00 octet.
+
+    byte* b;
+    jsonint num = obj->x.i;
+    
+    assert(obj->type == JSON_INTEGER);
+
+    if (num == 0) {
+        b = allocb(3);
+        b[0] = 0x02;
+        b[1] = 1;
+        b[2] = 0;
+        obj->der_encoding_length = 3;
+    } else {
+        size_t nbytes;
+        byte* encoded;
+
+        encoded = encode_integer(num, &nbytes);
+        b = allocb(nbytes + 2);
+        b[0] = 0x02;
+        b[1] = nbytes;
+        memcpy(&b[2], encoded, nbytes);
+
+        obj->der_encoding_length = nbytes+2;
+    }
+    obj->der_encoding = b;
+}
+
+static void set_der_encoding_real(JsonObject obj)
+{
+    // 0x09 <len> bytes...
+    // Complicated -- see X.690 Sect. 8.5.
+    // We always use base-2 encoding, meaning that we always set F=0
+    byte* b = NULL;
+    size_t blen = 0;            // length of the DER-encoded value
+    double num = obj->x.f;
+    int isnegative = signbit(num);
+    int i;
+
+    assert(obj->type == JSON_REAL);
+
+#if DEBUG & DBG_REAL
+    printf("set_der_encoding_real: num=%g (zero? %d; negative? %d)\n",
+           num, (num == 0.0), isnegative);
+#endif
+
+    switch (fpclassify(num)) {
+      case FP_INFINITE:
+        b = allocb(3);
+        b[0] = 0x09;
+        b[1] = 1;
+        b[2] = (isnegative ? 0x41 : 0x40);
+        blen = 3;
+        break;
+      case FP_NAN:
+        b = allocb(3);
+        b[0] = 0x09;
+        b[1] = 1;
+        b[2] = 0x42;
+        blen = 3;
+        break;
+      case FP_ZERO:
+        if (isnegative) {
+            b = allocb(3);
+            b[0] = 0x09;
+            b[1] = 1;
+            b[2] = 0x43;
+            blen = 3;
+        } else {
+            b = allocb(2);
+            b[0] = 0x09;
+            b[1] = 0;
+            blen = 2;
+        }
+        break;
+      case FP_SUBNORMAL:
+        unsupported_operation("Don't currently support converting subnormal floats");
+        break;
+      case FP_NORMAL:
+        {
+            //double numnorm;
+            uint64_t inorm;
+            int exponent;
+            size_t nbytes;
+            byte* enci;
+
+            b = allocb(2+1+2+8); /* may be an overestimate */
+            b[0] = 0x09;
+            blen = 3; // tag byte + length byte + first-content-octet
+            
+            if (isnegative) {
+                num = -num;
+            }
+
+            // I want the exponent and mantissa as integers.  There's
+            // a plausible-looking route to this via frexp, but that
+            // extracts the mantissa as a float, which we then have to
+            // convert to an integer using multiplications.
+            //
+            // Instead, assume 8-byte doubles, and IEEE754 floats
+            // (both of which are pretty safe for normal hardware),
+            // and get them by bit-twiddling.
+            {
+                union {
+                    double d;
+#if WORDS_BIGENDIAN
+                    struct {
+                        uint64_t s :  1;
+                        uint64_t e : 11;
+                        uint64_t m : 52;
+                    } b;
+#else
+                    struct {
+                        uint64_t m : 52;
+                        uint64_t e : 11;
+                        uint64_t s :  1;
+                    } b;
+#endif
+                } ieee;
+                uint64_t one = ((uint64_t)1)<<52;
+
+                // XXX it would be better to check this at compile time
+                // rather than run time.  However gcc seems to feel that
+                // sizeof(double) is not a constant-expression, even
+                // though ISO/IEC 9899:2011 6.6 para 8 suggests to me
+                // that it is.
+                assert(sizeof(double) == 8);
+
+                ieee.d = num;
+                inorm = ieee.b.m | one;
+                exponent = ieee.b.e - 1023 - 52;
+#if DEBUG & DBG_REAL
+                printf("    inorm=%" PRIx64 "  exponent=%d...\n", inorm, exponent);
+#endif
+                // remove all of the right-most zeros, to make the
+                // integer as small as possible
+                while ((inorm & 0x0f) == 0) {
+                    inorm >>= 4;
+                    exponent += 4;
+                }
+                while ((inorm & 0x01) == 0) {
+                    inorm >>= 1;
+                    exponent += 1;
+                }
+            }
+#if DEBUG & DBG_REAL
+            printf("    .....=%" PRIx64 "  exponent=%d...\n", inorm, exponent);
+#endif
+            
+            enci = encode_integer(exponent, &nbytes);
+            assert(nbytes == 1 || nbytes == 2);
+
+            b[2] = (isnegative ? 0xc0 : 0x80) | (nbytes == 1 ? 0x00 : 0x01);
+
+            if (nbytes == 1) {
+                b[3] = enci[0];
+                blen += 1;
+            } else {
+                b[3] = enci[0];
+                b[4] = enci[1];
+                blen += 2;
+            }
+
+            enci = encode_integer(inorm, &nbytes);
+            for (i=0; i<nbytes; i++, blen++) {
+                b[blen] = enci[i];
+            }
+
+            b[1] = blen-2;
+            
+#if DEBUG & DBG_REAL
+            printf("    num=%g-> inorm=%" PRIx64 " (%ld)  exponent=%d\n", num, inorm, nbytes, exponent);
+#endif
+        }
+        break;
+      default:
+        fprintf(stderr, "Impossible fpclassify result?! %d\n", fpclassify(num));
+        assert(0);
+    }
+
+    assert(b != NULL && blen > 0);
+
+    obj->der_encoding = b;
+    obj->der_encoding_length = blen;
+
+#if DEBUG & DBG_REAL
+    {
+        printf("    --> b=");
+        for (i=0; i<blen; i++) {
+            printf(" %02x", b[i]);
+        }
+        puts("");
+    }
+#endif
+}
+
+static void set_der_encoding_sequence(JsonObject obj, byte tag)
+{
+    // tag <len> bytes...
+    byte* b;
+    byte* bp;
+    size_t cptslen;
+    size_t headlen;
+    JsonObject* p = obj->x.a.p;
+    size_t np = obj->x.a.len;
+    int i;
+
+    cptslen = 0;
+    for (i=0; i<np; i++) {
+        size_t len;
+        (void) get_der_encoding_internal(p[i], &len);
+        cptslen += len;
+    }
+    
+    b = alloc_der_object(tag, cptslen, &headlen);    
+
+    bp = &b[headlen];
+    for (i=0; i<np; i++) {
+        size_t len;
+        byte* sub_b = get_der_encoding_internal(p[i], &len);
+        memcpy(bp, sub_b, len);
+        bp += len;
+    }
+    
+    obj->der_encoding = b;
+    obj->der_encoding_length = headlen + cptslen;
+}
+
+static void set_der_encoding_array(JsonObject obj) 
+{
+    // 0x30 <len> bytes...
+    assert(obj->type == JSON_ARRAY);
+    set_der_encoding_sequence(obj, 0x30);
+}
+
+/* 
+ * Compare two key-value objects.
+ *
+ * From Kaliski, describing the DER encoding of the SET OF type:
+ *
+ *   DER encoding. Constructed. Contents octets are the same as for the
+ *   BER encoding, except that there is an order, namely ascending
+ *   lexicographic order of BER encoding. Lexicographic comparison of
+ *   two different BER encodings is done as follows: Logically pad the
+ *   shorter BER encoding after the last octet with dummy octets that
+ *   are smaller in value than any normal octet. Scan the BER encodings
+ *   from left to right until a difference is found. The smaller-valued
+ *   BER encoding is the one with the smaller-valued octet at the point
+ *   of difference.
+ */
+static int kv_compare(const void* kv1, const void* kv2)
+{
+    JsonObject* o1 = (JsonObject*)kv1;
+    byte* b1;
+    size_t b1len;
+    JsonObject* o2 = (JsonObject*)kv2;
+    byte* b2;
+    size_t b2len;
+
+    size_t cmplen;
+    int cmp;
+    
+    assert((*o1)->type == JSON_KVPAIR && 
+           (*o2)->type == JSON_KVPAIR);
+
+    b1 = get_der_encoding_internal(*o1, &b1len);
+    b2 = get_der_encoding_internal(*o2, &b2len);
+    cmplen = (b1len < b2len ? b1len : b2len);
+
+    cmp = memcmp(b1, b2, cmplen);
+    if (cmp == 0) {
+        if (b1len == b2len)
+            cmp = 0;
+        else if (b1len < b2len)
+            cmp = -1;
+        else
+            cmp = +1;
+    }
+    
+    //printf("kv_compare: %s vs %s -> %d\n", (*o1)->x.kv.k, (*o2)->x.kv.k, cmp);
+    return cmp;
+}
+
+static void sort_kv_sequence(JsonObject obj)
+{
+    JsonObject* p;
+    size_t np;
+
+    assert(obj->type == JSON_OBJECT);
+
+    p = obj->x.a.p;
+    np = obj->x.a.len;
+
+    if (np == 0) {
+        return;                 /* JUMP OUT -- nothing to do */
+    }
+    
+    assert(np > 0);
+        
+    qsort((void*)p, np, sizeof(JsonObject), kv_compare);
+
+    return;
+}
+
+static void set_der_encoding_object(JsonObject obj)
+{
+    // 0x31 <len> bytes...
+    assert(obj->type == JSON_OBJECT);
+    sort_kv_sequence(obj);
+    set_der_encoding_sequence(obj, 0x31);
+}
+
+static void set_der_encoding_kvpair(JsonObject obj)
+{
+    // serialised to DER as a sequence of two objects, where the
+    // second may be composite
+    byte* b;
+    byte* bk;
+    byte* bv;
+    size_t bklen;
+    size_t bvlen;
+    size_t headlen;
+
+    assert(obj->type == JSON_KVPAIR);
+    bk = get_der_encoding_charp(obj->x.kv.k, &bklen);
+    bv = get_der_encoding_internal(obj->x.kv.v, &bvlen);
+
+    b = alloc_der_object(0x30, bklen + bvlen, &headlen);
+    
+    memcpy(&b[headlen], bk, bklen);
+    memcpy(&b[headlen+bklen], bv, bvlen);
+
+    obj->der_encoding = b;
+    obj->der_encoding_length = headlen + bklen + bvlen;
+}
+
+static void set_der_encoding_boolean(JsonObject obj)
+{
+    // 0x01 0x01 0xff/0x00
+    byte* b;
+    assert(obj->type == JSON_BOOLEAN);
+
+    b = allocb(3);
+    b[0] = 0x01;
+    b[1] = 0x01;
+    b[2] = (obj->x.b ? 0xff : 0x00);
+
+    obj->der_encoding = b;
+    obj->der_encoding_length = 3;
+}
+
+static void set_der_encoding_null(JsonObject obj)
+{
+    // DER encoding. Primitive. Contents octets are empty; the DER
+    // encoding of a NULL value is always 05 00.
+    byte* b;
+    assert(obj->type == JSON_NULL);
+    
+    b = allocb(2);
+    b[0] = 0x05;
+    b[1] = 0x00;
+    obj->der_encoding = b;
+    obj->der_encoding_length = 2;
+}
+
+static byte* get_der_encoding_internal(JsonObject obj, size_t *len)
+{
+    if (obj->der_encoding == NULL) {
+        switch (obj->type) {
+          case JSON_STRING:
+            set_der_encoding_string(obj);
+            break;
+          case JSON_INTEGER:
+            set_der_encoding_integer(obj);
+            break;
+          case JSON_REAL:
+            set_der_encoding_real(obj);
+            break;
+          case JSON_ARRAY:
+            set_der_encoding_array(obj);
+            break;
+          case JSON_OBJECT:
+            set_der_encoding_object(obj);
+            break;
+          case JSON_KVPAIR:
+            set_der_encoding_kvpair(obj);
+            break;
+          case JSON_BOOLEAN:
+            set_der_encoding_boolean(obj);
+            break;
+          case JSON_NULL:
+            set_der_encoding_null(obj);
+            break;
+          default:
+            fprintf(stderr, "obj->type = %d\n", obj->type);
+            assert(0);
+            break;
+        }
+    }
+    assert(obj->der_encoding != NULL);
+
+    if (len != NULL) {
+        *len = obj->der_encoding_length;
+    }
+    return obj->der_encoding;
+}
+
+// interface to parsing functions
+
+// lexer functions
+int yylex(void);
+int lex_setup(const char*);
+void lex_release(void);
+
+// parser functions
+JsonObject get_parsed_value(void);
+int yyparse(void);
+
+void yyerror(const char* msg)
+{
+    unsupported_operation("Parsing error: %s", msg);
+}
+
+JsonObject parse_json_string(const char* json_string)
+{
+    JsonObject rval;
+
+    lex_setup(json_string);
+    if (yyparse() == 0) {
+        rval = get_parsed_value();
+    } else {
+        rval = NULL;
+    }
+
+    lex_release();
+    return rval;
+}
+
+// DER decoding
+static JsonObject parse_der_bytes_internal(byte* b, size_t blen, size_t* actuallen);
+
+
+static JsonObject parse_der_ia5string_content(byte* b, size_t blen)
+{
+    // the content is a sequence of ASCII bytes -- simples!
+    char* str;
+    str = (char*)allocb(blen+1);
+    memcpy(str, b, blen);
+    str[blen] = '\0';
+    return make_string(str);
+}
+
+static jsonint parse_der_integer_content_to_integer(byte* b, size_t blen)
+{
+    jsonint result;
+    uintmax_t uresult;
+    int i;
+    jsonint one = 1;
+    
+    if (blen > sizeof(jsonint)) {
+        unsupported_operation("DER integer too large: %ld bytes (intmax_t=%ld bytes", blen, sizeof(jsonint));
+    }
+
+    uresult = 0;
+    for (i=0; i<blen; i++) {
+        uresult <<= 8;
+        uresult += b[i];
+    }
+
+    // uresult is a twos-complement version of the answer
+    if (uresult >= one<<(8*blen-1)) {
+        if (blen == sizeof(jsonint)) {
+            jsonint* p;
+            assert(sizeof(uintmax_t) == sizeof(jsonint));
+            // exploit the machines' architecture being
+            // twos-complement, and simply copy the bits
+            // (attempting the 'else' calculation would overflow)
+            p = (jsonint*)&uresult;
+            result = *p;
+        } else {
+            result = - ((one<<(8*blen)) - uresult);
+        }
+    } else {
+        result = uresult;
+    }
+
+    return result;
+}
+
+static JsonObject parse_der_integer_content(byte* b, size_t blen)
+{
+    jsonint result = parse_der_integer_content_to_integer(b, blen);
+    return make_integer(result);
+}
+
+static JsonObject parse_der_real_content(byte* b, size_t blen)
+{
+    if (blen == 0) {
+        return make_real(0.0);
+    } else {
+        int sign;
+        int exponent;
+        int nexp;
+        jsonint mantissa;
+        size_t mantidx;
+        double result;
+
+        if (! (b[0] & 0x80) || (b[0] & 0x3c)) {
+            // must have bit 8 set, and bits 6--3 clear
+            unsupported_operation("malformed first content byte for real: %02x",
+                                  b[0]);
+        }
+
+        sign = (b[0] & 0x40 ? -1 : +1);
+        nexp = (b[0] & 0x03);
+        switch (nexp) {
+          case 0:
+            exponent = parse_der_integer_content_to_integer(&b[1], 1);
+            mantidx = 2;
+            break;
+          case 1:
+            exponent = parse_der_integer_content_to_integer(&b[1], 2);
+            mantidx = 3;
+            break;
+          default:
+            unsupported_operation("unexpected exponent size: %d bytes", nexp+1);
+        }
+        mantissa = parse_der_integer_content_to_integer(&b[mantidx], blen-mantidx);
+        result = copysign(ldexp((double)mantissa, exponent), sign);
+
+#if DEBUG & DBG_REAL
+        printf("der_real: %d:%d:%ld -> %g\n", sign, exponent, mantissa, result);
+#endif
+
+        return make_real(result);
+    }
+}
+
+static JsonObject parse_der_boolean_content(byte* b, size_t blen)
+{
+    if (blen != 1) {
+        unsupported_operation("Boolean with a peculiar number of bytes (%d)", blen);
+    }
+    return make_boolean(b[0] ? 1 : 0);
+}
+
+static JsonObject parse_der_null_content(byte* b, size_t blen)
+{
+    if (blen != 0) {
+        unsupported_operation("Null with content (%d bytes)", blen);
+    }
+    return make_null();
+}
+
+static JsonObject parse_der_sequence_content(byte* b, size_t blen)
+{
+    JsonObject list_of_objects = NULL;
+    size_t olen;
+    JsonObject obj;
+
+    while (blen > 0) {
+        obj = parse_der_bytes_internal(b, blen, &olen);
+        b += olen;
+        blen -= olen;
+        if (list_of_objects == NULL) {
+            list_of_objects = make_array(obj);
+        } else {
+            list_of_objects = append_object(list_of_objects, obj);
+        }
+    }
+    return list_of_objects == NULL ? make_array(NULL) : list_of_objects;
+}
+
+static JsonObject parse_der_object_content(byte* b, size_t blen)
+{
+    JsonObject list_of_objects = NULL;
+    size_t olen;
+    JsonObject obj;
+    JsonObject key;
+    JsonObject value;
+
+    // Parse each of the der objects in the content of this byte
+    // array.  Each of these should be a two-element sequence (ie,
+    // parsing to a JSON_ARRAY) comprising a JSON_STRING and another object.
+    while (blen > 0) {
+        int ok_object = 1;
+        JsonObject array_obj;
+#if DEBUG & DBG_SEQUENCES
+        const char* pp;
+#endif
+   
+        array_obj = parse_der_bytes_internal(b, blen, &olen);
+#if DEBUG & DBG_SEQUENCES
+        pp = print_json_object(array_obj);
+        fprintf(stderr, "parse_der_object: obj=%s\n", pp);
+        free((void*)pp);
+#endif
+        if (! (array_obj->type == JSON_ARRAY) && (array_obj->x.a.len == 2)) {
+            ok_object = 0;
+        }
+        if (ok_object) {
+            key = array_obj->x.a.p[0];
+            if (key->type != JSON_STRING) {
+                ok_object = 0;
+            }
+        }
+        if (ok_object) {
+            value = array_obj->x.a.p[1];
+        }
+
+        if (! ok_object) {
+            unsupported_operation("set/object does not contain key-value pair");
+        }
+
+#if DEBUG & DBG_SEQUENCES
+        pp = print_json_object(key);
+        fprintf(stderr, "parse_der_object: key=%s (=%s)",
+                pp, key->x.s);
+        pp = print_json_object(value);
+        fprintf(stderr, "; value=%s\n", pp);
+#endif
+
+        obj = make_kv(key->x.s, value);
+        free_json_object(key, 0);
+        free_json_object(array_obj, 0);
+        
+        b += olen;
+        blen -= olen;
+        if (list_of_objects == NULL) {
+            list_of_objects = make_object(obj);
+        } else {
+            list_of_objects = append_object(list_of_objects, obj);
+        }
+    }
+    return list_of_objects == NULL ? make_object(NULL) : list_of_objects;
+}
+
+static JsonObject parse_der_bytes_internal(byte* b, size_t blen, size_t* actuallen)
+{
+    size_t headlen;
+    size_t contentlen;
+    int i;
+    JsonObject rval = NULL;
+
+    if (blen < 2) {
+        /* the shortest possible DER value is two bytes long
+           -- this sequence is immediately wrong */
+        return NULL;
+    }
+
+    if ((b[0] & 0x1f) == 0x1f) {
+        unsupported_operation("Can't decode high-tag-value DER objects (found %x)", b[0]);
+    }
+
+    // determine the length of this object, and then pass the content
+    // to a separate parser
+
+    headlen = 2;
+    if ((b[1] & 0x80)) {
+        // long-tag-length
+        int nb = (b[1] & 0x7f);
+        assert(nb > 0);
+        if (nb >= 8) {
+            unsupported_operation("ultra-long encoded field of %x bytes (are you sure?)", nb);
+        }
+        headlen = 2 + nb;
+        contentlen = 0;
+        for (i=0; i<nb; i++) {
+            contentlen = (contentlen << 8) + b[2+i];
+        }
+    } else {
+        headlen = 2;
+        contentlen = b[1];
+    }
+#if 0
+    fprintf(stderr, "parse_der_bytes:");
+    for (i=0; i<blen && i<headlen+contentlen; i++) {
+        fprintf(stderr, " %02x", b[i]);
+    }
+    fprintf(stderr, "(%ld) -> head=%ld, content=%ld\n", blen, headlen, contentlen);
+#endif
+
+    if (headlen + contentlen > blen) {
+        unsupported_operation("parse_der_bytes: tag+length is %d, but array length is %d", headlen + contentlen, blen);
+    }
+    
+    switch(b[0]) {
+      case 0x01: // boolean
+        rval = parse_der_boolean_content(b+headlen, contentlen);
+        break;
+      case 0x02: // integer
+        rval = parse_der_integer_content(b+headlen, contentlen);
+        break;
+      case 0x05: // null
+        rval = parse_der_null_content(b+headlen, contentlen);
+        break;
+      case 0x09: // real
+        rval = parse_der_real_content(b+headlen, contentlen);
+        break;
+      case 0x16: // string
+        rval = parse_der_ia5string_content(b+headlen, contentlen);
+        break;
+      case 0x30: // sequence
+        rval = parse_der_sequence_content(b+headlen, contentlen);
+        break;
+      case 0x31: // set of
+        rval = parse_der_object_content(b+headlen, contentlen);
+        break;
+      default:
+        unsupported_operation("Can't parse DER objects with identifier %2x\n", b[0]);
+        break;
+    }
+
+    if (actuallen != NULL) {
+        *actuallen = headlen + contentlen;
+    }
+
+    return rval;
+}
+
+JsonObject parse_der_bytes(byte* b, size_t blen, size_t* actuallen)
+{
+    if (setjmp(unsupported_jmp) != 0) {
+        // something has called unsupported_operation()
+        return NULL;
+    }
+    // normal behaviour
+    unsupported_msg[0] = '\0';
+    return parse_der_bytes_internal(b, blen, actuallen);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jason.h	Wed Sep 04 22:00:24 2013 +0100
@@ -0,0 +1,70 @@
+#ifndef JASON_H_DEFINED
+#define JASON_H_DEFINED 1
+
+#include <stdio.h>
+
+#include <inttypes.h> /* includes stdint.h */
+
+typedef uint8_t byte;
+
+typedef intmax_t jsonint;
+#define JSONFMT PRIiMAX
+
+typedef enum {
+    JSON_STRING,
+    JSON_INTEGER,
+    JSON_REAL,
+    JSON_ARRAY,
+    JSON_OBJECT,
+    JSON_BOOLEAN,
+    JSON_NULL,
+    JSON_KVPAIR,                /* not exposed */
+} JsonType;
+
+struct json_object {
+    JsonType type;
+    byte* der_encoding;
+    size_t der_encoding_length;
+    union {
+        const char* s;
+        jsonint i;
+        double f;
+        /* both arrays and objects are represented by having 'a'
+           point to the first in a linked list of their contents, of
+           whatever types */
+        struct {
+            struct json_object** p;
+            size_t alloc;
+            size_t len;
+        } a;
+        /* kv is a single key-value pair, which comprises the
+           contents of an object */
+        struct {
+            const char* k;
+            struct json_object* v;
+        } kv;
+        int b;
+    } x;
+};
+typedef struct json_object* JsonObject;
+
+JsonObject initialise_json_object(JsonObject, JsonType);
+void free_json_object(JsonObject o, int free_contents);
+const char* print_json_object(JsonObject obj);
+byte* get_der_encoding(JsonObject obj, size_t *len);
+char* bytes_to_string(byte* b, size_t blen);
+JsonObject parse_json_string(const char* json_string);
+JsonObject parse_der_bytes(byte*, size_t, size_t*);
+const char* unsupported_message();
+
+JsonObject make_string(const char*);
+JsonObject make_integer(jsonint);
+JsonObject make_real(double);
+JsonObject make_boolean(int);
+JsonObject make_null(void);
+JsonObject make_array(JsonObject);
+JsonObject make_object(JsonObject);
+JsonObject make_kv(const char*, JsonObject);
+JsonObject append_object(JsonObject, JsonObject);
+
+#endif /* JASON_H_DEFINED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/json-parse.y	Wed Sep 04 22:00:24 2013 +0100
@@ -0,0 +1,87 @@
+/* a little parser for JSON */
+
+%{
+    #include <assert.h>
+    #include "jason.h"
+
+    int yylex(void);
+    void yyerror (char const *msg);
+
+    static JsonObject return_value;
+    static void set_parsed_value(JsonObject);
+    JsonObject get_parsed_value(void);
+%}
+
+%union {
+    jsonint i;
+    double f;
+    char* str;
+    struct json_object* o;
+}
+
+%token <str> STRING
+%token <i> INTEGER
+%token <f> REAL
+%token COLON
+%token COMMA
+%token LEFTSQ
+%token RIGHTSQ
+%token LEFTCURLY
+%token RIGHTCURLY
+%token TRUE
+%token FALSE
+%token JNULL
+
+%type <o> value array object list.of.value list.of.kv
+
+%%
+input: value { set_parsed_value($1); }
+
+value: STRING { $$ = make_string($1); }
+  | INTEGER { $$ = make_integer($1); }
+  | REAL { $$ = make_real($1); }
+  | TRUE { $$ = make_boolean(1); }
+  | FALSE { $$ = make_boolean(0); }
+  | JNULL { $$ = make_null(); }
+  | object
+  | array
+
+array: LEFTSQ RIGHTSQ {
+    $$ = make_array(NULL);
+  }
+  | LEFTSQ list.of.value RIGHTSQ {
+    $$ = $2;
+  }
+
+list.of.value: value {
+    $$ = make_array($1);
+  }
+  | list.of.value COMMA value {
+    $$ = append_object($1, $3);
+  }
+
+object: LEFTCURLY RIGHTCURLY {
+    $$ = make_object(NULL);
+  }
+  | LEFTCURLY list.of.kv RIGHTCURLY {
+    $$ = $2;
+  }
+
+list.of.kv: STRING COLON value {
+    $$ = make_object(make_kv($1, $3));
+  }
+  | list.of.kv COMMA STRING COLON value {
+    $$ = append_object($1, make_kv($3, $5));
+  }
+
+%%
+static void set_parsed_value(JsonObject o)
+{
+    return_value = o;
+}
+ 
+JsonObject get_parsed_value(void)
+{
+    return return_value;
+}
+ 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/json2der.1	Wed Sep 04 22:00:24 2013 +0100
@@ -0,0 +1,73 @@
+.TH JSON2DER 1
+.SH NAME
+json2der
+.SH SYNOPSIS
+.B json2der
+[-h] [-D] [-ifile] [-ofile] [-ffmt] [-tfmt] [json-string]
+.SH DESCRIPTION
+Converts a JSON expression to a DER equivalent,
+or decodes the supported subset of DER into JSON.
+.P
+JSON integers, floats, booleans, and null, are encoded to the DER analogues.
+A JSON array is encoded to a DER
+.I SEQUENCE,
+an object to a
+.I SET OF
+two-element sequences, and
+strings to DER
+.I ia5string
+objects.
+.P
+If the argument
+.I json-string
+is present, then it indicates the content to be encoded.
+Giving this argument with the
+.I -D
+option is unlikely to work well.
+.SH OPTIONS
+.TP
+.I -D
+Equivalent to 
+.I -fder -tjson
+Decode a file from DER to JSON.  This supports only the subset of DER
+which is supported by the JSON-to-DER encoding.
+.TP
+.I -f format
+The format to encode
+.I from
+-- this can be either 
+.I json
+or
+.I der.
+This sets the 
+.I -t
+option to be the other format.
+.TP
+.I -tformat
+The format to convert
+.I to.
+.TP
+.I -i file
+The file to read from.
+If this is absent, the default is to read from stdin.
+It is an error to provide this option as well a providing a command argument.
+.TP
+.I -o file
+The file to write to.
+If this is absent, the default is to write to stdout.
+.TP
+.I -h
+Writes out a help message.
+.SH SEE ALSO
+DER is specified in ITU-T Rec. X.690, as part of the ASN/1 suite of
+standards.  See the ITU-T recommendation,
+.I http://www.itu.int/rec/T-REC-X.690-200811-I/en
+Wikipedia
+.I http://en.wikipedia.org/wiki/X.690
+and Burton S. Kaliski Jr.,
+.I A Layman's Guide to a Subset of ASN.1, BER, and DER
+(1993).
+.SH BUGS
+None known.
+.SH AUTHOR
+Norman Gray, http://nxg.me.uk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/json2der.c	Wed Sep 04 22:00:24 2013 +0100
@@ -0,0 +1,247 @@
+#include <stdio.h>
+#include <stdlib.h> /* for exit(3) */
+#include <unistd.h>
+#include <assert.h>
+#include <string.h>
+
+#include "jason.h"
+#include "json-parse.tab.h"
+
+#define DTMFX_IMPLEMENTATION 1
+#include "util.h"
+
+void Usage(void);
+const char* progname;
+
+typedef enum { FMT_DER, FMT_JSON, FMT_LEXEMES } Format;
+
+
+static void parse_and_display(Format input, Format output,
+                              byte* input_string, size_t input_string_length,
+                              FILE* out);
+static void display_lexemes(const char* input);
+
+
+int main(int argc, char** argv) 
+{
+    int ch;
+    Format input_format = FMT_JSON;
+    Format output_format = FMT_DER;
+    byte* input_string;
+    size_t input_string_length;
+    FILE* infile = stdin;
+    FILE* outfile = stdout;
+
+    progname = argv[0];
+
+    while ((ch = getopt(argc, argv, "DLhi:f:o:t:")) != -1) {
+        switch (ch) {
+          case 'D':             /* short for -fder -tjson */
+            input_format = FMT_DER;
+            output_format = FMT_JSON;
+            break;
+          case 'L':             /* show lexemes: for debugging */
+            output_format = FMT_LEXEMES;
+            break;
+          case 'f':
+            if (strcmp(optarg, "json") == 0) {
+                input_format = FMT_JSON;
+                output_format = FMT_DER;
+            } else if (strcmp(optarg, "der") == 0) {
+                input_format = FMT_DER;
+                output_format = FMT_JSON;
+            } else {
+                Usage();
+            }
+            break;
+          case 't':
+            if (strcmp(optarg, "der") == 0) {
+                output_format = FMT_DER;
+            } else if (strcmp(optarg, "json") == 0) {
+                output_format = FMT_JSON;
+            } else {
+                Usage();
+            }
+            break;
+          case 'i':
+            if ((infile = fopen(optarg, "r")) == NULL) {
+                fprintf(stderr, "Can't open file %s to read\n", optarg);
+            }
+            break;
+          case 'o':
+            if ((outfile = fopen(optarg, "w")) == NULL) {
+                fprintf(stderr, "Can't open file %s to write\n", optarg);
+            }
+            break;
+          case 'h':
+            /* FALL THROUGH */
+          default:
+            Usage();
+        }
+    }
+    argc -= optind;
+    argv += optind;
+
+    if (argc == 0) {
+#define BUFLEN 64
+        byte inbuf[BUFLEN];
+        StringBuilder b = make_append_buffer();
+
+        while (! feof(infile)) {
+            size_t nread = fread(inbuf, sizeof(byte), BUFLEN, infile);
+            append_to_buffer_n(b, inbuf, nread);
+        }
+        input_string = b->buf;
+        input_string_length = b->len;
+#undef BUFLEN
+    } else if (argc == 1) {
+        if (infile != stdin) {
+            fprintf(stderr, "Can't specify -i and give an argument\n");
+            exit(1);
+        }
+        input_string = (byte*)argv[0];
+        input_string_length = strlen((char*)input_string);
+    } else {
+        Usage();
+    }
+
+    if ((input_format == FMT_JSON || input_format == FMT_DER)
+        && (output_format == FMT_JSON || output_format == FMT_DER)) {
+        parse_and_display(input_format, output_format,
+                          input_string, input_string_length,
+                          outfile);
+    } else {
+        display_lexemes((const char*)input_string);
+    }
+
+    return 0;
+}
+
+static void parse_and_display(Format input, Format output,
+                              byte* input_string, size_t input_string_length,
+                              FILE* out)
+{
+    JsonObject parse_val;
+
+    switch (input) {
+      case FMT_JSON:
+        parse_val = parse_json_string((char*)input_string);
+        break;
+      case FMT_DER:
+        parse_val = parse_der_bytes((byte*)input_string, input_string_length, NULL);
+        break;
+      default:
+        Usage();
+    }
+    
+    if (parse_val == NULL) {
+        const char* msg = unsupported_message();
+        if (msg == NULL) {
+            fprintf(stderr, "Parsing failed\n");
+        } else {
+            fprintf(stderr, "Parsing failed: %s\n", msg);
+        }
+    } else {
+        switch (output) {
+          case FMT_JSON:
+            fputs(print_json_object(parse_val), out);
+            fputc('\n', out);
+            break;
+          case FMT_DER:
+            {
+                size_t blen;
+                byte* b = get_der_encoding(parse_val, &blen);
+                if (b == NULL) {
+                    fprintf(stderr, "Failed to DER-encode that value (%s)\n",
+                            unsupported_message());
+                    exit(1);
+                }
+                fwrite(b, sizeof(byte), blen, out);
+            }
+            break;
+          default:
+            // this should be impossible
+            assert(0);
+        }
+    }
+
+    free_json_object(parse_val, 1);
+}
+
+// defined in json_lex.c
+int yylex(void);
+char* lex_error_message(void);
+int lex_setup(const char*);
+void lex_release(void);
+
+static void display_lexemes(const char* input)
+{
+    int l;
+    
+    lex_setup(input);
+    
+    while ((l = yylex()) != 0) {
+
+        printf("l=%d ", l);
+        
+        switch (l) {
+          case STRING:
+            printf("string:%s\n", yylval.str);
+            break;
+          case INTEGER:
+            printf("int:%" JSONFMT "\n", yylval.i);
+            break;
+          case REAL:
+            printf("real:%g\n", yylval.f);
+            break;
+          case COLON:
+            printf("colon\n");
+            break;
+          case COMMA:
+            printf("comma\n");
+            break;
+          case LEFTSQ:
+            printf("[\n");
+            break;
+          case RIGHTSQ:
+            printf("]\n");
+            break;
+          case LEFTCURLY:
+            printf("{\n");
+            break;
+          case RIGHTCURLY:
+            printf("}\n");
+            break;
+          case TRUE:
+            printf("TRUE\n");
+            break;
+          case FALSE:
+            printf("FALSE\n");
+            break;
+          case JNULL:
+            printf("NULL\n");
+            break;
+          default:
+            printf("BAD VALUE: %d\n", l);
+            break;
+        }
+    }
+
+    if (lex_error_message()) {
+        printf("Lexing error: %s\n", lex_error_message());
+    }
+    
+    /* if (lex_unexpected_character() != '\0') { */
+    /*     printf("Unexpected character: %c\n", lex_unexpected_character()); */
+    /* } */
+    
+    lex_release();
+}
+
+void Usage(void)
+{
+    fprintf(stderr,
+            "Usage: json2der [-h] [-D] [-ifile] [-ofile] [-ffmt] [-tfmt] [json-string]\n"
+            "  fmt: der/json\n");
+    exit(1);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/json_lex.lex	Wed Sep 04 22:00:24 2013 +0100
@@ -0,0 +1,182 @@
+%{
+    /* need this for the call to atof() below */
+#include <math.h>
+#include <assert.h>
+#include <ctype.h>
+#include "jason.h"
+#include "json-parse.tab.h"
+#include "util.h"
+
+//static char* copy_string(const char* s);
+static StringBuilder stringbuilder;
+
+#define ERR_MSG_LEN 64
+static char err_msg[ERR_MSG_LEN];
+char* lex_error_message(void)
+{
+    if (err_msg[0] == '\0') {
+        return NULL;
+    } else {
+        return err_msg;
+    }
+}
+
+static const char* parse_string_endp = NULL;
+static const char* parse_string_p = NULL;
+static const char* parse_string_buf = NULL;
+int lex_setup(const char*);
+void lex_release(void);
+
+#define YY_INPUT(buf, result, max_size)                                 \
+    {                                                                   \
+        if (parse_string_p == NULL || parse_string_endp == NULL) {      \
+            fprintf(stderr, "fatal: Bad call to parser: need to call lex_setup first\n"); \
+            exit(1);                                                    \
+        }                                                               \
+        assert(parse_string_p != NULL && parse_string_endp != NULL);    \
+        if (parse_string_p >= parse_string_endp) {                      \
+            parse_string_p = parse_string_endp;                         \
+            result = YY_NULL;                                           \
+        } else {                                                        \
+            int cpylen = (parse_string_endp - parse_string_p > max_size \
+                          ? max_size                                    \
+                          : parse_string_endp - parse_string_p);        \
+            assert(buf != NULL);                                        \
+            memcpy(buf, parse_string_p, cpylen);                        \
+            parse_string_p += cpylen;                                   \
+            result = cpylen;                                            \
+        }                                                               \
+        assert(parse_string_p <= parse_string_endp);                    \
+    }
+ 
+
+%}
+
+DIGIT	[0-9]
+WS	[[:space:]]*
+
+%x string
+%option noyywrap
+
+%%
+
+-?(0|[1-9][0-9]*)"."[0-9]+([eE][+-]?[0-9]+)?{WS}	yylval.f = strtod(yytext, NULL); return REAL;
+-?(0|[1-9][0-9]*)("."[0-9]+)?([eE][+-]?[0-9]+){WS}	yylval.f = strtod(yytext, NULL); return REAL;
+
+
+("-"|"+")?{DIGIT}+{WS}	yylval.i = strtoimax(yytext, NULL, 10); return INTEGER;
+
+":"{WS}  		return COLON;
+
+","{WS}			return COMMA;
+
+\"			BEGIN(string); stringbuilder = make_append_buffer();
+<string>[^\\"\n]*	append_to_buffer_s(stringbuilder, yytext);
+<string>\\\"		append_to_buffer_c(stringbuilder, '\"');
+<string>\\\\		append_to_buffer_c(stringbuilder, '\\');
+<string>\\\/		append_to_buffer_c(stringbuilder, '/');
+<string>\\b		append_to_buffer_c(stringbuilder, '\b');
+<string>\\f		append_to_buffer_c(stringbuilder, '\f');
+<string>\\n		append_to_buffer_c(stringbuilder, '\n');
+<string>\\r		append_to_buffer_c(stringbuilder, '\r');
+<string>\\t		append_to_buffer_c(stringbuilder, '\t');
+<string>\\u		snprintf(err_msg, ERR_MSG_LEN, "don't support unicode yet\n"); return 0;
+<string>\\.		snprintf(err_msg, ERR_MSG_LEN, "bogus escape '%s' in string\n", yytext); return 0;
+<string>\n		snprintf(err_msg, ERR_MSG_LEN, "newline in string\n"); return 0;
+<string>\"		{
+        append_to_buffer_c(stringbuilder, '\0');
+        yylval.str = (char*)stringbuilder->buf;
+        free(stringbuilder);
+        BEGIN(INITIAL);
+        return STRING;
+    }
+
+"["{WS}			return LEFTSQ;
+"]"{WS}			return RIGHTSQ;
+"{"{WS}			return LEFTCURLY;
+"}"{WS}			return RIGHTCURLY;
+
+"true"{WS}		return TRUE;
+"false"{WS}		return FALSE;
+"null"{WS}		return JNULL;
+
+.			snprintf(err_msg, ERR_MSG_LEN, "invalid character: %c", yytext[0]); return 0;
+
+%%
+
+#if 0
+static char* copy_string(const char* s) 
+{
+    size_t slen = strlen(s);
+    char* r;
+    if ((r = (char*)malloc(slen+1)) == NULL) {
+        fprintf(stderr, "Can't allocate %ld bytes for string\n", slen);
+        exit(1);
+    }
+    strcpy(r, s);
+    return r;
+}
+#endif
+ 
+int lex_setup(const char* s) 
+{
+    int slen;                   /* length of buffer */
+    
+    YY_FLUSH_BUFFER;            /* flex-specific? */
+    /* ensure we're in the start state (in case this function is
+       called twice for some reason, such as showing the sequence of
+       lexemes before the parse) */
+    BEGIN(0);
+
+    if (parse_string_buf != NULL) {
+        // we've been called before -- forget that
+        lex_release();
+    }
+    assert(parse_string_buf == NULL);
+
+    /* Initialise/update the length of the copy_string buffer */
+    //copy_string_init(s);
+
+    // skip any leading whitespace (handling this in the grammar
+    // complicates it disproportionately)
+    while (*s != '\0' && isspace(*s)) {
+        s++;
+    }
+
+    slen = strlen(s) + 1;
+    if ((parse_string_buf = (const char*)malloc(slen)) == NULL) {
+        fprintf(stderr, "Can't allocate memory for %d-byte string", slen);
+        exit(1);                /* too drastic? */
+    }
+    assert(parse_string_buf != NULL);
+
+    memcpy((void*)parse_string_buf, (const void*)s, slen);
+    parse_string_p = parse_string_buf;
+    parse_string_endp = parse_string_p + slen - 1; /* point to trailing '\0' */
+    assert (*parse_string_endp == '\0');
+
+    err_msg[0] = '\0';
+    //invalid_character = '\0';
+
+    // insert a fake call to unput, to suppress a "defined but not
+    // used" warning
+    while (0) {
+        unput('x');
+    }
+
+    return 0;
+}
+
+void lex_release(void) 
+{
+    // lex_release releases the memory allocated in lex_setup.
+    // Without this, we'd have a memory leak after repeated calls.
+    // I spit on C.
+    if (parse_string_buf != NULL) {
+        free((void*)parse_string_buf);
+    }
+    parse_string_buf = parse_string_p = parse_string_endp = NULL;
+    assert(parse_string_buf == NULL);
+
+    return;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/Makefile.am	Wed Sep 04 22:00:24 2013 +0100
@@ -0,0 +1,11 @@
+TESTS=unit_tests
+
+bin_PROGRAMS = unit_tests
+unit_tests_SOURCES = unit_tests.c
+unit_tests.$(OBJEXT): ../jason.h
+unit_tests_LDADD = ../libjason.la lcut.o
+
+EXTRA_DIST=lcut/README lcut/apr_ring.h lcut/lcut.c lcut/lcut.h
+
+lcut.o:
+	$(CC) -c -o lcut.o $(CFLAGS) lcut/lcut.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/lcut/README	Wed Sep 04 22:00:24 2013 +0100
@@ -0,0 +1,5 @@
+These files are copied from <https://github.com/nxg/lcut>,
+which is a fork of <https://github.com/bigwhite/lcut>,
+with fixes included in the pull request at <https://github.com/bigwhite/lcut/pull/2>
+
+lcut has an Apache 2.0 licence.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/lcut/apr_ring.h	Wed Sep 04 22:00:24 2013 +0100
@@ -0,0 +1,516 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This code draws heavily from the 4.4BSD <sys/queue.h> macros
+ * and Dean Gaudet's "splim/ring.h".
+ * <http://www.freebsd.org/cgi/cvsweb.cgi/src/sys/sys/queue.h>
+ * <http://www.arctic.org/~dean/splim/>
+ *
+ * We'd use Dean's code directly if we could guarantee the
+ * availability of inline functions.
+ */
+
+#ifndef APR_RING_H
+#define APR_RING_H
+
+/**
+ * @file apr_ring.h
+ * @brief APR Rings
+ */
+
+/*
+ * for offsetof()
+ *
+ * !here the apr_general.h has been removed, Tony Bai
+ */
+#include <stddef.h>
+#define APR_OFFSETOF(s_type,field) offsetof(s_type,field)
+
+/**
+ * @defgroup apr_ring Ring Macro Implementations
+ * @ingroup APR 
+ * A ring is a kind of doubly-linked list that can be manipulated
+ * without knowing where its head is.
+ * @{
+ */
+
+/**
+ * The Ring Element
+ *
+ * A ring element struct is linked to the other elements in the ring
+ * through its ring entry field, e.g.
+ * <pre>
+ *      struct my_element_t {
+ *          APR_RING_ENTRY(my_element_t) link;
+ *          int foo;
+ *          char *bar;
+ *      };
+ * </pre>
+ *
+ * An element struct may be put on more than one ring if it has more
+ * than one APR_RING_ENTRY field. Each APR_RING_ENTRY has a corresponding
+ * APR_RING_HEAD declaration.
+ *
+ * @warning For strict C standards compliance you should put the APR_RING_ENTRY
+ * first in the element struct unless the head is always part of a larger
+ * object with enough earlier fields to accommodate the offsetof() used
+ * to compute the ring sentinel below. You can usually ignore this caveat.
+ */
+#define APR_RING_ENTRY(elem)						\
+    struct {								\
+	struct elem * volatile next;					\
+	struct elem * volatile prev;					\
+    }
+
+/**
+ * The Ring Head
+ *
+ * Each ring is managed via its head, which is a struct declared like this:
+ * <pre>
+ *      APR_RING_HEAD(my_ring_t, my_element_t);
+ *      struct my_ring_t ring, *ringp;
+ * </pre>
+ *
+ * This struct looks just like the element link struct so that we can
+ * be sure that the typecasting games will work as expected.
+ *
+ * The first element in the ring is next after the head, and the last
+ * element is just before the head.
+ */
+#define APR_RING_HEAD(head, elem)					\
+    struct head {							\
+	struct elem *next;						\
+	struct elem *prev;						\
+    }
+
+/**
+ * The Ring Sentinel
+ *
+ * This is the magic pointer value that occurs before the first and
+ * after the last elements in the ring, computed from the address of
+ * the ring's head.  The head itself isn't an element, but in order to
+ * get rid of all the special cases when dealing with the ends of the
+ * ring, we play typecasting games to make it look like one.
+ *
+ * Here is a diagram to illustrate the arrangements of the next and
+ * prev pointers of each element in a single ring. Note that they point
+ * to the start of each element, not to the APR_RING_ENTRY structure.
+ *
+ * <pre>
+ *     +->+------+<-+  +->+------+<-+  +->+------+<-+
+ *     |  |struct|  |  |  |struct|  |  |  |struct|  |
+ *    /   | elem |   \/   | elem |   \/   | elem |  \
+ * ...    |      |   /\   |      |   /\   |      |   ...
+ *        +------+  |  |  +------+  |  |  +------+
+ *   ...--|prev  |  |  +--|ring  |  |  +--|prev  |
+ *        |  next|--+     | entry|--+     |  next|--...
+ *        +------+        +------+        +------+
+ *        | etc. |        | etc. |        | etc. |
+ *        :      :        :      :        :      :
+ * </pre>
+ *
+ * The APR_RING_HEAD is nothing but a bare APR_RING_ENTRY. The prev
+ * and next pointers in the first and last elements don't actually
+ * point to the head, they point to a phantom place called the
+ * sentinel. Its value is such that last->next->next == first because
+ * the offset from the sentinel to the head's next pointer is the same
+ * as the offset from the start of an element to its next pointer.
+ * This also works in the opposite direction.
+ *
+ * <pre>
+ *        last                            first
+ *     +->+------+<-+  +->sentinel<-+  +->+------+<-+
+ *     |  |struct|  |  |            |  |  |struct|  |
+ *    /   | elem |   \/              \/   | elem |  \
+ * ...    |      |   /\              /\   |      |   ...
+ *        +------+  |  |  +------+  |  |  +------+
+ *   ...--|prev  |  |  +--|ring  |  |  +--|prev  |
+ *        |  next|--+     |  head|--+     |  next|--...
+ *        +------+        +------+        +------+
+ *        | etc. |                        | etc. |
+ *        :      :                        :      :
+ * </pre>
+ *
+ * Note that the offset mentioned above is different for each kind of
+ * ring that the element may be on, and each kind of ring has a unique
+ * name for its APR_RING_ENTRY in each element, and has its own type
+ * for its APR_RING_HEAD.
+ *
+ * Note also that if the offset is non-zero (which is required if an
+ * element has more than one APR_RING_ENTRY), the unreality of the
+ * sentinel may have bad implications on very perverse implementations
+ * of C -- see the warning in APR_RING_ENTRY.
+ *
+ * @param hp   The head of the ring
+ * @param elem The name of the element struct
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ */
+#define APR_RING_SENTINEL(hp, elem, link)				\
+    (struct elem *)((char *)(&(hp)->next) - APR_OFFSETOF(struct elem, link))
+
+/**
+ * The first element of the ring
+ * @param hp   The head of the ring
+ */
+#define APR_RING_FIRST(hp)	(hp)->next
+/**
+ * The last element of the ring
+ * @param hp   The head of the ring
+ */
+#define APR_RING_LAST(hp)	(hp)->prev
+/**
+ * The next element in the ring
+ * @param ep   The current element
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ */
+#define APR_RING_NEXT(ep, link)	(ep)->link.next
+/**
+ * The previous element in the ring
+ * @param ep   The current element
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ */
+#define APR_RING_PREV(ep, link)	(ep)->link.prev
+
+
+/**
+ * Initialize a ring
+ * @param hp   The head of the ring
+ * @param elem The name of the element struct
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ */
+#define APR_RING_INIT(hp, elem, link) do {				\
+	APR_RING_FIRST((hp)) = APR_RING_SENTINEL((hp), elem, link);	\
+	APR_RING_LAST((hp))  = APR_RING_SENTINEL((hp), elem, link);	\
+    } while (0)
+
+/**
+ * Determine if a ring is empty
+ * @param hp   The head of the ring
+ * @param elem The name of the element struct
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ * @return true or false
+ */
+#define APR_RING_EMPTY(hp, elem, link)					\
+    (APR_RING_FIRST((hp)) == APR_RING_SENTINEL((hp), elem, link))
+
+/**
+ * Initialize a singleton element
+ * @param ep   The element
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ */
+#define APR_RING_ELEM_INIT(ep, link) do {				\
+	APR_RING_NEXT((ep), link) = (ep);				\
+	APR_RING_PREV((ep), link) = (ep);				\
+    } while (0)
+
+
+/**
+ * Splice the sequence ep1..epN into the ring before element lep
+ *   (..lep.. becomes ..ep1..epN..lep..)
+ * @warning This doesn't work for splicing before the first element or on
+ *   empty rings... see APR_RING_SPLICE_HEAD for one that does
+ * @param lep  Element in the ring to splice before
+ * @param ep1  First element in the sequence to splice in
+ * @param epN  Last element in the sequence to splice in
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ */
+#define APR_RING_SPLICE_BEFORE(lep, ep1, epN, link) do {		\
+	APR_RING_NEXT((epN), link) = (lep);				\
+	APR_RING_PREV((ep1), link) = APR_RING_PREV((lep), link);	\
+	APR_RING_NEXT(APR_RING_PREV((lep), link), link) = (ep1);	\
+	APR_RING_PREV((lep), link) = (epN);				\
+    } while (0)
+
+/**
+ * Splice the sequence ep1..epN into the ring after element lep
+ *   (..lep.. becomes ..lep..ep1..epN..)
+ * @warning This doesn't work for splicing after the last element or on
+ *   empty rings... see APR_RING_SPLICE_TAIL for one that does
+ * @param lep  Element in the ring to splice after
+ * @param ep1  First element in the sequence to splice in
+ * @param epN  Last element in the sequence to splice in
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ */
+#define APR_RING_SPLICE_AFTER(lep, ep1, epN, link) do {			\
+	APR_RING_PREV((ep1), link) = (lep);				\
+	APR_RING_NEXT((epN), link) = APR_RING_NEXT((lep), link);	\
+	APR_RING_PREV(APR_RING_NEXT((lep), link), link) = (epN);	\
+	APR_RING_NEXT((lep), link) = (ep1);				\
+    } while (0)
+
+/**
+ * Insert the element nep into the ring before element lep
+ *   (..lep.. becomes ..nep..lep..)
+ * @warning This doesn't work for inserting before the first element or on
+ *   empty rings... see APR_RING_INSERT_HEAD for one that does
+ * @param lep  Element in the ring to insert before
+ * @param nep  Element to insert
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ */
+#define APR_RING_INSERT_BEFORE(lep, nep, link)				\
+	APR_RING_SPLICE_BEFORE((lep), (nep), (nep), link)
+
+/**
+ * Insert the element nep into the ring after element lep
+ *   (..lep.. becomes ..lep..nep..)
+ * @warning This doesn't work for inserting after the last element or on
+ *   empty rings... see APR_RING_INSERT_TAIL for one that does
+ * @param lep  Element in the ring to insert after
+ * @param nep  Element to insert
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ */
+#define APR_RING_INSERT_AFTER(lep, nep, link)				\
+	APR_RING_SPLICE_AFTER((lep), (nep), (nep), link)
+
+
+/**
+ * Splice the sequence ep1..epN into the ring before the first element
+ *   (..hp.. becomes ..hp..ep1..epN..)
+ * @param hp   Head of the ring
+ * @param ep1  First element in the sequence to splice in
+ * @param epN  Last element in the sequence to splice in
+ * @param elem The name of the element struct
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ */
+#define APR_RING_SPLICE_HEAD(hp, ep1, epN, elem, link)			\
+	APR_RING_SPLICE_AFTER(APR_RING_SENTINEL((hp), elem, link),	\
+			     (ep1), (epN), link)
+
+/**
+ * Splice the sequence ep1..epN into the ring after the last element
+ *   (..hp.. becomes ..ep1..epN..hp..)
+ * @param hp   Head of the ring
+ * @param ep1  First element in the sequence to splice in
+ * @param epN  Last element in the sequence to splice in
+ * @param elem The name of the element struct
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ */
+#define APR_RING_SPLICE_TAIL(hp, ep1, epN, elem, link)			\
+	APR_RING_SPLICE_BEFORE(APR_RING_SENTINEL((hp), elem, link),	\
+			     (ep1), (epN), link)
+
+/**
+ * Insert the element nep into the ring before the first element
+ *   (..hp.. becomes ..hp..nep..)
+ * @param hp   Head of the ring
+ * @param nep  Element to insert
+ * @param elem The name of the element struct
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ */
+#define APR_RING_INSERT_HEAD(hp, nep, elem, link)			\
+	APR_RING_SPLICE_HEAD((hp), (nep), (nep), elem, link)
+
+/**
+ * Insert the element nep into the ring after the last element
+ *   (..hp.. becomes ..nep..hp..)
+ * @param hp   Head of the ring
+ * @param nep  Element to insert
+ * @param elem The name of the element struct
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ */
+#define APR_RING_INSERT_TAIL(hp, nep, elem, link)			\
+	APR_RING_SPLICE_TAIL((hp), (nep), (nep), elem, link)
+
+/**
+ * Concatenate ring h2 onto the end of ring h1, leaving h2 empty.
+ * @param h1   Head of the ring to concatenate onto
+ * @param h2   Head of the ring to concatenate
+ * @param elem The name of the element struct
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ */
+#define APR_RING_CONCAT(h1, h2, elem, link) do {			\
+	if (!APR_RING_EMPTY((h2), elem, link)) {			\
+	    APR_RING_SPLICE_BEFORE(APR_RING_SENTINEL((h1), elem, link),	\
+				  APR_RING_FIRST((h2)),			\
+				  APR_RING_LAST((h2)), link);		\
+	    APR_RING_INIT((h2), elem, link);				\
+	}								\
+    } while (0)
+
+/**
+ * Prepend ring h2 onto the beginning of ring h1, leaving h2 empty.
+ * @param h1   Head of the ring to prepend onto
+ * @param h2   Head of the ring to prepend
+ * @param elem The name of the element struct
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ */
+#define APR_RING_PREPEND(h1, h2, elem, link) do {			\
+	if (!APR_RING_EMPTY((h2), elem, link)) {			\
+	    APR_RING_SPLICE_AFTER(APR_RING_SENTINEL((h1), elem, link),	\
+				  APR_RING_FIRST((h2)),			\
+				  APR_RING_LAST((h2)), link);		\
+	    APR_RING_INIT((h2), elem, link);				\
+	}								\
+    } while (0)
+
+/**
+ * Unsplice a sequence of elements from a ring
+ * @warning The unspliced sequence is left with dangling pointers at either end
+ * @param ep1  First element in the sequence to unsplice
+ * @param epN  Last element in the sequence to unsplice
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ */
+#define APR_RING_UNSPLICE(ep1, epN, link) do {				\
+	APR_RING_NEXT(APR_RING_PREV((ep1), link), link) =		\
+		     APR_RING_NEXT((epN), link);			\
+	APR_RING_PREV(APR_RING_NEXT((epN), link), link) =		\
+		     APR_RING_PREV((ep1), link);			\
+    } while (0)
+
+/**
+ * Remove a single element from a ring
+ * @warning The unspliced element is left with dangling pointers at either end
+ * @param ep   Element to remove
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ */
+#define APR_RING_REMOVE(ep, link)					\
+    APR_RING_UNSPLICE((ep), (ep), link)
+
+/**
+ * Iterate over a ring
+ * @param ep The current element
+ * @param head The head of the ring
+ * @param elem The name of the element struct
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ */
+#define APR_RING_FOREACH(ep, head, elem, link)                          \
+    for (ep = APR_RING_FIRST(head);                                     \
+         ep != APR_RING_SENTINEL(head, elem, link);                     \
+         ep = APR_RING_NEXT(ep, link))
+
+/**
+ * Iterate over a ring safe against removal of the current element
+ * @param ep1 The current element
+ * @param ep2 Iteration cursor
+ * @param head The head of the ring
+ * @param elem The name of the element struct
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ */
+#define APR_RING_FOREACH_SAFE(ep1, ep2, head, elem, link)               \
+    for (ep1 = APR_RING_FIRST(head), ep2 = APR_RING_NEXT(ep1, link);    \
+         ep1 != APR_RING_SENTINEL(head, elem, link);                    \
+         ep1 = ep2, ep2 = APR_RING_NEXT(ep1, link))
+
+/* Debugging tools: */
+
+#ifdef APR_RING_DEBUG
+#include <stdio.h>
+#include <assert.h>
+
+#define APR_RING_CHECK_ONE(msg, ptr)					\
+	fprintf(stderr, "*** %s %p\n", msg, ptr)
+
+#define APR_RING_CHECK(hp, elem, link, msg)				\
+	APR_RING_CHECK_ELEM(APR_RING_SENTINEL(hp, elem, link), elem, link, msg)
+
+#define APR_RING_CHECK_ELEM(ep, elem, link, msg) do {			\
+	struct elem *start = (ep);					\
+	struct elem *here = start;					\
+	fprintf(stderr, "*** ring check start -- %s\n", msg);		\
+	do {								\
+	    fprintf(stderr, "\telem %p\n", here);			\
+	    fprintf(stderr, "\telem->next %p\n",			\
+		    APR_RING_NEXT(here, link));				\
+	    fprintf(stderr, "\telem->prev %p\n",			\
+		    APR_RING_PREV(here, link));				\
+	    fprintf(stderr, "\telem->next->prev %p\n",			\
+		    APR_RING_PREV(APR_RING_NEXT(here, link), link));	\
+	    fprintf(stderr, "\telem->prev->next %p\n",			\
+		    APR_RING_NEXT(APR_RING_PREV(here, link), link));	\
+	    if (APR_RING_PREV(APR_RING_NEXT(here, link), link) != here) { \
+		fprintf(stderr, "\t*** elem->next->prev != elem\n");	\
+		break;							\
+	    }								\
+	    if (APR_RING_NEXT(APR_RING_PREV(here, link), link) != here) { \
+		fprintf(stderr, "\t*** elem->prev->next != elem\n");	\
+		break;							\
+	    }								\
+	    here = APR_RING_NEXT(here, link);				\
+	} while (here != start);					\
+	fprintf(stderr, "*** ring check end\n");			\
+    } while (0)
+
+#define APR_RING_CHECK_CONSISTENCY(hp, elem, link)			\
+	APR_RING_CHECK_ELEM_CONSISTENCY(APR_RING_SENTINEL(hp, elem, link),\
+					elem, link)
+
+#define APR_RING_CHECK_ELEM_CONSISTENCY(ep, elem, link) do {		\
+	struct elem *start = (ep);					\
+	struct elem *here = start;					\
+	do {								\
+	    assert(APR_RING_PREV(APR_RING_NEXT(here, link), link) == here); \
+	    assert(APR_RING_NEXT(APR_RING_PREV(here, link), link) == here); \
+	    here = APR_RING_NEXT(here, link);				\
+	} while (here != start);					\
+    } while (0)
+
+#else
+/**
+ * Print a single pointer value to STDERR
+ *   (This is a no-op unless APR_RING_DEBUG is defined.)
+ * @param msg Descriptive message
+ * @param ptr Pointer value to print
+ */
+#define APR_RING_CHECK_ONE(msg, ptr)
+/**
+ * Dump all ring pointers to STDERR, starting with the head and looping all
+ * the way around the ring back to the head.  Aborts if an inconsistency
+ * is found.
+ *   (This is a no-op unless APR_RING_DEBUG is defined.)
+ * @param hp   Head of the ring
+ * @param elem The name of the element struct
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ * @param msg  Descriptive message
+ */
+#define APR_RING_CHECK(hp, elem, link, msg)
+/**
+ * Loops around a ring and checks all the pointers for consistency.  Pops
+ * an assertion if any inconsistency is found.  Same idea as APR_RING_CHECK()
+ * except that it's silent if all is well.
+ *   (This is a no-op unless APR_RING_DEBUG is defined.)
+ * @param hp   Head of the ring
+ * @param elem The name of the element struct
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ */
+#define APR_RING_CHECK_CONSISTENCY(hp, elem, link)
+/**
+ * Dump all ring pointers to STDERR, starting with the given element and
+ * looping all the way around the ring back to that element.  Aborts if
+ * an inconsistency is found.
+ *   (This is a no-op unless APR_RING_DEBUG is defined.)
+ * @param ep   The element
+ * @param elem The name of the element struct
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ * @param msg  Descriptive message
+ */
+#define APR_RING_CHECK_ELEM(ep, elem, link, msg)
+/**
+ * Loops around a ring, starting with the given element, and checks all
+ * the pointers for consistency.  Pops an assertion if any inconsistency
+ * is found.  Same idea as APR_RING_CHECK_ELEM() except that it's silent
+ * if all is well.
+ *   (This is a no-op unless APR_RING_DEBUG is defined.)
+ * @param ep   The element
+ * @param elem The name of the element struct
+ * @param link The name of the APR_RING_ENTRY in the element struct
+ */
+#define APR_RING_CHECK_ELEM_CONSISTENCY(ep, elem, link)
+#endif
+
+/** @} */ 
+
+#endif /* !APR_RING_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/lcut/lcut.c	Wed Sep 04 22:00:24 2013 +0100
@@ -0,0 +1,453 @@
+/*
+ * Copyright (c) 2005-2010 Tony Bai <bigwhite.cn@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+#include "lcut.h"
+
+static lcut_symbol_head_t _symbol_list;
+
+static lcut_symbol_t* lookup_symbol(const char *symbol_name, int obj_type);
+static void add_value(lcut_symbol_t *s, void *value, int count);
+static lcut_value_t* get_value(lcut_symbol_t *s);
+
+#define RETURN_WHEN_FAILED(tc) do { \
+    if ((tc)->status == TEST_CASE_FAILURE) { \
+        return; \
+    } \
+} while(0)
+
+#define FILL_IN_FAILED_REASON(tc, fname, fcname, lineno, reason_fmt, ...) do { \
+    snprintf((tc)->fname, LCUT_MAX_NAME_LEN, "%s", (fname));         \
+    snprintf((tc)->fcname, LCUT_MAX_NAME_LEN, "%s", (fcname));       \
+    (tc)->line = (lineno); \
+    snprintf((tc)->reason, LCUT_MAX_STR_LEN, (reason_fmt), __VA_ARGS__); \
+    (tc)->status = TEST_CASE_FAILURE; \
+} while (0)
+                                  
+void lcut_int_equal(lcut_tc_t *tc,
+                    const int expected,
+                    const int actual,
+                    int lineno,
+                    const char *fcname,
+                    const char *fname) {
+    RETURN_WHEN_FAILED(tc);
+
+    if (expected != actual) {
+        FILL_IN_FAILED_REASON(tc, fname, fcname, lineno, 
+                              "expected<%d> : actual<%d>",
+                              expected,
+                              actual);
+    }
+}
+
+void lcut_int_nequal(lcut_tc_t *tc, const int expected, const int actual, int lineno,
+                     const char *fcname, const char *fname) {
+    RETURN_WHEN_FAILED(tc);
+
+    if (expected == actual) {
+        FILL_IN_FAILED_REASON(tc, fname, fcname, lineno, 
+                              "not expected<%d> : actual<%d>",
+                              expected,
+                              actual);
+    }
+}
+
+void lcut_str_equal(lcut_tc_t *tc, const char *expected, const char *actual, int lineno,
+                    const char *fcname, const char *fname) {
+    RETURN_WHEN_FAILED(tc);
+
+    /* 
+     * both pointers are NULL or pointing to the same memory 
+     */ 
+    if (expected == actual) return;
+
+    if (expected && actual) {
+        if (!strcmp(expected, actual)) {
+            return;
+        }
+    }
+
+    FILL_IN_FAILED_REASON(tc, fname, fcname, lineno,
+                          "expected<%s> : actual<%s>",
+                           expected ? expected : "NULL", 
+                           actual ? actual : "NULL");
+}
+
+void lcut_str_nequal(lcut_tc_t *tc, const char *expected, const char *actual, int lineno,
+                     const char *fcname, const char *fname) {
+    RETURN_WHEN_FAILED(tc);
+
+    if (expected && actual) {
+        if (strcmp(expected, actual)) {
+            return;
+        }
+    } else if (expected != actual) {
+        return;
+    }
+
+    FILL_IN_FAILED_REASON(tc, fname, fcname, lineno,
+                          "not expected<%s> : actual<%s>",
+                           expected ? expected : "NULL", 
+                           actual ? actual : "NULL");
+}
+
+void lcut_assert(lcut_tc_t *tc, const char *msg, int condition,
+                 int lineno, const char *fcname, const char *fname) {
+    RETURN_WHEN_FAILED(tc);
+
+    if (condition) {
+        return;
+    }
+
+    FILL_IN_FAILED_REASON(tc, fname, fcname, lineno,
+                          "%s",
+                          msg);
+}
+
+void lcut_true(lcut_tc_t *tc, int condition,
+               int lineno, const char *fcname, const char *fname) {
+    RETURN_WHEN_FAILED(tc);
+
+    if (condition) {
+        return;
+    }
+
+    FILL_IN_FAILED_REASON(tc, fname, fcname, lineno,
+                          "%s",
+                          "");
+}
+
+int lcut_test_init(lcut_test_t **test, const char *title, fixture_func setup, fixture_func teardown) {
+    int		        rv	= 0;
+    lcut_test_t		*p	= NULL;
+
+    p = malloc(sizeof(*p));
+    if (p == NULL) {
+        rv = errno;
+        printf("\t[LCUT]: malloc error!, errcode[%d]\n", errno);
+        return rv;
+    }
+
+    memset(p, 0, sizeof(lcut_test_t));
+    APR_RING_INIT(&(p->ts_head), lcut_ts_t, link);
+    strncpy(p->desc, title, LCUT_MAX_NAME_LEN);
+    p->setup = setup;
+    p->teardown = teardown;
+
+    APR_RING_INIT(&_symbol_list, lcut_symbol_t, link);
+
+    (*test) = p;
+
+    return rv;
+}
+
+void lcut_test_destroy(lcut_test_t **test) {
+    lcut_test_t     *p      = (*test);
+    lcut_ts_t       *ts     = NULL;
+    lcut_tc_t       *tc     = NULL;
+    lcut_symbol_t   *s      = NULL;
+    lcut_value_t    *v      = NULL;
+
+    while (!APR_RING_EMPTY(&_symbol_list, lcut_symbol_t, link)) {
+        s = APR_RING_FIRST(&_symbol_list);
+        if (s != NULL) {
+            while (!APR_RING_EMPTY(&(s->value_list), lcut_value_t, link)) {
+                v = APR_RING_FIRST(&(s->value_list));
+                if (v != NULL) {
+                    APR_RING_REMOVE(v, link);
+                    free(v);
+                    v = NULL;
+                }
+            }
+
+            APR_RING_REMOVE(s, link);
+            free(s);
+            s = NULL;
+        }
+    }
+
+    while (!APR_RING_EMPTY(&(p->ts_head), lcut_ts_t, link)) {
+        ts = APR_RING_FIRST(&(p->ts_head));
+        if (ts != NULL) {
+            while (!APR_RING_EMPTY(&(ts->tc_head), lcut_tc_t, link)) {
+                tc = APR_RING_FIRST(&(ts->tc_head));
+                if (tc != NULL) {
+                    APR_RING_REMOVE(tc, link);
+                    free(tc);
+                    tc = NULL;
+                }
+            }
+        }
+        APR_RING_REMOVE(ts, link);
+        free(ts);
+        ts = NULL;
+    }
+
+    free(p);
+    (*test) = NULL;
+}
+
+int lcut_ts_init(lcut_ts_t **ts, const char *title, fixture_func setup, fixture_func teardown) {
+    int         rv = 0;
+    lcut_ts_t	*p = NULL;
+
+    p = malloc(sizeof(lcut_ts_t));
+    if (p == NULL) {
+        rv = errno;
+        printf("\t[LCUT]: malloc error!, errcode[%d] n", errno);
+        return rv;
+    }
+    memset(p, 0, sizeof(lcut_ts_t));
+    APR_RING_INIT(&(p->tc_head), lcut_tc_t, link);
+    strncpy(p->desc, title, LCUT_MAX_NAME_LEN);
+    p->ran      = 0;
+    p->failed   = 0;
+    p->setup    = setup;
+    p->teardown = teardown;
+
+    (*ts) = p;
+
+    return rv;
+}
+
+void lcut_ts_add(lcut_test_t *test, lcut_ts_t *ts) {
+    lcut_ts_t	*p = ts;
+
+    APR_RING_ELEM_INIT(p, link);
+    APR_RING_INSERT_TAIL(&(test->ts_head), p, lcut_ts_t, link);
+    test->suites++;
+    test->cases += p->ran;
+}
+
+int lcut_tc_add(lcut_ts_t *ts,
+               const char *title,
+               tc_func func,
+               void *para,
+               fixture_func before,
+               fixture_func after) {
+    int	rv	= 0;
+    lcut_tc_t	*tc 	= NULL;
+
+    tc = malloc(sizeof(lcut_tc_t));
+    if (tc == NULL) {
+        rv = errno;
+        printf("\t[LCUT]: malloc error!, errcode[%d] n", rv);
+        return rv;
+    }
+    memset(tc, 0, sizeof(lcut_tc_t));
+
+    strncpy(tc->desc, title, LCUT_MAX_NAME_LEN);
+    tc->func = func;
+    tc->para = para;
+    tc->before = before;
+    tc->after = after;
+    APR_RING_ELEM_INIT(tc, link);
+    APR_RING_INSERT_TAIL(&(ts->tc_head), tc, lcut_tc_t, link);
+    ts->ran++;
+
+    return rv;
+}
+
+void lcut_test_run(lcut_test_t *test, int *result) {
+    lcut_ts_t	*ts	= NULL;
+    lcut_tc_t	*tc	= NULL;
+
+    printf("%s \n", LCUT_LOGO);
+    printf("Unit Test for '%s':\n\n", test->desc);
+
+    if (test->setup != NULL) {
+        test->setup();
+    }
+
+    APR_RING_FOREACH(ts, &(test->ts_head), lcut_ts_t, link) {
+        if (ts != NULL) {
+            printf("\tSuite <%s>: \n", ts->desc);
+            if (ts->setup != NULL) {
+                ts->setup();
+            }
+            APR_RING_FOREACH(tc, &(ts->tc_head), lcut_tc_t, link) {
+                if (tc != NULL) {
+                    if (tc->before != NULL) {
+                        tc->before();
+                    }
+
+                    tc->func(tc, tc->para);
+
+                    if (tc->after != NULL) {
+                        tc->after();
+                    }
+
+                    if (tc->status == TEST_CASE_SUCCESS) {
+                        printf(SUCCESS_TIP_FMT, tc->desc);
+                    } else if (tc->status == TEST_CASE_FAILURE) {
+                        ts->failed ++;
+                        printf(FAILURE_TIP_FMT, tc->desc, tc->fcname, tc->line,
+                               tc->fname, tc->reason);
+                        (*result) = TEST_CASE_FAILURE;
+                    }
+                }
+            }
+            if (ts->teardown != NULL) {
+                ts->teardown();
+            }
+        }
+    }
+
+    if (test->teardown != NULL) {
+        test->teardown();
+    }
+
+}
+
+void lcut_test_report(lcut_test_t *test) {
+    int failed_suites = 0;
+    int failed_cases  = 0;
+    lcut_ts_t *ts  = NULL;
+
+    APR_RING_FOREACH(ts, &(test->ts_head), lcut_ts_t, link) {
+        if (ts != NULL) {
+            if (ts->failed > 0) {
+                failed_suites++;
+            }
+            failed_cases += ts->failed;
+        }
+    }
+    printf("\nSummary: \n");
+    printf("\tTotal Suites: %d \n", test->suites);
+    printf("\tFailed Suites: %d \n", failed_suites);
+    printf("\tTotal Cases: %d \n", test->cases);
+    printf("\tFailed Cases: %d \n", failed_cases);
+
+    if (failed_suites == 0) {
+        printf(GREENBAR);
+    } else {
+        printf(REDBAR);
+    }
+}
+
+void* lcut_mock_obj(const char *fcname,
+                    int lineno,
+                    const char *fname,
+                    int obj_type) {
+    lcut_symbol_t   *s  = NULL;
+    lcut_value_t    *v  = NULL;
+    void            *p;
+
+    s = lookup_symbol(fcname, obj_type);
+    if (!s) {
+        printf("\t[LCUT]: can't find the symbol: <%s> which is being mocked!, %d line in file %s\n", 
+                fcname, lineno, fname);
+        exit(EXIT_FAILURE);
+    }
+
+    if (s->always_return_flag) return s->value;
+
+    v = get_value(s);
+    if (!v) {
+        printf("\t[LCUT]: you have not set the value of mock obj <%s>!, %d line in file %s\n", 
+                fcname, lineno, fname);
+        exit(EXIT_FAILURE);
+    }
+
+    p = v->value;
+
+    APR_RING_REMOVE(v, link);
+    free(v);
+
+    return p;
+}
+
+void lcut_mock_obj_return(const char *symbol_name,
+                          void *value,
+                          const char *fcname, 
+                          int lineno,
+                          const char *fname,
+                          int obj_type,
+                          int count) {
+  
+    lcut_symbol_t   *s  = NULL;
+
+    s = lookup_symbol(symbol_name, obj_type);
+    if (!s) {
+        errno = 0;
+        s = (lcut_symbol_t*)malloc(sizeof(*s));
+        if (!s) {
+            printf("\t[LCUT]: malloc error!, errcode[%d]\n", errno);
+            exit(EXIT_FAILURE);
+        }
+        memset(s, 0, sizeof(*s));
+        strncpy(s->desc, symbol_name, LCUT_MAX_NAME_LEN);
+        s->obj_type = obj_type;
+        APR_RING_INIT(&(s->value_list), lcut_value_t, link);
+        APR_RING_INSERT_TAIL(&_symbol_list, s, lcut_symbol_t, link);
+    }
+
+    return add_value(s, value, count);
+}
+
+static lcut_symbol_t* lookup_symbol(const char *symbol_name, int obj_type) {
+    lcut_symbol_t   *s  = NULL;
+
+    APR_RING_FOREACH(s, &_symbol_list, lcut_symbol_t, link) {
+        if (s != NULL) {
+            if ((s->obj_type == obj_type)
+                && (!strcmp(s->desc, symbol_name))) {
+                return s;
+            }
+        }
+    }
+
+    return NULL;
+}
+
+static void add_value(lcut_symbol_t *s, void *value, int count) {
+    lcut_value_t    *v  = NULL;
+    int i;
+
+    /* 
+     * make the obj always to return one same value 
+     * until another lcut_mock_obj_return invoking 
+     */
+    if (count == -1) {
+        s->always_return_flag = 1;
+        s->value = value;
+        return;
+    }
+
+    s->always_return_flag = 0;
+
+    for (i = 0; i < count; i++) {
+        errno = 0;    
+        v = (lcut_value_t*)malloc(sizeof(*v));
+        if (!v) {
+            printf("\t[LCUT]: malloc error!, errcode[%d]\n", errno);
+            exit(EXIT_FAILURE);
+        }
+        memset(v, 0, sizeof(*v));
+        v->value    = value;
+
+        APR_RING_INSERT_TAIL(&(s->value_list), v, lcut_value_t, link);
+    }
+}
+
+static lcut_value_t* get_value(lcut_symbol_t *s) {
+    lcut_value_t    *v  = NULL;
+
+    v = APR_RING_FIRST(&(s->value_list));
+    return v;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/lcut/lcut.h	Wed Sep 04 22:00:24 2013 +0100
@@ -0,0 +1,305 @@
+/*
+ * Copyright (c) 2005-2010 Tony Bai <bigwhite.cn@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * @file lcut.h
+ *
+ * @brief a Lightweight C Unit Testing framework,
+ *        working under single process and single-threaded environment
+ *
+ * Here is a diagram to illustrate the orgnization of LCUT
+ *     A logical Test
+ *           |
+ *           |
+ *      +-------------+
+ *     TS-1    ...  TS-N
+ *      |             |
+ *      |             |
+ * +-------+ ...   +--------+
+ * TC-1   TC-N     TC-1     TC-N
+ *
+ * TS - Test Suite, TC - Test Case
+ */
+
+#ifndef _LCUT_H
+#define _LCUT_H
+
+#ifdef _cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdlib.h> /* for exit */
+
+#include "apr_ring.h"
+
+#define LCUT_MAX_NAME_LEN 128
+#define LCUT_MAX_STR_LEN 128
+
+#define LCUT_LOGO \
+"*********************************************************\n\
+\tLCUT -- Lightweight C Unit Testing framework\n \
+\t\t By Tony Bai\n\
+*********************************************************"
+
+#define SUCCESS_TIP_FMT "\t\tCase '%s': Passed\n"
+#define FAILURE_TIP_FMT "\t\t\033[31mCase '%s': Failure occur in %s, %d line in file %s, %s\033[0m\n"
+
+#define REDBAR \
+"\n=======================\n\
+\t\033[31mRED BAR!\033[0m\n\
+=======================\n"
+
+#define GREENBAR \
+"\n==========================\n\
+\t\033[32mGREEN BAR!\033[0m\n\
+==========================\n"
+
+/* indicates the result of the Test Case executing */
+enum {
+    TEST_CASE_SUCCESS = 0,
+    TEST_CASE_FAILURE = 1
+};
+
+typedef struct lcut_tc_t lcut_tc_t;
+typedef void (*tc_func)(lcut_tc_t *tc, void *data);
+typedef void (*fixture_func)(void);
+
+struct lcut_tc_t {
+    APR_RING_ENTRY(lcut_tc_t)   link;
+    char                        desc[LCUT_MAX_NAME_LEN];    /* the description literal of the test case */
+    tc_func                     func;                       /* the executive body of the test case */
+    void                        *para;                      /* the parameter passed into the func above */
+    fixture_func                before;                     /* invoked before the test case func executed */
+    fixture_func                after;                      /* invoked after the test case func executed */
+    int                         status;                     /* the result of th test case executing*/
+    char                        fname[LCUT_MAX_NAME_LEN];   /* indicates the file name which the case lies in */
+    char                        fcname[LCUT_MAX_NAME_LEN];  /* indicates the func name when the case failed */
+    int                         line;                       /* indicates the line number when the case failed */
+    char                        reason[LCUT_MAX_STR_LEN];   /* string literal indicates the failed reason */
+};
+typedef APR_RING_HEAD(lcut_tc_head_t, lcut_tc_t) lcut_tc_head_t;
+
+typedef struct lcut_ts_t {
+    APR_RING_ENTRY(lcut_ts_t)   link;
+    char                        desc[LCUT_MAX_NAME_LEN];    /* the description literal of the test suite */
+    lcut_tc_head_t              tc_head;                    /* the head node of the test case ring */
+    fixture_func                setup;                      /* setup function */
+    fixture_func                teardown;                   /* teardown fucntion */
+    int                         ran;                        /* the total count of test cases */
+    int                         failed;                     /* the count of failed test case */
+} lcut_ts_t;
+typedef APR_RING_HEAD(lcut_ts_head_t, lcut_ts_t) lcut_ts_head_t;
+
+typedef struct lcut_test_t {
+    char                        desc[LCUT_MAX_NAME_LEN];    /* the description of a logical unit test */
+    lcut_ts_head_t              ts_head;                    /* the head node of the test suite ring */
+    fixture_func                setup;                      /* most top-level setup for the logic unit test */
+    fixture_func                teardown;                   /* most top-level teardown for the logic unit test */
+    int                         suites;                     /* the total count of test suites */
+    int                         cases;                      /* the total count of test cases */
+} lcut_test_t;
+
+int lcut_test_init(lcut_test_t **test, const char *title, fixture_func setup, fixture_func teardown);
+void lcut_test_destroy(lcut_test_t **test);
+int lcut_ts_init(lcut_ts_t **ts, const char *title, fixture_func setup, fixture_func teardown);
+void lcut_ts_add(lcut_test_t *test, lcut_ts_t *ts);
+int lcut_tc_add(lcut_ts_t *ts, const char *title, tc_func func,
+                void *para, fixture_func before, fixture_func after);
+void lcut_test_run(lcut_test_t *test, int *result);
+void lcut_test_report(lcut_test_t *test);
+
+/*
+ * LCUT_TEST_BEGIN and LCUT_TEST_END should be used in pair!
+ */
+#define LCUT_TEST_BEGIN(s, setup, teardown) \
+    lcut_test_t *_cut_test     = NULL; \
+    int   _cut_status; \
+    int   _cut_result    = TEST_CASE_SUCCESS; \
+    if ((_cut_status = lcut_test_init(&_cut_test, (s), (setup), (teardown))) != 0) { \
+        printf("[LCUT]: test init failed!, errcode[%d]\n", _cut_status); \
+        exit(1); \
+    }
+
+#define LCUT_TEST_END() do { \
+        lcut_test_destroy(&_cut_test); \
+    } while(0)
+
+/*
+ * initialize a Test Suite
+ *
+ * p -- lcut_ts_t*
+ * s -- suite description
+ * setup -- setup fixture function
+ * teardown -- teardown fixture function
+ */
+#define LCUT_TS_INIT(p, s, setup, teardown) do { \
+        if ((_cut_status = lcut_ts_init(&(p), (s), (setup), (teardown))) != 0) { \
+            printf("[LCUT]: test suite init failed!, errcode[%d]\n", _cut_status); \
+            exit(1); \
+        } \
+    } while(0)
+
+/*
+ * Add a Test Suite to a logical Test
+ *
+ * p -- lcut_ts_t*
+ *
+ * !notice: you must not add a test case into a test suite
+ * which has already been added into a logical test!
+ */
+#define LCUT_TS_ADD(p) do { \
+        lcut_ts_add(_cut_test, (p)); \
+    } while(0)
+
+/*
+ * Add a test case to a test suite
+ *
+ * p -- lcut_ts_t*
+ * s -- test case description
+ * f -- test case function
+ * e -- extra parameter
+ */
+#define LCUT_TC_ADD(p, s, f, e, before, after) do { \
+        if ((_cut_status = lcut_tc_add((p), (s), (f), (e), (before), (after))) != 0) { \
+            printf("[LCUT]: test case add failed!, errcode[%d]\n", _cut_status); \
+            exit(1); \
+        } \
+    } while(0)
+
+/*
+ * Run a logical unit test
+ */
+#define LCUT_TEST_RUN() do { \
+        lcut_test_run(_cut_test, &_cut_result); \
+    } while(0)
+
+/*
+ * Report the result of the logical test
+ */
+#define LCUT_TEST_REPORT() do { \
+        lcut_test_report(_cut_test); \
+    } while(0)
+
+/*
+ * Get the result of a logical test
+ *
+ * in order to integrated with a Continuous integration tool, e.g. cruisecontrol.rb
+ */
+#define LCUT_TEST_RESULT() do { \
+        exit(_cut_result); \
+    } while(0)
+
+void lcut_int_equal(lcut_tc_t *tc, const int expected, const int actual, int lineno,
+                    const char *fcname, const char *fname);
+void lcut_int_nequal(lcut_tc_t *tc, const int expected, const int actual, int lineno,
+                     const char *fcname, const char *fname);
+void lcut_str_equal(lcut_tc_t *tc, const char *expected, const char *actual, int lineno,
+                    const char *fcname, const char *fname);
+void lcut_str_nequal(lcut_tc_t *tc, const char *expected, const char *actual, int lineno, 
+                    const char *fcname, const char *fname);
+void lcut_assert(lcut_tc_t *tc, const char *msg, int condition,
+                 int lineno, const char *fcname, const char *fname);
+void lcut_true(lcut_tc_t *tc, int condition,
+               int lineno, const char *fcname, const char *fname);
+
+#define LCUT_INT_EQUAL(tc, expected, actual) do { \
+        lcut_int_equal(tc, expected, actual, __LINE__, __FUNCTION__, __FILE__); \
+    } while(0)
+
+#define LCUT_INT_NEQUAL(tc, expected, actual) do { \
+        lcut_int_nequal(tc, expected, actual, __LINE__, __FUNCTION__, __FILE__); \
+    } while(0)
+
+#define LCUT_STR_EQUAL(tc, expected, actual) do { \
+        lcut_str_equal(tc, expected, actual, __LINE__, __FUNCTION__, __FILE__); \
+    } while(0)
+
+#define LCUT_STR_NEQUAL(tc, expected, actual) do { \
+        lcut_str_nequal(tc, expected, actual, __LINE__, __FUNCTION__, __FILE__); \
+    } while(0)
+
+#define LCUT_ASSERT(tc, msg, condition) do { \
+        lcut_assert(tc, msg, (condition), __LINE__, __FUNCTION__, __FILE__); \
+    } while(0)
+
+#define LCUT_TRUE(tc, condition) do { \
+        lcut_true(tc, condition, __LINE__, __FUNCTION__, __FILE__); \
+    } while(0)
+
+/*
+ * mock symbol list
+ *
+ * ------------
+ * | symbol-#0|-> value_list
+ * ------------
+ * | symbol-#1|-> value_list
+ * ------------
+ * | symbol-#2|-> value_list
+ * ------------
+ * | ... ...  |-> value_list
+ * ------------
+ * | symbol-#n|-> value_list
+ * ------------
+ */
+
+typedef struct lcut_value_t {
+    APR_RING_ENTRY(lcut_value_t)    link;
+    void                            *value;
+} lcut_value_t;
+typedef APR_RING_HEAD(lcut_value_head_t, lcut_value_t) lcut_value_head_t;
+
+typedef struct lcut_symbol_t {
+    APR_RING_ENTRY(lcut_symbol_t)   link;
+    char                            desc[LCUT_MAX_NAME_LEN];
+    int                             obj_type;
+    int                             always_return_flag; /* 1: always return the same value; 0(default) */
+    void*                           value;
+    lcut_value_head_t               value_list;
+} lcut_symbol_t;
+typedef APR_RING_HEAD(lcut_symbol_head_t, lcut_symbol_t) lcut_symbol_head_t;
+
+void* lcut_mock_obj(const char *fcname, int lineno, const char *fname, int obj_type); 
+void lcut_mock_obj_return(const char *symbol_name, void *value, const char *fcname, 
+                          int lineno, const char *fname, int obj_type, int count);
+
+#define MOCK_ARG                0x0
+#define MOCK_RETV               0x1
+
+#define LCUT_MOCK_ARG() lcut_mock_obj(__FUNCTION__, __LINE__, __FILE__, MOCK_ARG)
+#define LCUT_MOCK_RETV() lcut_mock_obj(__FUNCTION__, __LINE__, __FILE__, MOCK_RETV)
+
+#define LCUT_ARG_RETURN(fcname, value) do { \
+        lcut_mock_obj_return(#fcname, (void*)value, __FUNCTION__, __LINE__, __FILE__, MOCK_ARG, 1); \
+    } while(0);
+
+#define LCUT_ARG_RETURN_COUNT(fcname, value, count) do { \
+        lcut_mock_obj_return(#fcname, (void*)value, __FUNCTION__, __LINE__, __FILE__, MOCK_ARG, count); \
+    } while(0);
+
+#define LCUT_RETV_RETURN(fcname, value) do { \
+        lcut_mock_obj_return(#fcname, (void*)value, __FUNCTION__, __LINE__, __FILE__, MOCK_RETV, 1); \
+    } while(0);
+
+#define LCUT_RETV_RETURN_COUNT(fcname, value, count) do { \
+        lcut_mock_obj_return(#fcname, (void*)value, __FUNCTION__, __LINE__, __FILE__, MOCK_RETV, count); \
+    } while(0);
+
+#ifdef _cplusplus
+}
+#endif
+
+#endif /* _LCUT_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/unit_tests.c	Wed Sep 04 22:00:24 2013 +0100
@@ -0,0 +1,500 @@
+#include <math.h>
+#include "../jason.h"
+#include "lcut/lcut.h"
+
+
+void tc_basic_json_parse(lcut_tc_t *tc, void *data)
+{
+    JsonObject o;
+    byte* b;
+    size_t blen;
+    
+    // Just a single uncomplicated one of each, to check the basics
+
+    o = parse_json_string("1");
+    b = get_der_encoding(o, &blen);
+    LCUT_STR_EQUAL(tc, "020101", bytes_to_string(b, blen));
+
+    o = parse_json_string("\"foo\"");
+    b = get_der_encoding(o, &blen);
+    LCUT_STR_EQUAL(tc, "1603666f6f", bytes_to_string(b, blen));
+
+    o = parse_json_string("true");
+    b = get_der_encoding(o, &blen);
+    LCUT_STR_EQUAL(tc, "0101ff", bytes_to_string(b, blen));
+
+    o = parse_json_string("false");
+    b = get_der_encoding(o, &blen);
+    LCUT_STR_EQUAL(tc, "010100", bytes_to_string(b, blen));
+
+    o = parse_json_string("null");
+    b = get_der_encoding(o, &blen);
+    LCUT_STR_EQUAL(tc, "0500", bytes_to_string(b, blen));
+
+    o = parse_json_string("[1, \"two\", true, null]");
+    b = get_der_encoding(o, &blen);
+    LCUT_STR_EQUAL(tc, "300d020101160374776f0101ff0500", bytes_to_string(b, blen));
+
+    o = parse_json_string("{\"one\": 1, \"two\": 2, \"thr\": 3}");
+    b = get_der_encoding(o, &blen);
+    // The resulting order is important: DER-encoding requires that
+    // the elements in the SET OF are ordered by the lexicographic
+    // order of their contents.  The test above is chosen so that the
+    // contents will have to be reordered irrespective of what order
+    // they're read internally.
+    LCUT_STR_EQUAL(tc,
+                   "311e"
+                   "300816036f6e65020101" // one:1
+                   "30081603746872020103" // three:3
+                   "3008160374776f020102", // two:2
+                   bytes_to_string(b, blen));
+
+    o = parse_json_string("2.0");
+    b = get_der_encoding(o, &blen);
+    LCUT_STR_EQUAL(tc, "0903800101", bytes_to_string(b, blen));
+}
+
+void tc_torture_syntax(lcut_tc_t *tc, void *data)
+{
+    JsonObject o;
+    size_t blen;
+    byte* b;
+
+    o = parse_json_string("  [ \n    1   ,  \"two\"  ,\t\ttrue, null ]    \n   ");
+    LCUT_ASSERT(tc, "string torture test failed", o != NULL);
+    b = get_der_encoding(o, &blen);
+    LCUT_STR_EQUAL(tc,
+                   "300d020101160374776f0101ff0500",
+                   bytes_to_string(b, blen));
+
+    // 4-byte integers
+    o = parse_json_string("[16777216,16777216,16777216,16777216]");
+    b = get_der_encoding(o, &blen);
+    LCUT_STR_EQUAL(tc,
+                   "3018020401000000020401000000020401000000020401000000",
+                   bytes_to_string(b, blen));
+
+    // more than 127 characters in DER encoding
+    // and including a string which is 200 characters long
+    o = parse_json_string("["
+                          "16777216,16777216,16777216,16777216,"
+                          "16777216,16777216,16777216,16777216,"
+                          "16777216,16777216,16777216,16777216,"
+                          "16777216,16777216,16777216,16777216,"
+                          "16777216,16777216,16777216,16777216,"
+                          "16777216,16777216,16777216,16777216,"
+                          "\"01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789\""
+                          "]");
+    b = get_der_encoding(o, &blen);
+    LCUT_STR_EQUAL(tc,
+                   "3082015b" // array with 347 octets of content
+                   "020401000000020401000000020401000000020401000000"
+                   "020401000000020401000000020401000000020401000000"
+                   "020401000000020401000000020401000000020401000000"
+                   "020401000000020401000000020401000000020401000000"
+                   "020401000000020401000000020401000000020401000000"
+                   "020401000000020401000000020401000000020401000000"
+                   "1681c8" // string with 200 octets
+                   "3031323334353637383930313233343536373839"
+                   "3031323334353637383930313233343536373839"
+                   "3031323334353637383930313233343536373839"
+                   "3031323334353637383930313233343536373839"
+                   "3031323334353637383930313233343536373839"
+                   "3031323334353637383930313233343536373839"
+                   "3031323334353637383930313233343536373839"
+                   "3031323334353637383930313233343536373839"
+                   "3031323334353637383930313233343536373839"
+                   "3031323334353637383930313233343536373839",
+                   bytes_to_string(b, blen));
+
+#define PARSEQ(str) do {                                                \
+        o = parse_json_string(str);                                     \
+        LCUT_ASSERT(tc, "parsing " str " should have succeeded", o != NULL); \
+    } while (0)
+
+    PARSEQ("{\"x\":1,\"two\":true}"); /* no spaces */
+    PARSEQ("\n\t  {\t\"x\" : 1  , \n  \"two\"  :  true   }   "); /* lots of spaces everywhere */
+    PARSEQ("  []");
+    PARSEQ("  {}");
+#undef PARSEQ
+}
+
+void tc_integers(lcut_tc_t *tc, void *data)
+{
+    JsonObject o;
+    byte* b;
+    size_t blen;
+
+    o = parse_json_string("[0,127,128,255,+256,-127,-128,-129]");
+    b = get_der_encoding(o, &blen);
+    LCUT_STR_EQUAL(tc,
+                   "301c" // array with 28 bytes of content
+                   "020100"
+                   "02017f"
+                   "020200"
+                   "80020200ff"
+                   "02020100"
+                   "020181"
+                   "020180"
+                   "0202ff7f",
+                   bytes_to_string(b, blen));
+    {
+        // The integer conversion code depends on the machine being a
+        // twos-complement machine, with sizeof(uintmax_t) being 8.
+        // Check this.
+        //
+        // I'm pretty sure this is a direct test of whether the
+        // machine is twos-complement, but I don't have a
+        // non-twos-complement machine to check it on!
+        unsigned char uc = 0x81;
+        signed char* scp = (signed char*)&uc;
+        LCUT_ASSERT(tc,
+                    "This machine does not appear to be twos-complement",
+                    *scp == -127);
+    }
+
+    /* integerconversion test: The following test only works if
+       uintmax_t is 8 (it could be generalised, but as long as the
+       test is occasionally run on a 64-bit machine, that's OK) */
+    if (sizeof(uintmax_t) == 8) {
+        char buf[128];
+        uintmax_t one = 1;
+        // presuming (u)intmax_t is 8 bytes / 64bits
+        uintmax_t um = (one<<(8*sizeof(uintmax_t)-1)); // 2^31
+        // sm is the largest signed integer; -sm is also a valid integer
+        jsonint   sm = (jsonint)(um-1);    // needs 8 bytes to represent
+        jsonint   s7 = 0x80000000000000-1; // needs 7 bytes to represent
+        snprintf(buf, 128, "[ %" JSONFMT ", %" JSONFMT
+                 ", %" JSONFMT ", %" JSONFMT
+                 ", 32767, -32767, 127, -127 ]",
+                 sm, -sm, s7, -s7);
+#if 0
+        fprintf(stderr,
+                "uintmax_t: n=%ld um=%" PRIuMAX "/%" PRIxMAX
+                "\n    sm=%" JSONFMT " -sm=%" JSONFMT "/%" PRIxMAX "\n"
+                "    buf=%s\n",
+                sizeof(uintmax_t), um, um, sm, -sm, -sm, buf);
+#endif
+        o = parse_json_string(buf);
+        b = get_der_encoding(o, &blen);
+#if 0
+        fprintf(stderr, "actual:%s\n", bytes_to_string(b, blen));
+#endif
+        LCUT_STR_EQUAL(tc,
+                       "3034"
+                       "02087fffffffffffffff"
+                       "02088000000000000001"
+                       "02077fffffffffffff"
+                       "020780000000000001"
+                       "02027fff"
+                       "02028001"
+                       "02017f"
+                       "020181",
+                       bytes_to_string(b, blen));
+    } else {
+        fputs("intmax_t test skipped", stderr);
+    }
+}
+
+void tc_reals(lcut_tc_t *tc, void *data)
+{
+    JsonObject o;
+    byte* b;
+    size_t blen;
+    char* benc;
+    char* expected;
+
+    o = parse_json_string("[ 0.0, 1.0, -1.0, "
+                          "2.0, -0.25,"
+                          "1e1," // +10
+                          "-1e1, -1.0e+1, -10.0, -1e+001, " // -10
+                          "-1.0E-1, 0.1,"
+                          "-1E12, 1.0e-2, "
+                          "1361129467683753853853498429727072845824.0" // 2^130
+                          " ]");
+
+    b = get_der_encoding(o, &blen);
+    benc = bytes_to_string(b, blen);
+    expected = "305e"
+        "0900" // 0.0
+        "0903800001" // 1x2^0 = 1
+        "0903c00001" // -1x2^0 = -1
+        "0903800101" // 1x2^1 = 2
+        "0903c0fe01" // -1x2^-2 = 0.25
+        "0903800105" // +10
+        "0903c00105" // -10x2^0 = -10
+        "0903c00105" // ditto
+        "0903c00105" // ditto
+        "0903c00105" // ditto
+        "0909c0c90ccccccccccccd" // -0.1 ?
+        "090980c90ccccccccccccd" // +0.1
+        "0906c00c0e8d4a51" // -1e+12 = 244140625 x 2^12
+        "090980c5147ae147ae147b" // 1e-2 = 5764607523034235 x 2^-59
+        "090481008201"; // 2^130
+
+#if 0
+    printf("--> expctd: %s\n    actual: %s\n", expected, benc);
+    {
+        size_t i = 0;
+        
+        while (expected[i] == benc[i] && expected[i] != '\0') {
+            i++;
+        }
+        if (expected[i] == '\0' && benc[i] == '\0') {
+            printf("    same\n");
+        } else {
+            printf("    first difference at %ld: %s != %s\n", i, &expected[i], &benc[i]);
+        }
+    }
+#endif
+    LCUT_STR_EQUAL(tc, expected, benc);
+
+    // check encoding of special real values (these can't be read in
+    // normal JSON, but it seems nice and complete to include these in
+    // the translation, just in case)
+    o = parse_json_string("1.0");
+    o->x.f = INFINITY;
+    b = get_der_encoding(o, &blen);
+    LCUT_STR_EQUAL(tc, "090140", bytes_to_string(b, blen));
+    o->der_encoding = NULL;     /* hack */
+    
+    o->x.f = -INFINITY;
+    b = get_der_encoding(o, &blen);
+    LCUT_STR_EQUAL(tc, "090141", bytes_to_string(b, blen));
+    o->der_encoding = NULL;     /* hack */
+    
+    o->x.f = nan("");
+    b = get_der_encoding(o, &blen);
+    LCUT_STR_EQUAL(tc, "090142", bytes_to_string(b, blen));
+    o->der_encoding = NULL;     /* hack */
+    
+    o->x.f = -0.0;
+    b = get_der_encoding(o, &blen);
+    LCUT_STR_EQUAL(tc, "090143", bytes_to_string(b, blen));
+    o->der_encoding = NULL;     /* hack */
+    
+}
+
+void tc_objects(lcut_tc_t *tc, void *data)
+{
+    JsonObject o;
+    size_t blen;
+    byte* b;
+
+    // Test DER-mandated sorting of the keys in an object.
+    // Remember that the sort order is based on the DER-encoded
+    // ["one",1], etc.
+    o = parse_json_string("{"
+                          "\"one\": 1,"
+                          "\"two\": 2,"
+                          "\"three\": 3,"
+                          "\"four\": 4,"
+                          "\"five\": 5,"
+                          "\"six\": 6,"
+                          "\"seven\": 7,"
+                          "\"eight\": 8,"
+                          "\"nine\": 9,"
+                          "\"ten\": 10 }");
+    b = get_der_encoding(o, &blen);
+    LCUT_STR_EQUAL(tc,
+                   "316d"
+                   "300816036f6e65020101" // one
+                   "30081603736978020106" // six
+                   "3008160374656e02010a" // ten
+                   "3008160374776f020102" // two
+                   "3009160466697665020105" // five
+                   "30091604666f7572020104" // four
+                   "300916046e696e65020109" // nine
+                   "300a16056569676874020108" // eight
+                   "300a1605736576656e020107" // seven
+                   "300a16057468726565020103",// three
+                   bytes_to_string(b, blen));
+}
+
+void tc_strings(lcut_tc_t *tc, void *data)
+{
+    JsonObject o;
+    byte* b;
+    size_t blen;
+
+    char* teststring = "\"q\\\" rs\\\\ s\\/ b\\b f\\f n\\n r\\r t\\t\"";
+    o = parse_json_string(teststring);
+
+    b = get_der_encoding(o, &blen);
+    {
+        char* expected =
+            "1618"
+            "712220" // q"
+            "72735c20" // rs<backslash>
+            "732f20" // s/
+            "620820" // b<backspace>
+            "660c20" // f<formfeed>
+            "6e0a20" // n<newline>
+            "720d20" // r<carriage-return>
+            "7409";  // t<tab>
+        char* actual = bytes_to_string(b, blen);
+        LCUT_STR_EQUAL(tc, expected, actual);
+    }
+
+#define SHOULDFAIL(str) do {                       \
+        o = parse_json_string(str);             \
+        LCUT_ASSERT(tc, "parsing " str " should have failed", o == NULL); \
+    } while (0)
+
+    SHOULDFAIL("\"bogus\\x\"");
+    SHOULDFAIL("\"newline\n\"");
+
+#undef SHOULDFAIL
+}
+
+void tc_reading_der(lcut_tc_t *tc, void *data)
+{
+    byte der_int[]    = { 0x02, 0x01, 0x01 }; // 1
+    char* str_int = "1";
+    
+    byte der_string[] = { 0x16, 0x03, 0x66, 0x6f, 0x6f }; // "foo"
+    char* str_string = "\"foo\"";
+
+    byte der_true[]   = { 0x01, 0x01, 0x12 }; // true (note, BER true, not DER)
+    char* str_true = "true";
+
+    byte der_false[]  = { 0x01, 0x01, 0x00 }; // false
+    char* str_false = "false";
+
+    byte der_null[]   = { 0x05, 0x00 }; // null
+    char* str_null = "null";
+
+    byte der_array[]  = { 0x30, 0x0d, // [1, "two", true, null]
+                                0x02, 0x01, 0x01,
+                                0x16, 0x03, 0x74, 0x77, 0x6f,
+                                0x01, 0x01, 0xff,
+                                0x05, 0x00 };
+    char* str_array = "[ 1, \"two\", true, null ]";
+
+    byte der_emptyarray[] = { 0x30, 0x00 };
+    char* str_emptyarray = "[  ]";
+
+    byte der_emptyobject[] = { 0x31, 0x00 };
+    char* str_emptyobject = "{  }";
+    
+    byte der_object[] = { 0x31, 0x14, // {"one": 1, "two": true}
+                                0x30, 0x08,
+                                      0x16, 0x03, 0x6f, 0x6e, 0x65,
+                                      0x02, 0x01, 0x01,
+                                0x30, 0x08,
+                                      0x16, 0x03, 0x74, 0x77, 0x6f,
+                                      0x01, 0x01, 0x01 }; // BER true
+    char* str_object = "{ \"one\": 1, \"two\": true }";
+
+    byte der_floatarray[] = { 0x30, 0x3f,  // 63
+                              0x09, 0x00,  // 0.0
+                              0x09, 0x03, 0x80, 0x00, 0x01,  // 1x2^0 = 1
+                              0x09, 0x03, 0xc0, 0x00, 0x01,  // -1x2^0 = -1
+                              0x09, 0x03, 0x80, 0x01, 0x01,  // 1x2^1 = 2
+                              0x09, 0x03, 0xc0, 0xfe, 0x01,  // -1x2^-2 = -0.25, 
+                              0x09, 0x03, 0xc0, 0x01, 0x05,  // -10x2^0 = -10
+                              0x09, 0x09, 0x80, 0xc9, 0x0c, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcd,  // +0.1
+                              0x09, 0x06, 0xc0, 0x0c, 0x0e, 0x8d, 0x4a, 0x51,  // -1e-12 = 244140625 x 2^12
+                              0x09, 0x09, 0x80, 0xc5, 0x14, 0x7a, 0xe1, 0x47, 0xae, 0x14, 0x7b,  // 1e-2 = 5764607523034235 x 2^-59
+                              0x09, 0x04, 0x81, 0x00, 0x82, 0x01 } ; // 2^130
+    char* str_floatarray = 
+        "[ 0.0, 1.0, -1.0, 2.0, -0.25, -10.0, 0.1, -1000000000000.0, 0.01, 1361129467683753853853498429727072845824.0 ]";
+
+    byte der_string_with_escapes[] = { 0x16, 0x18, 
+                                       0x71, 0x22, 0x20, 
+                                       0x72, 0x73, 0x5c, 0x20, 
+                                       0x73, 0x2f, 0x20, 
+                                       0x62, 0x08, 0x20, 
+                                       0x66, 0x0c, 0x20, 
+                                       0x6e, 0x0a, 0x20, 
+                                       0x72, 0x0d, 0x20, 
+                                       0x74, 0x09 };
+    char* str_string_with_escapes =
+        "\"q\\\" rs\\\\ s\\/ b\\b f\\f n\\n r\\r t\\t\"";
+    
+    const char* json_s;
+
+#define TESTDECODE(arr, str) do {                  \
+        json_s = print_json_object(parse_der_bytes(arr, sizeof(arr), NULL)); \
+        LCUT_STR_EQUAL(tc, str, json_s);                                \
+        if (json_s != NULL) {                                           \
+            free((void*)json_s);                                        \
+        }                                                               \
+      } while (0)
+
+    TESTDECODE(der_string, str_string);
+    TESTDECODE(der_int, str_int);
+    TESTDECODE(der_true, str_true);
+    TESTDECODE(der_false, str_false);
+    TESTDECODE(der_null, str_null);
+
+    // the following tests are slightly sensitive to the (irrelevant)
+    // details of the prettyprinting of JSON
+    TESTDECODE(der_array, str_array);
+    TESTDECODE(der_emptyarray, str_emptyarray);
+    TESTDECODE(der_emptyobject, str_emptyobject);
+    
+    if (sizeof(uintmax_t) == 8) {
+        // see the integerconversion test above
+        // (try this one only if sizeof(jsonint)==8
+        byte der_intarray[]
+            = { 0x30, 0x34, 
+                0x02, 0x08, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+                0x02, 0x08, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 
+                0x02, 0x07, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+                0x02, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 
+                0x02, 0x02, 0x7f, 0xff, 
+                0x02, 0x02, 0x80, 0x01, 
+                0x02, 0x01, 0x7f, 
+                0x02, 0x01, 0x81 };
+    
+        char buf[128];
+        uintmax_t one = 1;
+        // presuming (u)intmax_t is 8 bytes / 64bits
+        uintmax_t um = (one<<(8*sizeof(uintmax_t)-1)); // 2^31
+        // sm is the largest signed integer; -sm is also a valid integer
+        jsonint   sm = (jsonint)(um-1);    // needs 8 bytes to represent
+        jsonint   s7 = 0x80000000000000-1; // needs 7 bytes to represent
+        snprintf(buf, 128, "[ %" JSONFMT ", %" JSONFMT
+                 ", %" JSONFMT ", %" JSONFMT
+                 ", 32767, -32767, 127, -127 ]",
+                 sm, -sm, s7, -s7);
+        json_s = print_json_object(parse_der_bytes(der_intarray,
+                                                   sizeof(der_intarray),
+                                                   NULL));
+        LCUT_STR_EQUAL(tc, buf, json_s);
+        if (json_s != NULL) {
+            free((void*)json_s);
+        }
+    }
+
+    TESTDECODE(der_object, str_object);
+    
+    TESTDECODE(der_floatarray, str_floatarray);
+    TESTDECODE(der_string_with_escapes, str_string_with_escapes);
+
+#undef TESTDECODE
+}
+
+
+int main() {
+    lcut_ts_t   *suite = NULL;
+
+    LCUT_TEST_BEGIN("dtmfx tests", NULL, NULL);
+
+    LCUT_TS_INIT(suite, "dtmfx tests", NULL, NULL);
+    LCUT_TC_ADD(suite, "Basic json_parse", tc_basic_json_parse, NULL, NULL, NULL);
+    LCUT_TC_ADD(suite, "Integer encoding", tc_integers, NULL, NULL, NULL);
+    LCUT_TC_ADD(suite, "Real encoding", tc_reals, NULL, NULL, NULL);
+    LCUT_TC_ADD(suite, "String encoding", tc_strings, NULL, NULL, NULL);
+    LCUT_TC_ADD(suite, "Syntax torture", tc_torture_syntax, NULL, NULL, NULL);
+    LCUT_TC_ADD(suite, "Object wrinkles", tc_objects, NULL, NULL, NULL);
+    LCUT_TC_ADD(suite, "Reading DER",  tc_reading_der, NULL, NULL, NULL);
+    LCUT_TS_ADD(suite);
+
+    LCUT_TEST_RUN();
+    LCUT_TEST_REPORT();
+    LCUT_TEST_END();
+
+    LCUT_TEST_RESULT();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/util.c	Wed Sep 04 22:00:24 2013 +0100
@@ -0,0 +1,129 @@
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+//#include <unistd.h>
+//#include <fcntl.h>
+#include <assert.h>
+#include <string.h>
+//#include <sys/stat.h>
+//#include <sys/mman.h>
+
+#include "util.h"
+
+/*
+ * Produce an error message, and exit with a non-zero status.
+ * If the first character of 'fmt' is '!', then abort instead.
+ */
+void error_exit(const char* fmt, ...)
+{
+    va_list ap;
+    int abort_p = 0;
+    const char* actual_fmt = fmt;
+    
+    va_start(ap, fmt);
+
+    if (*fmt == '!') {
+        abort_p = 1;
+        actual_fmt = &fmt[1];
+    }
+    
+    fprintf(stderr, "libjason: ");
+    vfprintf(stderr, actual_fmt, ap);
+    if (abort_p) {
+        fprintf(stderr, " [this shouldn't happen]");
+    }
+    fprintf(stderr, "\n");
+    va_end(ap);
+
+    if (abort_p) {
+        abort();                /* flushes and closes streams */
+    } else {
+        exit(1);
+    }
+}
+
+#if 0
+const unsigned char* map_file(const char* filename, size_t* file_size)
+{
+    struct stat S;
+    int fileno;
+    void* mapped_file;
+
+    if ((fileno = open(filename, O_RDONLY)) < 0) {
+        error_exit("Can't open file %s to read", filename);
+    }
+    if (fstat(fileno, &S) == -1 || S.st_size == 0) {
+        error_exit("Can't stat open file %s", filename);
+    }
+    mapped_file = mmap(0, S.st_size, PROT_READ, MAP_FILE|MAP_PRIVATE, fileno, 0);
+    if (mapped_file == MAP_FAILED) {
+        close(fileno);
+        error_exit("Unable to map open file %s", filename);
+    }
+
+    if (file_size != NULL) {
+        *file_size = S.st_size;
+    }
+
+    return (const unsigned char*)mapped_file;
+}
+
+void unmap_file(const unsigned char* mapped_file, size_t file_size)
+{
+    munmap((void*)mapped_file, file_size);
+}
+#endif
+
+StringBuilder make_append_buffer()
+{
+    StringBuilder b;
+    
+    if ((b = (StringBuilder)malloc(sizeof(struct append_buffer))) == NULL) {
+        error_exit("Can't allocate space for append_buffer");
+    }
+    b->alloc = 128;
+    b->len = 0;
+    if ((b->buf = (byte*)malloc(b->alloc)) == NULL) {
+        error_exit("Can't allocate space for append_buffer");
+    }
+    return b;
+}
+
+void append_to_buffer_1(StringBuilder buf, byte b)
+{
+    if (buf->len == buf->alloc) {
+        buf->alloc *= 2;
+        if ((buf->buf = (byte*)realloc((void*)buf->buf, buf->alloc)) == NULL) {
+            error_exit("Can't reallocate space for append_to_buffer_1");
+        }
+    }
+    buf->buf[buf->len++] = b;
+    assert(buf->len <= buf->alloc);
+}
+
+void append_to_buffer_n(StringBuilder buf, const byte* b, size_t blen)
+{
+    if (buf->len + blen >= buf->alloc) {
+        while (buf->alloc < buf->len + blen) {
+            buf->alloc *= 2;
+        }
+        if ((buf->buf = (byte*)realloc((void*)buf->buf, buf->alloc)) == NULL) {
+            error_exit("Can't reallocate space for append_to_buffer_n");
+        }
+    }
+    memcpy(&(buf->buf[buf->len]), b, blen);
+    buf->len += blen;
+    assert(buf->len <= buf->alloc);
+}
+
+void append_to_buffer_c(StringBuilder buf, char c)
+{
+    append_to_buffer_1(buf, (byte)c);
+}
+
+void append_to_buffer_s(StringBuilder buf, const char* s)
+{
+    size_t slen = strlen(s);
+    append_to_buffer_n(buf, (byte*)s, slen);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/util.h	Wed Sep 04 22:00:24 2013 +0100
@@ -0,0 +1,44 @@
+#ifndef UTIL_H
+#define UTIL_H 1
+
+#include "jason.h"
+
+#if 0
+/**
+ * Set the program name, for error reports
+ */
+void jason_set_program_name(const char* s);
+
+/* Keep these in sequence, ending with DTMF_DEBUG */
+#define JASON_QUIET 0
+#define JASON_CHATTY 1
+#define JASON_DEBUG 2
+/**
+ * Set the program verbosity, which should be one of the constants
+ * JASON_QUIET, JASON_CHATTY, or JASON_DEBUG.
+ */
+void jason_set_program_verbosity(int i);
+#endif
+
+/* exported to library users, but not documented */
+void error_exit(const char* fmt, ...);
+
+/* const char* program_name(void); */
+/* int program_verbosity(void); */
+/* const unsigned char* map_file(const char* filename, size_t*); */
+/* void unmap_file(const unsigned char* mapped_file, size_t file_size); */
+
+struct append_buffer {
+    size_t alloc;
+    size_t len;
+    byte* buf;
+};
+typedef struct append_buffer* StringBuilder;
+struct append_buffer* make_append_buffer();
+void append_to_buffer_1(StringBuilder, byte);
+void append_to_buffer_n(StringBuilder, const byte*, size_t);
+void append_to_buffer_c(StringBuilder, char);
+void append_to_buffer_s(StringBuilder, const char*);
+
+
+#endif /* UTIL_H */