Mercurial > nxg > jason
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*)# + 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 */