Merge branch 'spudlib-merge' of https://github.com/hildjj/cn-cbor into hildjj-spudlib-merge

# Conflicts:
#	README.md
#	cn-manip.c
#	include/cn-cbor/cn-cbor.h
#	src/cn-get.c
#	src/cn-manip.c
diff --git a/.gitignore b/.gitignore
index 8ebe259..2eef09e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 cntest
 new.out
 *.o
+build
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..46022af
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,129 @@
+#
+#
+# top level build file for cn-cbor
+
+## prepare CMAKE
+cmake_minimum_required ( VERSION 3.0.0 )
+
+set ( VERSION_MAJOR 0   CACHE STRING "Project major version number")
+set ( VERSION_MINOR "1" CACHE STRING "Project minor version number" )
+set ( VERSION_PATCH "0" CACHE STRING "Project patch version number" )
+set ( CN_VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}" )
+mark_as_advanced(VERSION_MAJOR VERSION_MINOR VERSION_PATCH CN_VERSION)
+
+project ( "cn-cbor" VERSION "${CN_VERSION}")
+
+find_package(Doxygen)
+
+## setup options
+option ( use_context    "Use context pointer for CBOR functions" OFF )
+option ( verbose        "Produce verbose makefile output" OFF )
+option ( optimize       "Optimize for size" OFF )
+option ( fatal_warnings "Treat build warnings as errors" ON )
+option ( coveralls      "Generate coveralls data" ON )
+option ( coveralls_send "Send data to coveralls site" OFF )
+option ( build_docs "Create docs using Doxygen" ${DOXYGEN_FOUND} )
+
+set ( dist_dir    ${CMAKE_BINARY_DIR}/dist )
+set ( prefix      ${CMAKE_INSTALL_PREFIX} )
+set ( exec_prefix ${CMAKE_INSTALL_PREFIX}/bin )
+set ( libdir      ${CMAKE_INSTALL_PREFIX}/lib )
+set ( includedir  ${CMAKE_INSTALL_PREFIX}/include )
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cn-cbor.pc.in
+               ${CMAKE_CURRENT_BINARY_DIR}/cn-cbor.pc @ONLY)
+install (FILES ${CMAKE_CURRENT_BINARY_DIR}/cn-cbor.pc DESTINATION lib/pkgconfig )
+
+set ( package_prefix "${CMAKE_PACKAGE_NAME}-${CMAKE_SYSTEM_NAME}" )
+
+set ( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${dist_dir}/bin )
+set ( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${dist_dir}/lib )
+set ( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${dist_dir}/lib )
+
+if (NOT CMAKE_BUILD_TYPE)
+  if ( optimize )
+    set ( CMAKE_BUILD_TYPE MinSizeRel )
+    set ( coveralls OFF )
+    set ( coveralls_send OFF )
+  else ()
+    set ( CMAKE_BUILD_TYPE Debug )
+  endif ()
+endif()
+
+if ( CMAKE_C_COMPILER_ID STREQUAL "GNU" OR
+     CMAKE_C_COMPILER_ID MATCHES "Clang" )
+  message ( STATUS "adding GCC/Clang options ")
+  add_definitions ( -std=gnu99 -Wall -Wextra -pedantic )
+  if ( fatal_warnings )
+    add_definitions ( -Werror )
+  endif ()
+  if ( optimize )
+    add_definitions ( -Os )
+  endif ()
+elseif ( MSVC )
+  add_definitions ( /W3 )
+  if ( fatal_warnings )
+    add_definitions ( /WX )
+  endif ()
+else ()
+  message ( FATAL_ERROR "unhandled compiler id: ${CMAKE_C_COMPILER_ID}" )
+endif ()
+
+if ( verbose )
+  set ( CMAKE_VERBOSE_MAKEFILE ON )
+endif ()
+
+## include the parts
+add_subdirectory ( include )
+add_subdirectory ( src )
+add_subdirectory ( test )
+
+install (FILES LICENSE README.md DESTINATION .)
+
+## setup packaging
+set ( CPACK_GENERATOR "TGZ" )
+set ( CPACK_PACKAGE_VERSION "${PROJECT_VERSION}" )
+set ( CPACK_SOURCE_GENERATOR "TGZ" )
+set ( CPACK_SOURCE_IGNORE_FILES "/\\\\.git/" )
+file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/.gitignore igs)
+foreach (ig IN ITEMS ${igs})
+    # remove comments
+    string ( REGEX REPLACE "^\\s*#.*" "" ig "${ig}")
+    # remove any other whitespace
+    string ( STRIP "${ig}" ig)
+    # anything left?
+    if (ig)
+      # dots are literal
+      string ( REPLACE "." "\\\\." ig "${ig}" )
+      # stars are on thars
+      string ( REPLACE "*" ".*" ig "${ig}" )
+      list ( APPEND CPACK_SOURCE_IGNORE_FILES "/${ig}/" )
+    endif()
+endforeach()
+
+set ( CPACK_PACKAGE_DESCRIPTION_FILE ${CMAKE_CURRENT_SOURCE_DIR}/README.md )
+set ( CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" )
+
+include ( CPack )
+include ( CTest )
+
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake)
+include ( LCov )
+
+if (build_docs)
+    if(NOT DOXYGEN_FOUND)
+        message(FATAL_ERROR "Doxygen is needed to build the documentation.")
+    endif()
+
+    set(doxyfile_in ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in)
+    set(doxyfile ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
+
+    configure_file(${doxyfile_in} ${doxyfile} @ONLY)
+
+    add_custom_target(doc
+        COMMAND ${DOXYGEN_EXECUTABLE} ${doxyfile}
+        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+        COMMENT "Generating API documentation with Doxygen"
+        VERBATIM)
+
+    install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION share/doc)
+endif()
diff --git a/Doxyfile.in b/Doxyfile.in
new file mode 100644
index 0000000..7339517
--- /dev/null
+++ b/Doxyfile.in
@@ -0,0 +1,9 @@
+PROJECT_NAME           = "@CMAKE_PROJECT_NAME@"
+PROJECT_NUMBER         = @CN_VERSION@
+STRIP_FROM_PATH        = @PROJECT_SOURCE_DIR@ \
+                         @PROJECT_BINARY_DIR@
+INPUT                  = @PROJECT_SOURCE_DIR@/README.md \
+                         @PROJECT_SOURCE_DIR@/include
+FILE_PATTERNS          = *.h
+RECURSIVE              = YES
+USE_MDFILE_AS_MAINPAGE = @PROJECT_SOURCE_DIR@/README.md
diff --git a/README.md b/README.md
index 2230b04..67913d9 100644
--- a/README.md
+++ b/README.md
@@ -19,8 +19,14 @@
 time.  So here it is.  If I do get around to making these changes, the
 API will indeed change a bit, so please be forewarned.
 
+## Building
+
 There is a `Simple-Makefile` for playing around, as well as a complete
 [`cmake`](http://www.cmake.org)-based build environment.
 (You can choose what fits your needs better.)
 
+Building with `cmake`:
+
+    ./build.sh
+
 License: MIT
diff --git a/Simple-Makefile b/Simple-Makefile
index 6dbcc89..51e877d 100644
--- a/Simple-Makefile
+++ b/Simple-Makefile
@@ -1,6 +1,6 @@
 # enable this for armv7 builds, lazily using iPhone SDK
 #CFLAGS = -I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include -arch armv7 -Os
-CFLAGS = -Os -Wall -Wextra -Wno-unknown-pragmas -Werror-implicit-function-declaration -Werror -Wno-unused-parameter -Wdeclaration-after-statement -Wwrite-strings -Wstrict-prototypes -Wmissing-prototypes -Iinclude/cn-cbor
+CFLAGS = -Os -Wall -Wextra -Wno-unknown-pragmas -Werror-implicit-function-declaration -Werror -Wno-unused-parameter -Wdeclaration-after-statement -Wwrite-strings -Wstrict-prototypes -Wmissing-prototypes -Iinclude
 
 all: cntest
 
@@ -8,8 +8,8 @@
 	(cd test; env MallocStackLogging=true ../cntest) >new.out
 	-diff new.out test/expected.out
 
-cntest: src/cbor.h include/cn-cbor/cn-cbor.h src/cn-cbor.c src/cn-error.c src/cn-manip.c test/test.c
-	clang $(CFLAGS) src/cn-cbor.c src/cn-error.c src/cn-manip.c test/test.c -o cntest
+cntest: src/cbor.h include/cn-cbor/cn-cbor.h src/cn-cbor.c src/cn-error.c src/cn-get.c test/test.c
+	clang $(CFLAGS) src/cn-cbor.c src/cn-error.c src/cn-get.c test/test.c -o cntest
 
 size: cn-cbor.o
 	size cn-cbor.o
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..a07f37f
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,4 @@
+if [ ! -d "build" ]; then
+    mkdir build
+fi
+cd build && cmake .. && make $*
diff --git a/cmake/Coveralls.cmake b/cmake/Coveralls.cmake
new file mode 100644
index 0000000..d60adba
--- /dev/null
+++ b/cmake/Coveralls.cmake
@@ -0,0 +1,119 @@
+#
+# 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.
+#
+# Copyright (C) 2014 Joakim Söderberg <joakim.soderberg@gmail.com>
+#
+
+
+#
+# Param _COVERAGE_SRCS	A list of source files that coverage should be collected for.
+# Param _COVERALLS_UPLOAD Upload the result to coveralls?
+#
+function(coveralls_setup _COVERAGE_SRCS _COVERALLS_UPLOAD)
+
+	if (ARGC GREATER 2)
+		set(_CMAKE_SCRIPT_PATH ${ARGN})
+		message("Coveralls: Using alternate CMake script dir: ${_CMAKE_SCRIPT_PATH}")
+	else()
+		set(_CMAKE_SCRIPT_PATH ${PROJECT_SOURCE_DIR}/cmake)
+	endif()
+
+	if (NOT EXISTS "${_CMAKE_SCRIPT_PATH}/CoverallsClear.cmake")
+		message(FATAL_ERROR "Coveralls: Missing ${_CMAKE_SCRIPT_PATH}/CoverallsClear.cmake")
+	endif()
+
+	if (NOT EXISTS "${_CMAKE_SCRIPT_PATH}/CoverallsGenerateGcov.cmake")
+		message(FATAL_ERROR "Coveralls: Missing ${_CMAKE_SCRIPT_PATH}/CoverallsGenerateGcov.cmake")
+	endif()
+
+	# When passing a CMake list to an external process, the list
+	# will be converted from the format "1;2;3" to "1 2 3".
+	# This means the script we're calling won't see it as a list
+	# of sources, but rather just one long path. We remedy this
+	# by replacing ";" with "*" and then reversing that in the script
+	# that we're calling.
+	# http://cmake.3232098.n2.nabble.com/Passing-a-CMake-list-quot-as-is-quot-to-a-custom-target-td6505681.html
+	set(COVERAGE_SRCS_TMP ${_COVERAGE_SRCS})
+	set(COVERAGE_SRCS "")
+	foreach (COVERAGE_SRC ${COVERAGE_SRCS_TMP})
+		set(COVERAGE_SRCS "${COVERAGE_SRCS}*${COVERAGE_SRC}")
+	endforeach()
+
+	#message("Coverage sources: ${COVERAGE_SRCS}")
+	set(COVERALLS_FILE ${PROJECT_BINARY_DIR}/coveralls.json)
+
+	add_custom_target(coveralls_generate
+
+		# Zero the coverage counters.
+		COMMAND ${CMAKE_COMMAND}
+				-P "${_CMAKE_SCRIPT_PATH}/CoverallsClear.cmake"
+
+		# Run regress tests.
+		COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure
+
+		# Generate Gcov and translate it into coveralls JSON.
+		# We do this by executing an external CMake script.
+		# (We don't want this to run at CMake generation time, but after compilation and everything has run).
+		COMMAND ${CMAKE_COMMAND}
+				-DCOVERAGE_SRCS="${COVERAGE_SRCS}" # TODO: This is passed like: "a b c", not "a;b;c"
+				-DCOVERALLS_OUTPUT_FILE="${COVERALLS_FILE}"
+				-DCOV_PATH="${PROJECT_BINARY_DIR}"
+				-DPROJECT_ROOT="${PROJECT_SOURCE_DIR}"
+				-P "${_CMAKE_SCRIPT_PATH}/CoverallsGenerateGcov.cmake"
+
+		WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
+		COMMENT "Generating coveralls output..."
+		)
+
+	if (_COVERALLS_UPLOAD)
+		message("COVERALLS UPLOAD: ON")
+
+		find_program(CURL_EXECUTABLE curl)
+
+		if (NOT CURL_EXECUTABLE)
+			message(FATAL_ERROR "Coveralls: curl not found! Aborting")
+		endif()
+
+		add_custom_target(coveralls_upload
+			# Upload the JSON to coveralls.
+			COMMAND ${CURL_EXECUTABLE}
+					-S -F json_file=@${COVERALLS_FILE}
+					https://coveralls.io/api/v1/jobs
+
+			DEPENDS coveralls_generate
+
+			WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
+			COMMENT "Uploading coveralls output...")
+
+		add_custom_target(coveralls DEPENDS coveralls_upload)
+	else()
+		message("COVERALLS UPLOAD: OFF")
+		add_custom_target(coveralls DEPENDS coveralls_generate)
+	endif()
+
+endfunction()
+
+macro(coveralls_turn_on_coverage)
+	if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
+		message(FATAL_ERROR "Coveralls: Code coverage results with an optimised (non-Debug) build may be misleading! Add -DCMAKE_BUILD_TYPE=Debug")
+	endif()
+
+	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
+	set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage")
+endmacro()
diff --git a/cmake/CoverallsClear.cmake b/cmake/CoverallsClear.cmake
new file mode 100644
index 0000000..eb68695
--- /dev/null
+++ b/cmake/CoverallsClear.cmake
@@ -0,0 +1,24 @@
+#
+# 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.
+#
+# Copyright (C) 2014 Joakim Söderberg <joakim.soderberg@gmail.com>
+#
+
+file(REMOVE_RECURSE ${PROJECT_BINARY_DIR}/*.gcda)
+
diff --git a/cmake/CoverallsGenerateGcov.cmake b/cmake/CoverallsGenerateGcov.cmake
new file mode 100644
index 0000000..bb8bd5f
--- /dev/null
+++ b/cmake/CoverallsGenerateGcov.cmake
@@ -0,0 +1,429 @@
+#
+# 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.
+#
+# Copyright (C) 2014 Joakim Söderberg <joakim.soderberg@gmail.com>
+#
+# This is intended to be run by a custom target in a CMake project like this.
+# 0. Compile program with coverage support.
+# 1. Clear coverage data. (Recursively delete *.gcda in build dir)
+# 2. Run the unit tests.
+# 3. Run this script specifying which source files the coverage should be performed on.
+#
+# This script will then use gcov to generate .gcov files in the directory specified
+# via the COV_PATH var. This should probably be the same as your cmake build dir.
+#
+# It then parses the .gcov files to convert them into the Coveralls JSON format:
+# https://coveralls.io/docs/api
+#
+# Example for running as standalone CMake script from the command line:
+# (Note it is important the -P is at the end...)
+# $ cmake -DCOV_PATH=$(pwd)
+#         -DCOVERAGE_SRCS="catcierge_rfid.c;catcierge_timer.c"
+#         -P ../cmake/CoverallsGcovUpload.cmake
+#
+CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
+
+
+#
+# Make sure we have the needed arguments.
+#
+if (NOT COVERALLS_OUTPUT_FILE)
+	message(FATAL_ERROR "Coveralls: No coveralls output file specified. Please set COVERALLS_OUTPUT_FILE")
+endif()
+
+if (NOT COV_PATH)
+	message(FATAL_ERROR "Coveralls: Missing coverage directory path where gcov files will be generated. Please set COV_PATH")
+endif()
+
+if (NOT COVERAGE_SRCS)
+	message(FATAL_ERROR "Coveralls: Missing the list of source files that we should get the coverage data for COVERAGE_SRCS")
+endif()
+
+if (NOT PROJECT_ROOT)
+	message(FATAL_ERROR "Coveralls: Missing PROJECT_ROOT.")
+endif()
+
+# Since it's not possible to pass a CMake list properly in the
+# "1;2;3" format to an external process, we have replaced the
+# ";" with "*", so reverse that here so we get it back into the
+# CMake list format.
+string(REGEX REPLACE "\\*" ";" COVERAGE_SRCS ${COVERAGE_SRCS})
+
+find_program(GCOV_EXECUTABLE gcov)
+
+if (NOT GCOV_EXECUTABLE)
+	message(FATAL_ERROR "gcov not found! Aborting...")
+endif()
+
+find_package(Git)
+
+# TODO: Add these git things to the coveralls json.
+if (GIT_FOUND)
+	# Branch.
+	execute_process(
+		COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD
+		WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+		OUTPUT_VARIABLE GIT_BRANCH
+		OUTPUT_STRIP_TRAILING_WHITESPACE
+	)
+
+	macro (git_log_format FORMAT_CHARS VAR_NAME)
+		execute_process(
+			COMMAND ${GIT_EXECUTABLE} log -1 --pretty=format:%${FORMAT_CHARS}
+			WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+			OUTPUT_VARIABLE ${VAR_NAME}
+			OUTPUT_STRIP_TRAILING_WHITESPACE
+		)
+	endmacro()
+
+	git_log_format(an GIT_AUTHOR_EMAIL)
+	git_log_format(ae GIT_AUTHOR_EMAIL)
+	git_log_format(cn GIT_COMMITTER_NAME)
+	git_log_format(ce GIT_COMMITTER_EMAIL)
+	git_log_format(B GIT_COMMIT_MESSAGE)
+
+	message("Git exe: ${GIT_EXECUTABLE}")
+	message("Git branch: ${GIT_BRANCH}")
+	message("Git author: ${GIT_AUTHOR_NAME}")
+	message("Git e-mail: ${GIT_AUTHOR_EMAIL}")
+	message("Git commiter name: ${GIT_COMMITTER_NAME}")
+	message("Git commiter e-mail: ${GIT_COMMITTER_EMAIL}")
+	message("Git commit message: ${GIT_COMMIT_MESSAGE}")
+
+endif()
+
+############################# Macros #########################################
+
+#
+# This macro converts from the full path format gcov outputs:
+#
+#    /path/to/project/root/build/#path#to#project#root#subdir#the_file.c.gcov
+#
+# to the original source file path the .gcov is for:
+#
+#   /path/to/project/root/subdir/the_file.c
+#
+macro(get_source_path_from_gcov_filename _SRC_FILENAME _GCOV_FILENAME)
+
+	# /path/to/project/root/build/#path#to#project#root#subdir#the_file.c.gcov
+	# ->
+	# #path#to#project#root#subdir#the_file.c.gcov
+	get_filename_component(_GCOV_FILENAME_WEXT ${_GCOV_FILENAME} NAME)
+
+	# #path#to#project#root#subdir#the_file.c.gcov -> /path/to/project/root/subdir/the_file.c
+	string(REGEX REPLACE "\\.gcov$" "" SRC_FILENAME_TMP ${_GCOV_FILENAME_WEXT})
+	string(REGEX REPLACE "\#" "/" SRC_FILENAME_TMP ${SRC_FILENAME_TMP})
+	set(${_SRC_FILENAME} "${SRC_FILENAME_TMP}")
+endmacro()
+
+##############################################################################
+
+# Get the coverage data.
+file(GLOB_RECURSE GCDA_FILES "${COV_PATH}/*.gcda")
+message("GCDA files:")
+
+# Get a list of all the object directories needed by gcov
+# (The directories the .gcda files and .o files are found in)
+# and run gcov on those.
+foreach(GCDA ${GCDA_FILES})
+	message("Process: ${GCDA}")
+	message("------------------------------------------------------------------------------")
+	get_filename_component(GCDA_DIR ${GCDA} PATH)
+
+	#
+	# The -p below refers to "Preserve path components",
+	# This means that the generated gcov filename of a source file will
+	# keep the original files entire filepath, but / is replaced with #.
+	# Example:
+	#
+	# /path/to/project/root/build/CMakeFiles/the_file.dir/subdir/the_file.c.gcda
+	# ------------------------------------------------------------------------------
+	# File '/path/to/project/root/subdir/the_file.c'
+	# Lines executed:68.34% of 199
+	# /path/to/project/root/subdir/the_file.c:creating '#path#to#project#root#subdir#the_file.c.gcov'
+	#
+	# If -p is not specified then the file is named only "the_file.c.gcov"
+	#
+	execute_process(
+		COMMAND ${GCOV_EXECUTABLE} -p -o ${GCDA_DIR} ${GCDA}
+		WORKING_DIRECTORY ${COV_PATH}
+	)
+endforeach()
+
+# TODO: Make these be absolute path
+file(GLOB ALL_GCOV_FILES ${COV_PATH}/*.gcov)
+
+# Get only the filenames to use for filtering.
+#set(COVERAGE_SRCS_NAMES "")
+#foreach (COVSRC ${COVERAGE_SRCS})
+#	get_filename_component(COVSRC_NAME ${COVSRC} NAME)
+#	message("${COVSRC} -> ${COVSRC_NAME}")
+#	list(APPEND COVERAGE_SRCS_NAMES "${COVSRC_NAME}")
+#endforeach()
+
+#
+# Filter out all but the gcov files we want.
+#
+# We do this by comparing the list of COVERAGE_SRCS filepaths that the
+# user wants the coverage data for with the paths of the generated .gcov files,
+# so that we only keep the relevant gcov files.
+#
+# Example:
+# COVERAGE_SRCS =
+#				/path/to/project/root/subdir/the_file.c
+#
+# ALL_GCOV_FILES =
+#				/path/to/project/root/build/#path#to#project#root#subdir#the_file.c.gcov
+#				/path/to/project/root/build/#path#to#project#root#subdir#other_file.c.gcov
+#
+# Result should be:
+# GCOV_FILES =
+#				/path/to/project/root/build/#path#to#project#root#subdir#the_file.c.gcov
+#
+set(GCOV_FILES "")
+#message("Look in coverage sources: ${COVERAGE_SRCS}")
+message("\nFilter out unwanted GCOV files:")
+message("===============================")
+
+set(COVERAGE_SRCS_REMAINING ${COVERAGE_SRCS})
+
+foreach (GCOV_FILE ${ALL_GCOV_FILES})
+
+	#
+	# /path/to/project/root/build/#path#to#project#root#subdir#the_file.c.gcov
+	# ->
+	# /path/to/project/root/subdir/the_file.c
+	get_source_path_from_gcov_filename(GCOV_SRC_PATH ${GCOV_FILE})
+
+	# Is this in the list of source files?
+	# TODO: We want to match against relative path filenames from the source file root...
+	list(FIND COVERAGE_SRCS ${GCOV_SRC_PATH} WAS_FOUND)
+
+	if (NOT WAS_FOUND EQUAL -1)
+		message("YES: ${GCOV_FILE}")
+		list(APPEND GCOV_FILES ${GCOV_FILE})
+
+		# We remove it from the list, so we don't bother searching for it again.
+		# Also files left in COVERAGE_SRCS_REMAINING after this loop ends should
+		# have coverage data generated from them (no lines are covered).
+		list(REMOVE_ITEM COVERAGE_SRCS_REMAINING ${GCOV_SRC_PATH})
+	else()
+		message("NO:  ${GCOV_FILE}")
+	endif()
+endforeach()
+
+# TODO: Enable setting these
+set(JSON_SERVICE_NAME "travis-ci")
+set(JSON_SERVICE_JOB_ID $ENV{TRAVIS_JOB_ID})
+
+set(JSON_TEMPLATE
+"{
+  \"service_name\": \"\@JSON_SERVICE_NAME\@\",
+  \"service_job_id\": \"\@JSON_SERVICE_JOB_ID\@\",
+  \"source_files\": \@JSON_GCOV_FILES\@
+}"
+)
+
+set(SRC_FILE_TEMPLATE
+"{
+      \"name\": \"\@GCOV_SRC_REL_PATH\@\",
+      \"source_digest\": \"\@GCOV_CONTENTS_MD5\@\",
+      \"coverage\": \@GCOV_FILE_COVERAGE\@
+  }"
+)
+
+message("\nGenerate JSON for files:")
+message("=========================")
+
+set(JSON_GCOV_FILES "[")
+
+# Read the GCOV files line by line and get the coverage data.
+foreach (GCOV_FILE ${GCOV_FILES})
+
+	get_source_path_from_gcov_filename(GCOV_SRC_PATH ${GCOV_FILE})
+	file(RELATIVE_PATH GCOV_SRC_REL_PATH "${PROJECT_ROOT}" "${GCOV_SRC_PATH}")
+
+	# The new coveralls API doesn't need the entire source (Yay!)
+	# However, still keeping that part for now. Will cleanup in the future.
+	file(MD5 "${GCOV_SRC_PATH}" GCOV_CONTENTS_MD5)
+	message("MD5: ${GCOV_SRC_PATH} = ${GCOV_CONTENTS_MD5}")
+
+	# Loads the gcov file as a list of lines.
+	# (We first open the file and replace all occurences of [] with _
+	#  because CMake will fail to parse a line containing unmatched brackets...
+	#  also the \ to escaped \n in macros screws up things.)
+	# https://public.kitware.com/Bug/view.php?id=15369
+	file(READ ${GCOV_FILE} GCOV_CONTENTS)
+	string(REPLACE "[" "_" GCOV_CONTENTS "${GCOV_CONTENTS}")
+	string(REPLACE "]" "_" GCOV_CONTENTS "${GCOV_CONTENTS}")
+	string(REPLACE "\\" "_" GCOV_CONTENTS "${GCOV_CONTENTS}")
+	file(WRITE ${GCOV_FILE}_tmp "${GCOV_CONTENTS}")
+
+	file(STRINGS ${GCOV_FILE}_tmp GCOV_LINES)
+	list(LENGTH GCOV_LINES LINE_COUNT)
+
+	# Instead of trying to parse the source from the
+	# gcov file, simply read the file contents from the source file.
+	# (Parsing it from the gcov is hard because C-code uses ; in many places
+	#  which also happens to be the same as the CMake list delimeter).
+	file(READ ${GCOV_SRC_PATH} GCOV_FILE_SOURCE)
+
+	string(REPLACE "\\" "\\\\" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}")
+	string(REGEX REPLACE "\"" "\\\\\"" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}")
+	string(REPLACE "\t" "\\\\t" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}")
+	string(REPLACE "\r" "\\\\r" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}")
+	string(REPLACE "\n" "\\\\n" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}")
+	# According to http://json.org/ these should be escaped as well.
+	# Don't know how to do that in CMake however...
+	#string(REPLACE "\b" "\\\\b" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}")
+	#string(REPLACE "\f" "\\\\f" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}")
+	#string(REGEX REPLACE "\u([a-fA-F0-9]{4})" "\\\\u\\1" GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}")
+
+	# We want a json array of coverage data as a single string
+	# start building them from the contents of the .gcov
+	set(GCOV_FILE_COVERAGE "[")
+
+	set(GCOV_LINE_COUNT 1) # Line number for the .gcov.
+	set(DO_SKIP 0)
+	foreach (GCOV_LINE ${GCOV_LINES})
+		#message("${GCOV_LINE}")
+		# Example of what we're parsing:
+		# Hitcount  |Line | Source
+		# "        8:   26:        if (!allowed || (strlen(allowed) == 0))"
+		string(REGEX REPLACE
+			"^([^:]*):([^:]*):(.*)$"
+			"\\1;\\2;\\3"
+			RES
+			"${GCOV_LINE}")
+
+		# Check if we should exclude lines using the Lcov syntax.
+		string(REGEX MATCH "LCOV_EXCL_START" START_SKIP "${GCOV_LINE}")
+		string(REGEX MATCH "LCOV_EXCL_END" END_SKIP "${GCOV_LINE}")
+		string(REGEX MATCH "LCOV_EXCL_LINE" LINE_SKIP "${GCOV_LINE}")
+
+		set(RESET_SKIP 0)
+		if (LINE_SKIP AND NOT DO_SKIP)
+			set(DO_SKIP 1)
+			set(RESET_SKIP 1)
+		endif()
+
+		if (START_SKIP)
+			set(DO_SKIP 1)
+			message("${GCOV_LINE_COUNT}: Start skip")
+		endif()
+
+		if (END_SKIP)
+			set(DO_SKIP 0)
+		endif()
+
+		list(LENGTH RES RES_COUNT)
+
+		if (RES_COUNT GREATER 2)
+			list(GET RES 0 HITCOUNT)
+			list(GET RES 1 LINE)
+			list(GET RES 2 SOURCE)
+
+			string(STRIP ${HITCOUNT} HITCOUNT)
+			string(STRIP ${LINE} LINE)
+
+			# Lines with 0 line numbers are metadata and can be ignored.
+			if (NOT ${LINE} EQUAL 0)
+
+				if (DO_SKIP)
+					set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}null, ")
+				else()
+					# Translate the hitcount into valid JSON values.
+					if (${HITCOUNT} STREQUAL "#####")
+						set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}0, ")
+					elseif (${HITCOUNT} STREQUAL "-")
+						set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}null, ")
+					else()
+						set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}${HITCOUNT}, ")
+					endif()
+				endif()
+			endif()
+		else()
+			message(WARNING "Failed to properly parse line (RES_COUNT = ${RES_COUNT}) ${GCOV_FILE}:${GCOV_LINE_COUNT}\n-->${GCOV_LINE}")
+		endif()
+
+		if (RESET_SKIP)
+			set(DO_SKIP 0)
+		endif()
+		math(EXPR GCOV_LINE_COUNT "${GCOV_LINE_COUNT}+1")
+	endforeach()
+
+	message("${GCOV_LINE_COUNT} of ${LINE_COUNT} lines read!")
+
+	# Advanced way of removing the trailing comma in the JSON array.
+	# "[1, 2, 3, " -> "[1, 2, 3"
+	string(REGEX REPLACE ",[ ]*$" "" GCOV_FILE_COVERAGE ${GCOV_FILE_COVERAGE})
+
+	# Append the trailing ] to complete the JSON array.
+	set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}]")
+
+	# Generate the final JSON for this file.
+	message("Generate JSON for file: ${GCOV_SRC_REL_PATH}...")
+	string(CONFIGURE ${SRC_FILE_TEMPLATE} FILE_JSON)
+
+	set(JSON_GCOV_FILES "${JSON_GCOV_FILES}${FILE_JSON}, ")
+endforeach()
+
+# Loop through all files we couldn't find any coverage for
+# as well, and generate JSON for those as well with 0% coverage.
+foreach(NOT_COVERED_SRC ${COVERAGE_SRCS_REMAINING})
+
+	# Loads the source file as a list of lines.
+	file(STRINGS ${NOT_COVERED_SRC} SRC_LINES)
+
+	set(GCOV_FILE_COVERAGE "[")
+	set(GCOV_FILE_SOURCE "")
+
+	foreach (SOURCE ${SRC_LINES})
+		set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}0, ")
+
+		string(REPLACE "\\" "\\\\" SOURCE "${SOURCE}")
+		string(REGEX REPLACE "\"" "\\\\\"" SOURCE "${SOURCE}")
+		string(REPLACE "\t" "\\\\t" SOURCE "${SOURCE}")
+		string(REPLACE "\r" "\\\\r" SOURCE "${SOURCE}")
+		set(GCOV_FILE_SOURCE "${GCOV_FILE_SOURCE}${SOURCE}\\n")
+	endforeach()
+
+	# Remove trailing comma, and complete JSON array with ]
+	string(REGEX REPLACE ",[ ]*$" "" GCOV_FILE_COVERAGE ${GCOV_FILE_COVERAGE})
+	set(GCOV_FILE_COVERAGE "${GCOV_FILE_COVERAGE}]")
+
+	# Generate the final JSON for this file.
+	message("Generate JSON for non-gcov file: ${NOT_COVERED_SRC}...")
+	string(CONFIGURE ${SRC_FILE_TEMPLATE} FILE_JSON)
+	set(JSON_GCOV_FILES "${JSON_GCOV_FILES}${FILE_JSON}, ")
+endforeach()
+
+# Get rid of trailing comma.
+string(REGEX REPLACE ",[ ]*$" "" JSON_GCOV_FILES ${JSON_GCOV_FILES})
+set(JSON_GCOV_FILES "${JSON_GCOV_FILES}]")
+
+# Generate the final complete JSON!
+message("Generate final JSON...")
+string(CONFIGURE ${JSON_TEMPLATE} JSON)
+
+file(WRITE "${COVERALLS_OUTPUT_FILE}" "${JSON}")
+message("###########################################################################")
+message("Generated coveralls JSON containing coverage data:")
+message("${COVERALLS_OUTPUT_FILE}")
+message("###########################################################################")
diff --git a/cmake/LCov.cmake b/cmake/LCov.cmake
new file mode 100644
index 0000000..ebdc665
--- /dev/null
+++ b/cmake/LCov.cmake
@@ -0,0 +1,6 @@
+# TODO: parameterize for reuse
+add_custom_target(coverage_report
+  COMMAND lcov --directory src/CMakeFiles/cn-cbor.dir --capture --output-file cn-cbor.info
+  COMMAND genhtml --output-directory lcov cn-cbor.info
+  COMMAND echo "Coverage report in: file://${CMAKE_BINARY_DIR}/lcov/index.html"
+)
diff --git a/cn-cbor.pc.in b/cn-cbor.pc.in
new file mode 100644
index 0000000..0ede53a
--- /dev/null
+++ b/cn-cbor.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: cn-cbor
+Description: A constrained node implementation of CBOR in C
+URL: https://github.com/cabo/cn-cbor
+Version: @CN_VERSION@
+Libs: -L${libdir} -lcn-cbor
+Cflags: -I${includedir}
diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt
new file mode 100644
index 0000000..898676a
--- /dev/null
+++ b/include/CMakeLists.txt
@@ -0,0 +1,2 @@
+install ( DIRECTORY ../include DESTINATION .
+          PATTERN CMakeLists.txt EXCLUDE )
diff --git a/include/cn-cbor/cn-cbor.h b/include/cn-cbor/cn-cbor.h
index 2bf9416..56df2ba 100644
--- a/include/cn-cbor/cn-cbor.h
+++ b/include/cn-cbor/cn-cbor.h
@@ -1,3 +1,9 @@
+/**
+ * \file
+ * \brief
+ * CBOR parsing
+ */
+
 #ifndef CN_CBOR_H
 #define CN_CBOR_H
 
@@ -8,67 +14,235 @@
 } /* Duh. */
 #endif
 
+/**
+ * All of the different kinds of CBOR values.
+ */
 typedef enum cn_cbor_type {
+  /** null */
   CN_CBOR_NULL,
-  CN_CBOR_FALSE,   CN_CBOR_TRUE,
-  CN_CBOR_UINT,    CN_CBOR_INT,
-  CN_CBOR_BYTES,   CN_CBOR_TEXT,
-  CN_CBOR_BYTES_CHUNKED,   CN_CBOR_TEXT_CHUNKED, /* += 2 */
-  CN_CBOR_ARRAY,   CN_CBOR_MAP,
+  /** false */
+  CN_CBOR_FALSE,
+  /** true */
+  CN_CBOR_TRUE,
+  /** Positive integers */
+  CN_CBOR_UINT,
+  /** Negative integers */
+  CN_CBOR_INT,
+  /** Byte string */
+  CN_CBOR_BYTES,
+  /** UTF-8 string */
+  CN_CBOR_TEXT,
+  /** Byte string, in chunks.  Each chunk is a child. */
+  CN_CBOR_BYTES_CHUNKED,
+  /** UTF-8 string, in chunks.  Each chunk is a child */
+  CN_CBOR_TEXT_CHUNKED,
+  /** Array of CBOR values.  Each array element is a child, in order */
+  CN_CBOR_ARRAY,
+  /** Map of key/value pairs.  Each key and value is a child, alternating. */
+  CN_CBOR_MAP,
+  /** Tag describing the next value.  The next value is the single child. */
   CN_CBOR_TAG,
-  CN_CBOR_SIMPLE,  CN_CBOR_DOUBLE,
+  /** Simple value, other than the defined ones */
+  CN_CBOR_SIMPLE,
+  /** Doubles, floats, and half-floats */
+  CN_CBOR_DOUBLE,
+  /** An error has occurred */
   CN_CBOR_INVALID
 } cn_cbor_type;
 
+/**
+ * Flags used during parsing.  Not useful for consumers of the
+ * `cn_cbor` structure.
+ */
 typedef enum cn_cbor_flags {
+  /** The count field will be used for parsing */
   CN_CBOR_FL_COUNT = 1,
+  /** An indefinite number of children */
   CN_CBOR_FL_INDEF = 2,
+  /** Not used yet; the structure must free the v.str pointer when the
+     structure is freed */
   CN_CBOR_FL_OWNER = 0x80,            /* of str */
 } cn_cbor_flags;
 
+/**
+ * A CBOR value
+ */
 typedef struct cn_cbor {
+  /** The type of value */
   cn_cbor_type type;
+  /** Flags used at parse time */
   cn_cbor_flags flags;
+  /** Data associated with the value; different branches of the union are
+      used depending on the `type` field. */
   union {
+    /** CN_CBOR_BYTES, CN_CBOR_TEXT */
     const char* str;
+    /** CN_CBOR_INT */
     long sint;
+    /** CN_CBOR_UINT */
     unsigned long uint;
+    /** CN_CBOR_DOUBLE */
     double dbl;
-    unsigned long count;        /* for use during filling */
+    /** for use during parsing */
+    unsigned long count;
   } v;                          /* TBD: optimize immediate */
+  /** Number of children.
+    * @note: for maps, this is 2x the number of entries */
   int length;
+  /** The first child value */
   struct cn_cbor* first_child;
+  /** The last child value */
   struct cn_cbor* last_child;
+  /** The sibling after this one, or NULL if this is the last */
   struct cn_cbor* next;
+  /** The parent of this value, or NULL if this is the root */
   struct cn_cbor* parent;
 } cn_cbor;
 
+/**
+ * All of the different kinds of errors
+ */
 typedef enum cn_cbor_error {
+  /** No error has occurred */
   CN_CBOR_NO_ERROR,
+  /** More data was expected while parsing */
   CN_CBOR_ERR_OUT_OF_DATA,
+  /** Some extra data was left over at the end of parsing */
   CN_CBOR_ERR_NOT_ALL_DATA_CONSUMED,
+  /** A map should be alternating keys and values.  A break was found
+      when a value was expected */
   CN_CBOR_ERR_ODD_SIZE_INDEF_MAP,
+  /** A break was found where it wasn't expected */
   CN_CBOR_ERR_BREAK_OUTSIDE_INDEF,
+  /** Indefinite encoding works for bstrs, strings, arrays, and maps.
+      A different major type tried to use it. */
   CN_CBOR_ERR_MT_UNDEF_FOR_INDEF,
+  /** Additional Information values 28-30 are reserved */
   CN_CBOR_ERR_RESERVED_AI,
+  /** A chunked encoding was used for a string or bstr, and one of the elements
+      wasn't the expected (string/bstr) type */
   CN_CBOR_ERR_WRONG_NESTING_IN_INDEF_STRING,
-  CN_CBOR_ERR_OUT_OF_MEMORY,
+  /** An invalid parameter was passed to a function */
+  CN_CBOR_ERR_INVALID_PARAMETER,
+  /** Allocation failed */
+  CN_CBOR_ERR_OUT_OF_MEMORY
 } cn_cbor_error;
 
+/**
+ * Strings matching the `cn_cbor_error` conditions.
+ *
+ * @todo: turn into a function to make the type safety more clear?
+ */
 extern const char *cn_cbor_error_str[];
 
+/**
+ * Errors
+ */
 typedef struct cn_cbor_errback {
+  /** The position in the input where the erorr happened */
   int pos;
+  /** The error, or CN_CBOR_NO_ERROR if none */
   cn_cbor_error err;
 } cn_cbor_errback;
 
-const cn_cbor* cn_cbor_decode(const char* buf, size_t len, cn_cbor_errback *errp);
+#ifdef USE_CBOR_CONTEXT
+
+/**
+ * Allocate and zero out memory.  `count` elements of `size` are required,
+ * as for `calloc(3)`.  The `context` is the `cn_cbor_context` passed in
+ * earlier to the CBOR routine.
+ *
+ * @param[in] count   The number of items to allocate
+ * @param[in] size    The size of each item
+ * @param[in] context The allocation context
+ */
+typedef void* (*cn_calloc_func)(size_t count, size_t size, void *context);
+
+/**
+ * Free memory previously allocated with a context.  If using a pool allocator,
+ * this function will often be a no-op, but it must be supplied in order to
+ * prevent the CBOR library from calling `free(3)`.
+ *
+ * @note: it may be that this is never needed; if so, it will be removed for
+ * clarity and speed.
+ *
+ * @param  context [description]
+ * @return         [description]
+ */
+typedef void (*cn_free_func)(void *ptr, void *context);
+
+/**
+ * The allocation context.
+ */
+typedef struct cn_cbor_context {
+    /** The pool `calloc` routine.  Must allocate and zero. */
+    cn_calloc_func calloc_func;
+    /** The pool `free` routine.  Often a no-op, but required. */
+    cn_free_func  free_func;
+    /** Typically, the pool object, to be used when calling `calloc_func`
+      * and `free_func` */
+    void *context;
+} cn_cbor_context;
+
+/** When USE_CBOR_CONTEXT is defined, many functions take an extra `context`
+  * parameter */
+#define CBOR_CONTEXT , cn_cbor_context *context
+/** When USE_CBOR_CONTEXT is defined, some functions take an extra `context`
+  * parameter at the beginning */
+#define CBOR_CONTEXT_COMMA cn_cbor_context *context,
+
+#else
+
+#define CBOR_CONTEXT
+#define CBOR_CONTEXT_COMMA
+
+#endif
+
+/**
+ * Decode an array of CBOR bytes into structures.
+ *
+ * @param[in]  buf          The array of bytes to parse
+ * @param[in]  len          The number of bytes in the array
+ * @param[in]  CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
+ * @param[out] errp         Error, if NULL is returned
+ * @return                  The parsed CBOR structure, or NULL on error
+ */
+const cn_cbor* cn_cbor_decode(const unsigned char* buf, size_t len CBOR_CONTEXT, cn_cbor_errback *errp);
+
+/**
+ * Get a value from a CBOR map that has the given string as a key.
+ *
+ * @param[in]  cb           The CBOR map
+ * @param[in]  key          The string to look up in the map
+ * @return                  The matching value, or NULL if the key is not found
+ */
 const cn_cbor* cn_cbor_mapget_string(const cn_cbor* cb, const char* key);
+
+/**
+ * Get a value from a CBOR map that has the given integer as a key.
+ *
+ * @param[in]  cb           The CBOR map
+ * @param[in]  key          The int to look up in the map
+ * @return                  The matching value, or NULL if the key is not found
+ */
 const cn_cbor* cn_cbor_mapget_int(const cn_cbor* cb, int key);
+
+/**
+ * Get the item with the given index from a CBOR array.
+ *
+ * @param[in]  cb           The CBOR map
+ * @param[in]  idx          The array index
+ * @return                  The matching value, or NULL if the index is invalid
+ */
 const cn_cbor* cn_cbor_index(const cn_cbor* cb, unsigned int idx);
 
-const cn_cbor* cn_cbor_alloc(cn_cbor_type t);
-void cn_cbor_free(const cn_cbor* js);
+/**
+ * Free the given CBOR structure.
+ *
+ * @param[in]  cb           The CBOR value to free
+ * @param[in]  CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
+ */
+void cn_cbor_free(const cn_cbor* cb CBOR_CONTEXT);
 
 #ifdef  __cplusplus
 }
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..3667b40
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,48 @@
+#
+#
+# compiling/installing sources for cn-cbor
+
+set ( cbor_srcs
+      cn-cbor.c
+      cn-error.c
+      cn-get.c
+)
+
+if (use_context)
+  add_definitions(-DUSE_CBOR_CONTEXT)
+endif()
+add_library ( cn-cbor SHARED ${cbor_srcs} )
+target_include_directories ( cn-cbor PUBLIC ../include )
+target_include_directories ( cn-cbor PRIVATE ../src )
+
+install ( TARGETS cn-cbor
+          LIBRARY DESTINATION lib
+          ARCHIVE DESTINATION lib
+          RUNTIME DESTINATION bin)
+
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake)
+if (coveralls)
+    include(Coveralls)
+    coveralls_turn_on_coverage()
+
+    set(COVERAGE_SRCS "")
+    foreach (S ${cbor_srcs})
+      get_filename_component(S_ABS ${S} ABSOLUTE)
+      list (APPEND COVERAGE_SRCS ${S_ABS})
+    endforeach()
+
+    # Create the coveralls target.
+    coveralls_setup(
+        "${COVERAGE_SRCS}"
+        ${coveralls_send}                 # If we should upload.
+    )
+
+    #add_dependencies(coveralls, all)
+endif()
+
+add_custom_target(size
+  COMMAND echo "${CMAKE_BINARY_DIR}/src/CMakeFiles/cn-cbor.dir/cn-cbor.c.o"
+  COMMAND size "${CMAKE_BINARY_DIR}/src/CMakeFiles/cn-cbor.dir/cn-cbor.c.o"
+  COMMAND size -m "${CMAKE_BINARY_DIR}/src/CMakeFiles/cn-cbor.dir/cn-cbor.c.o"
+  DEPENDS cn-cbor
+COMMENT "Output the size of the parse routine")
diff --git a/src/cbor.h b/src/cbor.h
index 547ad65..901f106 100644
--- a/src/cbor.h
+++ b/src/cbor.h
@@ -66,4 +66,47 @@
 #define IB_FLOAT4 (IB_PRIM + AI_4)
 #define IB_FLOAT8 (IB_PRIM + AI_8)
 
+// These definitions are here because they aren't required for the public
+// interface, and they were quite confusing in cn-cbor.h
+
+#ifdef USE_CBOR_CONTEXT
+/**
+ * Allocate enough space for 1 `cn_cbor` structure.
+ *
+ * @param[in]  ctx  The allocation context, or NULL for calloc.
+ * @return          A pointer to a `cn_cbor` or NULL on failure
+ */
+#define CN_CALLOC(ctx) ((ctx) && (ctx)->calloc_func) ? \
+    (ctx)->calloc_func(1, sizeof(cn_cbor), (ctx)->context) : \
+    calloc(1, sizeof(cn_cbor));
+
+/**
+ * Free a
+ * @param  free_func [description]
+ * @return           [description]
+ */
+#define CN_FREE(ptr, ctx) ((ctx) && (ctx)->free_func) ? \
+    (ctx)->free_func((ptr), (ctx)->context) : \
+    free((ptr));
+
+#define CBOR_CONTEXT_PARAM , context
+#define CN_CALLOC_CONTEXT() CN_CALLOC(context)
+#define CN_CBOR_FREE_CONTEXT(p) CN_FREE(p, context)
+
+#else
+
+#define CBOR_CONTEXT_PARAM
+#define CN_CALLOC_CONTEXT() CN_CALLOC
+#define CN_CBOR_FREE_CONTEXT(p) CN_FREE(p)
+
+#ifndef CN_CALLOC
+#define CN_CALLOC calloc(1, sizeof(cn_cbor))
 #endif
+
+#ifndef CN_FREE
+#define CN_FREE free
+#endif
+
+#endif // USE_CBOR_CONTEXT
+
+#endif // CBOR_PROTOCOL_H__
diff --git a/src/cn-cbor.c b/src/cn-cbor.c
index 6336ee4..a4e30b0 100644
--- a/src/cn-cbor.c
+++ b/src/cn-cbor.c
@@ -14,18 +14,12 @@
 #include <assert.h>
 #include <math.h>
 
-#include "cn-cbor.h"
+#include "cn-cbor/cn-cbor.h"
 #include "cbor.h"
 
-// can be redefined, e.g. for pool allocation
-#ifndef CN_CBOR_CALLOC
-#define CN_CBOR_CALLOC() calloc(1, sizeof(cn_cbor))
-#define CN_CBOR_FREE(cb) free((void*)(cb))
-#endif
-
 #define CN_CBOR_FAIL(code) do { pb->err = code;  goto fail; } while(0)
 
-void cn_cbor_free(const cn_cbor* cb) {
+void cn_cbor_free(const cn_cbor* cb CBOR_CONTEXT) {
   cn_cbor* p = (cn_cbor*) cb;
   while (p) {
     cn_cbor* p1;
@@ -36,7 +30,7 @@
       if ((p1 = p->parent))
         p1->first_child = 0;
     }
-    CN_CBOR_FREE(p);
+    CN_CBOR_FREE_CONTEXT(p);
     p = p1;
   }
 }
@@ -81,7 +75,7 @@
   stmt;                                         \
   pos += n;
 
-static cn_cbor *decode_item (struct parse_buf *pb, cn_cbor* top_parent) {
+static cn_cbor *decode_item (struct parse_buf *pb CBOR_CONTEXT, cn_cbor* top_parent) {
   unsigned char *pos = pb->buf;
   unsigned char *ebuf = pb->ebuf;
   cn_cbor* parent = top_parent;
@@ -89,7 +83,7 @@
   unsigned int mt;
   int ai;
   uint64_t val;
-  cn_cbor* cb;
+  cn_cbor* cb = NULL;
   union {
     float f;
     uint32_t u;
@@ -119,7 +113,7 @@
   ai = ib & 0x1f;
   val = ai;
 
-  cb = CN_CBOR_CALLOC();
+  cb = CN_CALLOC_CONTEXT();
   if (!cb)
     CN_CBOR_FAIL(CN_CBOR_ERR_OUT_OF_MEMORY);
 
@@ -159,7 +153,7 @@
   case MT_BYTES: case MT_TEXT:
     cb->v.str = (char *) pos;
     cb->length = val;
-    TAKE(pos, ebuf, val, );
+    TAKE(pos, ebuf, val, ;);
     break;
   case MT_MAP:
     val <<= 1;
@@ -222,19 +216,24 @@
   return 0;
 }
 
-const cn_cbor* cn_cbor_decode(const char* buf, size_t len, cn_cbor_errback *errp) {
+const cn_cbor* cn_cbor_decode(const unsigned char* buf, size_t len CBOR_CONTEXT, cn_cbor_errback *errp) {
   cn_cbor catcher = {CN_CBOR_INVALID, 0, {0}, 0, NULL, NULL, NULL, NULL};
-  struct parse_buf pb = {(unsigned char *)buf, (unsigned char *)buf+len, CN_CBOR_NO_ERROR};
-  cn_cbor* ret = decode_item(&pb, &catcher);
+  struct parse_buf pb;
+  cn_cbor* ret;
+
+  pb.buf  = (unsigned char *)buf;
+  pb.ebuf = (unsigned char *)buf+len;
+  pb.err  = CN_CBOR_NO_ERROR;
+  ret = decode_item(&pb CBOR_CONTEXT_PARAM, &catcher);
   if (ret != NULL) {
     /* mark as top node */
     ret->parent = NULL;
   } else {
     if (catcher.first_child) {
       catcher.first_child->parent = 0;
-      cn_cbor_free(catcher.first_child);
+      cn_cbor_free(catcher.first_child CBOR_CONTEXT_PARAM);
     }
-  //fail:
+//fail:
     if (errp) {
       errp->err = pb.err;
       errp->pos = pb.buf - (unsigned char *)buf;
diff --git a/src/cn-error.c b/src/cn-error.c
index b1dd58c..d4407d0 100644
--- a/src/cn-error.c
+++ b/src/cn-error.c
@@ -7,5 +7,6 @@
  "CN_CBOR_ERR_MT_UNDEF_FOR_INDEF",
  "CN_CBOR_ERR_RESERVED_AI",
  "CN_CBOR_ERR_WRONG_NESTING_IN_INDEF_STRING",
+ "CN_CBOR_ERR_INVALID_PARAMETER",
  "CN_CBOR_ERR_OUT_OF_MEMORY"
 };
diff --git a/src/cn-manip.c b/src/cn-get.c
similarity index 97%
rename from src/cn-manip.c
rename to src/cn-get.c
index 130ab97..8f838d5 100644
--- a/src/cn-manip.c
+++ b/src/cn-get.c
@@ -2,7 +2,7 @@
 #include <string.h>
 #include <assert.h>
 
-#include "cn-cbor.h"
+#include "cn-cbor/cn-cbor.h"
 
 const cn_cbor* cn_cbor_mapget_int(const cn_cbor* cb, int key) {
   cn_cbor* cp;
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
new file mode 100644
index 0000000..65e95ad
--- /dev/null
+++ b/test/CMakeLists.txt
@@ -0,0 +1,33 @@
+#
+#
+# Compiling/running tests
+
+if (use_context)
+  add_definitions(-DUSE_CBOR_CONTEXT)
+endif()
+
+set ( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${dist_dir}/test )
+
+function (create_test name)
+  add_executable ( ${name}_test ${name}_test.c )
+  target_link_libraries ( ${name}_test PRIVATE cn-cbor )
+  target_include_directories ( ${name}_test PRIVATE ../include )
+  add_test ( NAME ${name} COMMAND ${name}_test )
+endfunction()
+
+create_test ( cbor )
+include ( CTest )
+
+add_executable (cn-test test.c )
+target_include_directories ( cn-test PRIVATE ../include )
+target_link_libraries ( cn-test PRIVATE cn-cbor )
+
+configure_file(cases.cbor cases.cbor COPYONLY)
+configure_file(expected.out expected.out COPYONLY)
+
+add_custom_target(difftest
+  COMMAND env MallocStackLogging=true ./cn-test >new.out
+  COMMAND diff new.out expected.out
+  DEPENDS cn-test
+  WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
+  COMMENT "generate differences between actual and expected output")
diff --git a/test/cbor_test.c b/test/cbor_test.c
new file mode 100644
index 0000000..b5c7d8b
--- /dev/null
+++ b/test/cbor_test.c
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2015 SPUDlib authors.  See LICENSE file.
+ */
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+#include "cn-cbor/cn-cbor.h"
+
+#define CTEST_MAIN
+#include "ctest.h"
+
+int main(int argc, const char *argv[])
+{
+    return ctest_main(argc, argv);
+}
+
+#ifdef USE_CBOR_CONTEXT
+#define CONTEXT_NULL , NULL
+#else
+#define CONTEXT_NULL
+#endif
+
+typedef struct _buffer {
+    size_t sz;
+    unsigned char *ptr;
+} buffer;
+
+static bool parse_hex(char *inp, buffer *b)
+{
+    int len = strlen(inp);
+    size_t i;
+    if (len%2 != 0) {
+        b->sz = -1;
+        b->ptr = NULL;
+        return false;
+    }
+    b->sz  = len / 2;
+    b->ptr = malloc(b->sz);
+    for (i=0; i<b->sz; i++) {
+        sscanf(inp+(2*i), "%02hhx", &b->ptr[i]);
+    }
+    return true;
+}
+
+CTEST(cbor, error)
+{
+    ASSERT_STR(cn_cbor_error_str[CN_CBOR_NO_ERROR], "CN_CBOR_NO_ERROR");
+    ASSERT_STR(cn_cbor_error_str[CN_CBOR_ERR_OUT_OF_DATA], "CN_CBOR_ERR_OUT_OF_DATA");
+    ASSERT_STR(cn_cbor_error_str[CN_CBOR_ERR_NOT_ALL_DATA_CONSUMED], "CN_CBOR_ERR_NOT_ALL_DATA_CONSUMED");
+    ASSERT_STR(cn_cbor_error_str[CN_CBOR_ERR_ODD_SIZE_INDEF_MAP], "CN_CBOR_ERR_ODD_SIZE_INDEF_MAP");
+    ASSERT_STR(cn_cbor_error_str[CN_CBOR_ERR_BREAK_OUTSIDE_INDEF], "CN_CBOR_ERR_BREAK_OUTSIDE_INDEF");
+    ASSERT_STR(cn_cbor_error_str[CN_CBOR_ERR_MT_UNDEF_FOR_INDEF], "CN_CBOR_ERR_MT_UNDEF_FOR_INDEF");
+    ASSERT_STR(cn_cbor_error_str[CN_CBOR_ERR_RESERVED_AI], "CN_CBOR_ERR_RESERVED_AI");
+    ASSERT_STR(cn_cbor_error_str[CN_CBOR_ERR_WRONG_NESTING_IN_INDEF_STRING], "CN_CBOR_ERR_WRONG_NESTING_IN_INDEF_STRING");
+    ASSERT_STR(cn_cbor_error_str[CN_CBOR_ERR_INVALID_PARAMETER], "CN_CBOR_ERR_INVALID_PARAMETER");
+    ASSERT_STR(cn_cbor_error_str[CN_CBOR_ERR_OUT_OF_MEMORY], "CN_CBOR_ERR_OUT_OF_MEMORY");
+}
+
+CTEST(cbor, parse)
+{
+    cn_cbor_errback err;
+    char *tests[] = {
+        "00",       // 0
+        "01",       // 1
+        "17",       // 23
+        "1818",     // 24
+        "190100",   // 256
+        "1a00010000", // 65536
+        "1b0000000100000000", // 4294967296
+        "20",       // -1
+        "37",       // -24
+        "3818",     // -25
+        "390100",   // -257
+        "3a00010000", // -65537
+        "3b0000000100000000", // -4294967297
+        "4161",     // h"a"
+        "6161",     // "a"
+        "8100",     // [0]
+        "818100",   // [[0]]
+        "a1616100",	// {"a":0}
+        "d8184100", // tag
+        "f4",	    // false
+        "f5",	    // true
+        "f6",	    // null
+        "f7",	    // undefined
+        "f8ff",     // simple(255)
+        "fb3ff199999999999a", // 1.1
+        "fb7ff8000000000000", // NaN
+        "5f42010243030405ff", // (_ h'0102', h'030405')
+        "7f61616161ff", // (_ "a", "a")
+        "9fff",     // [_ ]
+        "bf61610161629f0203ffff", // {_ "a": 1, "b": [_ 2, 3]}
+    };
+    const cn_cbor *cb;
+    buffer b;
+    size_t i;
+//    unsigned char encoded[1024];
+//    ssize_t enc_sz;
+
+    for (i=0; i<sizeof(tests)/sizeof(char*); i++) {
+        ASSERT_TRUE(parse_hex(tests[i], &b));
+        cb = cn_cbor_decode(b.ptr, b.sz CONTEXT_NULL, &err);
+        ASSERT_NOT_NULL(cb);
+
+        // enc_sz = cbor_encoder_write(encoded, 0, sizeof(encoded), cb);
+        // ASSERT_EQUAL(enc_sz, b.sz);
+        // ASSERT_EQUAL(memcmp(b.ptr, encoded, enc_sz), 0);
+        // free(b.ptr);
+        cn_cbor_free(cb CONTEXT_NULL);
+    }
+}
+
+typedef struct _cbor_failure
+{
+    char *hex;
+    cn_cbor_error err;
+} cbor_failure;
+
+CTEST(cbor, fail)
+{
+    cn_cbor_errback err;
+    cbor_failure tests[] = {
+        {"81", CN_CBOR_ERR_OUT_OF_DATA},
+        {"0000", CN_CBOR_ERR_NOT_ALL_DATA_CONSUMED},
+        {"bf00ff", CN_CBOR_ERR_ODD_SIZE_INDEF_MAP},
+        {"ff", CN_CBOR_ERR_BREAK_OUTSIDE_INDEF},
+        {"1f", CN_CBOR_ERR_MT_UNDEF_FOR_INDEF},
+        {"1c", CN_CBOR_ERR_RESERVED_AI},
+        {"7f4100", CN_CBOR_ERR_WRONG_NESTING_IN_INDEF_STRING},
+    };
+    const cn_cbor *cb;
+    buffer b;
+    size_t i;
+
+    for (i=0; i<sizeof(tests)/sizeof(cbor_failure); i++) {
+        ASSERT_TRUE(parse_hex(tests[i].hex, &b));
+        cb = cn_cbor_decode(b.ptr, b.sz CONTEXT_NULL, &err);
+        ASSERT_NULL(cb);
+        ASSERT_EQUAL(err.err, tests[i].err);
+
+        free(b.ptr);
+        cn_cbor_free(cb CONTEXT_NULL);
+    }
+}
+
+// Decoder loses float size information
+CTEST(cbor, float)
+{
+    cn_cbor_errback err;
+    char *tests[] = {
+        "f9c400", // -4.0
+        "fa47c35000", // 100000.0
+        "f97e00", // Half NaN, half beast
+        "f9fc00", // -Inf
+        "f97c00", // Inf
+    };
+    const cn_cbor *cb;
+    buffer b;
+    size_t i;
+
+    for (i=0; i<sizeof(tests)/sizeof(char*); i++) {
+        ASSERT_TRUE(parse_hex(tests[i], &b));
+        cb = cn_cbor_decode(b.ptr, b.sz CONTEXT_NULL, &err);
+        ASSERT_NOT_NULL(cb);
+
+        free(b.ptr);
+        cn_cbor_free(cb CONTEXT_NULL);
+    }
+}
+
+CTEST(cbor, getset)
+{
+    buffer b;
+    const cn_cbor *cb;
+    const cn_cbor *val;
+    cn_cbor_errback err;
+
+    ASSERT_TRUE(parse_hex("a3436363630262626201616100", &b));
+    cb = cn_cbor_decode(b.ptr, b.sz CONTEXT_NULL, &err);
+    ASSERT_NOT_NULL(cb);
+    val = cn_cbor_mapget_string(cb, "a");
+    ASSERT_NOT_NULL(val);
+    val = cn_cbor_mapget_string(cb, "bb");
+    ASSERT_NOT_NULL(val);
+    val = cn_cbor_mapget_string(cb, "ccc");
+    ASSERT_NOT_NULL(val);
+    val = cn_cbor_mapget_string(cb, "b");
+    ASSERT_NULL(val);
+    free(b.ptr);
+    cn_cbor_free(cb CONTEXT_NULL);
+
+    ASSERT_TRUE(parse_hex("a2006161206162", &b));
+    cb = cn_cbor_decode(b.ptr, b.sz CONTEXT_NULL, &err);
+    ASSERT_NOT_NULL(cb);
+    val = cn_cbor_mapget_int(cb, 0);
+    ASSERT_NOT_NULL(val);
+    val = cn_cbor_mapget_int(cb, -1);
+    ASSERT_NOT_NULL(val);
+    val = cn_cbor_mapget_int(cb, 1);
+    ASSERT_NULL(val);
+    free(b.ptr);
+    cn_cbor_free(cb CONTEXT_NULL);
+
+    ASSERT_TRUE(parse_hex("8100", &b));
+    cb = cn_cbor_decode(b.ptr, b.sz CONTEXT_NULL, &err);
+    ASSERT_NOT_NULL(cb);
+    val = cn_cbor_index(cb, 0);
+    ASSERT_NOT_NULL(val);
+    val = cn_cbor_index(cb, 1);
+    ASSERT_NULL(val);
+    val = cn_cbor_index(cb, -1);
+    ASSERT_NULL(val);
+    free(b.ptr);
+    cn_cbor_free(cb CONTEXT_NULL);
+}
diff --git a/test/ctest.h b/test/ctest.h
new file mode 100644
index 0000000..75fda66
--- /dev/null
+++ b/test/ctest.h
@@ -0,0 +1,459 @@
+/* Copyright 2011,2012 Bas van den Berg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CTEST_H
+#define CTEST_H
+
+#ifndef UNUSED_PARAM
+  /**
+   * \def UNUSED_PARAM(p);
+   *
+   * A macro for quelling compiler warnings about unused variables.
+   */
+#  define UNUSED_PARAM(p) ((void)&(p))
+#endif /* UNUSED_PARM */
+
+typedef void (*SetupFunc)(void*);
+typedef void (*TearDownFunc)(void*);
+
+struct ctest {
+    const char* ssname;  // suite name
+    const char* ttname;  // test name
+    void (*run)();
+    int skip;
+
+    void* data;
+    SetupFunc setup;
+    TearDownFunc teardown;
+
+    unsigned int magic;
+};
+
+#define __FNAME(sname, tname) __ctest_##sname##_##tname##_run
+#define __TNAME(sname, tname) __ctest_##sname##_##tname
+
+#define __CTEST_MAGIC (0xdeadbeef)
+#ifdef __APPLE__
+#define __Test_Section __attribute__ ((unused,section ("__DATA, .ctest")))
+#else
+#define __Test_Section __attribute__ ((unused,section (".ctest")))
+#endif
+
+#define __CTEST_STRUCT(sname, tname, _skip, __data, __setup, __teardown) \
+    struct ctest __TNAME(sname, tname) __Test_Section = { \
+        .ssname=#sname, \
+        .ttname=#tname, \
+        .run = __FNAME(sname, tname), \
+        .skip = _skip, \
+        .data = __data, \
+        .setup = (SetupFunc)__setup,					\
+        .teardown = (TearDownFunc)__teardown,				\
+        .magic = __CTEST_MAGIC };
+
+#define CTEST_DATA(sname) struct sname##_data
+
+#define CTEST_SETUP(sname) \
+    void __attribute__ ((weak)) sname##_setup(struct sname##_data* data)
+
+#define CTEST_TEARDOWN(sname) \
+    void __attribute__ ((weak)) sname##_teardown(struct sname##_data* data)
+
+#define __CTEST_INTERNAL(sname, tname, _skip) \
+    void __FNAME(sname, tname)(); \
+    __CTEST_STRUCT(sname, tname, _skip, NULL, NULL, NULL) \
+    void __FNAME(sname, tname)()
+
+#ifdef __APPLE__
+#define SETUP_FNAME(sname) NULL
+#define TEARDOWN_FNAME(sname) NULL
+#else
+#define SETUP_FNAME(sname) sname##_setup
+#define TEARDOWN_FNAME(sname) sname##_teardown
+#endif
+
+#define __CTEST2_INTERNAL(sname, tname, _skip) \
+    static struct sname##_data  __ctest_##sname##_data; \
+    CTEST_SETUP(sname); \
+    CTEST_TEARDOWN(sname); \
+    void __FNAME(sname, tname)(struct sname##_data* data); \
+    __CTEST_STRUCT(sname, tname, _skip, &__ctest_##sname##_data, SETUP_FNAME(sname), TEARDOWN_FNAME(sname)) \
+    void __FNAME(sname, tname)(struct sname##_data* data)
+
+
+void CTEST_LOG(char *fmt, ...);
+void CTEST_ERR(char *fmt, ...);  // doesn't return
+
+#define CTEST(sname, tname) __CTEST_INTERNAL(sname, tname, 0)
+#define CTEST_SKIP(sname, tname) __CTEST_INTERNAL(sname, tname, 1)
+
+#define CTEST2(sname, tname) __CTEST2_INTERNAL(sname, tname, 0)
+#define CTEST2_SKIP(sname, tname) __CTEST2_INTERNAL(sname, tname, 1)
+
+
+void assert_str(const char* exp, const char* real, const char* caller, int line);
+#define ASSERT_STR(exp, real) assert_str(exp, real, __FILE__, __LINE__)
+
+void assert_data(const unsigned char* exp, int expsize,
+                 const unsigned char* real, int realsize,
+                 const char* caller, int line);
+#define ASSERT_DATA(exp, expsize, real, realsize) \
+    assert_data(exp, expsize, real, realsize, __FILE__, __LINE__)
+
+void assert_equal(long exp, long real, const char* caller, int line);
+#define ASSERT_EQUAL(exp, real) assert_equal(exp, real, __FILE__, __LINE__)
+
+void assert_not_equal(long exp, long real, const char* caller, int line);
+#define ASSERT_NOT_EQUAL(exp, real) assert_not_equal(exp, real, __FILE__, __LINE__)
+
+void assert_null(void* real, const char* caller, int line);
+#define ASSERT_NULL(real) assert_null((void*)real, __FILE__, __LINE__)
+
+void assert_not_null(const void* real, const char* caller, int line);
+#define ASSERT_NOT_NULL(real) assert_not_null(real, __FILE__, __LINE__)
+
+void assert_true(int real, const char* caller, int line);
+#define ASSERT_TRUE(real) assert_true(real, __FILE__, __LINE__)
+
+void assert_false(int real, const char* caller, int line);
+#define ASSERT_FALSE(real) assert_false(real, __FILE__, __LINE__)
+
+void assert_fail(const char* caller, int line);
+#define ASSERT_FAIL() assert_fail(__FILE__, __LINE__)
+
+#ifdef CTEST_MAIN
+
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/time.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#ifdef __APPLE__
+#include <dlfcn.h>
+#endif
+
+//#define COLOR_OK
+
+static size_t ctest_errorsize;
+static char* ctest_errormsg;
+#define MSG_SIZE 4096
+static char ctest_errorbuffer[MSG_SIZE];
+static jmp_buf ctest_err;
+static int color_output = 1;
+static const char* suite_name;
+
+typedef int (*filter_func)(struct ctest*);
+
+#define ANSI_BLACK    "\033[0;30m"
+#define ANSI_RED      "\033[0;31m"
+#define ANSI_GREEN    "\033[0;32m"
+#define ANSI_YELLOW   "\033[0;33m"
+#define ANSI_BLUE     "\033[0;34m"
+#define ANSI_MAGENTA  "\033[0;35m"
+#define ANSI_CYAN     "\033[0;36m"
+#define ANSI_GREY     "\033[0;37m"
+#define ANSI_DARKGREY "\033[01;30m"
+#define ANSI_BRED     "\033[01;31m"
+#define ANSI_BGREEN   "\033[01;32m"
+#define ANSI_BYELLOW  "\033[01;33m"
+#define ANSI_BBLUE    "\033[01;34m"
+#define ANSI_BMAGENTA "\033[01;35m"
+#define ANSI_BCYAN    "\033[01;36m"
+#define ANSI_WHITE    "\033[01;37m"
+#define ANSI_NORMAL   "\033[0m"
+
+static CTEST(suite, test) { }
+
+static void msg_start(const char* color, const char* title) {
+    int size;
+    if (color_output) {
+        size = snprintf(ctest_errormsg, ctest_errorsize, "%s", color);
+        ctest_errorsize -= size;
+        ctest_errormsg += size;
+    }
+    size = snprintf(ctest_errormsg, ctest_errorsize, "  %s: ", title);
+    ctest_errorsize -= size;
+    ctest_errormsg += size;
+}
+
+static void msg_end() {
+    int size;
+    if (color_output) {
+        size = snprintf(ctest_errormsg, ctest_errorsize, ANSI_NORMAL);
+        ctest_errorsize -= size;
+        ctest_errormsg += size;
+    }
+    size = snprintf(ctest_errormsg, ctest_errorsize, "\n");
+    ctest_errorsize -= size;
+    ctest_errormsg += size;
+}
+
+void CTEST_LOG(char *fmt, ...)
+{
+    va_list argp;
+    msg_start(ANSI_BLUE, "LOG");
+
+    va_start(argp, fmt);
+    int size = vsnprintf(ctest_errormsg, ctest_errorsize, fmt, argp);
+    ctest_errorsize -= size;
+    ctest_errormsg += size;
+    va_end(argp);
+
+    msg_end();
+}
+
+void CTEST_ERR(char *fmt, ...)
+{
+    va_list argp;
+    msg_start(ANSI_YELLOW, "ERR");
+
+    va_start(argp, fmt);
+    int size = vsnprintf(ctest_errormsg, ctest_errorsize, fmt, argp);
+    ctest_errorsize -= size;
+    ctest_errormsg += size;
+    va_end(argp);
+
+    msg_end();
+    longjmp(ctest_err, 1);
+}
+
+void assert_str(const char* exp, const char*  real, const char* caller, int line) {
+    if ((exp == NULL && real != NULL) ||
+        (exp != NULL && real == NULL) ||
+        (exp && real && strcmp(exp, real) != 0)) {
+        CTEST_ERR("%s:%d  expected '%s', got '%s'", caller, line, exp, real);
+    }
+}
+
+void assert_data(const unsigned char* exp, int expsize,
+                 const unsigned char* real, int realsize,
+                 const char* caller, int line) {
+    int i;
+    if (expsize != realsize) {
+        CTEST_ERR("%s:%d  expected %d bytes, got %d", caller, line, expsize, realsize);
+    }
+    for (i=0; i<expsize; i++) {
+        if (exp[i] != real[i]) {
+            CTEST_ERR("%s:%d expected 0x%02x at offset %d got 0x%02x",
+                caller, line, exp[i], i, real[i]);
+        }
+    }
+}
+
+void assert_equal(long exp, long real, const char* caller, int line) {
+    if (exp != real) {
+        CTEST_ERR("%s:%d  expected %ld, got %ld", caller, line, exp, real);
+    }
+}
+
+void assert_not_equal(long exp, long real, const char* caller, int line) {
+    if ((exp) == (real)) {
+        CTEST_ERR("%s:%d  should not be %ld", caller, line, real);
+    }
+}
+
+void assert_null(void* real, const char* caller, int line) {
+    if ((real) != NULL) {
+        CTEST_ERR("%s:%d  should be NULL", caller, line);
+    }
+}
+
+void assert_not_null(const void* real, const char* caller, int line) {
+    if (real == NULL) {
+        CTEST_ERR("%s:%d  should not be NULL", caller, line);
+    }
+}
+
+void assert_true(int real, const char* caller, int line) {
+    if ((real) == 0) {
+        CTEST_ERR("%s:%d  should be true", caller, line);
+    }
+}
+
+void assert_false(int real, const char* caller, int line) {
+    if ((real) != 0) {
+        CTEST_ERR("%s:%d  should be false", caller, line);
+    }
+}
+
+void assert_fail(const char* caller, int line) {
+    CTEST_ERR("%s:%d  shouldn't come here", caller, line);
+}
+
+
+static int suite_all(struct ctest* t) {
+    UNUSED_PARAM(t);
+    return 1;
+}
+
+static int suite_filter(struct ctest* t) {
+    return strncmp(suite_name, t->ssname, strlen(suite_name)) == 0;
+}
+
+static uint64_t getCurrentTime() {
+    struct timeval now;
+    gettimeofday(&now, NULL);
+    uint64_t now64 = now.tv_sec;
+    now64 *= 1000000;
+    now64 += (now.tv_usec);
+    return now64;
+}
+
+static void color_print(const char* color, const char* text) {
+    if (color_output)
+        printf("%s%s"ANSI_NORMAL"\n", color, text);
+    else
+        printf("%s\n", text);
+}
+
+#ifdef __APPLE__
+static void *find_symbol(struct ctest *test, const char *fname)
+{
+    size_t len = strlen(test->ssname) + 1 + strlen(fname);
+    char *symbol_name = (char *) malloc(len + 1);
+    memset(symbol_name, 0, len + 1);
+    snprintf(symbol_name, len + 1, "%s_%s", test->ssname, fname);
+
+    //fprintf(stderr, ">>>> dlsym: loading %s\n", symbol_name);
+    void *symbol = dlsym(RTLD_DEFAULT, symbol_name);
+    if (!symbol) {
+        //fprintf(stderr, ">>>> ERROR: %s\n", dlerror());
+    }
+    // returns NULL on error
+
+    free(symbol_name);
+    return symbol;
+}
+#endif
+
+#ifdef CTEST_SEGFAULT
+#include <signal.h>
+static void sighandler(int signum)
+{
+    char msg[128];
+    sprintf(msg, "[SIGNAL %d: %s]", signum, sys_siglist[signum]);
+    color_print(ANSI_BRED, msg);
+    fflush(stdout);
+
+    /* "Unregister" the signal handler and send the signal back to the process
+     * so it can terminate as expected */
+    signal(signum, SIG_DFL);
+    kill(getpid(), signum);
+}
+#endif
+
+int ctest_main(int argc, const char *argv[])
+{
+    static int total = 0;
+    static int num_ok = 0;
+    static int num_fail = 0;
+    static int num_skip = 0;
+    static int index = 1;
+    static filter_func filter = suite_all;
+
+#ifdef CTEST_SEGFAULT
+    signal(SIGSEGV, sighandler);
+#endif
+
+    if (argc == 2) {
+        suite_name = argv[1];
+        filter = suite_filter;
+    }
+
+    color_output = isatty(1);
+    uint64_t t1 = getCurrentTime();
+
+    struct ctest* ctest_begin = &__TNAME(suite, test);
+    struct ctest* ctest_end = &__TNAME(suite, test);
+    // find begin and end of section by comparing magics
+    while (1) {
+        struct ctest* t = ctest_begin-1;
+        if (t->magic != __CTEST_MAGIC) break;
+        ctest_begin--;
+    }
+    while (1) {
+        struct ctest* t = ctest_end+1;
+        if (t->magic != __CTEST_MAGIC) break;
+        ctest_end++;
+    }
+    ctest_end++;    // end after last one
+
+    static struct ctest* test;
+    for (test = ctest_begin; test != ctest_end; test++) {
+        if (test == &__ctest_suite_test) continue;
+        if (filter(test)) total++;
+    }
+
+    for (test = ctest_begin; test != ctest_end; test++) {
+        if (test == &__ctest_suite_test) continue;
+        if (filter(test)) {
+            ctest_errorbuffer[0] = 0;
+            ctest_errorsize = MSG_SIZE-1;
+            ctest_errormsg = ctest_errorbuffer;
+            printf("TEST %d/%d %s:%s ", index, total, test->ssname, test->ttname);
+            fflush(stdout);
+            if (test->skip) {
+                color_print(ANSI_BYELLOW, "[SKIPPED]");
+                num_skip++;
+            } else {
+                int result = setjmp(ctest_err);
+                if (result == 0) {
+#ifdef __APPLE__
+                    if (!test->setup) {
+                        test->setup = (SetupFunc)find_symbol(test, "setup");
+                    }
+                    if (!test->teardown) {
+                        test->teardown = (SetupFunc)find_symbol(test, "teardown");
+                    }
+#endif
+
+                    if (test->setup) test->setup(test->data);
+                    if (test->data)
+                      test->run(test->data);
+                    else
+                      test->run();
+                    if (test->teardown) test->teardown(test->data);
+                    // if we got here it's ok
+#ifdef COLOR_OK
+                    color_print(ANSI_BGREEN, "[OK]");
+#else
+                    printf("[OK]\n");
+#endif
+                    num_ok++;
+                } else {
+                    color_print(ANSI_BRED, "[FAIL]");
+                    num_fail++;
+                }
+                if (ctest_errorsize != MSG_SIZE-1) printf("%s", ctest_errorbuffer);
+            }
+            index++;
+        }
+    }
+    uint64_t t2 = getCurrentTime();
+
+    const char* color = (num_fail) ? ANSI_BRED : ANSI_GREEN;
+    char results[80];
+    sprintf(results, "RESULTS: %d tests (%d ok, %d failed, %d skipped) ran in %"PRIu64" ms", total, num_ok, num_fail, num_skip, (t2 - t1)/1000);
+    color_print(color, results);
+    return num_fail;
+}
+
+#endif
+
+#endif
diff --git a/test/test.c b/test/test.c
index 5b949ee..8fc931f 100644
--- a/test/test.c
+++ b/test/test.c
@@ -1,14 +1,21 @@
 #include <unistd.h>
-#include "cn-cbor.h"
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <string.h>
 
+#include "cn-cbor/cn-cbor.h"
+
+#ifdef USE_CBOR_CONTEXT
+#define CBOR_CONTEXT_PARAM , NULL
+#else
+#define CBOR_CONTEXT_PARAM
+#endif
+
 #define ERROR(msg, p) fprintf(stderr, "ERROR: " msg " %s\n", (p));
 
-static char* load_file(const char* filepath, char **end) {
+static unsigned char* load_file(const char* filepath, unsigned char **end) {
   struct stat st;
   if (stat(filepath, &st)==-1) {
     ERROR("can't find file", filepath);
@@ -19,7 +26,7 @@
     ERROR("can't open file", filepath);
     return 0;
   }
-  char* text=malloc(st.st_size+1); // this is not going to be freed
+  unsigned char* text=malloc(st.st_size+1); // this is not going to be freed
   if (st.st_size!=read(fd, text, st.st_size)) {
     ERROR("can't read file", filepath);
     close(fd);
@@ -92,9 +99,9 @@
   "CN_CBOR_ERR_OUT_OF_MEMORY",
 };
 
-static void cn_cbor_decode_test(const char *buf, int len) {
+static void cn_cbor_decode_test(const unsigned char *buf, int len) {
   struct cn_cbor_errback back;
-  const cn_cbor *ret = cn_cbor_decode(buf, len, &back);
+  const cn_cbor *ret = cn_cbor_decode(buf, len CBOR_CONTEXT_PARAM, &back);
   if (ret)
     printf("oops 1");
   printf("%s at %d\n", err_name[back.err], back.pos);
@@ -102,24 +109,25 @@
 
 int main() {
   char buf[100000];
-  char *end;
-  char *s = load_file("cases.cbor", &end);
+  unsigned char *end;
+  char *bufend;
+  unsigned char *s = load_file("cases.cbor", &end);
   printf("%zd\n", end-s);
-  const cn_cbor *cb = cn_cbor_decode(s, end-s, 0);
+  const cn_cbor *cb = cn_cbor_decode(s, end-s CBOR_CONTEXT_PARAM, 0);
   if (cb) {
-    dump(cb, buf, &end, 0);
-    *end = 0;
+    dump(cb, buf, &bufend, 0);
+    *bufend = 0;
     printf("%s\n", buf);
-    cn_cbor_free(cb);
+    cn_cbor_free(cb CBOR_CONTEXT_PARAM);
     cb = 0;                     /* for leaks testing */
   }
-  cn_cbor_decode_test("\xff", 1);    /* break outside indef */
-  cn_cbor_decode_test("\x1f", 1);    /* mt undef for indef */
-  cn_cbor_decode_test("\x00\x00", 2);    /* not all data consumed */
-  cn_cbor_decode_test("\x81", 1);    /* out of data */
-  cn_cbor_decode_test("\x1c", 1);    /* reserved ai */
-  cn_cbor_decode_test("\xbf\x00\xff", 3);    /* odd size indef map */
-  cn_cbor_decode_test("\x7f\x40\xff", 3);    /* wrong nesting in indef string */
+  cn_cbor_decode_test((const unsigned char*)"\xff", 1);    /* break outside indef */
+  cn_cbor_decode_test((const unsigned char*)"\x1f", 1);    /* mt undef for indef */
+  cn_cbor_decode_test((const unsigned char*)"\x00\x00", 2);    /* not all data consumed */
+  cn_cbor_decode_test((const unsigned char*)"\x81", 1);    /* out of data */
+  cn_cbor_decode_test((const unsigned char*)"\x1c", 1);    /* reserved ai */
+  cn_cbor_decode_test((const unsigned char*)"\xbf\x00\xff", 3);    /* odd size indef map */
+  cn_cbor_decode_test((const unsigned char*)"\x7f\x40\xff", 3);    /* wrong nesting in indef string */
   system("leaks test");
 }