ConfIO

commit 56a48a5ce367b62ced4c82eabb69923138e17e29

Author: PQCraft <0456523@gmail.com>

Init

 .gitignore | 19 +++++
 LICENSE | 18 ++++
 Makefile | 149 +++++++++++++++++++++++++++++++++++++++++
 include/confio/confio.h | 57 +++++++++++++++
 include/confio/handlers.h | 31 ++++++++
 include/confio/version.h | 8 ++
 src/confio.c | 10 ++
 src/crc.c | 70 +++++++++++++++++++
 src/crc.h | 12 +++
 src/handlers.c | 1 
 src/vlb.h | 81 ++++++++++++++++++++++
 tests/.gitignore | 10 ++
 tests/Makefile | 29 +++++++
 tests/common.mk | 104 ++++++++++++++++++++++++++++
 tests/example/main.c | 22 ++++++
 tests/harness.h | 67 ++++++++++++++++++


diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..af0ab48d8b604598ef94df8ae2007450e78eb8c6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,19 @@
+*
+!/docs/
+!/docs/**
+!/examples/
+!/examples/**
+!/include/
+!/include/**
+!/src/
+!/src/**
+!/tests/
+!/tests/**
+!/*.md
+!/*.sh
+!/LICENSE
+!/Makefile
+!/README.md
+.**
+!/.gitattributes
+!/.gitignore




diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..120397215bdd48ed96c0a214856d5bd02726348c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,18 @@
+Copyright (c) non-bloat collective contributors
+
+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.




diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..3669c25fbb6f9bd3184275bcddc91fa492278c3a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,149 @@
+export CFLAGS CPPFLAGS LDFLAGS LDLIBS DEBUG ASAN O
+
+SRCDIR ?= src
+INCDIR ?= include
+OBJDIR ?= obj
+OUTDIR ?= lib
+
+ifneq ($(DEBUG),y)
+    OBJDIR := $(OBJDIR)/release
+else
+    OBJDIR := $(OBJDIR)/debug
+endif
+ifeq ($(ASAN),y)
+    OBJDIR := $(OBJDIR)_asan
+endif
+OBJDIR_STATIC := $(OBJDIR)/static
+OBJDIR_DYNAMIC := $(OBJDIR)/dynamic
+
+SOURCES := $(wildcard $(SRCDIR)/*.c)
+OBJECTS_STATIC := $(patsubst $(SRCDIR)/%.c,$(OBJDIR_STATIC)/%.o,$(SOURCES))
+OBJECTS_DYNAMIC := $(patsubst $(SRCDIR)/%.c,$(OBJDIR_DYNAMIC)/%.o,$(SOURCES))
+
+VER_MAJOR := $(shell grep -m 1 '#define CONFIO_VER_MAJOR ' $(INCDIR)/confio/version.h | sed 's/#define .* //')
+VER_MINOR := $(shell grep -m 1 '#define CONFIO_VER_MINOR ' $(INCDIR)/confio/version.h | sed 's/#define .* //')
+VER_PATCH := $(shell grep -m 1 '#define CONFIO_VER_PATCH ' $(INCDIR)/confio/version.h | sed 's/#define .* //')
+VER := $(VER_MAJOR).$(VER_MINOR).$(VER_PATCH)
+
+BIN ?= confio
+BIN_STATIC := lib$(BIN).a
+BIN_DYNAMIC := lib$(BIN).so
+
+TARGET_STATIC := $(OUTDIR)/$(BIN_STATIC)
+TARGET_DYNAMIC := $(OUTDIR)/$(BIN_DYNAMIC).$(VER)
+
+SYSROOT ?= /usr
+SYSINC ?= $(SYSROOT)/include
+SYSLIB ?= $(SYSROOT)/lib
+
+CC ?= gcc
+LD := $(CC)
+AR ?= ar
+_CC := $(TOOLCHAIN)$(CC)
+_LD := $(TOOLCHAIN)$(LD)
+_AR := $(TOOLCHAIN)$(AR)
+
+_CFLAGS := $(CFLAGS) -std=c99 -Wall -Wextra -Wuninitialized -Wundef
+_CPPFLAGS := $(CPPFLAGS) -I$(INCDIR)
+_LDFLAGS := $(LDFLAGS)
+_LDLIBS := $(LDLIBS)
+
+ifeq ($(DEBUG),y)
+    _CFLAGS += -g -fsanitize=address -fno-omit-frame-pointer -Wdouble-promotion
+    #_CFLAGS += -Wconversion
+    _LDFLAGS += -fsanitize=address
+    O ?= g
+else
+    _CPPFLAGS += -DNDEBUG
+    O ?= 2
+endif
+_CFLAGS += -O$(O)
+ifeq ($(ASAN),y)
+    _CFLAGS += -fsanitize=address
+    _LDFLAGS += -fsanitize=address
+endif
+
+.SECONDEXPANSION:
+
+define mkdir
+if [ ! -d '$(1)' ]; then echo 'Creating $(1)/...'; mkdir -p '$(1)'; fi; true
+endef
+define rm
+if [ -f '$(1)' ]; then echo 'Removing $(1)...'; rm -f '$(1)'; fi; true
+endef
+define rmdir
+if [ -d '$(1)' ]; then echo 'Removing $(1)/...'; rm -rf '$(1)'; fi; true
+endef
+
+deps.filter := %.c %.h
+deps.option := -MM
+define deps
+$$(filter $$(deps.filter),,$$(shell $(_CC) $(_CFLAGS) $(_CPPFLAGS) -E $(deps.option) $(1)))
+endef
+
+build: $(TARGET_STATIC) $(TARGET_DYNAMIC)
+	@:
+
+static: $(TARGET_STATIC)
+	@:
+
+dynamic: $(TARGET_DYNAMIC)
+	@:
+
+$(OUTDIR):
+	@$(call mkdir,$@)
+
+$(OBJDIR_STATIC):
+	@$(call mkdir,$@)
+
+$(OBJDIR_DYNAMIC):
+	@$(call mkdir,$@)
+
+$(OBJDIR_STATIC)/%.o: $(SRCDIR)/%.c $(call deps,$(SRCDIR)/%.c) | $(OBJDIR_STATIC) $(OUTDIR)
+	@echo Compiling $<...
+	@$(_CC) $(_CFLAGS) $(_CPPFLAGS) $< -c -o $@
+	@echo Compiled $<
+
+$(OBJDIR_DYNAMIC)/%.o: $(SRCDIR)/%.c $(call deps,$(SRCDIR)/%.c) | $(OBJDIR_DYNAMIC) $(OUTDIR)
+	@echo Compiling $<... '(dynamic)'
+	@$(_CC) $(_CFLAGS) -fPIC $(_CPPFLAGS) -DCONFIO_DYNAMIC $< -c -o $@
+	@echo Compiled $< '(dynamic)'
+
+$(TARGET_STATIC): $(OBJECTS_STATIC) | $(OUTDIR)
+	@echo Creating $@...
+	@$(_AR) rcs $@ $^
+	@echo Created $@
+
+$(TARGET_DYNAMIC): $(OBJECTS_DYNAMIC) | $(OUTDIR)
+	@echo Linking $@...
+	@$(_LD) $(_LDFLAGS) -shared $^ $(_LDLIBS) -o $@
+	-@cd $(OUTDIR) && ln -s $(BIN_DYNAMIC).$(VER) $(BIN_DYNAMIC).$(VER_MAJOR)
+	-@cd $(OUTDIR) && ln -s $(BIN_DYNAMIC).$(VER_MAJOR) $(BIN_DYNAMIC)
+	@echo Linked $@
+
+clean:
+	@$(call rmdir,$(OBJDIR))
+
+distclean: clean
+	@$(call rm,$(TARGET_STATIC))
+	@$(call rm,$(OUTDIR)/$(BIN_DYNAMIC))
+	@$(call rm,$(OUTDIR)/$(BIN_DYNAMIC).$(VER_MAJOR))
+	@$(call rm,$(TARGET_DYNAMIC))
+
+tests:
+	@$(MAKE) -C tests
+
+clean-tests:
+	@$(MAKE) -C tests clean
+
+distclean-tests:
+	@$(MAKE) -C tests distclean
+
+install:
+	@echo Installing headers...
+	@cp -r $(INCDIR)/* $(SYSINC)/
+	@echo Installing libraries...
+	@cp -r $(OUTDIR)/* $(SYSLIB)/
+	@echo Done
+
+.PHONY: build static dynamic clean distclean tests clean-tests distclean-tests install




diff --git a/include/confio/confio.h b/include/confio/confio.h
new file mode 100644
index 0000000000000000000000000000000000000000..861489a09a161d93fb32c29cf4d27ead710a2ec4
--- /dev/null
+++ b/include/confio/confio.h
@@ -0,0 +1,57 @@
+#ifndef CONFIO_H
+#define CONFIO_H
+
+#include <stdio.h> /* TODO: 'NO_STDIO' option */
+#include <stdbool.h>
+
+#include "version.h"
+
+// Get the version (useless for static linking, slightly useful for dynamic linking)
+// - returns a pointer to {major, minor, patch}
+const unsigned* ConfIO_GetVersion(void);
+
+// Called when a var is being read in
+// - return false to indicate an error
+typedef bool ConfIO_ReadVarFunc(void* ctx, const char* val, size_t len);
+typedef ConfIO_ReadVarFunc* ConfIO_ReadVar;
+// Called when a var is being written out
+// - *len is initialized to -1 which indicates that strlen() should be called on the return value if it is non-NULL
+// - return NULL to indicate an error
+typedef const char* ConfIO_WriteVarFunc(void* ctx, size_t* len);
+typedef ConfIO_WriteVarFunc* ConfIO_WriteVar;
+
+typedef struct ConfIO ConfIO;
+
+ConfIO* ConfIO_New(void);
+void ConfIO_Del(ConfIO*);
+
+// Set the handlers for a variable
+// - if 'sectLen' is -1, use strlen() to get the length of 'sect'
+// - if 'varLen' is -1, use strlen() to get the length of 'var'
+bool ConfIO_SetHandlers(
+	ConfIO*, char* sect, size_t sectLen, char* var, size_t varLen,
+	ConfIO_ReadVar, void* readVarCtx, ConfIO_WriteVar, void* writeVarCtx
+);
+
+// Read in a file
+bool ConfIO_ReadFile(ConfIO*, FILE*);
+// Write out a file
+bool ConfIO_WriteFile(ConfIO*, FILE*);
+// TODO: ReadStream and WriteStream
+
+// Set a variable by calling its ReadVar handler
+// - if 'sectLen' is -1, use strlen() to get the length of 'sect'
+// - if 'varLen' is -1, use strlen() to get the length of 'var'
+// - returns false if no ReadVar handler is set, or the handler indicates an error
+bool ConfIO_SetVar(ConfIO*, char* sect, size_t sectLen, char* var, size_t varLen, const char* val, size_t valLen);
+// Get a variable by calling its WriteVar handler
+// - 'valLen' can be NULL if you do not want strlen() to be called if the handler outputs a 'len' of -1
+// - returns NULL if no WriteVar handler is set, or the handler indicates an error
+const char* ConfIO_GetVar(ConfIO*, char* sect, size_t sectLen, char* var, size_t varLen, size_t* valLen);
+
+#ifndef CONFIO_NO_DEFAULT_HANDLERS
+	// Includes the default ConfIO_{Read|Write}Var_... handlers
+	#include "handlers.h"
+#endif
+
+#endif




diff --git a/include/confio/handlers.h b/include/confio/handlers.h
new file mode 100644
index 0000000000000000000000000000000000000000..7a9e035a3852251ea94d172d3b612448f945c0ed
--- /dev/null
+++ b/include/confio/handlers.h
@@ -0,0 +1,31 @@
+#ifndef CONFIO_HANDLERS_H
+#define CONFIO_HANDLERS_H
+
+#include "confio.h"
+
+#include <stdint.h>
+
+// Convenience macro for ConfIO_SetHandlers
+#define CONFIO_HANDLERS(TYPE, VAR) ConfIO_ReadVar_##TYPE, &VAR, ConfIO_WriteVar_##TYPE, &VAR
+
+// char, short, int, long, long long
+ConfIO_ReadVarFunc ConfIO_ReadVar_C, ConfIO_ReadVar_SI, ConfIO_ReadVar_I, ConfIO_ReadVar_LI, ConfIO_ReadVar_LLI;
+ConfIO_WriteVarFunc ConfIO_WriteVar_C, ConfIO_WriteVar_SI, ConfIO_WriteVar_I, ConfIO_WriteVar_LI, ConfIO_WriteVar_LLI;
+
+// unsigned char, short unsigned, unsigned, long unsigned, long long unsigned
+ConfIO_ReadVarFunc ConfIO_ReadVar_UC, ConfIO_ReadVar_SU, ConfIO_ReadVar_U, ConfIO_ReadVar_LU, ConfIO_ReadVar_LLU;
+ConfIO_WriteVarFunc ConfIO_WriteVar_UC, ConfIO_WriteVar_SU, ConfIO_WriteVar_U, ConfIO_WriteVar_LU, ConfIO_WriteVar_LLU;
+
+// float, double, long double
+ConfIO_ReadVarFunc ConfIO_ReadVar_F, ConfIO_ReadVar_D, ConfIO_ReadVar_LD;
+ConfIO_WriteVarFunc ConfIO_WriteVar_F, ConfIO_WriteVar_D, ConfIO_WriteVar_LD;
+
+// int8_t, int16_t, int32_t, int64_t
+ConfIO_ReadVarFunc ConfIO_ReadVar_I8, ConfIO_ReadVar_I16, ConfIO_ReadVar_I32, ConfIO_ReadVar_I64;
+ConfIO_WriteVarFunc ConfIO_WriteVar_I8, ConfIO_WriteVar_I16, ConfIO_WriteVar_I32, ConfIO_WriteVar_I64;
+
+// uint8_t, uint16_t, uint32_t, uint64_t
+ConfIO_ReadVarFunc ConfIO_ReadVar_U8, ConfIO_ReadVar_U16, ConfIO_ReadVar_U32, ConfIO_ReadVar_U64;
+ConfIO_WriteVarFunc ConfIO_WriteVar_U8, ConfIO_WriteVar_U16, ConfIO_WriteVar_U32, ConfIO_WriteVar_U64;
+
+#endif




diff --git a/include/confio/version.h b/include/confio/version.h
new file mode 100644
index 0000000000000000000000000000000000000000..2542ebe45884fc29a8508e5fffc623863921cace
--- /dev/null
+++ b/include/confio/version.h
@@ -0,0 +1,8 @@
+#ifndef CONFIO_VERSION_H
+#define CONFIO_VERSION_H
+
+#define CONFIO_VER_MAJOR 1
+#define CONFIO_VER_MINOR 0
+#define CONFIO_VER_PATCH 0
+
+#endif




diff --git a/src/confio.c b/src/confio.c
new file mode 100644
index 0000000000000000000000000000000000000000..1f143d3d1a86907d0694a7e2350c34836787b66a
--- /dev/null
+++ b/src/confio.c
@@ -0,0 +1,10 @@
+#include <confio/confio.h>
+
+const unsigned* ConfIO_GetVersion(void) {
+	static const unsigned ver[3] = {
+		CONFIO_VER_MAJOR,
+		CONFIO_VER_MINOR,
+		CONFIO_VER_PATCH
+	};
+	return ver;
+}




diff --git a/src/crc.c b/src/crc.c
new file mode 100644
index 0000000000000000000000000000000000000000..2b8c6b08ea91396d8f24ab521d194c9b5687a099
--- /dev/null
+++ b/src/crc.c
@@ -0,0 +1,70 @@
+#include "crc.h"
+
+#include <ctype.h>
+
+static const uint32_t crc32Table[] = {
+	0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
+	0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
+	0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
+	0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
+	0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
+	0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
+	0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
+	0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
+	0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
+	0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
+	0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
+	0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
+	0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
+	0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
+	0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
+	0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
+	0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
+	0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
+	0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
+	0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
+	0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
+	0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
+	0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
+	0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
+	0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
+	0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
+	0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
+	0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
+	0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
+	0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
+	0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
+	0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
+};
+
+uint32_t ConfIO_Crc32(const char* d, size_t l) {
+	uint32_t crc = 0;
+	for (size_t i = 0; i < l; ++i) {
+		crc = crc32Table[(crc ^ ((unsigned char*)d)[i]) & 0xFF] ^ (crc >> 8);
+	}
+	return crc;
+}
+
+uint32_t ConfIO_CaseCrc32(const char* d, size_t l) {
+	uint32_t crc = 0;
+	for (size_t i = 0; i < l; ++i) {
+		char c = tolower(d[i]);
+		crc = crc32Table[(crc ^ c) & 0xFF] ^ (crc >> 8);
+	}
+	return crc;
+}
+
+uint32_t ConfIO_ContCrc32(uint32_t crc, const char* d, size_t l) {
+	for (size_t i = 0; i < l; ++i) {
+		crc = crc32Table[(crc ^ ((unsigned char*)d)[i]) & 0xFF] ^ (crc >> 8);
+	}
+	return crc;
+}
+
+uint32_t ConfIO_ContCaseCrc32(uint32_t crc, const char* d, size_t l) {
+	for (size_t i = 0; i < l; ++i) {
+		char c = tolower(d[i]);
+		crc = crc32Table[(crc ^ c) & 0xFF] ^ (crc >> 8);
+	}
+	return crc;
+}




diff --git a/src/crc.h b/src/crc.h
new file mode 100644
index 0000000000000000000000000000000000000000..d3b2735a2a2b05c8046077765cc82e70949ee3a9
--- /dev/null
+++ b/src/crc.h
@@ -0,0 +1,12 @@
+#ifndef CONFIO_CRC_H
+#define CONFIO_CRC_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+uint32_t ConfIO_Crc32(const char*, size_t);
+uint32_t ConfIO_CaseCrc32(const char*, size_t);
+uint32_t ConfIO_ContCrc32(uint32_t, const char*, size_t);
+uint32_t ConfIO_ContCaseCrc32(uint32_t, const char*, size_t);
+
+#endif




diff --git a/src/handlers.c b/src/handlers.c
new file mode 100644
index 0000000000000000000000000000000000000000..daa98bb591ce6b1d80c874c142f9df2137ab2984
--- /dev/null
+++ b/src/handlers.c
@@ -0,0 +1 @@
+#include <confio/handlers.h>




diff --git a/src/vlb.h b/src/vlb.h
new file mode 100644
index 0000000000000000000000000000000000000000..4e3bfead7a058b0be3a585510bd17d5df7cc02cc
--- /dev/null
+++ b/src/vlb.h
@@ -0,0 +1,81 @@
+#ifndef CONFIO_VLB_H
+#define CONFIO_VLB_H
+
+#include <stddef.h>
+#include <stdlib.h>
+
+#define VLB(T) {\
+    T* data;\
+    size_t len;\
+    size_t size;\
+}
+
+#define VLB_OOM_NOP
+
+#define VLB__EXP(VLB__b, VLB__en, VLB__ed, VLB__do, ...) do {\
+    if ((VLB__b).len != (VLB__b).size) {\
+        VLB__do;\
+    } else {\
+        register size_t VLB__tmp = (VLB__b).size;\
+        VLB__tmp = VLB__tmp * (VLB__en) / (VLB__ed);\
+        if (VLB__tmp <= (VLB__b).size) VLB__tmp = (VLB__b).size + 1;\
+        void* VLB__ptr = realloc((VLB__b).data, VLB__tmp * sizeof(*(VLB__b).data));\
+        if (VLB__ptr) {(VLB__b).data = VLB__ptr; VLB__do; (VLB__b).size = VLB__tmp;}\
+        else {__VA_ARGS__}\
+    }\
+} while (0)
+#define VLB__EXP_MULTI(VLB__b, VLB__l, VLB__en, VLB__ed, ...) do {\
+    if ((VLB__l) > (VLB__b).size) {\
+        register size_t VLB__tmp = (VLB__b).size;\
+        do {\
+            register size_t VLB__old = VLB__tmp;\
+            VLB__tmp = VLB__tmp * (VLB__en) / (VLB__ed);\
+            if (VLB__tmp <= VLB__old) VLB__tmp = VLB__old + 1;\
+        } while (VLB__tmp < (VLB__l));\
+        void* VLB__ptr = realloc((VLB__b).data, VLB__tmp * sizeof(*(VLB__b).data));\
+        if (VLB__ptr) {(VLB__b).data = VLB__ptr; (VLB__b).len = (VLB__l); (VLB__b).size = VLB__tmp;}\
+        else {__VA_ARGS__}\
+    } else if ((VLB__l) > (VLB__b).len) {\
+        (VLB__b).len = (VLB__l);\
+    }\
+} while (0)
+
+#define VLB_INIT(VLB__b, VLB__sz, ...) do {\
+    (VLB__b).data = malloc((VLB__sz) * sizeof(*(VLB__b).data));\
+    if ((VLB__b).data || !(VLB__sz)) {\
+        (VLB__b).len = 0;\
+        (VLB__b).size = (VLB__sz);\
+    } else {\
+        __VA_ARGS__\
+    }\
+} while (0)
+#define VLB_ZINIT(VLB__b) do {\
+    (VLB__b).data = NULL;\
+    (VLB__b).len = 0;\
+    (VLB__b).size = 0;\
+} while (0)
+#define VLB_FREE(b) free((b).data)
+
+#define VLB_ADD(VLB__b, VLB__d, VLB__en, VLB__ed, ...) do {\
+    VLB__EXP((VLB__b), (VLB__en), (VLB__ed), (VLB__b).data[(VLB__b).len++] = (VLB__d), __VA_ARGS__);\
+} while (0)
+#define VLB_NEXTPTR(VLB__b, VLB__o, VLB__en, VLB__ed, ...) do {\
+    VLB__EXP((VLB__b), (VLB__en), (VLB__ed), (VLB__o) = &(VLB__b).data[(VLB__b).len++], __VA_ARGS__);\
+} while (0)
+
+#define VLB_EXPANDBY(VLB__b, VLB__a, VLB__en, VLB__ed, ...) do {\
+    register size_t VLB__l = (VLB__b).len + (VLB__a);\
+    VLB__EXP_MULTI((VLB__b), (VLB__l), (VLB__en), (VLB__ed), __VA_ARGS__);\
+} while (0)
+#define VLB_EXPANDTO(VLB__b, VLB__l, VLB__en, VLB__ed, ...) do {\
+    VLB__EXP_MULTI((VLB__b), (VLB__l), (VLB__en), (VLB__ed), __VA_ARGS__);\
+} while (0)
+#define VLB_SHRINK(VLB__b, ...) do {\
+    if ((VLB__b).len != (VLB__b).size) {\
+        void* VLB__ptr = realloc((VLB__b).data, (VLB__b).len * sizeof(*(VLB__b).data));\
+        if (VLB__ptr || !(VLB__b).len) {(VLB__b).data = VLB__ptr; (VLB__b).size = (VLB__b).len;}\
+        else {__VA_ARGS__}\
+    }\
+} while (0)
+
+#endif




diff --git a/tests/.gitignore b/tests/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..77cbd2032a3b8ee61b70c6498a767e4d34da84db
--- /dev/null
+++ b/tests/.gitignore
@@ -0,0 +1,10 @@
+*
+!/*/
+!/*/*.c
+!/*/*.h
+!/*.h
+!/common.mk
+!/Makefile
+!/README.md
+.**
+!/.gitignore




diff --git a/tests/Makefile b/tests/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..e29b34a7fb05c22af6613a423edd0a9363885d6c
--- /dev/null
+++ b/tests/Makefile
@@ -0,0 +1,29 @@
+export
+
+DIRS := $(patsubst %/,%,$(dir $(wildcard */)))
+
+RUN := $(DIRS:%=run\ %)
+BUILD := $(DIRS:%=build\ %)
+CLEAN := $(DIRS:%=clean\ %)
+DISTCLEAN := $(DIRS:%=distclean\ %)
+
+all run: $(RUN)
+$(RUN):
+	@$(MAKE) -C $(wordlist 2,2,$@) -f ../common.mk run
+
+build: $(BUILD)
+$(BUILD):
+	@$(MAKE) -C $(wordlist 2,2,$@) -f ../common.mk build
+
+clean: $(CLEAN)
+$(CLEAN):
+	@$(MAKE) -C $(wordlist 2,2,$@) -f ../common.mk clean
+
+distclean: $(DISTCLEAN)
+$(DISTCLEAN):
+	@$(MAKE) -C $(wordlist 2,2,$@) -f ../common.mk distclean
+
+ls:
+	@printf '%s\n' $(DIRS)
+
+.PHONY: all run $(RUN) build $(BUILD) clean $(CLEAN) distclean $(DISTCLEAN) ls




diff --git a/tests/common.mk b/tests/common.mk
new file mode 100644
index 0000000000000000000000000000000000000000..ba3accaea97ecd803d30455e0aa262b230d6e586
--- /dev/null
+++ b/tests/common.mk
@@ -0,0 +1,104 @@
+export CFLAGS CPPFLAGS LDFLAGS LDLIBS DEBUG ASAN O
+
+SRCDIR ?= .
+OBJDIR ?= obj
+OUTDIR ?= .
+
+CONFIO_DIR ?= ../../
+CONFIO_SRCDIR ?= $(CONFIO_DIR)/src
+CONFIO_INCDIR ?= $(CONFIO_DIR)/include
+CONFIO_OUTDIR ?= $(CONFIO_DIR)/lib
+
+CONFIO_SOURCES := $(wildcard $(CONFIO_SRCDIR)/*.c)
+
+ifneq ($(DEBUG),y)
+    OBJDIR := $(OBJDIR)/release
+else
+    OBJDIR := $(OBJDIR)/debug
+endif
+ifeq ($(ASAN),y)
+    OBJDIR := $(OBJDIR)_asan
+endif
+
+SOURCES := $(wildcard $(SRCDIR)/*.c)
+OBJECTS := $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SOURCES))
+
+BIN := $(notdir $(CURDIR))
+TARGET := $(OUTDIR)/$(BIN)
+
+CC ?= gcc
+LD := $(CC)
+_CC := $(TOOLCHAIN)$(CC)
+_LD := $(TOOLCHAIN)$(LD)
+
+_CFLAGS := $(CFLAGS) -std=c99 -Wall -Wextra -Wuninitialized -Wundef
+_CPPFLAGS := $(CPPFLAGS) -I$(CONFIO_INCDIR) -I$(CONFIO_SRCDIR) -DTESTNAME='"$(BIN)"'
+_LDFLAGS := $(LDFLAGS) -L$(CONFIO_OUTDIR)
+_LDLIBS := $(LDLIBS) -lconfio
+
+ifeq ($(DEBUG),y)
+    _CFLAGS += -g -fsanitize=address -fno-omit-frame-pointer -Wdouble-promotion
+    #_CFLAGS += -Wconversion
+    _LDFLAGS += -fsanitize=address
+    O ?= g
+else
+    _CPPFLAGS += -DNDEBUG
+    O ?= 2
+endif
+_CFLAGS += -O$(O)
+ifeq ($(ASAN),y)
+    _CFLAGS += -fsanitize=address
+    _LDFLAGS += -fsanitize=address
+endif
+
+.SECONDEXPANSION:
+
+define mkdir
+if [ ! -d '$(1)' ]; then echo 'Creating $(1)/...'; mkdir -p '$(1)'; fi; true
+endef
+define rm
+if [ -f '$(1)' ]; then echo 'Removing $(1)...'; rm -f '$(1)'; fi; true
+endef
+define rmdir
+if [ -d '$(1)' ]; then echo 'Removing $(1)/...'; rm -rf '$(1)'; fi; true
+endef
+
+deps.filter := %.c %.h
+deps.option := -MM
+define deps
+$$(filter $$(deps.filter),,$$(shell $(_CC) $(_CFLAGS) $(_CPPFLAGS) -E $(deps.option) $(1)))
+endef
+
+run: build
+	@echo Running $(BIN)...
+	@'$(dir $(BIN))$(notdir $(BIN))' $(RUNFLAGS)
+
+build: $(TARGET)
+	@:
+
+$(OUTDIR):
+	@$(call mkdir,$@)
+
+$(OBJDIR):
+	@$(call mkdir,$@)
+
+$(OBJDIR)/%.o: $(SRCDIR)/%.c $(call deps,$(SRCDIR)/%.c) | $(OBJDIR) $(OUTDIR)
+	@echo Compiling $(notdir $(CURDIR))/$<...
+	@$(_CC) $(_CFLAGS) $(_CPPFLAGS) $< -c -o $@
+	@echo Compiled $(notdir $(CURDIR))/$<
+
+$(TARGET): $(OBJECTS) $(CONFIO_OUTDIR)/libconfio.a | $(OUTDIR)
+	@echo Linking $@...
+	@$(_LD) $(_LDFLAGS) $(OBJECTS) $(_LDLIBS) -o $@
+	@echo Linked $@
+
+$(CONFIO_OUTDIR)/libconfio.a: $(CONFIO_SOURCES)
+	@$(MAKE) -C $(CONFIO_DIR) static
+
+clean:
+	@$(call rmdir,$(OBJDIR))
+
+distclean: clean
+	@$(call rm,$(TARGET))
+
+.PHONY: build run clean distclean




diff --git a/tests/example/main.c b/tests/example/main.c
new file mode 100644
index 0000000000000000000000000000000000000000..be1ba9a15b5f7385b608c00e9298db6f7876292e
--- /dev/null
+++ b/tests/example/main.c
@@ -0,0 +1,22 @@
+#define H_STATUSPREFIX "    " TESTNAME ": "
+#include "../harness.h"
+
+#include <confio/confio.h>
+
+#include <stdio.h>
+
+H_TEST(version, "Example (check if version matches)",
+	const unsigned* ver = ConfIO_GetVersion();
+	if (!ver) H_FAIL();
+
+	if (ver[0] != CONFIO_VER_MAJOR) H_FAILEX("Major %u != %u", ver[0], CONFIO_VER_MAJOR);
+	if (ver[1] != CONFIO_VER_MINOR) H_FAILEX("Minor %u != %u", ver[1], CONFIO_VER_MINOR);
+	if (ver[2] != CONFIO_VER_PATCH) H_FAILEX("Patch %u != %u", ver[2], CONFIO_VER_PATCH);
+
+	// H_PASS() is implied
+)
+
+int main() {
+	if (!H_DOTEST(version)) return 1;
+	return 0;
+}




diff --git a/tests/harness.h b/tests/harness.h
new file mode 100644
index 0000000000000000000000000000000000000000..05b980c00ceba6a62ffc0643a1a25fd86a727b9d
--- /dev/null
+++ b/tests/harness.h
@@ -0,0 +1,67 @@
+#ifndef NB_HARNESS_H
+#define NB_HARNESS_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#define H__STR_EVAL(VAL) #VAL
+#define H__STR(VAL) H__STR_EVAL(VAL)
+
+#ifndef H_STATUSPREFIX
+	#define H_STATUSPREFIX ""
+#endif
+
+#define H_TEST(H__NAME, H__FANCYNAME, ...) static bool H__TEST_##H__NAME(void* H_USERDATA) { \
+	/* silence "unused parameter" warning */ \
+	(void)H_USERDATA; \
+	\
+	/* internal defs */ \
+	unsigned H__FAILLINE; \
+	\
+	/* user-facing defs */ \
+	static const char* H_NAME = (H__FANCYNAME) ? H__FANCYNAME : H__STR(H__NAME); \
+	\
+	printf("%sRunning test \"%s\"...", (H_STATUSPREFIX), H_NAME); \
+	fflush(stdout); \
+	static const unsigned H__FIRSTLINE = __LINE__; \
+	{__VA_ARGS__} \
+	\
+	H__PASS: \
+	puts(" PASS"); \
+	return true; \
+	\
+	H__FAIL: \
+	printf(" FAIL on line %u (%s:%u)\n", H__FAILLINE - H__FIRSTLINE, __FILE__, H__FAILLINE); \
+	return false; \
+	\
+	/* silence "unused label" warnings */ \
+	goto H__PASS;\
+	goto H__FAIL;\
+}
+
+#define H_PASS() do { \
+	goto H_PASS; \
+} while (0)
+#define H_FAIL() do { \
+	H__FAILLINE = __LINE__; \
+	goto H__FAIL; \
+} while (0)
+#define H_PASSEX(...) do { \
+	fputs(" PASS: ", stdout); \
+	printf(__VA_ARGS__); \
+	putchar('\n'); \
+	return true; \
+} while (0)
+#define H_FAILEX(...) do { \
+	H__FAILLINE = __LINE__; \
+	printf(" FAIL on line %u (%s:%u): ", H__FAILLINE - H__FIRSTLINE, __FILE__, H__FAILLINE); \
+	printf(__VA_ARGS__); \
+	putchar('\n'); \
+	return false; \
+} while (0)
+
+#define H_DOTEST(NAME) H__TEST_##NAME(NULL)
+#define H_DOTESTEX(NAME, USERDATA) H__TEST_##NAME((USERDATA))
+
+#endif