Initial commit, finally
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..bc17916
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,16 @@
+all: size test
+	env MallocStackLogging=true ./test >new.out
+	-diff new.out expected.out
+
+test: test.c cbor.h cn-cbor.h cn-cbor.c
+	clang cn-cbor.c test.c -o test
+
+size: cn-cbor.o
+	size cn-cbor.o
+	size -m cn-cbor.o
+
+cn-cbor.o: cn-cbor.c cn-cbor.h cbor.h
+	clang -I /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk/usr/include -arch armv7 -Os -c cn-cbor.c
+
+cn-cbor-play.zip: Makefile cbor.h cn-cbor.c cn-cbor.h expected.out test.c
+	zip $@ $^
diff --git a/README.md b/README.md
index a1e9a8f..9bf6816 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,25 @@
-# cn-cbor
-A constrained node implementation of CBOR in C
+# cn-cbor: A constrained node implementation of CBOR in C
+
+This is a constrained node implementation of [CBOR](http://cbor.io) in
+C that I threw together in 2013, before the publication of
+[RFC 7049](http://tools.ietf.org/html/rfc7049), to validate certain
+implementability considerations.
+
+Its API model was inspired by
+[nxjson](https://bitbucket.org/yarosla/nxjson).  It turns out that
+this API model actually works even better with the advantages of the
+CBOR format.
+
+This code has been used in a number of research implementations on
+constrained nodes, with resulting code sizes appreciably under 1 KiB
+on ARM platforms.
+
+I always meant to improve the interface some more with certain API
+changes, in order to get even closer to 0.5 KiB, but I ran out of
+time.  So here it is.  If I do get around to making these changes, the
+API will indeed change a bit, so please be forewarned.
+
+(To run the test, please add
+[cases.cbor](https://github.com/cabo/cbor-ruby/blob/master/spec/cases.cbor).)
+
+License: MIT
diff --git a/cbor.h b/cbor.h
new file mode 100644
index 0000000..547ad65
--- /dev/null
+++ b/cbor.h
@@ -0,0 +1,69 @@
+#ifndef CBOR_PROTOCOL_H__
+#define CBOR_PROTOCOL_H__
+
+/* The 8 major types */
+#define MT_UNSIGNED 0
+#define MT_NEGATIVE 1
+#define MT_BYTES 2
+#define MT_TEXT 3
+#define MT_ARRAY 4
+#define MT_MAP 5
+#define MT_TAG 6
+#define MT_PRIM 7
+
+/* The initial bytes resulting from those */
+#define IB_UNSIGNED (MT_UNSIGNED << 5)
+#define IB_NEGATIVE (MT_NEGATIVE << 5)
+#define IB_BYTES (MT_BYTES << 5)
+#define IB_TEXT (MT_TEXT << 5)
+#define IB_ARRAY (MT_ARRAY << 5)
+#define IB_MAP (MT_MAP << 5)
+#define IB_TAG (MT_TAG << 5)
+#define IB_PRIM (MT_PRIM << 5)
+
+#define IB_NEGFLAG (IB_NEGATIVE - IB_UNSIGNED)
+#define IB_NEGFLAG_AS_BIT(ib) ((ib) >> 5)
+#define IB_TEXTFLAG (IB_TEXT - IB_BYTES)
+
+#define IB_AI(ib) ((ib) & 0x1F)
+#define IB_MT(ib) ((ib) >> 5)
+
+/* Tag numbers handled by this implementation */
+#define TAG_TIME_EPOCH 1
+#define TAG_BIGNUM 2
+#define TAG_BIGNUM_NEG 3
+#define TAG_URI 32
+#define TAG_RE 35
+
+/* Initial bytes of those tag numbers */
+#define IB_TIME_EPOCH (IB_TAG + TAG_TIME_EPOCH)
+#define IB_BIGNUM (IB_TAG + TAG_BIGNUM)
+#define IB_BIGNUM_NEG (IB_TAG + TAG_BIGNUM_NEG)
+/* TAG_URI and TAG_RE are non-immediate tags */
+
+/* Simple values handled by this implementation */
+#define VAL_NIL 22
+#define VAL_FALSE 20
+#define VAL_TRUE 21
+
+/* Initial bytes of those simple values */
+#define IB_NIL (IB_PRIM + VAL_NIL)
+#define IB_FALSE (IB_PRIM + VAL_FALSE)
+#define IB_TRUE (IB_PRIM + VAL_TRUE)
+
+/* AI values with more data in head */
+#define AI_1 24
+#define AI_2 25
+#define AI_4 26
+#define AI_8 27
+#define AI_INDEF 31
+#define IB_BREAK (IB_PRIM + AI_INDEF)
+/* For  */
+#define IB_UNUSED (IB_TAG + AI_INDEF)
+
+/* Floating point initial bytes */
+#define IB_FLOAT2 (IB_PRIM + AI_2)
+#define IB_FLOAT4 (IB_PRIM + AI_4)
+#define IB_FLOAT8 (IB_PRIM + AI_8)
+
+#endif
diff --git a/cn-cbor.c b/cn-cbor.c
new file mode 100644
index 0000000..9e3e814
--- /dev/null
+++ b/cn-cbor.c
@@ -0,0 +1,247 @@
+#ifndef CN_CBOR_C
+#define CN_CBOR_C
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+#ifdef EMACS_INDENTATION_HELPER
+} /* Duh. */
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <math.h>
+
+#include "cn-cbor.h"
+
+// can be redefined, e.g. for pool allocation
+#ifndef CN_CBOR_CALLOC
+#define CN_CBOR_CALLOC() calloc(1, sizeof(cn_cbor))
+#define CN_CBOR_FREE(cb) free((void*)(cb))
+#endif
+
+#define CN_CBOR_FAIL(code) do { pb->err = code;  goto fail; } while(0)
+
+void cn_cbor_free(const cn_cbor* cb) {
+  cn_cbor* p = (cn_cbor*) cb;
+  while (p) {
+    cn_cbor* p1;
+    while ((p1 = p->first_child)) { /* go down */
+      p = p1;
+    }
+    if (!(p1 = p->next)) {   /* go up next */
+      if ((p1 = p->parent))
+        p1->first_child = 0;
+    }
+    CN_CBOR_FREE(p);
+    p = p1;
+  }
+}
+
+static double decode_half(int half) {
+  int exp = (half >> 10) & 0x1f;
+  int mant = half & 0x3ff;
+  double val;
+  if (exp == 0) val = ldexp(mant, -24);
+  else if (exp != 31) val = ldexp(mant + 1024, exp - 25);
+  else val = mant == 0 ? INFINITY : NAN;
+  return half & 0x8000 ? -val : val;
+}
+
+/* Fix these if you can't do non-aligned reads */
+#define ntoh8p(p) (*(unsigned char*)(p))
+#define ntoh16p(p) (ntohs(*(unsigned short*)(p)))
+#define ntoh32p(p) (ntohl(*(unsigned long*)(p)))
+static uint64_t ntoh64p(unsigned char *p) {
+  uint64_t ret = ntoh32p(p);
+  ret <<= 32;
+  ret += ntoh32p(p+4);
+  return ret;
+}
+
+static cn_cbor_type mt_trans[] = {
+  CN_CBOR_UINT,    CN_CBOR_INT,
+  CN_CBOR_BYTES,   CN_CBOR_TEXT,
+  CN_CBOR_ARRAY,   CN_CBOR_MAP,
+  CN_CBOR_TAG,     CN_CBOR_SIMPLE,
+};
+
+struct parse_buf {
+  unsigned char *buf;
+  unsigned char *ebuf;
+  cn_cbor_error err;
+};
+
+#define TAKE(pos, ebuf, n, stmt)                \
+  if (n > (ebuf - pos))                         \
+    CN_CBOR_FAIL(CN_CBOR_ERR_OUT_OF_DATA);                \
+  stmt;                                         \
+  pos += n;
+
+static cn_cbor *decode_item (struct parse_buf *pb, cn_cbor* top_parent) {
+  unsigned char *pos = pb->buf;
+  unsigned char *ebuf = pb->ebuf;
+  cn_cbor* parent = top_parent;
+again:
+  TAKE(pos, ebuf, 1, int ib = ntoh8p(pos) );
+  if (ib == IB_BREAK) {
+    if (!(parent->flags & CN_CBOR_FL_INDEF))
+      CN_CBOR_FAIL(CN_CBOR_ERR_BREAK_OUTSIDE_INDEF);
+    switch (parent->type) {
+    case CN_CBOR_BYTES: case CN_CBOR_TEXT:
+      parent->type += 2;            /* CN_CBOR_* -> CN_CBOR_*_CHUNKED */
+      break;
+    case CN_CBOR_MAP:
+      if (parent->length & 1)
+        CN_CBOR_FAIL(CN_CBOR_ERR_ODD_SIZE_INDEF_MAP);
+    default:;
+    }
+    goto complete;
+  }
+  unsigned int mt = ib >> 5;
+  int ai = ib & 0x1f;
+  uint64_t val = ai;
+
+  cn_cbor* cb = CN_CBOR_CALLOC();
+  if (!cb)
+    CN_CBOR_FAIL(CN_CBOR_ERR_OUT_OF_MEMORY);
+
+  cb->type = mt_trans[mt];
+
+  cb->parent = parent;
+  if (parent->last_child) {
+    parent->last_child->next = cb;
+  } else {
+    parent->first_child = cb;
+  }
+  parent->last_child = cb;
+  parent->length++;
+
+  cn_cbor *it;
+  cn_cbor *it2;
+  uint64_t i;
+
+  switch (ai) {
+  case AI_1: TAKE(pos, ebuf, 1, val = ntoh8p(pos)) ; break;
+  case AI_2: TAKE(pos, ebuf, 2, val = ntoh16p(pos)) ; break;
+  case AI_4: TAKE(pos, ebuf, 4, val = ntoh32p(pos)) ; break;
+  case AI_8: TAKE(pos, ebuf, 8, val = ntoh64p(pos)) ; break;
+  case 28: case 29: case 30: CN_CBOR_FAIL(CN_CBOR_ERR_RESERVED_AI);
+  case AI_INDEF:
+    if ((mt - MT_BYTES) <= MT_MAP) {
+      cb->flags |= CN_CBOR_FL_INDEF;
+      goto push;
+    } else {
+      CN_CBOR_FAIL(CN_CBOR_ERR_MT_UNDEF_FOR_INDEF);
+    }
+  }
+  // process content
+  switch (mt) {
+  case MT_UNSIGNED:
+    cb->v.uint = val;           /* to do: Overflow check */
+    break;
+  case MT_NEGATIVE:
+    cb->v.sint = ~val;          /* to do: Overflow check */
+    break;
+  case MT_BYTES: case MT_TEXT:
+    cb->v.str = (char *) pos;
+    cb->length = val;
+    TAKE(pos, ebuf, val, );
+    break;
+  case MT_MAP:
+    val <<= 1;
+    /* fall through */
+  case MT_ARRAY:
+    if ((cb->v.count = val)) {
+      cb->flags |= CN_CBOR_FL_COUNT;
+      goto push;
+    }
+    break;
+  case MT_TAG:
+    cb->v.uint = val;
+    goto push;
+  case MT_PRIM:
+    switch (ai) {
+    case VAL_NIL: cb->type = CN_CBOR_NULL; break;
+    case VAL_FALSE: cb->type = CN_CBOR_FALSE; break;
+    case VAL_TRUE: cb->type = CN_CBOR_TRUE; break;
+    case AI_2: cb->type = CN_CBOR_DOUBLE; cb->v.dbl = decode_half(val); break;
+    case AI_4:
+      cb->type = CN_CBOR_DOUBLE;
+      union {
+        float f;
+        uint32_t u;
+      } u32;
+      u32.u = val;
+      cb->v.dbl = u32.f;
+      break;
+    case AI_8:
+      cb->type = CN_CBOR_DOUBLE;
+      union {
+        double d;
+        uint64_t u;
+      } u64;
+      u64.u = val;
+      cb->v.dbl = u64.d;
+      break;
+    default: cb->v.uint = val;
+    }
+  }
+fill:                           /* emulate loops */
+  if (parent->flags & CN_CBOR_FL_INDEF) {
+    if (parent->type == CN_CBOR_BYTES || parent->type == CN_CBOR_TEXT)
+      if (cb->type != parent->type)
+          CN_CBOR_FAIL(CN_CBOR_ERR_WRONG_NESTING_IN_INDEF_STRING);
+    goto again;
+  }
+  if (parent->flags & CN_CBOR_FL_COUNT) {
+    if (--parent->v.count)
+      goto again;
+  }
+  /* so we are done filling parent. */
+complete:                       /* emulate return from call */
+  if (parent == top_parent) {
+    if (pos != ebuf)            /* XXX do this outside */
+      CN_CBOR_FAIL(CN_CBOR_ERR_NOT_ALL_DATA_CONSUMED);
+    pb->buf = pos;
+    return cb;
+  }
+  cb = parent;
+  parent = parent->parent;
+  goto fill;
+push:                           /* emulate recursive call */
+  parent = cb;
+  goto again;
+fail:
+  pb->buf = pos;
+  return 0;
+}
+
+const cn_cbor* cn_cbor_decode(const char* buf, size_t len, cn_cbor_errback *errp) {
+  cn_cbor catcher = {CN_CBOR_INVALID};
+  struct parse_buf pb = {(unsigned char *)buf, (unsigned char *)buf+len};
+  cn_cbor* ret = decode_item(&pb, &catcher);
+  if (ret) {
+    ret->parent = 0;            /* mark as top node */
+  } else {
+    if (catcher.first_child) {
+      catcher.first_child->parent = 0;
+      cn_cbor_free(catcher.first_child);
+    }
+  fail:
+    if (errp) {
+      errp->err = pb.err;
+      errp->pos = pb.buf - (unsigned char *)buf;
+    }
+    return 0;
+  }
+  return ret;
+}
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif  /* CN_CBOR_C */
diff --git a/cn-cbor.h b/cn-cbor.h
new file mode 100644
index 0000000..62d8b78
--- /dev/null
+++ b/cn-cbor.h
@@ -0,0 +1,78 @@
+#ifndef CN_CBOR_H
+#define CN_CBOR_H
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+#ifdef EMACS_INDENTATION_HELPER
+} /* Duh. */
+#endif
+
+/* protocol constants: */
+#include "cbor.h"
+
+typedef enum cn_cbor_type {
+  CN_CBOR_NULL,
+  CN_CBOR_FALSE,   CN_CBOR_TRUE,
+  CN_CBOR_UINT,    CN_CBOR_INT,
+  CN_CBOR_BYTES,   CN_CBOR_TEXT,
+  CN_CBOR_BYTES_CHUNKED,   CN_CBOR_TEXT_CHUNKED, /* += 2 */
+  CN_CBOR_ARRAY,   CN_CBOR_MAP,
+  CN_CBOR_TAG,
+  CN_CBOR_SIMPLE,  CN_CBOR_DOUBLE,
+  CN_CBOR_INVALID
+} cn_cbor_type;
+
+typedef enum cn_cbor_flags {
+  CN_CBOR_FL_COUNT = 1,
+  CN_CBOR_FL_INDEF = 2,
+  CN_CBOR_FL_OWNER = 0x80,            /* of str */
+} cn_cbor_flags;
+
+typedef struct cn_cbor {
+  cn_cbor_type type;
+  cn_cbor_flags flags;
+  union {
+    const char* str;
+    long sint;
+    unsigned long uint;
+    double dbl;
+    unsigned long count;        /* for use during filling */
+  } v;                          /* TBD: optimize immediate */
+  int length;
+  struct cn_cbor* first_child;
+  struct cn_cbor* last_child;
+  struct cn_cbor* next;
+  struct cn_cbor* parent;
+} cn_cbor;
+
+typedef enum cn_cbor_error {
+  CN_CBOR_NO_ERROR,
+  CN_CBOR_ERR_OUT_OF_DATA,
+  CN_CBOR_ERR_NOT_ALL_DATA_CONSUMED,
+  CN_CBOR_ERR_ODD_SIZE_INDEF_MAP,
+  CN_CBOR_ERR_BREAK_OUTSIDE_INDEF,
+  CN_CBOR_ERR_MT_UNDEF_FOR_INDEF,
+  CN_CBOR_ERR_RESERVED_AI,
+  CN_CBOR_ERR_WRONG_NESTING_IN_INDEF_STRING,
+  CN_CBOR_ERR_OUT_OF_MEMORY,
+} cn_cbor_error;
+
+typedef struct cn_cbor_errback {
+  int pos;
+  cn_cbor_error err;
+} cn_cbor_errback;
+
+const cn_cbor* cn_cbor_decode(const char* buf, size_t len, cn_cbor_errback *errp);
+const cn_cbor* cn_cbor_mapget_string(const cn_cbor* cb, const char* key);
+const cn_cbor* cn_cbor_mapget_int(const cn_cbor* cb, int key);
+const cn_cbor* cn_cbor_index(const cn_cbor* cb, int idx);
+
+const cn_cbor* cn_cbor_alloc(cn_cbor_type t);
+void cn_cbor_free(const cn_cbor* js);
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif  /* CN_CBOR_H */
diff --git a/expected.out b/expected.out
new file mode 100644
index 0000000..3ed0d13
--- /dev/null
+++ b/expected.out
@@ -0,0 +1,277 @@
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
+511
+[
+  0
+  1
+  10
+  23
+  24
+  25
+  100
+  1000
+  1000000
+  1000000000000
+  18446744073709551615
+  2(
+    h'010000000000000000'
+  )
+  OVERFLOW
+  3(
+    h'010000000000000000'
+  )
+  -1
+  -10
+  -100
+  -1000
+  0.000000e+00
+  -0.000000e+00
+  1.000000e+00
+  1.100000e+00
+  1.500000e+00
+  6.550400e+04
+  1.000000e+05
+  3.402823e+38
+  1.000000e+300
+  5.960464e-08
+  6.103516e-05
+  -4.000000e+00
+  -4.100000e+00
+  inf
+  nan
+  -inf
+  inf
+  nan
+  -inf
+  inf
+  nan
+  -inf
+  false
+  true
+  null
+  simple(23)
+  simple(16)
+  simple(24)
+  simple(255)
+  0(
+    "2013-03-21T20:04:00Z"
+  )
+  1(
+    1363896240
+  )
+  1(
+    1.363896e+09
+  )
+  23(
+    h'01020304'
+  )
+  24(
+    h'6449455446'
+  )
+  32(
+    "http://www.example.com"
+  )
+  h''
+  h'01020304'
+  ""
+  "a"
+  "IETF"
+  ""\"
+  "ü"
+  "水"
+  "𐅑"
+  [
+  ]
+  [
+    1
+    2
+    3
+  ]
+  [
+    1
+    [
+      2
+      3
+    ]
+    [
+      4
+      5
+    ]
+  ]
+  [
+    1
+    2
+    3
+    4
+    5
+    6
+    7
+    8
+    9
+    10
+    11
+    12
+    13
+    14
+    15
+    16
+    17
+    18
+    19
+    20
+    21
+    22
+    23
+    24
+    25
+  ]
+  {
+  }
+  {
+    1
+    2
+    3
+    4
+  }
+  {
+    "a"
+    1
+    "b"
+    [
+      2
+      3
+    ]
+  }
+  [
+    "a"
+    {
+      "b"
+      "c"
+    }
+  ]
+  {
+    "a"
+    "A"
+    "b"
+    "B"
+    "c"
+    "C"
+    "d"
+    "D"
+    "e"
+    "E"
+  }
+  (_
+
+    h'0102'
+    h'030405'
+  )
+  (_
+    "strea"
+    "ming"
+  )
+  [
+  ]
+  [
+    1
+    [
+      2
+      3
+    ]
+    [
+      4
+      5
+    ]
+  ]
+  [
+    1
+    [
+      2
+      3
+    ]
+    [
+      4
+      5
+    ]
+  ]
+  [
+    1
+    [
+      2
+      3
+    ]
+    [
+      4
+      5
+    ]
+  ]
+  [
+    1
+    [
+      2
+      3
+    ]
+    [
+      4
+      5
+    ]
+  ]
+  [
+    1
+    2
+    3
+    4
+    5
+    6
+    7
+    8
+    9
+    10
+    11
+    12
+    13
+    14
+    15
+    16
+    17
+    18
+    19
+    20
+    21
+    22
+    23
+    24
+    25
+  ]
+  {
+    "a"
+    1
+    "b"
+    [
+      2
+      3
+    ]
+  }
+  [
+    "a"
+    {
+      "b"
+      "c"
+    }
+  ]
+  {
+    "Fun"
+    true
+    "Amt"
+    -2
+  }
+]
+
+CN_CBOR_ERR_BREAK_OUTSIDE_INDEF at 1
+CN_CBOR_ERR_MT_UNDEF_FOR_INDEF at 1
+CN_CBOR_ERR_NOT_ALL_DATA_CONSUMED at 1
+CN_CBOR_ERR_OUT_OF_DATA at 1
+CN_CBOR_ERR_RESERVED_AI at 1
+CN_CBOR_ERR_ODD_SIZE_INDEF_MAP at 3
+CN_CBOR_ERR_WRONG_NESTING_IN_INDEF_STRING at 2
+
+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
diff --git a/test.c b/test.c
new file mode 100644
index 0000000..0d8acab
--- /dev/null
+++ b/test.c
@@ -0,0 +1,126 @@
+#include <unistd.h>
+#include "cn-cbor.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+
+#define ERROR(msg, p) fprintf(stderr, "ERROR: " msg " %s\n", (p));
+
+static char* load_file(const char* filepath, char **end) {
+  struct stat st;
+  if (stat(filepath, &st)==-1) {
+    // ERROR("can't find file", filepath);
+    return 0;
+  }
+  int fd=open(filepath, O_RDONLY);
+  if (fd==-1) {
+    ERROR("can't open file", filepath);
+    return 0;
+  }
+  char* text=malloc(st.st_size+1); // this is not going to be freed
+  if (st.st_size!=read(fd, text, st.st_size)) {
+    ERROR("can't read file", filepath);
+    close(fd);
+    return 0;
+  }
+  close(fd);
+  text[st.st_size]='\0';
+  *end = text + st.st_size;
+  return text;
+}
+
+static void dump(const cn_cbor* cb, char* out, char** end, int indent) {
+  if (!cb)
+    goto done;
+  int i;
+  cn_cbor* cp;
+  char finchar = ')';           /* most likely */
+
+#define CPY(s, l) memcpy(out, s, l); out += l;
+#define OUT(s) CPY(s, sizeof(s)-1)
+#define PRF(f, a) out += sprintf(out, f, a)
+
+  for (i = 0; i < indent; i++) *out++ = ' ';
+  switch (cb->type) {
+  case CN_CBOR_TEXT_CHUNKED:   OUT("(_\n");                  goto sequence;
+  case CN_CBOR_BYTES_CHUNKED:  OUT("(_\n\n");                goto sequence;
+  case CN_CBOR_TAG:            PRF("%ld(\n", cb->v.sint);    goto sequence;
+  case CN_CBOR_ARRAY:  finchar = ']'; OUT("[\n");            goto sequence;
+  case CN_CBOR_MAP:    finchar = '}'; OUT("{\n");            goto sequence;
+  sequence:
+    for (cp = cb->first_child; cp; cp = cp->next) {
+      dump(cp, out, &out, indent+2);
+    }
+    for (i=0; i<indent; i++) *out++ = ' ';
+    *out++ = finchar;
+    break;
+  case CN_CBOR_BYTES:   OUT("h'");
+    for (i=0; i<cb->length; i++)
+      PRF("%02x", cb->v.str[i] & 0xff);
+    *out++ = '\'';
+    break;
+  case CN_CBOR_TEXT:    *out++ = '"';
+    CPY(cb->v.str, cb->length); /* should escape stuff */
+    *out++ = '"';
+    break;
+  case CN_CBOR_NULL:   OUT("null");                      break;
+  case CN_CBOR_TRUE:   OUT("true");                      break;
+  case CN_CBOR_FALSE:  OUT("false");                     break;
+  case CN_CBOR_INT:    PRF("%ld", cb->v.sint);           break;
+  case CN_CBOR_UINT:   PRF("%lu", cb->v.uint);           break;
+  case CN_CBOR_DOUBLE: PRF("%e", cb->v.dbl);             break;
+  case CN_CBOR_SIMPLE: PRF("simple(%ld)", cb->v.sint);   break;
+  default:             PRF("???%d???", cb->type);        break;
+  }
+  *out++ = '\n';
+done:
+  *end = out;
+}
+
+
+char *err_name[] = {
+  "CN_CBOR_NO_ERROR",
+  "CN_CBOR_ERR_OUT_OF_DATA",
+  "CN_CBOR_ERR_NOT_ALL_DATA_CONSUMED",
+  "CN_CBOR_ERR_ODD_SIZE_INDEF_MAP",
+  "CN_CBOR_ERR_BREAK_OUTSIDE_INDEF",
+  "CN_CBOR_ERR_MT_UNDEF_FOR_INDEF",
+  "CN_CBOR_ERR_RESERVED_AI",
+  "CN_CBOR_ERR_WRONG_NESTING_IN_INDEF_STRING",
+  "CN_CBOR_ERR_OUT_OF_MEMORY",
+};
+
+void cn_cbor_decode_test(char *buf, int len) {
+  struct cn_cbor_errback back;
+  const cn_cbor *ret = cn_cbor_decode(buf, len, &back);
+  if (ret)
+    printf("oops 1");
+  printf("%s at %d\n", err_name[back.err], back.pos);
+}
+
+int main() {
+  char buf[100000];
+  char *end;
+  char *s = load_file("cases.cbor", &end);
+  printf("%zd\n", end-s);
+  const cn_cbor *cb = cn_cbor_decode(s, end-s, 0);
+  if (cb) {
+    dump(cb, buf, &end, 0);
+    *end = 0;
+    printf("%s\n", buf);
+    cn_cbor_free(cb);
+    cb = 0;                     /* for leaks testing */
+  }
+  cn_cbor_decode_test("\xff", 1);    /* break outside indef */
+  cn_cbor_decode_test("\x1f", 1);    /* mt undef for indef */
+  cn_cbor_decode_test("\x00\x00", 2);    /* not all data consumed */
+  cn_cbor_decode_test("\x81", 1);    /* out of data */
+  cn_cbor_decode_test("\x1c", 1);    /* reserved ai */
+  cn_cbor_decode_test("\xbf\x00\xff", 3);    /* odd size indef map */
+  cn_cbor_decode_test("\x7f\x40\xff", 3);    /* wrong nesting in indef string */
+  system("leaks test");
+}
+
+/* cn-cbor.c:112:    CN_CBOR_FAIL("out of memory"); */