~fsx/cbare

b058becc8702371e83d952fac30d6654cb33ac48 — Frank Smit 2 years ago 6e92bd4
Rewrite cbare.

- Don't depende on Meson and Python.
- Simplify (de)serialization APIs.
- Simplify tests (don't generate from text files).
- Add a man page (cbare.3).
- Build on OpenBSD.
51 files changed, 1883 insertions(+), 1569 deletions(-)

M .builds/alpine.yml
M .builds/archlinux.yml
A .builds/openbsd.yml
M .gitignore
D LICENSE
A Makefile
M README.md
D baretest.c.mako
A cbare.3.scd
D cbare.c
D cbare.h
A cbare.pc.in
D doanalyze.sh
D dodebug.sh
D dorelease.sh
D membuf.c
D membuf.h
D membuftest.c
D meson.build
A scripts/asan.sh
D scripts/genbaretests.py
A scripts/lib.sh
A scripts/msan.sh
A scripts/static-analysis.sh
A scripts/ubsan.sh
R alloc.c => src/alloc.c
R alloc.h => src/alloc.h
A src/cbare.c
A src/cbare.h
R die.c => src/die.c
R die.h => src/die.h
R utf8.h => src/utf8.h
A test/baretest.c
R utf8test.c => test/utf8test.c
D testdata/bool.txt
D testdata/data.txt
D testdata/f32.txt
D testdata/f64.txt
D testdata/fixed_data.txt
D testdata/i16.txt
D testdata/i32.txt
D testdata/i64.txt
D testdata/i8.txt
D testdata/int.txt
D testdata/string.txt
D testdata/u16.txt
D testdata/u32.txt
D testdata/u64.txt
D testdata/u8.txt
D testdata/uint.txt
D todo.md
M .builds/alpine.yml => .builds/alpine.yml +6 -10
@@ 1,19 1,15 @@
image: alpine/3.15
image: alpine/edge
arch: x86_64
packages:
  - gcc
  - git
  - meson
  - ninja
  - python3
  - py3-pip
  - py3-click
  - py3-mako
  - make
  - scdoc
sources:
  - https://git.sr.ht/~fsx/cbare
tasks:
  - python: |
      pip install parsimonious
  - build: |
      cd cbare
      ./dorelease.sh
      CCLD=gcc make check
      ./baretest
      ./utf8test

M .builds/archlinux.yml => .builds/archlinux.yml +5 -11
@@ 1,20 1,14 @@
image: archlinux
packages:
  - gcc
  - clang
  - git
  - meson
  - ninja
  - python
  - python-pip
  - python-click
  - python-mako
  - make
  - scdoc
sources:
  - https://git.sr.ht/~fsx/cbare
tasks:
  - python: |
      pip install parsimonious
  - build: |
      cd cbare
      ./dodebug.sh
      ./doanalyze.sh
      CC=clang CCLD=clang make check
      ./baretest
      ./utf8test

A .builds/openbsd.yml => .builds/openbsd.yml +13 -0
@@ 0,0 1,13 @@
image: openbsd/7.1
packages:
  - git
  - gmake
  - scdoc
sources:
  - https://git.sr.ht/~fsx/cbare
tasks:
  - build: |
      cd cbare
      CC=clang make check
      ./baretest
      ./utf8test

M .gitignore => .gitignore +8 -1
@@ 1,1 1,8 @@
/build-*
/baretest
/utf8test
/libcbare.so
/libcbare.a
/cbare.pc
/cbare.3
/notes.md
*.o

D LICENSE => LICENSE +0 -21
@@ 1,21 0,0 @@
MIT License

Copyright (c) 2022 Frank Smit

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

A Makefile => Makefile +66 -0
@@ 0,0 1,66 @@
.POSIX:
.SUFFIXES: .o .c
.PHONY: all clean check install uninstall

VERSION=0.0.1
PREFIX ?= /usr/local
LIBDIR ?= $(PREFIX)/lib
INCDIR ?= $(PREFIX)/include
MANDIR ?= $(PREFIX)/share/man
PCDIR ?= $(PREFIX)/share/pkgconfig

CCLD ?= clang

DEFAULT_CFLAGS := \
	-std=c99 \
	-Wall -Werror -Wextra -Wpedantic \
	-Wno-unused-function

all: libcbare.so libcbare.a cbare.3 cbare.pc

check: baretest utf8test

baretest: test/baretest.o src/alloc.o src/die.o libcbare.a
	$(CCLD) $(LDFLAGS) test/baretest.o src/alloc.o src/die.o libcbare.a -o $@

utf8test: test/utf8test.o src/die.o
	$(CCLD) $(LDFLAGS) test/utf8test.o src/die.o -o $@

libcbare.so: src/cbare.o
	$(CCLD) $(LDFLAGS) src/cbare.o -shared -o $@

libcbare.a: src/cbare.o
	$(AR) rcs $@ src/cbare.o

.c.o:
	$(CC) -std=c99 $(CFLAGS) $(DEFAULT_CFLAGS) -I./src -c $< -o $@

src/cbare.o: src/cbare.h
src/die.o: src/die.h
src/alloc.o: src/alloc.h src/die.h
test/baretest.o: src/cbare.h src/alloc.h src/die.h
test/utf8test.o: src/utf8.h src/die.h

cbare.3: cbare.3.scd
	scdoc < $< > $@

cbare.pc: cbare.pc.in
	sed -e 's:@prefix@:$(PREFIX):g' -e 's:@version@:$(VERSION):g' < $< > $@

install: all
	mkdir -p $(DESTDIR)/$(LIBDIR) $(DESTDIR)/$(MANDIR)/man3 $(DESTDIR)/$(PCDIR) $(DESTDIR)/$(INCDIR)
	install -m644 libcbare.so $(DESTDIR)$(LIBDIR)/libcbare.so
	install -m644 libcbare.a $(DESTDIR)$(LIBDIR)/libcbare.a
	install -m644 src/cbare.h $(DESTDIR)$(INCDIR)/cbare.h
	install -m644 cbare.3 $(DESTDIR)/$(MANDIR)/man3/cbare.3
	install -m644 cbare.pc $(DESTDIR)/$(PCDIR)/cbare.pc

uninstall:
	rm $(DESTDIR)$(LIBDIR)/libcbare.so
	rm $(DESTDIR)$(LIBDIR)/libcbare.a
	rm $(DESTDIR)$(INCDIR)/cbare.h
	rm $(DESTDIR)/$(MANDIR)/man3/cbare.3
	rm $(DESTDIR)/$(PCDIR)/cbare.pc

clean:
	rm -vf *.a *.so *.o */*.o baretest utf8test cbare.3 cbare.pc

M README.md => README.md +17 -23
@@ 1,6 1,6 @@
# cbare

[BARE][] encoder and decoder.
cbare is an I/O agnostic C implementation of the [BARE][] message encoding.

Build status: [![builds.sr.ht status](https://builds.sr.ht/~fsx/cbare.svg)](https://builds.sr.ht/~fsx/cbare?)



@@ 13,51 13,45 @@ Schema DSL parser and code generator are in development.

## Building

Install [Meson][].

Setup your build:
For shared and static libraries, package config, and man page:

```
meson --werror --buildtype=release ./build --prefix /usr
make
```

(note: omit `--buildtype=release` for development)

compile:
You can use different compilers and linkers:

```
meson compile -C ./build
CC=cproc CCLD=gcc LDFLAGS='-fuse-ld=mold' make
```

and run tests:
Tests:

```
meson test -C ./build
make check
./baretest && echo pass || echo fail
./utf8test && echo pass || echo fail
```


## Installation

Run:

```
meson install -C ./build
make install
```

Or provide `--destdir` to install to a different directory:
You can pass PREFIX or DESTDIR to make if you'd like:

```
meson install -C ./build --destdir ./package-root
mkdir ./tmp
make DESTDIR=./tmp PREFIX=/usr install
```

Uninstallation is similar:

## Notes

`bare_put_uint` and `bare_get_uint` are from [poolparty][].

`bare_put_int` and `bare_get_int` are from Go's standard library.
```
make uninstall
```


[BARE]: https://baremessages.org/
[Meson]: https://mesonbuild.com/Quick-guide.html
[poolparty]: https://github.com/andrewchambers/poolparty/blob/master/csrc/poolparty.c

D baretest.c.mako => baretest.c.mako +0 -297
@@ 1,297 0,0 @@
<%!

import textwrap

def cval(v):
	if isinstance(v, (tuple, list)):
		items = ", ".join(map(cval, v))
		items = "\n".join(textwrap.wrap(
			items,
			width=70,
			expand_tabs=False,
			initial_indent="\t\t",
			subsequent_indent="\t\t",
		))
		return f"{{\n{items}\n\t}}"

	return v

%>\
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <stdio.h>
#include <stdbool.h>

#include "alloc.h"
#define BARE_CALLOC(num, size) ckalloc(size)
#include "cbare.h"
#include "die.h"
#include "membuf.h"

/* macros are used to report the correct line numbers */

void
_check(bool result, char *msg, ...)
{
	va_list args;

	va_start(args, msg);
	if (!result) {
		die_va(msg, &args);
	}
	va_end(args);
}
\
<%text>
#define check1(result, msg, ...) \
	do { \
		char tmp[1024] = {0}; \
		char *prefix = "%s:%d: "; \
		strcat(tmp, prefix); \
		if ((strlen(prefix) + strlen(msg) + 1) > 1024) { \
			die("tmp[1024] is too small"); \
		} \
		strcat(tmp, msg); \
		_check(result, tmp, __FILE__, __LINE__, __VA_ARGS__); \
	} while (0)

#define check(result, msg, ...) check1(result, msg, __VA_ARGS__, "\n")
</%text>\

<%text>
#define bincheck(a, b, sz) \
	do { \
		uint8_t *ap = a; \
		uint8_t *bp = b; \
		bool result = memcmp(ap, bp, sz) == 0; \
		if (!result) { \
			printf("a:"); \
			for (size_t i = 0; i < sz; i++) { \
				printf(" %02x", (uint8_t) *ap); \
				ap++; \
			} \
			printf("\n"); \
			printf("b:"); \
			for (size_t i = 0; i < sz; i++) { \
				printf(" %x", (uint8_t) *bp); \
				bp++; \
			} \
			printf("\n"); \
		} \
		check(result, "%s", "a != b"); \
	} while (0)
</%text>\

static uint64_t
buf_reader(struct bare_ctx *const ctx, void *const data, uint64_t nbytes)
{
	if (!membuf_read((struct membuf *) ctx->buf, data, nbytes)) {
		ctx->error = BARE_ERROR_READ_FAILED;
		nbytes = 0;
	}

	return nbytes;
}

static uint64_t
buf_writer(struct bare_ctx *const ctx, const void *const data, uint64_t nbytes)
{
	membuf_write((struct membuf *) ctx->buf, data, nbytes);
	return nbytes;
}

% for name, test in tests.items():
void test_${name}()
{
	struct membuf buf;
	struct bare_ctx ctx;

	membuf_init(&buf, 1024);
	bare_init(&ctx, &buf, buf_reader, buf_writer);

% for p in test.inputs:
<% var = f"{p.name}_input" %>\
% if p.type == 'uint':
	uint64_t ${var} = ${cval(p.value)};
	bare_put_uint(&ctx, ${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_put_uint failed");
% elif p.type == 'u8':
	uint8_t ${var} = ${cval(p.value)};
	bare_put_u8(&ctx, ${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_put_u8 failed");
% elif p.type == 'u16':
	uint16_t ${var} = ${cval(p.value)};
	bare_put_u16(&ctx, ${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_put_u16 failed");
% elif p.type == 'u32':
	uint32_t ${var} = ${cval(p.value)};
	bare_put_u32(&ctx, ${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_put_u32 failed");
% elif p.type == 'u64':
	uint64_t ${var} = ${cval(p.value)};
	bare_put_u64(&ctx, ${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_put_u64 failed");
% elif p.type == 'int':
	int64_t ${var} = ${cval(p.value)};
	bare_put_int(&ctx, ${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_put_int failed");
% elif p.type == 'i8':
	int8_t ${var} = ${cval(p.value)};
	bare_put_i8(&ctx, ${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_put_i8 failed");
% elif p.type == 'i16':
	int16_t ${var} = ${cval(p.value)};
	bare_put_i16(&ctx, ${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_put_i16 failed");
% elif p.type == 'i32':
	int32_t ${var} = ${cval(p.value)};
	bare_put_i32(&ctx, ${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_put_i32 failed");
% elif p.type == 'i64':
	int64_t ${var} = ${cval(p.value)};
	bare_put_i64(&ctx, ${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_put_i64 failed");
% elif p.type == 'f32':
	float ${var} = ${cval(p.value)};
	bare_put_f32(&ctx, ${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_put_f32 failed");
% elif p.type == 'f64':
	double ${var} = ${cval(p.value)};
	bare_put_f64(&ctx, ${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_put_f64 failed");
% elif p.type == 'bool':
	double ${var} = ${cval(p.value)};
	bare_put_bool(&ctx, ${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_put_bool failed");
% elif p.type == 'string':
	char *${var} = ${cval(p.value)};
	bare_put_str(&ctx, ${var}, strlen(${var}));
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_put_str failed");
% elif p.type == 'data':
	uint8_t ${var}[] = ${cval(p.value)};
	bare_put_data(&ctx, ${var}, ${len(p.value)});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_put_data failed");
% elif p.type == 'fixed_data':
	uint8_t ${var}[] = ${cval(p.value)};
	bare_put_fixed_data(&ctx, (uint8_t *)${var}, ${len(p.value)});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_put_fixed_data failed");
% endif
% endfor

	uint8_t expect[] = ${cval(test.expect)};
	size_t expectsz = ${len(test.expect)};
	check(buf.size == expectsz, "%d != %d", buf.size == expectsz);
	bincheck(buf.data, expect, expectsz);

% for p in test.inputs:
<% var = f"{p.name}_output" %>\
<% varsz = f"{p.name}_sz" %>\
<% varin = f"{p.name}_input" %>\
% if p.type == 'uint':
	uint64_t ${var};
	bare_get_uint(&ctx, &${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_get_uint failed");
	check(${var} == ${varin}, "%s", "${var} != ${varin}");
% elif p.type == 'u8':
	uint8_t ${var};
	bare_get_u8(&ctx, &${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_get_u8 failed");
	check(${var} == ${varin}, "%s", "${var} != ${varin}");
% elif p.type == 'u16':
	uint16_t ${var};
	bare_get_u16(&ctx, &${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_get_u16 failed");
	check(${var} == ${varin}, "%s", "${var} != ${varin}");
% elif p.type == 'u32':
	uint32_t ${var};
	bare_get_u32(&ctx, &${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_get_u32 failed");
	check(${var} == ${varin}, "%s", "${var} != ${varin}");
% elif p.type == 'u64':
	uint64_t ${var};
	bare_get_u64(&ctx, &${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_get_u64 failed");
	check(${var} == ${varin}, "%s", "${var} != ${varin}");
% elif p.type == 'int':
	int64_t ${var};
	bare_get_int(&ctx, &${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_get_int failed");
	check(${var} == ${varin}, "%s", "${var} != ${varin}");
% elif p.type == 'i8':
	int8_t ${var};
	bare_get_i8(&ctx, &${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_get_i8 failed");
	check(${var} == ${varin}, "%s", "${var} != ${varin}");
% elif p.type == 'i16':
	int16_t ${var};
	bare_get_i16(&ctx, &${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_get_i16 failed");
	check(${var} == ${varin}, "%s", "${var} != ${varin}");
% elif p.type == 'i32':
	int32_t ${var};
	bare_get_i32(&ctx, &${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_get_i32 failed");
	check(${var} == ${varin}, "%s", "${var} != ${varin}");
% elif p.type == 'i64':
	int64_t ${var};
	bare_get_i64(&ctx, &${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_get_i64 failed");
	check(${var} == ${varin}, "%s", "${var} != ${varin}");
% elif p.type == 'f32':
	float ${var};
	bare_get_f32(&ctx, &${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_get_f32 failed");
	check(${var} == ${varin}, "%s", "${var} != ${varin}");
% elif p.type == 'f64':
	double ${var};
	bare_get_f64(&ctx, &${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_get_f64 failed");
	check(${var} == ${varin}, "%s", "${var} != ${varin}");
% elif p.type == 'bool':
	bool ${var};
	bare_get_bool(&ctx, &${var});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_get_bool failed");
	check(${var} == ${varin}, "%s", "${var} != ${varin}");
% elif p.type == 'string':
	size_t ${varsz} = 1024;
	char *${var} = NULL;
	bare_get_str(&ctx, &${var}, &${varsz});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_get_str failed");
	check(${varsz} == strlen(${var}), "%lld == %lld", strlen(${var}), ${varsz});
	check(strcmp(${var}, ${varin}) == 0, "%s", "${var} != ${varin}");
	free(${var});
% elif p.type == 'data':
	size_t ${varsz};
	uint8_t *${var} = NULL;
	bare_get_data(&ctx, &${var}, &${varsz});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_get_data failed");
	check(${varsz} == ${len(p.value)}, "%ln == %s", ${varsz}, "${len(p.value)}");
	bincheck(${var}, ${varin}, ${len(p.value)});
	free(${var});
% elif p.type == 'fixed_data':
	size_t ${varsz} = ${len(p.value)};
	uint8_t *${var} = ckalloc(${varsz});
	bare_get_fixed_data(&ctx, ${var}, ${varsz});
	check(ctx.error == BARE_ERROR_NONE, "%s", "bare_get_fixed_data failed");
	bincheck(${var}, ${varin}, ${varsz});
	free(${var});
% endif
% endfor

	membuf_fini(&buf);
}

% endfor

int main(int argc, char const *argv[])
{
	(void)argc;
	(void)argv;

% for name in tests.keys():
	test_${name}();
% endfor

	return 0;
}

A cbare.3.scd => cbare.3.scd +146 -0
@@ 0,0 1,146 @@
CBARE(3)

# NAME

cbare - BARE encoder and decoder.

# SYNOPSIS

typedef enum {++
	BARE_ERROR_NONE,++
	BARE_ERROR_WRITE_FAILED,++
	BARE_ERROR_READ_FAILED,++
	BARE_ERROR_BUFFER_TOO_SMALL,++
	BARE_ERROR_INVALID_UTF8,++
} bare_error;

typedef bare_error (\*bare_write_func)(void \*buffer, void \*src, uint64_t nbytes);++
typedef bare_error (\*bare_read_func)(void \*buffer, void \*dst, uint64_t nbytes);

struct bare_writer {++
	void \*buffer;++
	bare_write_func write;++
};

struct bare_reader {++
	void \*buffer;++
	bare_read_func read;++
};

bare_error bare_put_uint(struct bare_writer \*ctx, uint64_t x);++
bare_error bare_get_uint(struct bare_reader \*ctx, uint64_t \*x);++
bare_error bare_put_u8(struct bare_writer \*ctx, uint8_t x);++
bare_error bare_get_u8(struct bare_reader \*ctx, uint8_t \*x);++
bare_error bare_put_u16(struct bare_writer \*ctx, uint16_t x);++
bare_error bare_get_u16(struct bare_reader \*ctx, uint16_t \*x);++
bare_error bare_put_u32(struct bare_writer \*ctx, uint32_t x);++
bare_error bare_get_u32(struct bare_reader \*ctx, uint32_t \*x);++
bare_error bare_put_u64(struct bare_writer \*ctx, uint64_t x);++
bare_error bare_get_u64(struct bare_reader \*ctx, uint64_t \*x);

bare_error bare_put_int(struct bare_writer \*ctx, int64_t x);++
bare_error bare_get_int(struct bare_reader \*ctx, int64_t \*x);++
bare_error bare_put_i8(struct bare_writer \*ctx, int8_t x);++
bare_error bare_get_i8(struct bare_reader \*ctx, int8_t \*x);++
bare_error bare_put_i16(struct bare_writer \*ctx, int16_t x);++
bare_error bare_get_i16(struct bare_reader \*ctx, int16_t \*x);++
bare_error bare_put_i32(struct bare_writer \*ctx, int32_t x);++
bare_error bare_get_i32(struct bare_reader \*ctx, int32_t \*x);++
bare_error bare_put_i64(struct bare_writer \*ctx, int64_t x);++
bare_error bare_get_i64(struct bare_reader \*ctx, int64_t \*x);

bare_error bare_put_f32(struct bare_writer \*ctx, float x);++
bare_error bare_get_f32(struct bare_reader \*ctx, float \*x);++
bare_error bare_put_f64(struct bare_writer \*ctx, double x);++
bare_error bare_get_f64(struct bare_reader \*ctx, double \*x);

bare_error bare_put_bool(struct bare_writer \*ctx, bool x);++
bare_error bare_get_bool(struct bare_reader \*ctx, bool \*x);

bare_error bare_put_fixed_data(struct bare_writer \*ctx, uint8_t \*src, uint64_t sz);++
bare_error bare_get_fixed_data(struct bare_reader \*ctx, uint8_t \*dst, uint64_t sz);++
bare_error bare_put_data(struct bare_writer \*ctx, uint8_t \*src, uint64_t sz);++
bare_error bare_get_data(struct bare_reader \*ctx, uint8_t \*dst, uint64_t sz);++
bare_error bare_put_str(struct bare_writer \*ctx, char \*src, uint64_t sz);++
bare_error bare_get_str(struct bare_reader \*ctx, char \*dst, uint64_t sz);

# DESCRIPTION

cbare is an I/O agnostic C implementation of the BARE message encoding.

Each function need a pointer to either *struct bare_writer* or
*struct bare_reader* and a value or a pointer to a value. The data and
string functions also need a size.

# EXAMPLES

Serialize a uint:

```
struct bare_writer ctx = {.buffer=..., .write=...};
bare_put_uint(&ctx, 34);
```

Deserialize a uint:

```
struct bare_reader ctx = {.buffer=..., .read=...};
uint64_t x = 0;
bare_get_uint(&ctx, &x);
```

Write callback for buffer:

```
struct buf {
	uint8_t *data;
	uint64_t cur;
	uint64_t len;
};

static bare_error
buf_write(void *buffer, void *src, uint64_t sz)
{
	struct buf *b = (struct buf *)buffer;

	if (sz > (b->len-b->cur)) {
		return BARE_ERROR_WRITE_FAILED;
	}

	memcpy(b->data + b->cur, src, sz);
	b->cur += sz;

	return BARE_ERROR_NONE;
}

struct buf b = {.data=..., .cur=0, .len=1024};
struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};
```

And a read callback:

```
static bare_error
buf_read(void *buffer, void *dst, uint64_t sz)
{
	struct buf *b = (struct buf *)buffer;

	if (sz > (b->len-b->cur)) {
		return BARE_ERROR_READ_FAILED;
	}

	memcpy(dst, b->data + b->cur, sz);
	b->cur += sz;

	return BARE_ERROR_NONE;
}

struct buf b = {.data=..., .cur=0, .len=1024};
struct bare_reader ctx = {.buffer=(void *)&b, .read=&buf_read};
```

# RESOURCES

- cbare: https://git.sr.ht/~fsx/cbare
- BARE message encoding: https://baremessages.org/
- Internet Draft: https://www.ietf.org/archive/id/draft-devault-bare-07.html

D cbare.c => cbare.c +0 -440
@@ 1,440 0,0 @@
#include <math.h>
#include <stdlib.h>
#include <string.h>

#include "cbare.h"
#include "utf8.h"

#ifndef BARE_CALLOC
#define BARE_CALLOC(num, size) calloc(num, size)
#endif

#ifndef BARE_FREE
#define BARE_FREE(ptr) free(ptr)
#endif

#define ptr8(v) ((uint8_t *)v)

enum {
	U8SZ = 1,
	U16SZ = 2,
	U32SZ = 4,
	U64SZ = 8,
	MAXVARINTSZ = 10,
};

static bool
error_is_set(struct bare_ctx *ctx)
{
	return ctx->error != BARE_ERROR_NONE;
}

static bool
checkstr(const char *x, uint64_t nbytes)
{
	if (x == NULL || nbytes == 0) {
		return true;
	}

    int err;
    uint32_t cp;

	char *buf = (void *)x;

	for (; (nbytes - 3) > 0; nbytes -= 4) {
		buf = utf8_decode(buf, &cp, &err);
		if (err > 0) {
			return false;
		}
	}

	char *pad = (char *)(char[4]){0, 0, 0, 0};

	if (nbytes > 0) {
		memcpy(pad, buf, nbytes);
		utf8_decode(pad, &cp, &err);
		if (err > 0) {
			return false;
		}
	}

	return true;
}

void
bare_init(struct bare_ctx *ctx, void *buf, bare_reader read, bare_writer write)
{
	memset(ctx, 0, sizeof(*ctx));
	ctx->error = BARE_ERROR_NONE;
	ctx->buf = buf;
	ctx->read = read;
	ctx->write = write;
}

void
bare_init_reader(struct bare_ctx *ctx, void *buf, bare_reader read)
{
	bare_init(ctx, buf, read, NULL);
}

void
bare_init_writer(struct bare_ctx *ctx, void *buf, bare_writer write)
{
	bare_init(ctx, buf, NULL, write);
}

void
bare_put_uint(struct bare_ctx *ctx, uint64_t x)
{
	uint64_t i = 0;
	uint8_t b[MAXVARINTSZ];

	while (x >= 0x80) {
		b[i] = (uint8_t)x | 0x80;
		x >>= 7;
		i++;
	}

	b[i] = (uint8_t)x;
	i++;

	ctx->write(ctx, b, i);
}

void
bare_get_uint(struct bare_ctx *ctx, uint64_t *x)
{
	uint8_t s = 0;
	uint64_t y = 0;

	uint64_t i = 0;
	for (i = 0;;i++) {
		uint8_t b;
		ctx->read(ctx, &b, U8SZ);
		if (error_is_set(ctx)) {
			break;
		}

		if (b < 0x80) {
			if (i > 9 || (i == 9 && b > 1)) {
				ctx->error = BARE_ERROR_VARUINT_OVERFLOW;
				break;
			}

			y |= (uint64_t)b << s;
			break;
		}

		y |= ((uint64_t)b & 0x7f) << s;
		s += 7;
	}

	*x = y;
}

void
bare_put_u8(struct bare_ctx *ctx, uint8_t x)
{
	ctx->write(ctx, &x, U8SZ);
}

void
bare_get_u8(struct bare_ctx *ctx, uint8_t *x)
{
	ctx->read(ctx, x, U8SZ);
}

void
bare_put_u16(struct bare_ctx *ctx, uint16_t x)
{
	ctx->write(ctx, (uint8_t[U16SZ]){x, x >> 8}, U16SZ);
}

void
bare_get_u16(struct bare_ctx *ctx, uint16_t *x)
{
	ctx->read(ctx, x, U16SZ);

	if (error_is_set(ctx)) {
		return;
	}

	*x = (uint16_t)ptr8(x)[0]
	   | (uint16_t)ptr8(x)[1] << 8;
}

void
bare_put_u32(struct bare_ctx *ctx, uint32_t x)
{
	uint8_t b[U32SZ];

	b[0] = (uint8_t)(x);
	b[1] = (uint8_t)(x >> 8);
	b[2] = (uint8_t)(x >> 16);
	b[3] = (uint8_t)(x >> 24);

	ctx->write(ctx, b, U32SZ);
}

void
bare_get_u32(struct bare_ctx *ctx, uint32_t *x)
{
	ctx->read(ctx, x, U32SZ);

	if (error_is_set(ctx)) {
		return;
	}

	*x = (uint32_t)(ptr8(x)[0])
	   | (uint32_t)(ptr8(x)[1] << 8)
	   | (uint32_t)(ptr8(x)[2] << 16)
	   | (uint32_t)(ptr8(x)[3] << 24);
}

void
bare_put_u64(struct bare_ctx *ctx, uint64_t x)
{
	uint8_t b[U64SZ];

	b[0] = x;
	b[1] = x >> 8;
	b[2] = x >> 16;
	b[3] = x >> 24;
	b[4] = x >> 32;
	b[5] = x >> 40;
	b[6] = x >> 48;
	b[7] = x >> 56;

	ctx->write(ctx, b, U64SZ);
}

void
bare_get_u64(struct bare_ctx *ctx, uint64_t *x)
{
	ctx->read(ctx, x, U64SZ);

	if (error_is_set(ctx)) {
		return;
	}

	*x = (uint64_t)ptr8(x)[0]
	   | (uint64_t)ptr8(x)[1] << 8
	   | (uint64_t)ptr8(x)[2] << 16
	   | (uint64_t)ptr8(x)[3] << 24
	   | (uint64_t)ptr8(x)[4] << 32
	   | (uint64_t)ptr8(x)[5] << 40
	   | (uint64_t)ptr8(x)[6] << 48
	   | (uint64_t)ptr8(x)[7] << 56;
}

void
bare_put_int(struct bare_ctx *ctx, int64_t x)
{
	uint64_t ux = (uint64_t)x << 1;

	if (x < 0) {
		ux = ~ux;
	}

	bare_put_uint(ctx, ux);
}

void
bare_get_int(struct bare_ctx *ctx, int64_t *x)
{
	uint64_t ux;

	bare_get_uint(ctx, &ux);

	if (error_is_set(ctx)) {
		return;
	}

	*x = (int64_t)(ux >> 1);

	if ((ux & 1) != 0) {
		*x = ~(*x);
	}
}

void
bare_put_i8(struct bare_ctx *ctx, int8_t x)
{
	bare_put_u8(ctx, x);
}

void
bare_get_i8(struct bare_ctx *ctx, int8_t *x)
{
	bare_get_u8(ctx, (uint8_t *)x);
}

void
bare_put_i16(struct bare_ctx *ctx, int16_t x)
{
	bare_put_u16(ctx, x);
}

void
bare_get_i16(struct bare_ctx *ctx, int16_t *x)
{
	bare_get_u16(ctx, (uint16_t *)x);
}

void
bare_put_i32(struct bare_ctx *ctx, int32_t x)
{
	bare_put_u32(ctx, x);
}

void
bare_get_i32(struct bare_ctx *ctx, int32_t *x)
{
	bare_get_u32(ctx, (uint32_t *)x);
}

void
bare_put_i64(struct bare_ctx *ctx, int64_t x)
{
	bare_put_u64(ctx, x);
}

void
bare_get_i64(struct bare_ctx *ctx, int64_t *x)
{
	bare_get_u64(ctx, (uint64_t *)x);
}

void
bare_put_f32(struct bare_ctx *ctx, float x)
{
	if (isnan(x)) {
		ctx->error = BARE_ERROR_NAN;
		return;
	}

	uint32_t b;
	memcpy(&b, &x, U32SZ);

	bare_put_u32(ctx, b);
}

void
bare_get_f32(struct bare_ctx *ctx, float *x)
{
	ctx->read(ctx, x, U32SZ);
}

void
bare_put_f64(struct bare_ctx *ctx, double x)
{
	if (isnan(x)) {
		ctx->error = BARE_ERROR_NAN;
		return;
	}

	uint64_t b;
	memcpy(&b, &x, U64SZ);

	bare_put_u64(ctx, b);
}

void
bare_get_f64(struct bare_ctx *ctx, double *x)
{
	ctx->read(ctx, x, U64SZ);
}

void
bare_put_bool(struct bare_ctx *ctx, bool x)
{
	bare_put_u8(ctx, (uint8_t)x);
}

void
bare_get_bool(struct bare_ctx *ctx, bool *x)
{
	bare_get_u8(ctx, (uint8_t *)x);
}

uint64_t
bare_put_fixed_data(struct bare_ctx *ctx, const uint8_t *x, uint64_t nbytes)
{
	return ctx->write(ctx, x, nbytes);
}

uint64_t
bare_get_fixed_data(struct bare_ctx *ctx, uint8_t *x, uint64_t nbytes)
{
	return ctx->read(ctx, x, nbytes);
}

void
bare_put_data(struct bare_ctx *ctx, const uint8_t *x, uint64_t nbytes)
{
	bare_put_uint(ctx, nbytes);

	if (error_is_set(ctx)) {
		return;
	}

	bare_put_fixed_data(ctx, x, nbytes);
}

void
bare_get_data(struct bare_ctx *ctx, uint8_t **x, uint64_t *const nbytes)
{
	uint64_t read = 0, leftover = 0;

	bare_get_uint(ctx, &leftover);

	if (error_is_set(ctx)) {
		goto error;
	}

	// No need to set the last element to \0 as ckalloc zeroes all memory.
	*x = BARE_CALLOC(1, leftover + 1);
	if (*x == NULL) {
		ctx->error = BARE_ERROR_ALLOC_FAILED;
		return;
	}

	while (leftover > 0) {
		read += bare_get_fixed_data(ctx, (*x) + read, leftover);
		leftover -= read;

		if (error_is_set(ctx)) {
			goto error;
		}
	}

	*nbytes = read;

	return;

error:
	if (*x != NULL) {
		BARE_FREE(*x);
	}
}

void
bare_put_str(struct bare_ctx *ctx, const char *x, uint64_t nbytes)
{
	if (!checkstr(x, nbytes)) {
		ctx->error = BARE_ERROR_INVALID_UTF8;
		return;
	}

	bare_put_data(ctx, (const uint8_t *const)x, nbytes);
}

void
bare_get_str(struct bare_ctx *ctx, char **x, uint64_t *const nbytes)
{
	bare_get_data(ctx, (uint8_t **)x, nbytes);

	if (!error_is_set(ctx) && !checkstr(*x, *nbytes)) {
		ctx->error = BARE_ERROR_INVALID_UTF8;
	}
}

D cbare.h => cbare.h +0 -78
@@ 1,78 0,0 @@
#ifndef BARE_H
#define BARE_H

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

struct bare_ctx;

enum bare_error {
	BARE_ERROR_NONE,
	BARE_ERROR_WRITE_FAILED,
	BARE_ERROR_READ_FAILED,
	BARE_ERROR_VARUINT_OVERFLOW,
	BARE_ERROR_NAN,
	BARE_ERROR_ALLOC_FAILED,
	BARE_ERROR_INVALID_UTF8,
};

typedef uint64_t (*bare_reader)(struct bare_ctx *ctx, void *const data, uint64_t nbytes);
typedef uint64_t (*bare_writer)(struct bare_ctx *ctx, const void *const data, uint64_t nbytes);

struct bare_ctx {
	enum bare_error error;
	void *buf;
	bare_reader read;
	bare_writer write;
	uint64_t dlen;
};

/* Initialization */
void bare_init(struct bare_ctx *ctx, void *buf, bare_reader read, const bare_writer write);
void bare_init_reader(struct bare_ctx *ctx, void *buf, const bare_reader read);
void bare_init_writer(struct bare_ctx *ctx, void *buf, const bare_writer write);

/* Unsigned integers */
void bare_put_uint(struct bare_ctx *ctx, uint64_t x);  /* varuint */
void bare_get_uint(struct bare_ctx *ctx, uint64_t *const x); /* varuint */
void bare_put_u8(struct bare_ctx *ctx, uint8_t x);
void bare_get_u8(struct bare_ctx *ctx, uint8_t *const x);
void bare_put_u16(struct bare_ctx *ctx, uint16_t x);
void bare_get_u16(struct bare_ctx *ctx, uint16_t *const x);
void bare_put_u32(struct bare_ctx *ctx, uint32_t x);
void bare_get_u32(struct bare_ctx *ctx, uint32_t *const x);
void bare_put_u64(struct bare_ctx *ctx, uint64_t x);
void bare_get_u64(struct bare_ctx *ctx, uint64_t *const x);

/* Signed integers */
void bare_put_int(struct bare_ctx *ctx, int64_t x);	/* varint */
void bare_get_int(struct bare_ctx *ctx, int64_t *const x); /* varint */
void bare_put_i8(struct bare_ctx *ctx, int8_t x);
void bare_get_i8(struct bare_ctx *ctx, int8_t *const x);
void bare_put_i16(struct bare_ctx *ctx, int16_t x);
void bare_get_i16(struct bare_ctx *ctx, int16_t *const x);
void bare_put_i32(struct bare_ctx *ctx, int32_t x);
void bare_get_i32(struct bare_ctx *ctx, int32_t *const x);
void bare_put_i64(struct bare_ctx *ctx, int64_t x);
void bare_get_i64(struct bare_ctx *ctx, int64_t *const x);

/* Floating point numbers */
void bare_put_f32(struct bare_ctx *ctx, float x);
void bare_get_f32(struct bare_ctx *ctx, float *const x);
void bare_put_f64(struct bare_ctx *ctx, double x);
void bare_get_f64(struct bare_ctx *ctx, double *const x);

/* Booleans */
void bare_put_bool(struct bare_ctx *ctx, bool x);
void bare_get_bool(struct bare_ctx *ctx, bool *const x);

/* UTF-8 strings and data */
uint64_t bare_put_fixed_data(struct bare_ctx *ctx, const uint8_t *const x, uint64_t nbytes);
uint64_t bare_get_fixed_data(struct bare_ctx *ctx, uint8_t *const x, uint64_t nbytes);
void bare_put_data(struct bare_ctx *ctx, const uint8_t *const x, uint64_t nbytes);
void bare_get_data(struct bare_ctx *ctx, uint8_t **x, uint64_t *const nbytes);
void bare_put_str(struct bare_ctx *ctx, const char *const x, uint64_t nbytes);
void bare_get_str(struct bare_ctx *ctx, char **x, uint64_t *nbytes);

#endif /* BARE_H */

A cbare.pc.in => cbare.pc.in +10 -0
@@ 0,0 1,10 @@
prefix=@prefix@
exec_prefix=${prefix}
includedir=${prefix}/include
libdir=${prefix}/lib

Name: cbare
Description: BARE encoder and decoder.
Version: @version@
Cflags: -I${includedir}
Libs: -L${libdir} -lcbare

D doanalyze.sh => doanalyze.sh +0 -11
@@ 1,11 0,0 @@
#!/bin/sh

set -e

builddir="build-analyze"

meson \
    --werror \
    --buildtype debug \
    "$builddir"
ninja -C "$builddir" scan-build

D dodebug.sh => dodebug.sh +0 -13
@@ 1,13 0,0 @@
#!/bin/sh

set -e

builddir="build-debug"

meson \
    --werror \
    --buildtype debug \
    -Db_sanitize=address,undefined \
    "$builddir"
meson compile -C "$builddir"
meson test -C "$builddir"

D dorelease.sh => dorelease.sh +0 -12
@@ 1,12 0,0 @@
#!/bin/sh

set -e

builddir="build-release"

meson \
    --werror \
    --buildtype release \
    "$builddir"
meson compile -C "$builddir"
meson test -C "$builddir"

D membuf.c => membuf.c +0 -82
@@ 1,82 0,0 @@
#include "membuf.h"

#include "alloc.h"

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

void
membuf_init(struct membuf *buf, size_t capacity)
{
	memset(buf, 0, sizeof(*buf));
	buf->size = 0;
	buf->cursor = 0;
	buf->capacity = capacity;
	buf->data = ckalloc(buf->capacity);
}

void
membuf_fini(struct membuf *buf)
{
	if (buf->data) {
		free(buf->data);
	}
}

void
membuf_clear(struct membuf *buf)
{
	buf->size = 0;
	buf->cursor = 0;
	memset(buf->data, 0, buf->capacity);
}

void
membuf_grow(struct membuf *buf, size_t new_capacity)
{
	if (new_capacity <= buf->capacity) {
		return;
	}

	buf->data = ckrealloc(buf->data, new_capacity);
	buf->capacity = new_capacity;
}

void
membuf_write(struct membuf *buf, const uint8_t *data, size_t size)
{
	if (buf->capacity < buf->size + size) {
		membuf_grow(buf, buf->size + buf->size / 2 + size);
	}

	memcpy(buf->data + buf->size, data, size);
	buf->size += size;
}

bool
membuf_read(struct membuf *buf, uint8_t *dst, size_t size)
{
	if ((buf->cursor + size) > buf->size) {
		return false;
	}

	memcpy(dst, buf->data + buf->cursor, size);
	buf->cursor += size;

	return true;
}

bool
membuf_seek(struct membuf *buf, size_t pos)
{
	if (pos > buf->size) {
		return false;
	}

	buf->cursor = pos;

	return true;
}

D membuf.h => membuf.h +0 -23
@@ 1,23 0,0 @@
#ifndef MEMBUF_H
#define MEMBUF_H

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

struct membuf {
	uint8_t *data;
	size_t capacity;
	size_t size;
	size_t cursor; // read cursor
};

void membuf_init(struct membuf *buf, size_t capacity);
void membuf_fini(struct membuf *buf);
void membuf_clear(struct membuf *buf);
void membuf_grow(struct membuf *buf, size_t new_capacity);
void membuf_write(struct membuf *buf, const uint8_t *data, size_t size);
bool membuf_read(struct membuf *buf, uint8_t *dst, size_t size);
bool membuf_seek(struct membuf *buf, size_t pos);

#endif /* MEMBUF_H */

D membuftest.c => membuftest.c +0 -46
@@ 1,46 0,0 @@
#include "die.h"
#include "membuf.h"

void
check(bool result, char *msg, ...)
{
	va_list args;

	va_start(args, msg);
	if (!result) {
		die_va(msg, &args);
	}
	va_end(args);
}

int
main(int argc, char const *argv[])
{
	(void)argc;
	(void)argv;

	struct membuf buf;
	size_t capacity = 1024;

	membuf_init(&buf, capacity);

	check(buf.data != NULL, "data is null");
	check(buf.capacity == capacity, "%ld != %ld", buf.capacity, capacity);
	check(buf.size == 0, "%ld != %ld", buf.size, 0);
	check(buf.cursor == 0, "%ld != %ld", buf.cursor, 0);

	capacity = 2 * capacity;
	membuf_grow(&buf, capacity);

	check(buf.capacity == capacity, "%ld != %ld", buf.capacity, capacity);
	check(buf.size == 0, "%ld != %ld", buf.size, 0);

	uint8_t data[] = {0xFF, 0xFF, 0xFF, 0xFF};
	membuf_write(&buf, data, sizeof(data));

	check(buf.size == 4, "%ld != %ld", buf.size, 4);

	membuf_fini(&buf);

	return 0;
}

D meson.build => meson.build +0 -75
@@ 1,75 0,0 @@
project(
  'cbare',
  'c',
  version : '0.1',
  default_options : [
    'c_std=c11',
    'warning_level=3',
  ],
)

install_headers('cbare.h')

sources = [
  'cbare.c',
  'alloc.c',
  'die.c',
]

cbare_static = static_library('cbare', sources, install : true)
cbare_shared = shared_library('cbare', sources, install : true)

membuftest = executable(
  'membuftest',
  'membuftest.c',
  'membuf.c',
  'alloc.c',
  'die.c',
)

pyinst = import('python').find_installation('python3')

baretest_c = custom_target(
  'baretest.c',
  output : ['baretest.c'],
  input : [
    'scripts/genbaretests.py',
    'baretest.c.mako',
    'testdata/f32.txt',
    'testdata/i16.txt',
    'testdata/i8.txt',
    'testdata/u16.txt',
    'testdata/u8.txt',
    'testdata/bool.txt',
    'testdata/f64.txt',
    'testdata/i32.txt',
    'testdata/int.txt',
    'testdata/u32.txt',
    'testdata/uint.txt',
    'testdata/data.txt',
    'testdata/fixed_data.txt',
    'testdata/i64.txt',
    'testdata/string.txt',
    'testdata/u64.txt',
  ],
  command : [pyinst, '@INPUT@', '@OUTPUT@'],
)

baretest = executable(
  'baretest',
  baretest_c,
  'alloc.c',
  'die.c',
  'membuf.c',
  link_with : [cbare_static],
)

utf8test = executable(
  'utf8test',
  'utf8test.c',
  'die.c',
)

test('baretest', baretest)
test('membuftest', membuftest)
test('utf8test', utf8test)

A scripts/asan.sh => scripts/asan.sh +23 -0
@@ 0,0 1,23 @@
#!/bin/sh

set -eu

# See: https://clang.llvm.org/docs/AddressSanitizer.html

scriptdir="$(dirname "$(realpath "$0")")"
. "${scriptdir}/lib.sh"
cd "${scriptdir}/.."

export ASAN_OPTIONS="detect_leaks=1"
export CFLAGS="-fPIC \
               -gdwarf-4 \
               -fsanitize=address \
               -fno-omit-frame-pointer \
               -fno-optimize-sibling-calls \
               -fsanitize-address-use-after-scope"
export LDFLAGS="-fsanitize=address"

buildcheck
runtest ./baretest
runtest ./utf8test
doexit

D scripts/genbaretests.py => scripts/genbaretests.py +0 -205
@@ 1,205 0,0 @@
#!/usr/bin/env python3

import re
import sys
from pprint import pprint
from dataclasses import dataclass
from pathlib import Path

import click
from mako.template import Template
from parsimonious.grammar import Grammar
from parsimonious.nodes import NodeVisitor, Node
from parsimonious.exceptions import ParseError


@click.command()
@click.argument('template', type=click.Path(exists=True, path_type=Path))
@click.argument(
    'test_data',
    type=click.Path(exists=True, path_type=Path),
    nargs=-1,
    required=True,
)
@click.argument('output', type=click.Path(exists=False, path_type=Path))
def main(output, template, test_data):
    template = Template(filename=str(template.resolve()))
    tests = {}

    for fn in test_data:
        try:
            tests[fn.stem] = AttrDict(TestdataParser().parse(fn.read_text()))
        except ParseError as e:
            eprint(f"{fn}:{e.line()}:{e.column()}: {e!s}")
            sys.exit(1)

    output.write_text(template.render(tests=tests))


def eprint(*args, **kwargs):
    print(*args, file=sys.stderr, **kwargs)


class TestdataParser(NodeVisitor):
    grammar = Grammar(
        r"""
        test        = ws inputs expect ws

        inputs      = (name type value)+
        expect      = "expect" sp bytes

        name        = "name" sp ~r"[a-z_][a-z0-9_]*"i nl
        type        = "type" sp type_name nl
        value       = "value" sp type_value nl

        type_name   = "uint"
                    / "u8"
                    / "u16"
                    / "u32"
                    / "u64"
                    / "int"
                    / "i8"
                    / "i16"
                    / "i32"
                    / "i64"
                    / "f32"
                    / "f64"
                    / "bool"
                    / "string"
                    / "data"
                    / "fixed_data"

        type_value  = bool
                    / float
                    / sint
                    / uint
                    / string
                    / bytes

        bytes       = "[" ws byte (ws+ byte)* ws "]"
        byte        = ~r"0x[a-f0-9]{2}"i
                    / ~r"0b[01]{1,8}"i
                    / ~r"(?:[12][0-9]{2}|[1-9][0-9]|[0-9])"i

        sint        = "-" uint
        uint        = ~r"0x[a-z0-9]+"i
                    / ~r"0b[01]+"i
                    / ~r"\d+"
        float       = ~r"\d+\.\d+"

        string      = ~r'"(?:\\"|.)*?"'

        bool        = "true" / "false"

        nl          = "\n"+
        sp          = (" " / "\t")+
        ws          = (" " / "\t" / "\n" / "\r")*
        """
    )

    def generic_visit(self, node, visited_children):
        return visited_children or node

    def visit_test(self, node, visited_children):
        result = {}
        for child in visited_children:
            if isinstance(child, dict):
                result.update(child)
        return result

    def visit_inputs(self, node, visited_children):
        inputs = []

        for child in visited_children:
            tmp = AttrDict()
            for entry in child:
                tmp.update(entry)
            inputs.append(tmp)

        return {"inputs": inputs}

    def visit_expect(self, node, visited_children):
        return {"expect": visited_children[2]}

    def visit_name(self, node, visited_children):
        return {"name": node.children[2].text}

    def visit_type(self, node, visited_children):
        return {"type": node.children[2].text}

    def visit_value(self, node, visited_children):
        return {"value": visited_children[2][0]}

    def visit_bytes(self, node, visited_children):
        return Bytes(collect(visited_children, Byte))

    def visit_byte(self, node, visited_children):
        return Byte(node.text)

    def visit_sint(self, node, visited_children):
        return visited_children[0].text + visited_children[1]

    def _text_node(self, node, visited_children):
        return node.text

    visit_string = _text_node
    visit_uint = _text_node
    visit_float = _text_node
    visit_bool = _text_node

    def _ignore_node(self, node, visited_children):
        return Ignore(node)

    visit_nl = _ignore_node
    visit_sp = _ignore_node
    visit_ws = _ignore_node


def collect( tree, value_type):
    xs = []

    def _collect(tree):
        for x in tree:
            if isinstance(x, list):
                _collect(x)
            elif isinstance(x, value_type):
                xs.append(str(x))

    _collect(tree)

    return xs


class Bytes(list):
    pass


class Byte(str):
    pass


@dataclass
class Ignore:
    node: Node

    def __repr__(self):
        return f"<IGNORED: {self.node.expr.name}>"


class AttrDict(dict):
    def __getattr__(self, key):
        try:
            value = self.__getitem__(key)
            if not isinstance(value, self.__class__) \
                    and isinstance(value, dict):
                self[key] = value = self.__class__(value)
            return value
        except KeyError:
            raise AttributeError(f'{key} not in {self.keys()}')

    def __setattr__(self, key, value):
        self.__setitem__(key, value)


if __name__ == '__main__':
    main()

A scripts/lib.sh => scripts/lib.sh +49 -0
@@ 0,0 1,49 @@
#!/bin/sh

STATUS=0
DEBUG=0
CLEAN=0

usage() {
	echo "PROGRAM [-d] [-c] [-h]"
}

while getopts "dc" o; do
	case "${o}" in
		d)
			DEBUG=1
			;;
		c)
			CLEAN=1
			;;
		h|*)
			usage
			exit 0
	esac
done
shift $((OPTIND-1))

buildcheck() {
	if [ "$CLEAN" = 1 ]; then
		make clean
	fi
	make check
}

runtest() {
	cmd=$1
	if [ "$DEBUG" = "1" ]; then
		cmd="gdb -ex run ${cmd}"
	fi

	if $cmd; then
		echo "$1 passed"
	else
		echo "$1 failed"
		STATUS=1
	fi
}

doexit() {
	exit $STATUS
}

A scripts/msan.sh => scripts/msan.sh +22 -0
@@ 0,0 1,22 @@
#!/bin/sh

set -eu

# See: https://clang.llvm.org/docs/MemorySanitizer.html

scriptdir="$(dirname "$(realpath "$0")")"
. "${scriptdir}/lib.sh"
cd "${scriptdir}/.."

export CC=clang
export CFLAGS="-fPIC \
               -gdwarf-4 \
               -fsanitize=memory \
               -fno-omit-frame-pointer \
               -fno-optimize-sibling-calls"
export LDFLAGS="-fsanitize=memory"

buildcheck
runtest ./baretest
runtest ./utf8test
doexit

A scripts/static-analysis.sh => scripts/static-analysis.sh +15 -0
@@ 0,0 1,15 @@
#!/bin/sh

set -eu

# See: https://clang-analyzer.llvm.org/

scriptdir="$(dirname "$(realpath "$0")")"
. "${scriptdir}/lib.sh"
cd "${scriptdir}/.."

export CC=clang
export CFLAGS="-fPIC -gdwarf-4"

make clean
scan-build make check

A scripts/ubsan.sh => scripts/ubsan.sh +19 -0
@@ 0,0 1,19 @@
#!/bin/sh

set -eu

# See: https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html

scriptdir="$(dirname "$(realpath "$0")")"
. "${scriptdir}/lib.sh"
cd "${scriptdir}/.."

export CFLAGS="-fPIC \
               -gdwarf-4 \
               -fsanitize=undefined"
export LDFLAGS="-fsanitize=undefined"

buildcheck
runtest ./baretest
runtest ./utf8test
doexit

R alloc.c => src/alloc.c +0 -0
R alloc.h => src/alloc.h +0 -0
A src/cbare.c => src/cbare.c +378 -0
@@ 0,0 1,378 @@
#include <string.h>
#include <stdbool.h>

#include "cbare.h"
#include "utf8.h"

#define UNUSED(x) (void)(x)

enum {
	U8SZ = 1,
	U16SZ = 2,
	U32SZ = 4,
	U64SZ = 8,
	MAXVARINTSZ = 10,
};

static bool
checkstr(const char *x, uint64_t sz)
{
	if (x == NULL || sz == 0) {
		return true;
	}

	int err = 0;
	uint32_t cp = 0;
	char *buf = (void *)x;
	uint64_t chunk = 4;
	char *pad = (char *)(char[4]){0, 0, 0, 0};

#define _utf8_decode(buf) \
	do { \
		buf = utf8_decode(buf, &cp, &err); \
		if (err > 0) { \
			return false; \
		} \
	} while (0)

	for (; sz >= chunk; sz -= chunk) {
		_utf8_decode(buf);
	}

	if (sz > 0) {
		memcpy(pad, buf, sz);
		_utf8_decode(pad);
	}

#undef _utf8_decode

	return true;
}

bare_error
bare_put_uint(struct bare_writer *ctx, uint64_t x)
{
	uint64_t i = 0;
	uint8_t b[MAXVARINTSZ];

	while (x >= 0x80) {
		b[i] = (uint8_t)x | 0x80;
		x >>= 7;
		i++;
	}

	b[i] = (uint8_t)x;
	i++;

	return ctx->write(ctx->buffer, b, i);
}

bare_error
bare_get_uint(struct bare_reader *ctx, uint64_t *x)
{
	bare_error err = BARE_ERROR_NONE;

	uint8_t shift = 0;
	uint64_t result = 0;

	for (uint8_t i = 0;i < 10;i++) {
		uint8_t b;

		err = ctx->read(ctx->buffer, &b, U8SZ);
		if (err != BARE_ERROR_NONE) {
			break;
		}

		if (b < 0x80) {
			result |= (uint64_t)b << shift;
			break;
		} else {
			result |= ((uint64_t)b & 0x7f) << shift;
			shift += 7;
		}
	}

	*x = result;

	return err;
}

bare_error
bare_put_int(struct bare_writer *ctx, int64_t x)
{
	uint64_t ux = (uint64_t)x << 1;

	if (x < 0) {
		ux = ~ux;
	}

	return bare_put_uint(ctx, ux);
}

bare_error
bare_get_int(struct bare_reader *ctx, int64_t *x)
{
	uint64_t ux;

	bare_error err = bare_get_uint(ctx, &ux);

	if (err == BARE_ERROR_NONE) {
		*x = (int64_t)(ux >> 1);

		if ((ux & 1) != 0) {
			*x = ~(*x);
		}
	}

	return err;
}

bare_error
bare_put_u8(struct bare_writer *ctx, uint8_t x)
{
	return ctx->write(ctx->buffer, &x, U8SZ);
}

bare_error
bare_get_u8(struct bare_reader *ctx, uint8_t *x)
{
	return ctx->read(ctx->buffer, x, U8SZ);
}

bare_error
bare_put_u16(struct bare_writer *ctx, uint16_t x)
{
	return ctx->write(ctx->buffer, (uint8_t[U16SZ]){x, x >> 8}, U16SZ);
}

bare_error
bare_get_u16(struct bare_reader *ctx, uint16_t *x)
{
	bare_error err = ctx->read(ctx->buffer, x, U16SZ);

	if (err == BARE_ERROR_NONE) {
		*x = (uint16_t)((uint8_t *)x)[0]
		   | (uint16_t)((uint8_t *)x)[1] << 8;
	}

	return err;
}

bare_error
bare_put_u32(struct bare_writer *ctx, uint32_t x)
{
	uint8_t buf[U32SZ];

	buf[0] = (uint8_t)(x);
	buf[1] = (uint8_t)(x >> 8);
	buf[2] = (uint8_t)(x >> 16);
	buf[3] = (uint8_t)(x >> 24);

	return ctx->write(ctx->buffer, buf, U32SZ);
}

bare_error
bare_get_u32(struct bare_reader *ctx, uint32_t *x)
{
	bare_error err = ctx->read(ctx->buffer, x, U32SZ);

	if (err == BARE_ERROR_NONE) {
		*x = (uint32_t)(((uint8_t *)x)[0])
		   | (uint32_t)(((uint8_t *)x)[1] << 8)
		   | (uint32_t)(((uint8_t *)x)[2] << 16)
		   | (uint32_t)(((uint8_t *)x)[3] << 24);
	}

	return err;
}

bare_error
bare_put_u64(struct bare_writer *ctx, uint64_t x)
{
	uint8_t buf[U64SZ];

	buf[0] = x;
	buf[1] = x >> 8;
	buf[2] = x >> 16;
	buf[3] = x >> 24;
	buf[4] = x >> 32;
	buf[5] = x >> 40;
	buf[6] = x >> 48;
	buf[7] = x >> 56;

	return ctx->write(ctx->buffer, buf, U64SZ);
}

bare_error
bare_get_u64(struct bare_reader *ctx, uint64_t *x)
{
	bare_error err = ctx->read(ctx->buffer, x, U64SZ);

	if (err == BARE_ERROR_NONE) {
		*x = (uint64_t)((uint8_t *)x)[0]
		   | (uint64_t)((uint8_t *)x)[1] << 8
		   | (uint64_t)((uint8_t *)x)[2] << 16
		   | (uint64_t)((uint8_t *)x)[3] << 24
		   | (uint64_t)((uint8_t *)x)[4] << 32
		   | (uint64_t)((uint8_t *)x)[5] << 40
		   | (uint64_t)((uint8_t *)x)[6] << 48
		   | (uint64_t)((uint8_t *)x)[7] << 56;
	}

	return err;
}

bare_error
bare_put_i8(struct bare_writer *ctx, int8_t x)
{
	return bare_put_u8(ctx, x);
}

bare_error
bare_get_i8(struct bare_reader *ctx, int8_t *x)
{
	return bare_get_u8(ctx, (uint8_t *)x);
}

bare_error
bare_put_i16(struct bare_writer *ctx, int16_t x)
{
	return bare_put_u16(ctx, x);
}

bare_error
bare_get_i16(struct bare_reader *ctx, int16_t *x)
{
	return bare_get_u16(ctx, (uint16_t *)x);
}

bare_error
bare_put_i32(struct bare_writer *ctx, int32_t x)
{
	return bare_put_u32(ctx, x);
}

bare_error
bare_get_i32(struct bare_reader *ctx, int32_t *x)
{
	return bare_get_u32(ctx, (uint32_t *)x);
}

bare_error
bare_put_i64(struct bare_writer *ctx, int64_t x)
{
	return bare_put_u64(ctx, x);
}

bare_error
bare_get_i64(struct bare_reader *ctx, int64_t *x)
{
	return bare_get_u64(ctx, (uint64_t *)x);
}

bare_error
bare_put_f32(struct bare_writer *ctx, float x)
{
	uint32_t b;
	memcpy(&b, &x, U32SZ);

	return bare_put_u32(ctx, b);
}

bare_error
bare_get_f32(struct bare_reader *ctx, float *x)
{
	return ctx->read(ctx->buffer, x, U32SZ);
}

bare_error
bare_put_f64(struct bare_writer *ctx, double x)
{
	uint64_t b;
	memcpy(&b, &x, U64SZ);

	return bare_put_u64(ctx, b);
}

bare_error
bare_get_f64(struct bare_reader *ctx, double *x)
{
	return ctx->read(ctx->buffer, x, U64SZ);
}

bare_error
bare_put_bool(struct bare_writer *ctx, bool x)
{
	return bare_put_u8(ctx, (uint8_t)x);
}

bare_error
bare_get_bool(struct bare_reader *ctx, bool *x)
{
	return bare_get_u8(ctx, (uint8_t *)x);
}

bare_error
bare_put_fixed_data(struct bare_writer *ctx, uint8_t *src, uint64_t sz)
{
	return ctx->write(ctx->buffer, (void *)src, sz);
}

bare_error
bare_get_fixed_data(struct bare_reader *ctx, uint8_t *dst, uint64_t sz)
{
	return ctx->read(ctx->buffer, dst, sz);
}

bare_error
bare_put_data(struct bare_writer *ctx, uint8_t *src, uint64_t sz)
{
	bare_error err = BARE_ERROR_NONE;

	err = bare_put_uint(ctx, sz);

	if (err == BARE_ERROR_NONE) {
		err = bare_put_fixed_data(ctx, src, sz);
	}

	return err;
}

bare_error
bare_get_data(struct bare_reader *ctx, uint8_t *dst, uint64_t sz)
{
	bare_error err = BARE_ERROR_NONE;
	uint64_t ssz = 0;

	err = bare_get_uint(ctx, &ssz);

	if (err == BARE_ERROR_NONE) {
		err = ssz <= sz \
			? bare_get_fixed_data(ctx, dst, ssz) \
			: BARE_ERROR_BUFFER_TOO_SMALL;
	}

	return err;
}

bare_error
bare_put_str(struct bare_writer *ctx, char *src, uint64_t sz)
{
	if (!checkstr(src, sz)) {
		return BARE_ERROR_INVALID_UTF8;
	}

	return bare_put_data(ctx, (uint8_t *)src, sz);
}

bare_error
bare_get_str(struct bare_reader *ctx, char *dst, uint64_t sz)
{
	bare_error err = bare_get_data(ctx, (uint8_t *)dst, sz);\

	if (err == BARE_ERROR_NONE) {
		err = !checkstr(dst, sz) ? BARE_ERROR_INVALID_UTF8 : err;
	}

	return err;
}

A src/cbare.h => src/cbare.h +64 -0
@@ 0,0 1,64 @@
#ifndef BARE_H
#define BARE_H

#include <stdint.h>

typedef enum {
	BARE_ERROR_NONE,
	BARE_ERROR_WRITE_FAILED,
	BARE_ERROR_READ_FAILED,
	BARE_ERROR_BUFFER_TOO_SMALL,
	BARE_ERROR_INVALID_UTF8,
} bare_error;

typedef bare_error (*bare_write_func)(void *buffer, void *src, uint64_t sz);
typedef bare_error (*bare_read_func)(void *buffer, void *dst, uint64_t sz);

struct bare_writer {
	void *buffer;
	bare_write_func write;
};

struct bare_reader {
	void *buffer;
	bare_read_func read;
};

bare_error bare_put_uint(struct bare_writer *ctx, uint64_t x); /* varuint */
bare_error bare_get_uint(struct bare_reader *ctx, uint64_t *x); /* varuint */
bare_error bare_put_u8(struct bare_writer *ctx, uint8_t x);
bare_error bare_get_u8(struct bare_reader *ctx, uint8_t *x);
bare_error bare_put_u16(struct bare_writer *ctx, uint16_t x);
bare_error bare_get_u16(struct bare_reader *ctx, uint16_t *x);
bare_error bare_put_u32(struct bare_writer *ctx, uint32_t x);
bare_error bare_get_u32(struct bare_reader *ctx, uint32_t *x);
bare_error bare_put_u64(struct bare_writer *ctx, uint64_t x);
bare_error bare_get_u64(struct bare_reader *ctx, uint64_t *x);

bare_error bare_put_int(struct bare_writer *ctx, int64_t x); /* varint */
bare_error bare_get_int(struct bare_reader *ctx, int64_t *x); /* varint */
bare_error bare_put_i8(struct bare_writer *ctx, int8_t x);
bare_error bare_get_i8(struct bare_reader *ctx, int8_t *x);
bare_error bare_put_i16(struct bare_writer *ctx, int16_t x);
bare_error bare_get_i16(struct bare_reader *ctx, int16_t *x);
bare_error bare_put_i32(struct bare_writer *ctx, int32_t x);
bare_error bare_get_i32(struct bare_reader *ctx, int32_t *x);
bare_error bare_put_i64(struct bare_writer *ctx, int64_t x);
bare_error bare_get_i64(struct bare_reader *ctx, int64_t *x);

bare_error bare_put_f32(struct bare_writer *ctx, float x);
bare_error bare_get_f32(struct bare_reader *ctx, float *x);
bare_error bare_put_f64(struct bare_writer *ctx, double x);
bare_error bare_get_f64(struct bare_reader *ctx, double *x);

bare_error bare_put_bool(struct bare_writer *ctx, bool x);
bare_error bare_get_bool(struct bare_reader *ctx, bool *x);

bare_error bare_put_fixed_data(struct bare_writer *ctx, uint8_t *src, uint64_t sz);
bare_error bare_get_fixed_data(struct bare_reader *ctx, uint8_t *dst, uint64_t sz);
bare_error bare_put_data(struct bare_writer *ctx, uint8_t *src, uint64_t sz);
bare_error bare_get_data(struct bare_reader *ctx, uint8_t *dst, uint64_t sz);
bare_error bare_put_str(struct bare_writer *ctx, char *src, uint64_t sz);
bare_error bare_get_str(struct bare_reader *ctx, char *dst, uint64_t sz);

#endif /* BARE_H */

R die.c => src/die.c +0 -0
R die.h => src/die.h +2 -2
@@ 3,7 3,7 @@

#include <stdarg.h>

_Noreturn void die(char *msg, ...);
_Noreturn void die_va(char *msg, va_list *args);
void die(char *msg, ...);
void die_va(char *msg, va_list *args);

#endif /* DIE_H */

R utf8.h => src/utf8.h +0 -0
A test/baretest.c => test/baretest.c +926 -0
@@ 0,0 1,926 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <inttypes.h>

#include <cbare.h>

#include "alloc.h"
#include "die.h"

#define UNUSED(x) (void)(x)

#define ARRLEN(a) sizeof(a)/sizeof(a[0])

#define FOR_EACH_ARRAY(type, elem, array) \
	for (type elem = &(array)[0]; \
	     elem < &(array)[ARRLEN(array)]; \
	     ++elem)

static void
ok(bool result, char *msg, ...)
{
	va_list args;

	va_start(args, msg);
	if (!result) {
		die_va(msg, &args);
	}
	va_end(args);
}

static void
binok(uint8_t *a, uint8_t *b, size_t len)
{
	bool result = memcmp(a, b, len) == 0;

	if (!result) {
		printf("a:");

		for (size_t i = 0; i < len; i++) {
			printf(" %02x", (uint8_t) *a);
			a++;
		}

		printf("\n");
		printf("b:");

		for (size_t i = 0; i < len; i++) {
			printf(" %02x", (uint8_t) *b);
			b++;
		}

		printf("\n");
	}

	ok(result, "%s", "a != b");
}

struct buf {
	uint8_t *data;
	uint64_t cur;
	uint64_t len;
};

static struct buf
buf_alloc(uint64_t sz)
{
	return (struct buf){.data=ckalloc(sz), .cur=0, .len=sz};
}

static struct buf
buf_alloc_1kb()
{
	return buf_alloc(1024);
}

static bare_error
buf_read(void *buffer, void *dst, uint64_t sz)
{
	struct buf *b = (struct buf *)buffer;

	if (sz > (b->len-b->cur)) {
		return BARE_ERROR_READ_FAILED;
	}

	memcpy(dst, b->data + b->cur, sz);
	b->cur += sz;

	return BARE_ERROR_NONE;
}

static bare_error
buf_write(void *buffer, void *src, uint64_t sz)
{
	struct buf *b = (struct buf *)buffer;

	if (sz > (b->len-b->cur)) {
		return BARE_ERROR_WRITE_FAILED;
	}

	memcpy(b->data + b->cur, src, sz);
	b->cur += sz;

	return BARE_ERROR_NONE;
}

static void
test_bare_put_uint()
{
	struct test {
		uint64_t input;
		uint8_t *output;
		size_t len;
	};

	struct test tests[] = {
		{.input=0, .output=(uint8_t[]){0x00}, 1},
		{.input=1, .output=(uint8_t[]){0x01}, 1},
		{.input=126, .output=(uint8_t[]){0x7e}, 1},
		{.input=127, .output=(uint8_t[]){0x7f}, 1},
		{.input=128, .output=(uint8_t[]){0x80, 0x01}, 2},
		{.input=129, .output=(uint8_t[]){0x81, 0x01}, 2},
		{.input=255, .output=(uint8_t[]){0xFF, 0x01}, 2},
		{.input=5345342, .output=(uint8_t[]){0xBE, 0xA0, 0xC6, 0x02}, 4},
		{.input=INT64_MAX, .output=(uint8_t[]){0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}, 9},
		{.input=UINT64_MAX, .output=(uint8_t[]){0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01}, 10},
	};

	FOR_EACH_ARRAY(struct test *, t, tests) {
		struct buf b = buf_alloc_1kb();
		struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

		ok(bare_put_uint(&ctx, t->input) == BARE_ERROR_NONE, "bare_put_uint failed");
		binok(b.data, t->output, t->len);
		ok(b.cur == t->len, "more data than expected: %" PRIu64 " > %" PRIu64, b.cur, t->len);

		free(b.data);
	}
}

static void
test_bare_get_uint()
{
	struct test {
		uint8_t *input;
		uint64_t output;
		size_t len;
	};

	struct test tests[] = {
		{.input=(uint8_t[]){0x00}, .output=0, 1},
		{.input=(uint8_t[]){0x01}, .output=1, 1},
		{.input=(uint8_t[]){0x7e}, .output=126, 1},
		{.input=(uint8_t[]){0x7f}, .output=127, 1},
		{.input=(uint8_t[]){0x80, 0x01}, .output=128, 2},
		{.input=(uint8_t[]){0x81, 0x01}, .output=129, 2},
		{.input=(uint8_t[]){0xFF, 0x01}, .output=255, 2},
		{.input=(uint8_t[]){0xBE, 0xA0, 0xC6, 0x02}, .output=5345342, 4},
		{.input=(uint8_t[]){0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}, .output=INT64_MAX, 9},
		{.input=(uint8_t[]){0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01}, .output=UINT64_MAX, 10},
		{.input=(uint8_t[]){0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, .output=UINT64_MAX, 11},
	};

	FOR_EACH_ARRAY(struct test *, t, tests) {
		struct buf b = {.data=t->input, .cur=0, .len=t->len};
		struct bare_reader ctx = {.buffer=(void *)&b, .read=&buf_read};

		uint64_t x = 0;
		ok(bare_get_uint(&ctx, &x) == BARE_ERROR_NONE, "bare_get_uint failed");
		ok(x == t->output, "%" PRIu64 " != %" PRIu64, x, t->output);
	}
}

static void
test_bare_put_int()
{
	struct test {
		int64_t input;
		uint8_t *output;
		size_t len;
	};

	struct test tests[] = {
		{.input=0, .output=(uint8_t[]){0x00}, 1},
		{.input=1, .output=(uint8_t[]){0x02}, 1},
		{.input=-1, .output=(uint8_t[]){0x01}, 1},
		{.input=63, .output=(uint8_t[]){0x7e}, 1},
		{.input=-63, .output=(uint8_t[]){0x7d}, 1},
		{.input=64, .output=(uint8_t[]){0x80 , 0x1}, 2},
		{.input=-64, .output=(uint8_t[]){0x7f}, 1},
		{.input=65, .output=(uint8_t[]){0x82, 0x01}, 2},
		{.input=-65, .output=(uint8_t[]){0x81, 0x01}, 2},
		{.input=255, .output=(uint8_t[]){0xFE, 0x03}, 2},
		{.input=-255, .output=(uint8_t[]){0xFD, 0x03}, 2},
		{.input=INT64_MAX, .output=(uint8_t[]){0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01}, 10},
		{.input=INT64_MIN, .output=(uint8_t[]){0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01}, 10},
	};

	FOR_EACH_ARRAY(struct test *, t, tests) {
		struct buf b = buf_alloc_1kb();
		struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

		ok(bare_put_int(&ctx, t->input) == BARE_ERROR_NONE, "bare_put_int failed");
		binok(b.data, t->output, t->len);
		ok(b.cur == t->len, "more data than expected: %" PRIu64 " > %" PRIu64, b.cur, t->len);

		free(b.data);
	}
}

static void
test_bare_get_int()
{
	struct test {
		uint8_t *input;
		int64_t output;
		size_t len;
	};

	struct test tests[] = {
		{.input=(uint8_t[]){0x00}, .output=0, 1},
		{.input=(uint8_t[]){0x02}, .output=1, 1},
		{.input=(uint8_t[]){0x01}, .output=-1, 1},
		{.input=(uint8_t[]){0x7e}, .output=63, 1},
		{.input=(uint8_t[]){0x7d}, .output=-63, 1},
		{.input=(uint8_t[]){0x80 , 0x1}, .output=64, 2},
		{.input=(uint8_t[]){0x7f}, .output=-64, 1},
		{.input=(uint8_t[]){0x82, 0x01}, .output=65, 2},
		{.input=(uint8_t[]){0x81, 0x01}, .output=-65, 2},
		{.input=(uint8_t[]){0xFE, 0x03}, .output=255, 2},
		{.input=(uint8_t[]){0xFD, 0x03}, .output=-255, 2},
		{.input=(uint8_t[]){0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01}, .output=INT64_MAX, 10},
		{.input=(uint8_t[]){0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01}, .output=INT64_MIN, 10},
		{.input=(uint8_t[]){0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, .output=INT64_MIN, 10},
		{.input=(uint8_t[]){0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, .output=INT64_MIN, 11},
	};

	FOR_EACH_ARRAY(struct test *, t, tests) {
		struct buf b = {.data=t->input, .cur=0, .len=t->len};
		struct bare_reader ctx = {.buffer=(void *)&b, .read=&buf_read};

		int64_t x = 0;
		ok(bare_get_int(&ctx, &x) == BARE_ERROR_NONE, "bare_get_int failed");
		ok(x == t->output, "%" PRIi64 " != %" PRIi64, x, t->output);
	}
}

static void
test_bare_put_u8()
{
	struct test {
		uint64_t input;
		uint8_t *output;
		size_t len;
	};

	struct test tests[] = {
		{.input=0, .output=(uint8_t[]){0x00}, 1},
		{.input=1, .output=(uint8_t[]){0x01}, 1},
		{.input=126, .output=(uint8_t[]){0x7e}, 1},
		{.input=255, .output=(uint8_t[]){0xFF}, 1},
	};

	FOR_EACH_ARRAY(struct test *, t, tests) {
		struct buf b = buf_alloc_1kb();
		struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

		ok(bare_put_u8(&ctx, t->input) == BARE_ERROR_NONE, "bare_put_u8 failed");
		binok(b.data, t->output, t->len);
		ok(b.cur == t->len, "more data than expected: %" PRIu64 " > %" PRIu64, b.cur, t->len);

		free(b.data);
	}

}

static void
test_bare_get_u8()
{
	struct test {
		uint8_t *input;
		uint8_t output;
		size_t len;
	};

	struct test tests[] = {
		{.input=(uint8_t[]){0x00}, .output=0, 1},
		{.input=(uint8_t[]){0x01}, .output=1, 1},
		{.input=(uint8_t[]){0x7e}, .output=126, 1},
		{.input=(uint8_t[]){0xFF}, .output=255, 1},
	};

	FOR_EACH_ARRAY(struct test *, t, tests) {
		struct buf b = {.data=t->input, .cur=0, .len=t->len};
		struct bare_reader ctx = {.buffer=(void *)&b, .read=&buf_read};

		uint8_t x = 0;
		ok(bare_get_u8(&ctx, &x) == BARE_ERROR_NONE, "bare_get_u8 failed");
		ok(x == t->output, "%" PRIu8 " != %" PRIu8, x, t->output);
	}
}

static void
test_bare_put_u32()
{
	struct test {
		uint32_t input;
		uint8_t *output;
		size_t len;
	};

	struct test tests[] = {
		{.input=0, .output=(uint8_t[]){0x00, 0x00, 0x00, 0x00}, 4},
		{.input=1, .output=(uint8_t[]){0x01, 0x00, 0x00, 0x00}, 4},
		{.input=255, .output=(uint8_t[]){0xFF, 0x00, 0x00, 0x00}, 4},
	};

	FOR_EACH_ARRAY(struct test *, t, tests) {
		struct buf b = buf_alloc_1kb();
		struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

		ok(bare_put_u32(&ctx, t->input) == BARE_ERROR_NONE, "bare_put_u32 failed");
		binok(b.data, t->output, t->len);
		ok(b.cur == t->len, "more data than expected: %" PRIu64 " > %" PRIu64, b.cur, t->len);

		free(b.data);
	}
}

static void
test_bare_get_u32()
{
	struct test {
		uint8_t *input;
		uint32_t output;
		size_t len;
	};

	struct test tests[] = {
		{.input=(uint8_t[]){0x00, 0x00, 0x00, 0x00}, .output=0, 4},
		{.input=(uint8_t[]){0x01, 0x00, 0x00, 0x00}, .output=1, 4},
		{.input=(uint8_t[]){0xFF, 0x00, 0x00, 0x00}, .output=255, 4},
	};

	FOR_EACH_ARRAY(struct test *, t, tests) {
		struct buf b = {.data=t->input, .cur=0, .len=t->len};
		struct bare_reader ctx = {.buffer=(void *)&b, .read=&buf_read};

		uint32_t x = 0;
		ok(bare_get_u32(&ctx, &x) == BARE_ERROR_NONE, "bare_get_u32 failed");
		ok(x == t->output, "%" PRIu32 " != %" PRIu32, x, t->output);
	}
}

static void
test_bare_put_i16()
{
	struct test {
		int16_t input;
		uint8_t *output;
		size_t len;
	};

	struct test tests[] = {
		{.input=0, .output=(uint8_t[]){0x00, 0x00}, 2},
		{.input=1, .output=(uint8_t[]){0x01, 0x00}, 2},
		{.input=-1, .output=(uint8_t[]){0xFF, 0xFF}, 2},
		{.input=255, .output=(uint8_t[]){0xFF, 0x00}, 2},
		{.input=-255, .output=(uint8_t[]){0x01, 0xFF}, 2},
	};

	FOR_EACH_ARRAY(struct test *, t, tests) {
		struct buf b = buf_alloc_1kb();
		struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

		ok(bare_put_i16(&ctx, t->input) == BARE_ERROR_NONE, "bare_put_i16 failed");
		binok(b.data, t->output, t->len);
		ok(b.cur == t->len, "more data than expected: %" PRIu64 " > %" PRIu64, b.cur, t->len);

		free(b.data);
	}
}

static void
test_bare_get_i16()
{
	struct test {
		uint8_t *input;
		int16_t output;
		size_t len;
	};

	struct test tests[] = {
		{.input=(uint8_t[]){0x00, 0x00}, .output=0, 2},
		{.input=(uint8_t[]){0x01, 0x00}, .output=1, 2},
		{.input=(uint8_t[]){0xFF, 0xFF}, .output=-1, 2},
		{.input=(uint8_t[]){0xFF, 0x00}, .output=255, 2},
		{.input=(uint8_t[]){0x01, 0xFF}, .output=-255, 2},
	};

	FOR_EACH_ARRAY(struct test *, t, tests) {
		struct buf b = {.data=t->input, .cur=0, .len=t->len};
		struct bare_reader ctx = {.buffer=(void *)&b, .read=&buf_read};

		int16_t x = 0;
		ok(bare_get_i16(&ctx, &x) == BARE_ERROR_NONE, "bare_get_i16 failed");
		ok(x == t->output, "%" PRIi16 " != %" PRIi16, x, t->output);
	}
}

static void
test_bare_put_f64()
{


	struct test {
		double input;
		uint8_t *output;
		size_t len;
	};

	struct test tests[] = {
		{.input=1.0, .output=(uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f}, 8},
		{.input=2.55, .output=(uint8_t[]){0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x04, 0x40}, 8},
		{.input=-25.5, .output=(uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x39, 0xC0}, 8},
		{.input=0.0, .output=(uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 8},
	};

	FOR_EACH_ARRAY(struct test *, t, tests) {
		struct buf b = buf_alloc_1kb();
		struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

		ok(bare_put_f64(&ctx, t->input) == BARE_ERROR_NONE, "bare_put_f64 failed");
		binok(b.data, t->output, t->len);
		ok(b.cur == t->len, "more data than expected: %" PRIu64 " > %" PRIu64, b.cur, t->len);

		free(b.data);
	}
}

static void
test_bare_get_f64()
{
	struct test {
		uint8_t *input;
		double output;
		size_t len;
	};

	struct test tests[] = {
		{.input=(uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f}, .output=1.0, 8},
		{.input=(uint8_t[]){0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x04, 0x40}, .output=2.55, 8},
		{.input=(uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x39, 0xC0}, .output=-25.5, 8},
		{.input=(uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, .output=0.0, 8},
	};

	FOR_EACH_ARRAY(struct test *, t, tests) {
		struct buf b = {.data=t->input, .cur=0, .len=t->len};
		struct bare_reader ctx = {.buffer=(void *)&b, .read=&buf_read};

		double x = 0;
		ok(bare_get_f64(&ctx, &x) == BARE_ERROR_NONE, "bare_get_f64 failed");
		ok(x == t->output, "%g != %g", x, t->output);
	}
}

static void
test_bare_put_bool()
{
	struct test {
		bool input;
		uint8_t *output;
		size_t len;
	};

	struct test tests[] = {
		{.input=true, .output=(uint8_t[]){0x01}, 1},
		{.input=false, .output=(uint8_t[]){0x00}, 1},
	};

	FOR_EACH_ARRAY(struct test *, t, tests) {
		struct buf b = buf_alloc_1kb();
		struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

		ok(bare_put_bool(&ctx, t->input) == BARE_ERROR_NONE, "bare_put_bool failed");
		binok(b.data, t->output, t->len);
		ok(b.cur == t->len, "more data than expected: %" PRIu64 " > %" PRIu64, b.cur, t->len);

		free(b.data);
	}
}

static void
test_bare_get_bool()
{
	struct test {
		uint8_t *input;
		bool output;
		size_t len;
	};

	struct test tests[] = {
		{.input=(uint8_t[]){0x01}, .output=true, 1},
		{.input=(uint8_t[]){0x00}, .output=false, 1},
	};

	FOR_EACH_ARRAY(struct test *, t, tests) {
		struct buf b = {.data=t->input, .cur=0, .len=t->len};
		struct bare_reader ctx = {.buffer=(void *)&b, .read=&buf_read};

		bool x = 0;
		ok(bare_get_bool(&ctx, &x) == BARE_ERROR_NONE, "bare_get_bool failed");
		ok(x == t->output, "%d != %d", x, t->output);
	}
}

static void
test_bare_put_str()
{
	struct buf b = buf_alloc_1kb();
	struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

	char *in = "BARE";
	uint64_t inlen = strlen(in);

	uint8_t out[] = {0x04, 0x42, 0x41, 0x52, 0x45};
	uint64_t outlen = inlen + 1;

	ok(bare_put_str(&ctx, in, inlen) == BARE_ERROR_NONE, "bare_put_str failed");
	binok(b.data, out, outlen);
	ok(b.cur == outlen, "more data than expected: %" PRIu64 " > %" PRIu64, b.cur, outlen);

	free(b.data);
}

static void
test_bare_get_str()
{
	char got[] = {0, 0, 0, 0};
	char want[] = {0x42, 0x41, 0x52, 0x45};

	struct buf b = {
		.data=(uint8_t[]){0x04, 0x42, 0x41, 0x52, 0x45},
		.cur=0,
		.len=5,
	};
	struct bare_reader ctx = {.buffer=(void *)&b, .read=&buf_read};

	ok(bare_get_str(&ctx, got, ARRLEN(got)) == BARE_ERROR_NONE, "bare_get_str failed");
	ok(memcmp(got, want, ARRLEN(got)) == 0, "%s != %s", got, want);
}

static void
test_bare_put_data()
{
	struct buf b = buf_alloc_1kb();
	struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

	uint8_t in[] = {
		0xAA, 0xEE, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0xEE, 0xDD, 0xCC, 0xBB,
		0xEE, 0xDD, 0xCC, 0xBB,
	};
	uint64_t inlen = 16;

	uint8_t out[] = {
		0x10, 0xAA, 0xEE, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0xEE, 0xDD, 0xCC,
		0xBB, 0xEE, 0xDD, 0xCC, 0xBB,
	};
	uint64_t outlen = 17;

	ok(bare_put_data(&ctx, in, inlen) == BARE_ERROR_NONE, "bare_put_data failed");
	binok(b.data, out, outlen);
	ok(b.cur == outlen, "more data than expected: %" PRIu64 " > %" PRIu64, b.cur, outlen);

	free(b.data);
}

static void
test_bare_get_data()
{
	uint8_t got[16] = {0};
	uint8_t want[16] = {
		0xAA, 0xEE, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0xEE, 0xDD, 0xCC, 0xBB,
		0xEE, 0xDD, 0xCC, 0xBB,
	};

	struct buf b = {
		.data=(uint8_t[]){
			0x10, 0xAA, 0xEE, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0xEE, 0xDD,
			0xCC, 0xBB, 0xEE, 0xDD, 0xCC, 0xBB,
		},
		.cur=0,
		.len=17,
	};
	struct bare_reader ctx = {.buffer=(void *)&b, .read=&buf_read};

	ok(bare_get_data(&ctx, got, ARRLEN(got)) == BARE_ERROR_NONE, "bare_get_data failed");
	ok(memcmp(got, want, ARRLEN(got)) == 0, "%s != %s", got, want);
}

static void
test_bare_put_fixed_data()
{
	struct buf b = buf_alloc_1kb();
	struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

	uint8_t in[] = {
		0xAA, 0xEE, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0xEE, 0xDD, 0xCC, 0xBB,
		0xEE, 0xDD, 0xCC, 0xBB,
	};
	uint64_t inlen = 16;

	uint8_t out[] = {
		0xAA, 0xEE, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0xEE, 0xDD, 0xCC, 0xBB,
		0xEE, 0xDD, 0xCC, 0xBB,
	};
	uint64_t outlen = 16;

	ok(bare_put_fixed_data(&ctx, in, inlen) == BARE_ERROR_NONE, "bare_put_fixed_data failed");
	binok(b.data, out, outlen);
	ok(b.cur == outlen, "more data than expected: %" PRIu64 " > %" PRIu64, b.cur, outlen);

	free(b.data);
}

static void
test_bare_get_fixed_data()
{
	uint8_t got[16] = {0};
	uint8_t want[16] = {
		0xAA, 0xEE, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0xEE, 0xDD, 0xCC, 0xBB,
		0xEE, 0xDD, 0xCC, 0xBB,
	};

	struct buf b = {
		.data=(uint8_t[]){
			0xAA, 0xEE, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0xEE, 0xDD, 0xCC,
			0xBB, 0xEE, 0xDD, 0xCC, 0xBB,
		},
		.cur=0,
		.len=16,
	};
	struct bare_reader ctx = {.buffer=(void *)&b, .read=&buf_read};

	ok(bare_get_fixed_data(&ctx, got, ARRLEN(got)) == BARE_ERROR_NONE, "bare_get_fixed_data failed");
	ok(memcmp(got, want, ARRLEN(got)) == 0, "%s != %s", got, want);
}

// Example Values: enum {FOO BAR = 255 BUZZ}
static void
test_bare_enum()
{
	struct buf b = buf_alloc_1kb();
	struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

	bare_put_uint(&ctx, 0);
	bare_put_uint(&ctx, 255);
	bare_put_uint(&ctx, 256);

	uint8_t want[] = {0x00, 0xFF, 0x01, 0x80, 0x02};
	binok(b.data, want, ARRLEN(want));

	free(b.data);
}

// Example Values: optional<u32> = 255
static void
test_bare_optional_u32()
{
	struct buf b = buf_alloc_1kb();
	struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

	bare_put_bool(&ctx, true);
	bare_put_u32(&ctx, 255);

	uint8_t want[] = {0x01, 0xFF, 0x00, 0x00, 0x00};
	binok(b.data, want, ARRLEN(want));

	free(b.data);
}

// Example Values: list<str> = "foo" "bar" "buzz"
static void
test_bare_list_str()
{
	struct buf b = buf_alloc_1kb();
	struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

	bare_put_uint(&ctx, 3);
	bare_put_str(&ctx, "foo", 3);
	bare_put_str(&ctx, "bar", 3);
	bare_put_str(&ctx, "buzz", 4);

	uint8_t want[] = {0x03, 0x03, 0x66, 0x6f, 0x6f, 0x03, 0x62, 0x61, 0x72, 0x04, 0x62, 0x75, 0x7A, 0x7A};
	binok(b.data, want, ARRLEN(want));

	free(b.data);
}


// Example Values: list<uint>[10] = 0 1 254 255 256 257 126 127 128 129
static void
test_bare_list_uint()
{
	struct buf b = buf_alloc_1kb();
	struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

	bare_put_uint(&ctx, 0);
	bare_put_uint(&ctx, 1);
	bare_put_uint(&ctx, 254);
	bare_put_uint(&ctx, 255);
	bare_put_uint(&ctx, 256);
	bare_put_uint(&ctx, 257);
	bare_put_uint(&ctx, 126);
	bare_put_uint(&ctx, 127);
	bare_put_uint(&ctx, 128);
	bare_put_uint(&ctx, 129);

	uint8_t want[] = {
		0x00, 0x01, 0xFE, 0x01, 0xFF, 0x01, 0x80, 0x02, 0x81, 0x02, 0x7E, 0x7F,
		0x80, 0x01, 0x81, 0x01,
	};
	binok(b.data, want, ARRLEN(want));

	free(b.data);
}

// Example Values: map<u32><str> = 0 => "zero" 1 => "one" 255 => "two hundreds and fifty five"
static void
test_bare_map_u32_str()
{
	struct buf b = buf_alloc_1kb();
	struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

	bare_put_uint(&ctx, 3);
	bare_put_u32(&ctx, 0);
	bare_put_str(&ctx, "zero", 4);
	bare_put_u32(&ctx, 1);
	bare_put_str(&ctx, "one", 3);
	bare_put_u32(&ctx, 255);
	bare_put_str(&ctx, "two hundreds and fifty five", 27);

	uint8_t want[] = {
		0x03, 0x00, 0x00, 0x00, 0x00, 0x04, 0x7A, 0x65, 0x72, 0x6F, 0x01, 0x00,
		0x00, 0x00, 0x03, 0x6F, 0x6E, 0x65, 0xFF, 0x00, 0x00, 0x00, 0x1B, 0x74,
		0x77, 0x6F, 0x20, 0x68, 0x75, 0x6E, 0x64, 0x72, 0x65, 0x64, 0x73, 0x20,
		0x61, 0x6E, 0x64, 0x20, 0x66, 0x69, 0x66, 0x74, 0x79, 0x20, 0x66, 0x69,
		0x76, 0x65,
	};
	binok(b.data, want, ARRLEN(want));

	free(b.data);
}

// Example Values: union {int | uint = 255 | str}
static void
test_bare_union()
{
	// 0, int
	{
		struct buf b = buf_alloc_1kb();
		struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

		bare_put_uint(&ctx, 0);
		bare_put_int(&ctx, 0);

		uint8_t want[] = {0x00, 0x00};
		binok(b.data, want, ARRLEN(want));

		free(b.data);
	}

	// 1, int
	{
		struct buf b = buf_alloc_1kb();
		struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

		bare_put_uint(&ctx, 0);
		bare_put_int(&ctx, 1);

		uint8_t want[] = {0x00, 0x02};
		binok(b.data, want, ARRLEN(want));

		free(b.data);
	}

	// 1, uint
	{
		struct buf b = buf_alloc_1kb();
		struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

		bare_put_uint(&ctx, 255);
		bare_put_uint(&ctx, 1);

		uint8_t want[] = {0xFF, 0x01, 0x01};
		binok(b.data, want, ARRLEN(want));

		free(b.data);
	}

	// -1, int
	{
		struct buf b = buf_alloc_1kb();
		struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

		bare_put_uint(&ctx, 0);
		bare_put_int(&ctx, -1);

		uint8_t want[] = {0x00, 0x01};
		binok(b.data, want, ARRLEN(want));

		free(b.data);
	}

	// 255, int
	{
		struct buf b = buf_alloc_1kb();
		struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

		bare_put_uint(&ctx, 0);
		bare_put_int(&ctx, 255);

		uint8_t want[] = {0x00, 0xFE, 0x03};
		binok(b.data, want, ARRLEN(want));

		free(b.data);
	}

	// 255, uint
	{
		struct buf b = buf_alloc_1kb();
		struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

		bare_put_uint(&ctx, 255);
		bare_put_uint(&ctx, 255);

		uint8_t want[] = {0xFF, 0x01, 0xFF, 0x01};
		binok(b.data, want, ARRLEN(want));

		free(b.data);
	}

	// -255, int
	{
		struct buf b = buf_alloc_1kb();
		struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

		bare_put_uint(&ctx, 0);
		bare_put_int(&ctx, -255);

		uint8_t want[] = {0x00, 0xFD, 0x03};
		binok(b.data, want, ARRLEN(want));

		free(b.data);
	}

	// "BARE", str
	{
		struct buf b = buf_alloc_1kb();
		struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

		bare_put_uint(&ctx, 256);
		bare_put_str(&ctx, "BARE", 4);

		uint8_t want[] = {0x80, 0x02, 0x04, 0x42, 0x41, 0x52, 0x45};
		binok(b.data, want, ARRLEN(want));

		free(b.data);
	}
}

// Example Values: struct {foo: uint bar: int buzz: str} = foo => 255 bar => -255 buzz => "BARE"
static void
test_bare_struct()
{
	struct buf b = buf_alloc_1kb();
	struct bare_writer ctx = {.buffer=(void *)&b, .write=&buf_write};

	bare_put_uint(&ctx, 255);
	bare_put_int(&ctx, -255);
	bare_put_str(&ctx, "BARE", 4);

	uint8_t want[] = {0xFF, 0x01, 0xFD, 0x03, 0x04, 0x42, 0x41, 0x52, 0x45};
	binok(b.data, want, ARRLEN(want));

	free(b.data);
}

int
main(int argc, char const *argv[])
{
	UNUSED(argc);
	UNUSED(argv);

	test_bare_put_uint();
	test_bare_get_uint();
	test_bare_put_int();
	test_bare_get_int();
	test_bare_put_u8();
	test_bare_get_u8();
	test_bare_put_u32();
	test_bare_get_u32();
	test_bare_put_i16();
	test_bare_get_i16();
	test_bare_put_f64();
	test_bare_get_f64();
	test_bare_put_bool();
	test_bare_get_bool();
	test_bare_put_str();
	test_bare_get_str();
	test_bare_put_data();
	test_bare_get_data();
	test_bare_put_fixed_data();
	test_bare_get_fixed_data();
	test_bare_enum();
	test_bare_optional_u32();
	test_bare_list_str();
	test_bare_list_uint();
	test_bare_map_u32_str();
	test_bare_union();
	test_bare_struct();

	return 0;
}

R utf8test.c => test/utf8test.c +114 -114
@@ 21,26 21,26 @@ is_surrogate(uint32_t c)
static void *
utf8_encode(void *buf, uint32_t c)
{
    uint8_t *s = buf;
    if (c >= (1L << 16)) {
        s[0] = 0xf0 |  (c >> 18);
        s[1] = 0x80 | ((c >> 12) & 0x3f);
        s[2] = 0x80 | ((c >>  6) & 0x3f);
        s[3] = 0x80 | ((c >>  0) & 0x3f);
        return s + 4;
    } else if (c >= (1L << 11)) {
        s[0] = 0xe0 |  (c >> 12);
        s[1] = 0x80 | ((c >>  6) & 0x3f);
        s[2] = 0x80 | ((c >>  0) & 0x3f);
        return s + 3;
    } else if (c >= (1L << 7)) {
        s[0] = 0xc0 |  (c >>  6);
        s[1] = 0x80 | ((c >>  0) & 0x3f);
        return s + 2;
    } else {
        s[0] = c;
        return s + 1;
    }
	uint8_t *s = buf;
	if (c >= (1L << 16)) {
		s[0] = 0xf0 |  (c >> 18);
		s[1] = 0x80 | ((c >> 12) & 0x3f);
		s[2] = 0x80 | ((c >>  6) & 0x3f);
		s[3] = 0x80 | ((c >>  0) & 0x3f);
		return s + 4;
	} else if (c >= (1L << 11)) {
		s[0] = 0xe0 |  (c >> 12);
		s[1] = 0x80 | ((c >>  6) & 0x3f);
		s[2] = 0x80 | ((c >>  0) & 0x3f);
		return s + 3;
	} else if (c >= (1L << 7)) {
		s[0] = 0xc0 |  (c >>  6);
		s[1] = 0x80 | ((c >>  0) & 0x3f);
		return s + 2;
	} else {
		s[0] = c;
		return s + 1;
	}
}

void


@@ 61,100 61,100 @@ main(int argc, char const *argv[])
	(void)argc;
	(void)argv;

    /* Make sure it can decode every character */
    {
        long failures = 0;
        for (uint32_t i = 0; i < 0x10ffff; i++) {
            if (!is_surrogate(i)) {
                int e;
                uint32_t c;
                uint8_t buf[8] = {0};
                uint8_t *end = utf8_encode(buf, i);
                uint8_t *res = utf8_decode(buf, &c, &e);
                failures += end != res || c != i || e;
            }
        }
        check(failures == 0, "decode all, errors: %ld", failures);
    }

    /* Reject everything outside of U+0000..U+10FFFF */
    {
        long failures = 0;
        for (uint32_t i = 0x110000; i < 0x1fffff; i++) {
            int e;
            uint32_t c;
            uint8_t buf[8] = {0};
            utf8_encode(buf, i);
            uint8_t *end = utf8_decode(buf, &c, &e);
            failures += !e;
            failures += end - buf != 4;
        }
        check(failures == 0, "out of range, errors: %ld", failures);
    }


    /* Does it reject all surrogate halves? */
    {
        long failures = 0;
        for (uint32_t i = 0xd800; i <= 0xdfff; i++) {
            int e;
            uint32_t c;
            uint8_t buf[8] = {0};
            utf8_encode(buf, i);
            utf8_decode(buf, &c, &e);
            failures += !e;
        }
        check(failures == 0, "surrogate halves, errors: %ld", failures);
    }

    /* How about non-canonical encodings? */
    {
        int e;
        uint32_t c;
        uint8_t *end;

        uint8_t buf2[8] = {0xc0, 0xA4};
        end = utf8_decode(buf2, &c, &e);
        check(e, "non-canonical len 2, 0x%02x", e);
        check(end == buf2 + 2, "non-canonical recover 2, U+%04lx",
             (uint32_t)c);

        uint8_t buf3[8] = {0xe0, 0x80, 0xA4};
        end = utf8_decode(buf3, &c, &e);
        check(e, "non-canonical len 3, 0x%02x", e);
        check(end == buf3 + 3, "non-canonical recover 3, U+%04lx",
             (uint32_t)c);

        uint8_t buf4[8] = {0xf0, 0x80, 0x80, 0xA4};
        end = utf8_decode(buf4, &c, &e);
        check(e, "non-canonical encoding len 4, 0x%02x", e);
        check(end == buf4 + 4, "non-canonical recover 4, U+%04lx",
             (uint32_t)c);
    }

    /* Let's try some bogus byte sequences */
    {
        int len, e;
        uint32_t c;

        /* Invalid first byte */
        uint8_t buf0[4] = {0xff};
        len = (uint8_t *)utf8_decode(buf0, &c, &e) - buf0;
        check(e, "bogus [ff] 0x%02x U+%04lx", e, (uint32_t)c);
        check(len == 1, "bogus [ff] recovery %d", len);

        /* Invalid first byte */
        uint8_t buf1[4] = {0x80};
        len = (uint8_t *)utf8_decode(buf1, &c, &e) - buf1;
        check(e, "bogus [80] 0x%02x U+%04lx", e, (uint32_t)c);
        check(len == 1, "bogus [80] recovery %d", len);

        /* Looks like a two-byte sequence but second byte is wrong */
        uint8_t buf2[4] = {0xc0, 0x0a};
        len = (uint8_t *)utf8_decode(buf2, &c, &e) - buf2;
        check(e, "bogus [c0 0a] 0x%02x U+%04lx", e, (uint32_t)c);
        check(len == 2, "bogus [c0 0a] recovery %d", len);
    }
	/* Make sure it can decode every character */
	{
		long failures = 0;
		for (uint32_t i = 0; i < 0x10ffff; i++) {
			if (!is_surrogate(i)) {
				int e;
				uint32_t c;
				uint8_t buf[8] = {0};
				uint8_t *end = utf8_encode(buf, i);
				uint8_t *res = utf8_decode(buf, &c, &e);
				failures += end != res || c != i || e;
			}
		}
		check(failures == 0, "decode all, errors: %ld", failures);
	}

	/* Reject everything outside of U+0000..U+10FFFF */
	{
		long failures = 0;
		for (uint32_t i = 0x110000; i < 0x1fffff; i++) {
			int e;
			uint32_t c;
			uint8_t buf[8] = {0};
			utf8_encode(buf, i);
			uint8_t *end = utf8_decode(buf, &c, &e);
			failures += !e;
			failures += end - buf != 4;
		}
		check(failures == 0, "out of range, errors: %ld", failures);
	}


	/* Does it reject all surrogate halves? */
	{
		long failures = 0;
		for (uint32_t i = 0xd800; i <= 0xdfff; i++) {
			int e;
			uint32_t c;
			uint8_t buf[8] = {0};
			utf8_encode(buf, i);
			utf8_decode(buf, &c, &e);
			failures += !e;
		}
		check(failures == 0, "surrogate halves, errors: %ld", failures);
	}

	/* How about non-canonical encodings? */
	{
		int e;
		uint32_t c;
		uint8_t *end;

		uint8_t buf2[8] = {0xc0, 0xA4};
		end = utf8_decode(buf2, &c, &e);
		check(e, "non-canonical len 2, 0x%02x", e);
		check(end == buf2 + 2, "non-canonical recover 2, U+%04lx",
			 (uint32_t)c);

		uint8_t buf3[8] = {0xe0, 0x80, 0xA4};
		end = utf8_decode(buf3, &c, &e);
		check(e, "non-canonical len 3, 0x%02x", e);
		check(end == buf3 + 3, "non-canonical recover 3, U+%04lx",
			 (uint32_t)c);

		uint8_t buf4[8] = {0xf0, 0x80, 0x80, 0xA4};
		end = utf8_decode(buf4, &c, &e);
		check(e, "non-canonical encoding len 4, 0x%02x", e);
		check(end == buf4 + 4, "non-canonical recover 4, U+%04lx",
			 (uint32_t)c);
	}

	/* Let's try some bogus byte sequences */
	{
		int len, e;
		uint32_t c;

		/* Invalid first byte */
		uint8_t buf0[4] = {0xff};
		len = (uint8_t *)utf8_decode(buf0, &c, &e) - buf0;
		check(e, "bogus [ff] 0x%02x U+%04lx", e, (uint32_t)c);
		check(len == 1, "bogus [ff] recovery %d", len);

		/* Invalid first byte */
		uint8_t buf1[4] = {0x80};
		len = (uint8_t *)utf8_decode(buf1, &c, &e) - buf1;
		check(e, "bogus [80] 0x%02x U+%04lx", e, (uint32_t)c);
		check(len == 1, "bogus [80] recovery %d", len);

		/* Looks like a two-byte sequence but second byte is wrong */
		uint8_t buf2[4] = {0xc0, 0x0a};
		len = (uint8_t *)utf8_decode(buf2, &c, &e) - buf2;
		check(e, "bogus [c0 0a] 0x%02x U+%04lx", e, (uint32_t)c);
		check(len == 2, "bogus [c0 0a] recovery %d", len);
	}

	return 0;
}

D testdata/bool.txt => testdata/bool.txt +0 -9
@@ 1,9 0,0 @@
name   a
type   bool
value  false

name   b
type   bool
value  true

expect [0x00 0x01]

D testdata/data.txt => testdata/data.txt +0 -5
@@ 1,5 0,0 @@
name   a
type   data
value  [0x13 0x37 0x42]

expect [0x03 0x13 0x37 0x42]

D testdata/f32.txt => testdata/f32.txt +0 -5
@@ 1,5 0,0 @@
name   a
type   f32
value  1337.42

expect [0x71 0x2D 0xA7 0x44]

D testdata/f64.txt => testdata/f64.txt +0 -5
@@ 1,5 0,0 @@
name   a
type   f64
value  133713371337.42424242

expect [0x9B 0x6C 0xC9 0x20 0xF0 0x21 0x3F 0x42]

D testdata/fixed_data.txt => testdata/fixed_data.txt +0 -5
@@ 1,5 0,0 @@
name   a
type   fixed_data
value  [0x13 0x37 0x42]

expect [0x13 0x37 0x42]

D testdata/i16.txt => testdata/i16.txt +0 -5
@@ 1,5 0,0 @@
name   a
type   i16
value  -1234

expect [0x2E 0xFB]

D testdata/i32.txt => testdata/i32.txt +0 -5
@@ 1,5 0,0 @@
name   a
type   i32
value  -12345678

expect [0xB2 0x9E 0x43 0xFF]

D testdata/i64.txt => testdata/i64.txt +0 -5
@@ 1,5 0,0 @@
name   a
type   i64
value  -12345678987654321

expect [0x4F 0x0B 0x6E 0x9D 0xAB 0x23 0xD4 0xFF]

D testdata/i8.txt => testdata/i8.txt +0 -5
@@ 1,5 0,0 @@
name   a
type   i8
value  -42

expect [0xD6]

D testdata/int.txt => testdata/int.txt +0 -9
@@ 1,9 0,0 @@
name   a
type   int
value  42

name   b
type   int
value  -1337

expect [0x54 0xf1 0x14]

D testdata/string.txt => testdata/string.txt +0 -7
@@ 1,7 0,0 @@
name   japanese
type   string
value  "こんにちは、世界!"

expect [0x1B 0xE3 0x81 0x93 0xE3 0x82 0x93 0xE3 0x81 0xAB 0xE3 0x81 0xA1
        0xE3 0x81 0xAF 0xE3 0x80 0x81 0xE4 0xB8 0x96 0xE7 0x95 0x8C 0xEF
        0xBC 0x81]

D testdata/u16.txt => testdata/u16.txt +0 -5
@@ 1,5 0,0 @@
name   a
type   u16
value  0xCAFE

expect [0xFE 0xCA]

D testdata/u32.txt => testdata/u32.txt +0 -5
@@ 1,5 0,0 @@
name   a
type   u32
value  0xDEADBEEF

expect [0xEF 0xBE 0xAD 0xDE]

D testdata/u64.txt => testdata/u64.txt +0 -5
@@ 1,5 0,0 @@
name   a
type   u64
value  0xCAFEBABEDEADBEEF

expect [0xEF 0xBE 0xAD 0xDE 0xBE 0xBA 0xFE 0xCA]

D testdata/u8.txt => testdata/u8.txt +0 -5
@@ 1,5 0,0 @@
name   a
type   u8
value  0x42

expect [0x42]

D testdata/uint.txt => testdata/uint.txt +0 -9
@@ 1,9 0,0 @@
name   a
type   uint
value  0x7F

name   b
type   uint
value  0x1337

expect [0x7F 0xB7 0x26]

D todo.md => todo.md +0 -11
@@ 1,11 0,0 @@
# TODO

- [ ] Add example with static buffers.
- [ ] Add example with membuf.
- [ ] Look into using [theft][1].
- [ ] Add functions for making messages, unions, etc.
- [ ] Replace Python script for generating tests with a C program or
      Lua + lpeg.
- [ ] Build with cproc.

[1]: https://github.com/silentbicycle/theft