aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--CMakeLists.txt129
-rw-r--r--Doxyfile.in9
-rw-r--r--README.md6
-rw-r--r--Simple-Makefile6
-rwxr-xr-xbuild.sh4
-rw-r--r--cmake/Coveralls.cmake119
-rw-r--r--cmake/CoverallsClear.cmake24
-rw-r--r--cmake/CoverallsGenerateGcov.cmake429
-rw-r--r--cmake/LCov.cmake6
-rw-r--r--cn-cbor.pc.in11
-rw-r--r--include/CMakeLists.txt2
-rw-r--r--include/cn-cbor/cn-cbor.h196
-rw-r--r--src/CMakeLists.txt48
-rw-r--r--src/cbor.h43
-rw-r--r--src/cn-cbor.c35
-rw-r--r--src/cn-error.c1
-rw-r--r--src/cn-get.c (renamed from src/cn-manip.c)2
-rw-r--r--test/CMakeLists.txt33
-rw-r--r--test/cbor_test.c219
-rw-r--r--test/ctest.h459
-rw-r--r--test/test.c44
22 files changed, 1775 insertions, 51 deletions
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 @@ changes, in order to get even closer to 0.5 KiB, but I ran out of
time. So here it is. If I do get around to making these changes, the
API will indeed change a bit, so please be forewarned.
+## 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 @@ test: cntest
(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 @@ extern "C" {
} /* 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 @@ extern "C" {
#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 @@ void cn_cbor_free(const cn_cbor* cb) {
if ((p1 = p->parent))
p1->first_child = 0;
}
- CN_CBOR_FREE(p);
+ CN_CBOR_FREE_CONTEXT(p);
p = p1;
}
}
@@ -81,7 +75,7 @@ struct parse_buf {
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 @@ static cn_cbor *decode_item (struct parse_buf *pb, cn_cbor* top_parent) {
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 @@ again:
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 @@ again:
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 @@ fail:
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 @@ const char *cn_cbor_error_str[] = {
"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
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 @@ static char* load_file(const char* filepath, char **end) {
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 @@ const char *err_name[] = {
"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 @@ static void cn_cbor_decode_test(const char *buf, int len) {
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");
}