diff options
author | Janis Danisevskis <jdanis@google.com> | 2018-01-18 18:17:59 +0000 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2018-01-18 18:17:59 +0000 |
commit | 8a9d358531e6ec95340d95b0a42ee84310e3701e (patch) | |
tree | f31948b9ce23f6cce9d3a9450ca3c90b4ac73355 | |
parent | fd4e34b1b3a898829f1fedaafbaea001193f2536 (diff) | |
parent | 3798baae63cca5d67e550ee3ef449bc56f80958b (diff) | |
download | cn-cbor-8a9d358531e6ec95340d95b0a42ee84310e3701e.tar.gz |
Merge upstream for the first time
am: 3798baae63
Change-Id: I88dff63df6e103fe8a285218eec83160db49ef7a
-rw-r--r-- | .editorconfig | 15 | ||||
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | .travis.yml | 17 | ||||
-rw-r--r-- | CMakeLists.txt | 136 | ||||
-rw-r--r-- | Doxyfile.in | 9 | ||||
-rw-r--r-- | LICENSE | 21 | ||||
-rw-r--r-- | README.md | 44 | ||||
-rw-r--r-- | Simple-Makefile | 25 | ||||
-rwxr-xr-x | build.sh | 5 | ||||
-rw-r--r-- | cmake/Coveralls.cmake | 119 | ||||
-rw-r--r-- | cmake/CoverallsClear.cmake | 26 | ||||
-rw-r--r-- | cmake/CoverallsGenerateGcov.cmake | 429 | ||||
-rw-r--r-- | cmake/LCov.cmake | 12 | ||||
-rw-r--r-- | cn-cbor.pc.in | 11 | ||||
-rw-r--r-- | include/CMakeLists.txt | 2 | ||||
-rw-r--r-- | include/cn-cbor/cn-cbor.h | 401 | ||||
-rw-r--r-- | src/CMakeLists.txt | 50 | ||||
-rw-r--r-- | src/cbor.h | 118 | ||||
-rw-r--r-- | src/cn-cbor.c | 272 | ||||
-rw-r--r-- | src/cn-create.c | 184 | ||||
-rw-r--r-- | src/cn-encoder.c | 309 | ||||
-rw-r--r-- | src/cn-error.c | 13 | ||||
-rw-r--r-- | src/cn-get.c | 63 | ||||
-rw-r--r-- | test/CMakeLists.txt | 37 | ||||
-rw-r--r-- | test/cases.cbor | bin | 0 -> 511 bytes | |||
-rw-r--r-- | test/cbor_test.c | 430 | ||||
-rw-r--r-- | test/ctest.h | 459 | ||||
-rw-r--r-- | test/expected.out | 277 | ||||
-rw-r--r-- | test/test.c | 136 |
29 files changed, 3624 insertions, 0 deletions
diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..91dbe7f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# Matches multiple files with brace expansion notation +# Set default charset +[*.{c,h}] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2eef09e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +cntest +new.out +*.o +build diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..1d16cc7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,17 @@ +language: c +compiler: +- clang +- gcc +sudo: false +addons: + apt: + sources: + - george-edison55-precise-backports + packages: + - cmake + - cmake-data +script: +- "./build.sh all test" +notifications: + slack: + secure: WdgYxQrnFR5eu/eKygPuLjlFsuZxD9m2PLRWTLT85aj+18Gp2ooPjnI9UFdb1xY87+4InhWk6PvQU35j4bG0etPQtX+0H4T4Zdk/aD6KxgJBHIYGqtfZUMmdFfVpUH9cCPx99Jjw81mhKrxM+6rXiZdiWXuNhvbJOApRT6uxE2k= diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..195c780 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,136 @@ +# +# +# 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} ) +option ( no_floats "Build without floating point support" OFF ) + +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() + +message ( "Build type: ${CMAKE_BUILD_TYPE}" ) + +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 ( no_floats ) + add_definitions(-DCBOR_NO_FLOAT) +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 @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Carsten Bormann <cabo@tzi.org> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5820858 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +[![Build Status](https://travis-ci.org/cabo/cn-cbor.png?branch=master)](https://travis-ci.org/cabo/cn-cbor) + +# cn-cbor: A constrained node implementation of CBOR in C + +This is a constrained node implementation of [CBOR](http://cbor.io) in +C that I threw together in 2013, before the publication of +[RFC 7049](http://tools.ietf.org/html/rfc7049), to validate certain +implementability considerations. + +Its API model was inspired by +[nxjson](https://bitbucket.org/yarosla/nxjson). It turns out that +this API model actually works even better with the advantages of the +CBOR format. + +This code has been used in a number of research implementations on +constrained nodes, with resulting code sizes appreciably under 1 KiB +on ARM platforms. + +I always meant to improve the interface some more with certain API +changes, in order to get even closer to 0.5 KiB, but I ran out of +time. So here it is. If I do get around to making these changes, the +API will indeed change a bit, so please be forewarned. + +## 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 + +Building including testing: + + ./build.sh all test + +Generating a test coverage report (requires lcov[^1]; result in `build/lcov/index.html`): + + ./build.sh all coveralls coverage_report + +License: MIT + +[^1]: Installation with homebrew: `brew install lcov` diff --git a/Simple-Makefile b/Simple-Makefile new file mode 100644 index 0000000..51e877d --- /dev/null +++ b/Simple-Makefile @@ -0,0 +1,25 @@ +# 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 + +all: cntest + +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-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 + size -m cn-cbor.o + +cn-cbor.o: src/cn-cbor.c include/cn-cbor/cn-cbor.h src/cbor.h + clang $(CFLAGS) -c src/cn-cbor.c + +cn-cbor-play.zip: Makefile src/cbor.h src/cn-cbor.c include/cn-cbor/cn-cbor.h test/expected.out test/test.c + zip $@ $^ + +clean: + $(RM) cntest *.o new.out cn-cbor-play.zip diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..69dd2e9 --- /dev/null +++ b/build.sh @@ -0,0 +1,5 @@ +#!/bin/sh +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..f6b0ace --- /dev/null +++ b/cmake/CoverallsClear.cmake @@ -0,0 +1,26 @@ +# +# 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> +# + +message ( "Clearing coverage data" ) +file(REMOVE_RECURSE ${PROJECT_BINARY_DIR}/*.gcda) +file(REMOVE ${PROJECT_BINARY_DIR}/*.gcov) +file(REMOVE ${PROJECT_BINARY_DIR}/*.gcov_tmp) diff --git a/cmake/CoverallsGenerateGcov.cmake b/cmake/CoverallsGenerateGcov.cmake new file mode 100644 index 0000000..2c26bd9 --- /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} -c -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..1ac9ec3 --- /dev/null +++ b/cmake/LCov.cmake @@ -0,0 +1,12 @@ +FIND_PROGRAM( LCOV_PATH lcov ) +FIND_PROGRAM( GENHTML_PATH genhtml ) + +if (LCOV_PATH) + # message ( "lcov: ${LCOV_PATH}" ) + + add_custom_target(coverage_report + COMMAND "${LCOV_PATH}" --rc lcov_branch_coverage=1 --no-checksum --base-directory "${CMAKE_CURRENT_SOURCE_DIR}" --directory src/CMakeFiles/${PROJECT_NAME}.dir --no-external --capture --output-file ${PROJECT_NAME}.info + COMMAND "${GENHTML_PATH}" --rc genhtml_branch_coverage=1 --output-directory lcov ${PROJECT_NAME}.info + COMMAND echo "Coverage report in: file://${CMAKE_BINARY_DIR}/lcov/index.html" + ) +endif() 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 new file mode 100644 index 0000000..bf71af8 --- /dev/null +++ b/include/cn-cbor/cn-cbor.h @@ -0,0 +1,401 @@ +/** + * \file + * \brief + * CBOR parsing + */ + +#ifndef CN_CBOR_H +#define CN_CBOR_H + +#ifdef __cplusplus +extern "C" { +#endif +#ifdef EMACS_INDENTATION_HELPER +} /* Duh. */ +#endif + +#include <stdbool.h> +#include <stdint.h> +#include <unistd.h> + +/** + * All of the different kinds of CBOR values. + */ +typedef enum cn_cbor_type { + /** false */ + CN_CBOR_FALSE, + /** true */ + CN_CBOR_TRUE, + /** null */ + CN_CBOR_NULL, + /** undefined */ + CN_CBOR_UNDEF, + /** 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, + /** 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 */ + const uint8_t* bytes; + /** CN_CBOR_TEXT */ + const char* str; + /** CN_CBOR_INT */ + long sint; + /** CN_CBOR_UINT */ + unsigned long uint; + /** CN_CBOR_DOUBLE */ + double dbl; + /** 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, + /** An invalid parameter was passed to a function */ + CN_CBOR_ERR_INVALID_PARAMETER, + /** Allocation failed */ + CN_CBOR_ERR_OUT_OF_MEMORY, + /** A float was encountered during parse but the library was built without + support for float types. */ + CN_CBOR_ERR_FLOAT_NOT_SUPPORTED +} 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; + +#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 + */ +cn_cbor* cn_cbor_decode(const uint8_t *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 + */ +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 + */ +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 + */ +cn_cbor* cn_cbor_index(const cn_cbor* cb, unsigned int idx); + +/** + * Free the given CBOR structure. + * You MUST NOT try to free a cn_cbor structure with a parent (i.e., one + * that is not a root in the tree). + * + * @param[in] cb The CBOR value to free. May be NULL, or a root object. + * @param[in] CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined) + */ +void cn_cbor_free(cn_cbor* cb CBOR_CONTEXT); + +/** + * Write a CBOR value and all of the child values. + * + * @param[in] buf The buffer into which to write + * @param[in] buf_offset The offset (in bytes) from the beginning of the buffer + * to start writing at + * @param[in] buf_size The total length (in bytes) of the buffer + * @param[in] cb [description] + * @return -1 on fail, or number of bytes written + */ +ssize_t cn_cbor_encoder_write(uint8_t *buf, + size_t buf_offset, + size_t buf_size, + const cn_cbor *cb); + +/** + * Create a CBOR map. + * + * @param[in] CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined) + * @param[out] errp Error, if NULL is returned + * @return The created map, or NULL on error + */ +cn_cbor* cn_cbor_map_create(CBOR_CONTEXT_COMMA cn_cbor_errback *errp); + +/** + * Create a CBOR byte string. The data in the byte string is *not* owned + * by the CBOR object, so it is not freed automatically. + * + * @param[in] data The data + * @param[in] len The number of bytes of data + * @param[in] CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined) + * @param[out] errp Error, if NULL is returned + * @return The created object, or NULL on error + */ +cn_cbor* cn_cbor_data_create(const uint8_t* data, int len + CBOR_CONTEXT, + cn_cbor_errback *errp); + +/** + * Create a CBOR UTF-8 string. The data is not checked for UTF-8 correctness. + * The data being stored in the string is *not* owned the CBOR object, so it is + * not freed automatically. + * + * @note: Do NOT use this function with untrusted data. It calls strlen, and + * relies on proper NULL-termination. + * + * @param[in] data NULL-terminated UTF-8 string + * @param[in] CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined) + * @param[out] errp Error, if NULL is returned + * @return The created object, or NULL on error + */ +cn_cbor* cn_cbor_string_create(const char* data + CBOR_CONTEXT, + cn_cbor_errback *errp); + +/** + * Create a CBOR integer (either positive or negative). + * + * @param[in] value the value of the integer + * @param[in] CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined) + * @param[out] errp Error, if NULL is returned + * @return The created object, or NULL on error + */ +cn_cbor* cn_cbor_int_create(int64_t value + CBOR_CONTEXT, + cn_cbor_errback *errp); + +/** + * Put a CBOR object into a map with a CBOR object key. Duplicate checks are NOT + * currently performed. + * + * @param[in] cb_map The map to insert into + * @param[in] key The key + * @param[in] cb_value The value + * @param[out] errp Error + * @return True on success + */ +bool cn_cbor_map_put(cn_cbor* cb_map, + cn_cbor *cb_key, cn_cbor *cb_value, + cn_cbor_errback *errp); + +/** + * Put a CBOR object into a map with an integer key. Duplicate checks are NOT + * currently performed. + * + * @param[in] cb_map The map to insert into + * @param[in] key The integer key + * @param[in] cb_value The value + * @param[in] CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined) + * @param[out] errp Error + * @return True on success + */ +bool cn_cbor_mapput_int(cn_cbor* cb_map, + int64_t key, cn_cbor* cb_value + CBOR_CONTEXT, + cn_cbor_errback *errp); + +/** + * Put a CBOR object into a map with a string key. Duplicate checks are NOT + * currently performed. + * + * @note: do not call this routine with untrusted string data. It calls + * strlen, and requires a properly NULL-terminated key. + * + * @param[in] cb_map The map to insert into + * @param[in] key The string key + * @param[in] cb_value The value + * @param[in] CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined) + * @param[out] errp Error + * @return True on success + */ +bool cn_cbor_mapput_string(cn_cbor* cb_map, + const char* key, cn_cbor* cb_value + CBOR_CONTEXT, + cn_cbor_errback *errp); + +/** + * Create a CBOR array + * + * @param[in] CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined) + * @param[out] errp Error, if NULL is returned + * @return The created object, or NULL on error + */ +cn_cbor* cn_cbor_array_create(CBOR_CONTEXT_COMMA cn_cbor_errback *errp); + +/** + * Append an item to the end of a CBOR array. + * + * @param[in] cb_array The array into which to insert + * @param[in] cb_value The value to insert + * @param[out] errp Error + * @return True on success + */ +bool cn_cbor_array_append(cn_cbor* cb_array, + cn_cbor* cb_value, + cn_cbor_errback *errp); + +#ifdef __cplusplus +} +#endif + +#endif /* CN_CBOR_H */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..ceb0608 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,50 @@ +# +# +# compiling/installing sources for cn-cbor + +set ( cbor_srcs + cn-cbor.c + cn-create.c + cn-encoder.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 new file mode 100644 index 0000000..1859f09 --- /dev/null +++ b/src/cbor.h @@ -0,0 +1,118 @@ +#ifndef CBOR_PROTOCOL_H__ +#define CBOR_PROTOCOL_H__ + +/* The 8 major types */ +#define MT_UNSIGNED 0 +#define MT_NEGATIVE 1 +#define MT_BYTES 2 +#define MT_TEXT 3 +#define MT_ARRAY 4 +#define MT_MAP 5 +#define MT_TAG 6 +#define MT_PRIM 7 + +/* The initial bytes resulting from those */ +#define IB_UNSIGNED (MT_UNSIGNED << 5) +#define IB_NEGATIVE (MT_NEGATIVE << 5) +#define IB_BYTES (MT_BYTES << 5) +#define IB_TEXT (MT_TEXT << 5) +#define IB_ARRAY (MT_ARRAY << 5) +#define IB_MAP (MT_MAP << 5) +#define IB_TAG (MT_TAG << 5) +#define IB_PRIM (MT_PRIM << 5) + +#define IB_NEGFLAG (IB_NEGATIVE - IB_UNSIGNED) +#define IB_NEGFLAG_AS_BIT(ib) ((ib) >> 5) +#define IB_TEXTFLAG (IB_TEXT - IB_BYTES) + +#define IB_AI(ib) ((ib) & 0x1F) +#define IB_MT(ib) ((ib) >> 5) + +/* Tag numbers handled by this implementation */ +#define TAG_TIME_EPOCH 1 +#define TAG_BIGNUM 2 +#define TAG_BIGNUM_NEG 3 +#define TAG_URI 32 +#define TAG_RE 35 + +/* Initial bytes of those tag numbers */ +#define IB_TIME_EPOCH (IB_TAG | TAG_TIME_EPOCH) +#define IB_BIGNUM (IB_TAG | TAG_BIGNUM) +#define IB_BIGNUM_NEG (IB_TAG | TAG_BIGNUM_NEG) +/* TAG_URI and TAG_RE are non-immediate tags */ + +/* Simple values handled by this implementation */ +#define VAL_FALSE 20 +#define VAL_TRUE 21 +#define VAL_NIL 22 +#define VAL_UNDEF 23 + +/* Initial bytes of those simple values */ +#define IB_FALSE (IB_PRIM | VAL_FALSE) +#define IB_TRUE (IB_PRIM | VAL_TRUE) +#define IB_NIL (IB_PRIM | VAL_NIL) +#define IB_UNDEF (IB_PRIM | VAL_UNDEF) + +/* AI values with more data in head */ +#define AI_1 24 +#define AI_2 25 +#define AI_4 26 +#define AI_8 27 +#define AI_INDEF 31 +#define IB_BREAK (IB_PRIM | AI_INDEF) +/* For */ +#define IB_UNUSED (IB_TAG | AI_INDEF) + +/* Floating point initial bytes */ +#define IB_FLOAT2 (IB_PRIM | AI_2) +#define IB_FLOAT4 (IB_PRIM | AI_4) +#define IB_FLOAT8 (IB_PRIM | AI_8) + +// 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 + +#ifndef UNUSED_PARAM +#define UNUSED_PARAM(p) ((void)&(p)) +#endif + +#endif // CBOR_PROTOCOL_H__ diff --git a/src/cn-cbor.c b/src/cn-cbor.c new file mode 100644 index 0000000..a7677ae --- /dev/null +++ b/src/cn-cbor.c @@ -0,0 +1,272 @@ +#ifndef CN_CBOR_C +#define CN_CBOR_C + +#ifdef __cplusplus +extern "C" { +#endif +#ifdef EMACS_INDENTATION_HELPER +} /* Duh. */ +#endif + +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <assert.h> +#include <math.h> +#include <arpa/inet.h> // needed for ntohl (e.g.) on Linux + +#include "cn-cbor/cn-cbor.h" +#include "cbor.h" + +#define CN_CBOR_FAIL(code) do { pb->err = code; goto fail; } while(0) + +void cn_cbor_free(cn_cbor* cb CBOR_CONTEXT) { + cn_cbor* p = cb; + assert(!p || !p->parent); + while (p) { + cn_cbor* p1; + while ((p1 = p->first_child)) { /* go down */ + p = p1; + } + if (!(p1 = p->next)) { /* go up next */ + if ((p1 = p->parent)) + p1->first_child = 0; + } + CN_CBOR_FREE_CONTEXT(p); + p = p1; + } +} + +#ifndef CBOR_NO_FLOAT +static double decode_half(int half) { + int exp = (half >> 10) & 0x1f; + int mant = half & 0x3ff; + double val; + if (exp == 0) val = ldexp(mant, -24); + else if (exp != 31) val = ldexp(mant + 1024, exp - 25); + else val = mant == 0 ? INFINITY : NAN; + return half & 0x8000 ? -val : val; +} +#endif /* CBOR_NO_FLOAT */ + +/* Fix these if you can't do non-aligned reads */ +#define ntoh8p(p) (*(unsigned char*)(p)) +#define ntoh16p(p) (ntohs(*(unsigned short*)(p))) +#define ntoh32p(p) (ntohl(*(unsigned long*)(p))) +static uint64_t ntoh64p(unsigned char *p) { + uint64_t ret = ntoh32p(p); + ret <<= 32; + ret += ntoh32p(p+4); + return ret; +} + +static cn_cbor_type mt_trans[] = { + CN_CBOR_UINT, CN_CBOR_INT, + CN_CBOR_BYTES, CN_CBOR_TEXT, + CN_CBOR_ARRAY, CN_CBOR_MAP, + CN_CBOR_TAG, CN_CBOR_SIMPLE, +}; + +struct parse_buf { + unsigned char *buf; + unsigned char *ebuf; + cn_cbor_error err; +}; + +#define TAKE(pos, ebuf, n, stmt) \ + if (n > (size_t)(ebuf - pos)) \ + CN_CBOR_FAIL(CN_CBOR_ERR_OUT_OF_DATA); \ + stmt; \ + pos += n; + +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; + int ib; + unsigned int mt; + int ai; + uint64_t val; + cn_cbor* cb = NULL; +#ifndef CBOR_NO_FLOAT + union { + float f; + uint32_t u; + } u32; + union { + double d; + uint64_t u; + } u64; +#endif /* CBOR_NO_FLOAT */ + +again: + TAKE(pos, ebuf, 1, ib = ntoh8p(pos) ); + if (ib == IB_BREAK) { + if (!(parent->flags & CN_CBOR_FL_INDEF)) + CN_CBOR_FAIL(CN_CBOR_ERR_BREAK_OUTSIDE_INDEF); + switch (parent->type) { + case CN_CBOR_BYTES: case CN_CBOR_TEXT: + parent->type += 2; /* CN_CBOR_* -> CN_CBOR_*_CHUNKED */ + break; + case CN_CBOR_MAP: + if (parent->length & 1) + CN_CBOR_FAIL(CN_CBOR_ERR_ODD_SIZE_INDEF_MAP); + default:; + } + goto complete; + } + mt = ib >> 5; + ai = ib & 0x1f; + val = ai; + + cb = CN_CALLOC_CONTEXT(); + if (!cb) + CN_CBOR_FAIL(CN_CBOR_ERR_OUT_OF_MEMORY); + + cb->type = mt_trans[mt]; + + cb->parent = parent; + if (parent->last_child) { + parent->last_child->next = cb; + } else { + parent->first_child = cb; + } + parent->last_child = cb; + parent->length++; + + switch (ai) { + case AI_1: TAKE(pos, ebuf, 1, val = ntoh8p(pos)) ; break; + case AI_2: TAKE(pos, ebuf, 2, val = ntoh16p(pos)) ; break; + case AI_4: TAKE(pos, ebuf, 4, val = ntoh32p(pos)) ; break; + case AI_8: TAKE(pos, ebuf, 8, val = ntoh64p(pos)) ; break; + case 28: case 29: case 30: CN_CBOR_FAIL(CN_CBOR_ERR_RESERVED_AI); + case AI_INDEF: + if ((mt - MT_BYTES) <= MT_MAP) { + cb->flags |= CN_CBOR_FL_INDEF; + goto push; + } else { + CN_CBOR_FAIL(CN_CBOR_ERR_MT_UNDEF_FOR_INDEF); + } + } + // process content + switch (mt) { + case MT_UNSIGNED: + cb->v.uint = val; /* to do: Overflow check */ + break; + case MT_NEGATIVE: + cb->v.sint = ~val; /* to do: Overflow check */ + break; + case MT_BYTES: case MT_TEXT: + cb->v.str = (char *) pos; + cb->length = val; + TAKE(pos, ebuf, val, ;); + break; + case MT_MAP: + val <<= 1; + /* fall through */ + case MT_ARRAY: + if ((cb->v.count = val)) { + cb->flags |= CN_CBOR_FL_COUNT; + goto push; + } + break; + case MT_TAG: + cb->v.uint = val; + goto push; + case MT_PRIM: + switch (ai) { + case VAL_FALSE: cb->type = CN_CBOR_FALSE; break; + case VAL_TRUE: cb->type = CN_CBOR_TRUE; break; + case VAL_NIL: cb->type = CN_CBOR_NULL; break; + case VAL_UNDEF: cb->type = CN_CBOR_UNDEF; break; + case AI_2: +#ifndef CBOR_NO_FLOAT + cb->type = CN_CBOR_DOUBLE; + cb->v.dbl = decode_half(val); +#else /* CBOR_NO_FLOAT */ + CN_CBOR_FAIL(CN_CBOR_ERR_FLOAT_NOT_SUPPORTED); +#endif /* CBOR_NO_FLOAT */ + break; + case AI_4: +#ifndef CBOR_NO_FLOAT + cb->type = CN_CBOR_DOUBLE; + u32.u = val; + cb->v.dbl = u32.f; +#else /* CBOR_NO_FLOAT */ + CN_CBOR_FAIL(CN_CBOR_ERR_FLOAT_NOT_SUPPORTED); +#endif /* CBOR_NO_FLOAT */ + break; + case AI_8: +#ifndef CBOR_NO_FLOAT + cb->type = CN_CBOR_DOUBLE; + u64.u = val; + cb->v.dbl = u64.d; +#else /* CBOR_NO_FLOAT */ + CN_CBOR_FAIL(CN_CBOR_ERR_FLOAT_NOT_SUPPORTED); +#endif /* CBOR_NO_FLOAT */ + break; + default: cb->v.uint = val; + } + } +fill: /* emulate loops */ + if (parent->flags & CN_CBOR_FL_INDEF) { + if (parent->type == CN_CBOR_BYTES || parent->type == CN_CBOR_TEXT) + if (cb->type != parent->type) + CN_CBOR_FAIL(CN_CBOR_ERR_WRONG_NESTING_IN_INDEF_STRING); + goto again; + } + if (parent->flags & CN_CBOR_FL_COUNT) { + if (--parent->v.count) + goto again; + } + /* so we are done filling parent. */ +complete: /* emulate return from call */ + if (parent == top_parent) { + if (pos != ebuf) /* XXX do this outside */ + CN_CBOR_FAIL(CN_CBOR_ERR_NOT_ALL_DATA_CONSUMED); + pb->buf = pos; + return cb; + } + cb = parent; + parent = parent->parent; + goto fill; +push: /* emulate recursive call */ + parent = cb; + goto again; +fail: + pb->buf = pos; + return 0; +} + +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; + 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 CBOR_CONTEXT_PARAM); + } +//fail: + if (errp) { + errp->err = pb.err; + errp->pos = pb.buf - (unsigned char *)buf; + } + return NULL; + } + return ret; +} + +#ifdef __cplusplus +} +#endif + +#endif /* CN_CBOR_C */ diff --git a/src/cn-create.c b/src/cn-create.c new file mode 100644 index 0000000..bc448e9 --- /dev/null +++ b/src/cn-create.c @@ -0,0 +1,184 @@ +#ifndef CN_CREATE_C +#define CN_CREATE_C + +#ifdef __cplusplus +extern "C" { +#endif + +#include <string.h> +#include <stdlib.h> + +#include "cn-cbor/cn-cbor.h" +#include "cbor.h" + +#define INIT_CB(v) \ + if (errp) {errp->err = CN_CBOR_NO_ERROR;} \ + (v) = CN_CALLOC_CONTEXT(); \ + if (!(v)) { if (errp) {errp->err = CN_CBOR_ERR_OUT_OF_MEMORY;} return NULL; } + +cn_cbor* cn_cbor_map_create(CBOR_CONTEXT_COMMA cn_cbor_errback *errp) +{ + cn_cbor* ret; + INIT_CB(ret); + + ret->type = CN_CBOR_MAP; + ret->flags |= CN_CBOR_FL_COUNT; + + return ret; +} + +cn_cbor* cn_cbor_data_create(const uint8_t* data, int len + CBOR_CONTEXT, + cn_cbor_errback *errp) +{ + cn_cbor* ret; + INIT_CB(ret); + + ret->type = CN_CBOR_BYTES; + ret->length = len; + ret->v.str = (const char*) data; // TODO: add v.ustr to the union? + + return ret; +} + +cn_cbor* cn_cbor_string_create(const char* data + CBOR_CONTEXT, + cn_cbor_errback *errp) +{ + cn_cbor* ret; + INIT_CB(ret); + + ret->type = CN_CBOR_TEXT; + ret->length = strlen(data); + ret->v.str = data; + + return ret; +} + +cn_cbor* cn_cbor_int_create(int64_t value + CBOR_CONTEXT, + cn_cbor_errback *errp) +{ + cn_cbor* ret; + INIT_CB(ret); + + if (value<0) { + ret->type = CN_CBOR_INT; + ret->v.sint = value; + } else { + ret->type = CN_CBOR_UINT; + ret->v.uint = value; + } + + return ret; +} + +static bool _append_kv(cn_cbor *cb_map, cn_cbor *key, cn_cbor *val) +{ + //Connect key and value and insert them into the map. + key->parent = cb_map; + key->next = val; + val->parent = cb_map; + val->next = NULL; + + if(cb_map->last_child) { + cb_map->last_child->next = key; + } else { + cb_map->first_child = key; + } + cb_map->last_child = val; + cb_map->length += 2; + return true; +} + +bool cn_cbor_map_put(cn_cbor* cb_map, + cn_cbor *cb_key, cn_cbor *cb_value, + cn_cbor_errback *errp) +{ + //Make sure input is a map. Otherwise + if(!cb_map || !cb_key || !cb_value || cb_map->type != CN_CBOR_MAP) + { + if (errp) {errp->err = CN_CBOR_ERR_INVALID_PARAMETER;} + return false; + } + + return _append_kv(cb_map, cb_key, cb_value); +} + +bool cn_cbor_mapput_int(cn_cbor* cb_map, + int64_t key, cn_cbor* cb_value + CBOR_CONTEXT, + cn_cbor_errback *errp) +{ + cn_cbor* cb_key; + + //Make sure input is a map. Otherwise + if(!cb_map || !cb_value || cb_map->type != CN_CBOR_MAP) + { + if (errp) {errp->err = CN_CBOR_ERR_INVALID_PARAMETER;} + return false; + } + + cb_key = cn_cbor_int_create(key CBOR_CONTEXT_PARAM, errp); + if (!cb_key) { return false; } + return _append_kv(cb_map, cb_key, cb_value); +} + +bool cn_cbor_mapput_string(cn_cbor* cb_map, + const char* key, cn_cbor* cb_value + CBOR_CONTEXT, + cn_cbor_errback *errp) +{ + cn_cbor* cb_key; + + //Make sure input is a map. Otherwise + if(!cb_map || !cb_value || cb_map->type != CN_CBOR_MAP) + { + if (errp) {errp->err = CN_CBOR_ERR_INVALID_PARAMETER;} + return false; + } + + cb_key = cn_cbor_string_create(key CBOR_CONTEXT_PARAM, errp); + if (!cb_key) { return false; } + return _append_kv(cb_map, cb_key, cb_value); +} + +cn_cbor* cn_cbor_array_create(CBOR_CONTEXT_COMMA cn_cbor_errback *errp) +{ + cn_cbor* ret; + INIT_CB(ret); + + ret->type = CN_CBOR_ARRAY; + ret->flags |= CN_CBOR_FL_COUNT; + + return ret; +} + +bool cn_cbor_array_append(cn_cbor* cb_array, + cn_cbor* cb_value, + cn_cbor_errback *errp) +{ + //Make sure input is an array. + if(!cb_array || !cb_value || cb_array->type != CN_CBOR_ARRAY) + { + if (errp) {errp->err = CN_CBOR_ERR_INVALID_PARAMETER;} + return false; + } + + cb_value->parent = cb_array; + cb_value->next = NULL; + if(cb_array->last_child) { + cb_array->last_child->next = cb_value; + } else { + cb_array->first_child = cb_value; + } + cb_array->last_child = cb_value; + cb_array->length++; + return true; +} + +#ifdef __cplusplus +} +#endif + +#endif /* CN_CBOR_C */ diff --git a/src/cn-encoder.c b/src/cn-encoder.c new file mode 100644 index 0000000..8593b39 --- /dev/null +++ b/src/cn-encoder.c @@ -0,0 +1,309 @@ +#ifndef CN_ENCODER_C +#define CN_ENCODER_C + +#ifdef __cplusplus +extern "C" { +#endif +#ifdef EMACS_INDENTATION_HELPER +} /* Duh. */ +#endif + +#include <arpa/inet.h> +#include <string.h> +#include <strings.h> +#include <stdbool.h> +#include <assert.h> + +#include "cn-cbor/cn-cbor.h" +#include "cbor.h" + +#define hton8p(p) (*(uint8_t*)(p)) +#define hton16p(p) (htons(*(uint16_t*)(p))) +#define hton32p(p) (htonl(*(uint32_t*)(p))) +static uint64_t hton64p(const uint8_t *p) { + /* TODO: does this work on both BE and LE systems? */ + uint64_t ret = hton32p(p); + ret <<= 32; + ret |= hton32p(p+4); + return ret; +} + +typedef struct _write_state +{ + uint8_t *buf; + ssize_t offset; + ssize_t size; +} cn_write_state; + +#define ensure_writable(sz) if ((ws->offset<0) || (ws->offset + (sz) >= ws->size)) { \ + ws->offset = -1; \ + return; \ +} + +#define write_byte_and_data(b, data, sz) \ +ws->buf[ws->offset++] = (b); \ +memcpy(ws->buf+ws->offset, (data), (sz)); \ +ws->offset += sz; + +#define write_byte(b) \ +ws->buf[ws->offset++] = (b); \ + +#define write_byte_ensured(b) \ +ensure_writable(1); \ +write_byte(b); \ + +static uint8_t _xlate[] = { + IB_FALSE, /* CN_CBOR_FALSE */ + IB_TRUE, /* CN_CBOR_TRUE */ + IB_NIL, /* CN_CBOR_NULL */ + IB_UNDEF, /* CN_CBOR_UNDEF */ + IB_UNSIGNED, /* CN_CBOR_UINT */ + IB_NEGATIVE, /* CN_CBOR_INT */ + IB_BYTES, /* CN_CBOR_BYTES */ + IB_TEXT, /* CN_CBOR_TEXT */ + IB_BYTES, /* CN_CBOR_BYTES_CHUNKED */ + IB_TEXT, /* CN_CBOR_TEXT_CHUNKED */ + IB_ARRAY, /* CN_CBOR_ARRAY */ + IB_MAP, /* CN_CBOR_MAP */ + IB_TAG, /* CN_CBOR_TAG */ + IB_PRIM, /* CN_CBOR_SIMPLE */ + 0xFF, /* CN_CBOR_DOUBLE */ + 0xFF /* CN_CBOR_INVALID */ +}; + +static inline bool is_indefinite(const cn_cbor *cb) +{ + return (cb->flags & CN_CBOR_FL_INDEF) != 0; +} + +static void _write_positive(cn_write_state *ws, cn_cbor_type typ, uint64_t val) { + uint8_t ib; + + assert((size_t)typ < sizeof(_xlate)); + + ib = _xlate[typ]; + if (ib == 0xFF) { + ws->offset = -1; + return; + } + + if (val < 24) { + ensure_writable(1); + write_byte(ib | val); + } else if (val < 256) { + ensure_writable(2); + write_byte(ib | 24); + write_byte((uint8_t)val); + } else if (val < 65536) { + uint16_t be16 = (uint16_t)val; + ensure_writable(3); + be16 = hton16p(&be16); + write_byte_and_data(ib | 25, (const void*)&be16, 2); + } else if (val < 0x100000000L) { + uint32_t be32 = (uint32_t)val; + ensure_writable(5); + be32 = hton32p(&be32); + write_byte_and_data(ib | 26, (const void*)&be32, 4); + } else { + uint64_t be64; + ensure_writable(9); + be64 = hton64p((const uint8_t*)&val); + write_byte_and_data(ib | 27, (const void*)&be64, 8); + } +} + +#ifndef CBOR_NO_FLOAT +static void _write_double(cn_write_state *ws, double val) +{ + float float_val = val; + if (float_val == val) { /* 32 bits is enough and we aren't NaN */ + uint32_t be32; + uint16_t be16, u16; + union { + float f; + uint32_t u; + } u32; + u32.f = float_val; + if ((u32.u & 0x1FFF) == 0) { /* worth trying half */ + int s16 = (u32.u >> 16) & 0x8000; + int exp = (u32.u >> 23) & 0xff; + int mant = u32.u & 0x7fffff; + if (exp == 0 && mant == 0) + ; /* 0.0, -0.0 */ + else if (exp >= 113 && exp <= 142) /* normalized */ + s16 += ((exp - 112) << 10) + (mant >> 13); + else if (exp >= 103 && exp < 113) { /* denorm, exp16 = 0 */ + if (mant & ((1 << (126 - exp)) - 1)) + goto float32; /* loss of precision */ + s16 += ((mant + 0x800000) >> (126 - exp)); + } else if (exp == 255 && mant == 0) { /* Inf */ + s16 += 0x7c00; + } else + goto float32; /* loss of range */ + + ensure_writable(3); + u16 = s16; + be16 = hton16p((const uint8_t*)&u16); + + write_byte_and_data(IB_PRIM | 25, (const void*)&be16, 2); + return; + } + float32: + ensure_writable(5); + be32 = hton32p((const uint8_t*)&u32.u); + + write_byte_and_data(IB_PRIM | 26, (const void*)&be32, 4); + + } else if (val != val) { /* NaN -- we always write a half NaN*/ + ensure_writable(3); + write_byte_and_data(IB_PRIM | 25, (const void*)"\x7e\x00", 2); + } else { + uint64_t be64; + /* Copy the same problematic implementation from the decoder. */ + union { + double d; + uint64_t u; + } u64; + + u64.d = val; + + ensure_writable(9); + be64 = hton64p((const uint8_t*)&u64.u); + + write_byte_and_data(IB_PRIM | 27, (const void*)&be64, 8); + + } +} +#endif /* CBOR_NO_FLOAT */ + +// TODO: make public? +typedef void (*cn_visit_func)(const cn_cbor *cb, int depth, void *context); +static void _visit(const cn_cbor *cb, + cn_visit_func visitor, + cn_visit_func breaker, + void *context) +{ + const cn_cbor *p = cb; + int depth = 0; + while (p) + { +visit: + visitor(p, depth, context); + if (p->first_child) { + p = p->first_child; + depth++; + } else{ + // Empty indefinite + if (is_indefinite(p)) { + breaker(p->parent, depth, context); + } + if (p->next) { + p = p->next; + } else { + while (p->parent) { + depth--; + if (is_indefinite(p->parent)) { + breaker(p->parent, depth, context); + } + if (p->parent->next) { + p = p->parent->next; + goto visit; + } + p = p->parent; + } + return; + } + } + } +} + +#define CHECK(st) (st); \ +if (ws->offset < 0) { return; } + +void _encoder_visitor(const cn_cbor *cb, int depth, void *context) +{ + cn_write_state *ws = context; + UNUSED_PARAM(depth); + + switch (cb->type) { + case CN_CBOR_ARRAY: + if (is_indefinite(cb)) { + write_byte_ensured(IB_ARRAY | AI_INDEF); + } else { + CHECK(_write_positive(ws, CN_CBOR_ARRAY, cb->length)); + } + break; + case CN_CBOR_MAP: + if (is_indefinite(cb)) { + write_byte_ensured(IB_MAP | AI_INDEF); + } else { + CHECK(_write_positive(ws, CN_CBOR_MAP, cb->length/2)); + } + break; + case CN_CBOR_BYTES_CHUNKED: + case CN_CBOR_TEXT_CHUNKED: + write_byte_ensured(_xlate[cb->type] | AI_INDEF); + break; + + case CN_CBOR_TEXT: + case CN_CBOR_BYTES: + CHECK(_write_positive(ws, cb->type, cb->length)); + ensure_writable(cb->length); + memcpy(ws->buf+ws->offset, cb->v.str, cb->length); + ws->offset += cb->length; + break; + + case CN_CBOR_FALSE: + case CN_CBOR_TRUE: + case CN_CBOR_NULL: + case CN_CBOR_UNDEF: + write_byte_ensured(_xlate[cb->type]); + break; + + case CN_CBOR_TAG: + case CN_CBOR_UINT: + case CN_CBOR_SIMPLE: + CHECK(_write_positive(ws, cb->type, cb->v.uint)); + break; + + case CN_CBOR_INT: + assert(cb->v.sint < 0); + CHECK(_write_positive(ws, CN_CBOR_INT, ~(cb->v.sint))); + break; + + case CN_CBOR_DOUBLE: +#ifndef CBOR_NO_FLOAT + CHECK(_write_double(ws, cb->v.dbl)); +#endif /* CBOR_NO_FLOAT */ + break; + + case CN_CBOR_INVALID: + ws->offset = -1; + break; + } +} + +void _encoder_breaker(const cn_cbor *cb, int depth, void *context) +{ + cn_write_state *ws = context; + UNUSED_PARAM(cb); + UNUSED_PARAM(depth); + write_byte_ensured(IB_BREAK); +} + +ssize_t cn_cbor_encoder_write(uint8_t *buf, + size_t buf_offset, + size_t buf_size, + const cn_cbor *cb) +{ + cn_write_state ws = { buf, buf_offset, buf_size }; + _visit(cb, _encoder_visitor, _encoder_breaker, &ws); + if (ws.offset < 0) { return -1; } + return ws.offset - buf_offset; +} + +#ifdef __cplusplus +} +#endif + +#endif /* CN_CBOR_C */ diff --git a/src/cn-error.c b/src/cn-error.c new file mode 100644 index 0000000..4953cc9 --- /dev/null +++ b/src/cn-error.c @@ -0,0 +1,13 @@ +const char *cn_cbor_error_str[] = { + "CN_CBOR_NO_ERROR", + "CN_CBOR_ERR_OUT_OF_DATA", + "CN_CBOR_ERR_NOT_ALL_DATA_CONSUMED", + "CN_CBOR_ERR_ODD_SIZE_INDEF_MAP", + "CN_CBOR_ERR_BREAK_OUTSIDE_INDEF", + "CN_CBOR_ERR_MT_UNDEF_FOR_INDEF", + "CN_CBOR_ERR_RESERVED_AI", + "CN_CBOR_ERR_WRONG_NESTING_IN_INDEF_STRING", + "CN_CBOR_ERR_INVALID_PARAMETER", + "CN_CBOR_ERR_OUT_OF_MEMORY", + "CN_CBOR_ERR_FLOAT_NOT_SUPPORTED" +}; diff --git a/src/cn-get.c b/src/cn-get.c new file mode 100644 index 0000000..79d3d72 --- /dev/null +++ b/src/cn-get.c @@ -0,0 +1,63 @@ +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include "cn-cbor/cn-cbor.h" + +cn_cbor* cn_cbor_mapget_int(const cn_cbor* cb, int key) { + cn_cbor* cp; + assert(cb); + for (cp = cb->first_child; cp && cp->next; cp = cp->next->next) { + switch(cp->type) { + case CN_CBOR_UINT: + if (cp->v.uint == (unsigned long)key) { + return cp->next; + } + break; + case CN_CBOR_INT: + if (cp->v.sint == (long)key) { + return cp->next; + } + break; + default: + ; // skip non-integer keys + } + } + return NULL; +} + +cn_cbor* cn_cbor_mapget_string(const cn_cbor* cb, const char* key) { + cn_cbor *cp; + int keylen; + assert(cb); + assert(key); + keylen = strlen(key); + for (cp = cb->first_child; cp && cp->next; cp = cp->next->next) { + switch(cp->type) { + case CN_CBOR_TEXT: // fall-through + case CN_CBOR_BYTES: + if (keylen != cp->length) { + continue; + } + if (memcmp(key, cp->v.str, keylen) == 0) { + return cp->next; + } + default: + ; // skip non-string keys + } + } + return NULL; +} + +cn_cbor* cn_cbor_index(const cn_cbor* cb, unsigned int idx) { + cn_cbor *cp; + unsigned int i = 0; + assert(cb); + for (cp = cb->first_child; cp; cp = cp->next) { + if (i == idx) { + return cp; + } + i++; + } + return NULL; +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..3181a8d --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,37 @@ +# +# +# 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 ) + +if (APPLE) + # difftest uses Apple-specific memory tests + 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") + +endif() diff --git a/test/cases.cbor b/test/cases.cbor Binary files differnew file mode 100644 index 0000000..db6d126 --- /dev/null +++ b/test/cases.cbor diff --git a/test/cbor_test.c b/test/cbor_test.c new file mode 100644 index 0000000..3326497 --- /dev/null +++ b/test/cbor_test.c @@ -0,0 +1,430 @@ +/* + * 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 +#define CONTEXT_NULL_COMMA NULL, +#else +#define CONTEXT_NULL +#define CONTEXT_NULL_COMMA +#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"); + ASSERT_STR(cn_cbor_error_str[CN_CBOR_ERR_FLOAT_NOT_SUPPORTED], "CN_CBOR_ERR_FLOAT_NOT_SUPPORTED"); +} + +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" + "80", // [] + "8100", // [0] + "820102", // [1,2] + "818100", // [[0]] + "a1616100", // {"a":0} + "d8184100", // tag + "f4", // false + "f5", // true + "f6", // null + "f7", // undefined + "f8ff", // simple(255) +#ifndef CBOR_NO_FLOAT + "f93c00", // 1.0 + "f9bc00", // -1.0 + "f903ff", // 6.097555160522461e-05 + "f90400", // 6.103515625e-05 + "f907ff", // 0.00012201070785522461 + "f90800", // 0.0001220703125 + "fa47800000", // 65536.0 + "fb3ff199999999999a", // 1.1 + "f97e00", // NaN +#endif /* CBOR_NO_FLOAT */ + "5f42010243030405ff", // (_ h'0102', h'030405') + "7f61616161ff", // (_ "a", "a") + "9fff", // [_ ] + "9f9f9fffffff", // [_ [_ [_ ]]] + "9f009f00ff00ff", // [_ 0, [_ 0], 0] + "bf61610161629f0203ffff", // {_ "a": 1, "b": [_ 2, 3]} + }; + 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)); + err.err = CN_CBOR_NO_ERROR; + cb = cn_cbor_decode(b.ptr, b.sz CONTEXT_NULL, &err); + //CTEST_LOG("%s: %s", tests[i], cn_cbor_error_str[err.err]); + ASSERT_EQUAL(err.err, CN_CBOR_NO_ERROR); + ASSERT_NOT_NULL(cb); + + enc_sz = cn_cbor_encoder_write(encoded, 0, sizeof(encoded), cb); + ASSERT_DATA(b.ptr, b.sz, encoded, enc_sz); + free(b.ptr); + cn_cbor_free(cb CONTEXT_NULL); + } +} + + +CTEST(cbor, parse_normalize) +{ + cn_cbor_errback err; + char *basic_tests[] = { + "00", "00", // 0 + "1800", "00", + "1818", "1818", + "190000", "00", + "190018", "1818", + "1a00000000", "00", + "1b0000000000000000", "00", + "20", "20", // -1 + "3800", "20", + "c600", "c600", // 6(0) (undefined tag) + "d80600", "c600", + "d9000600", "c600", + }; + char *float_tests[] = { + "fb3ff0000000000000", "f93c00", // 1.0 + "fbbff0000000000000", "f9bc00", // -1.0 + "fb40f86a0000000000", "fa47c35000", // 100000.0 + "fb7ff8000000000000", "f97e00", // NaN + "fb3e70000000000000", "f90001", // 5.960464477539063e-08 + "fb3e78000000000000", "fa33c00000", // 8.940696716308594e-08 + "fb3e80000000000000", "f90002", // 1.1920928955078125e-07 + }; + cn_cbor *cb; + buffer b, b2; + size_t i; + unsigned char encoded[1024]; + ssize_t enc_sz; + + for (i=0; i<sizeof(basic_tests)/sizeof(char*); i+=2) { + ASSERT_TRUE(parse_hex(basic_tests[i], &b)); + ASSERT_TRUE(parse_hex(basic_tests[i+1], &b2)); + err.err = CN_CBOR_NO_ERROR; + cb = cn_cbor_decode(b.ptr, b.sz CONTEXT_NULL, &err); + CTEST_LOG("%s: %s", basic_tests[i], cn_cbor_error_str[err.err]); + ASSERT_EQUAL(err.err, CN_CBOR_NO_ERROR); + ASSERT_NOT_NULL(cb); + + enc_sz = cn_cbor_encoder_write(encoded, 0, sizeof(encoded), cb); + ASSERT_DATA(b2.ptr, b2.sz, encoded, enc_sz); + free(b.ptr); + free(b2.ptr); + cn_cbor_free(cb CONTEXT_NULL); + } + + for (i=0; i<sizeof(float_tests)/sizeof(char*); i+=2) { + ASSERT_TRUE(parse_hex(float_tests[i], &b)); + ASSERT_TRUE(parse_hex(float_tests[i+1], &b2)); + err.err = CN_CBOR_NO_ERROR; + cb = cn_cbor_decode(b.ptr, b.sz CONTEXT_NULL, &err); + CTEST_LOG("%s: %s", float_tests[i], cn_cbor_error_str[err.err]); +#ifndef CBOR_NO_FLOAT + ASSERT_EQUAL(err.err, CN_CBOR_NO_ERROR); + ASSERT_NOT_NULL(cb); +#else /* CBOR_NO_FLOAT */ + ASSERT_EQUAL(err.err, CN_CBOR_ERR_FLOAT_NOT_SUPPORTED); + ASSERT_NULL(cb); +#endif /* CBOR_NO_FLOAT */ + + /* enc_sz = cn_cbor_encoder_write(encoded, 0, sizeof(encoded), cb); */ + /* ASSERT_DATA(b2.ptr, b2.sz, encoded, enc_sz); */ + free(b.ptr); + free(b2.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}, + }; + cn_cbor *cb; + buffer b; + size_t i; + uint8_t buf[10]; + cn_cbor inv = {CN_CBOR_INVALID, 0, {0}, 0, NULL, NULL, NULL, NULL}; + + ASSERT_EQUAL(-1, cn_cbor_encoder_write(buf, 0, sizeof(buf), &inv)); + + 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) +{ +#ifndef CBOR_NO_FLOAT + cn_cbor_errback err; + char *tests[] = { + "f90001", // 5.960464477539063e-08 + "f9c400", // -4.0 + "fa47c35000", // 100000.0 + "f97e00", // Half NaN, half beast + "f9fc00", // -Inf + "f97c00", // Inf + }; + 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 = cn_cbor_encoder_write(encoded, 0, sizeof(encoded), cb); + ASSERT_DATA(b.ptr, b.sz, encoded, enc_sz); + + free(b.ptr); + cn_cbor_free(cb CONTEXT_NULL); + } +#endif /* CBOR_NO_FLOAT */ +} + +CTEST(cbor, getset) +{ + buffer b; + cn_cbor *cb; + cn_cbor *val; + cn_cbor_errback err; + + ASSERT_TRUE(parse_hex("a40000436363630262626201616100", &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("a3616100006161206162", &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); +} + +CTEST(cbor, create) +{ + cn_cbor_errback err; + const cn_cbor* val; + const char* data = "abc"; + cn_cbor *cb_map = cn_cbor_map_create(CONTEXT_NULL_COMMA &err); + cn_cbor *cb_int; + cn_cbor *cb_data; + + ASSERT_NOT_NULL(cb_map); + ASSERT_TRUE(err.err == CN_CBOR_NO_ERROR); + + cb_int = cn_cbor_int_create(256 CONTEXT_NULL, &err); + ASSERT_NOT_NULL(cb_int); + ASSERT_TRUE(err.err == CN_CBOR_NO_ERROR); + + cb_data = cn_cbor_data_create((const uint8_t*)data, 4 CONTEXT_NULL, &err); + ASSERT_NOT_NULL(cb_data); + ASSERT_TRUE(err.err == CN_CBOR_NO_ERROR); + + cn_cbor_mapput_int(cb_map, 5, cb_int CONTEXT_NULL, &err); + ASSERT_TRUE(err.err == CN_CBOR_NO_ERROR); + ASSERT_TRUE(cb_map->length == 2); + + cn_cbor_mapput_int(cb_map, -7, cb_data CONTEXT_NULL, &err); + ASSERT_TRUE(err.err == CN_CBOR_NO_ERROR); + ASSERT_TRUE(cb_map->length == 4); + + cn_cbor_mapput_string(cb_map, "foo", + cn_cbor_string_create(data CONTEXT_NULL, &err) + CONTEXT_NULL, &err); + ASSERT_TRUE(err.err == CN_CBOR_NO_ERROR); + ASSERT_TRUE(cb_map->length == 6); + + cn_cbor_map_put(cb_map, + cn_cbor_string_create("bar" CONTEXT_NULL, &err), + cn_cbor_string_create("qux" CONTEXT_NULL, &err), + &err); + ASSERT_TRUE(err.err == CN_CBOR_NO_ERROR); + ASSERT_TRUE(cb_map->length == 8); + + val = cn_cbor_mapget_int(cb_map, 5); + ASSERT_NOT_NULL(val); + ASSERT_TRUE(val->v.sint == 256); + + val = cn_cbor_mapget_int(cb_map, -7); + ASSERT_NOT_NULL(val); + ASSERT_STR(val->v.str, "abc"); + + cn_cbor_free(cb_map CONTEXT_NULL); +} + +CTEST(cbor, map_errors) +{ + cn_cbor_errback err; + cn_cbor *ci; + ci = cn_cbor_int_create(65536, CONTEXT_NULL_COMMA &err); + cn_cbor_mapput_int(ci, -5, NULL, CONTEXT_NULL_COMMA &err); + ASSERT_EQUAL(err.err, CN_CBOR_ERR_INVALID_PARAMETER); + cn_cbor_mapput_string(ci, "foo", NULL, CONTEXT_NULL_COMMA &err); + ASSERT_EQUAL(err.err, CN_CBOR_ERR_INVALID_PARAMETER); + cn_cbor_map_put(ci, NULL, NULL, &err); +} + +CTEST(cbor, array) +{ + cn_cbor_errback err; + cn_cbor *a = cn_cbor_array_create(CONTEXT_NULL_COMMA &err); + ASSERT_NOT_NULL(a); + ASSERT_TRUE(err.err == CN_CBOR_NO_ERROR); + ASSERT_EQUAL(a->length, 0); + + cn_cbor_array_append(a, cn_cbor_int_create(256, CONTEXT_NULL_COMMA &err), &err); + ASSERT_TRUE(err.err == CN_CBOR_NO_ERROR); + ASSERT_EQUAL(a->length, 1); + + cn_cbor_array_append(a, cn_cbor_string_create("five", CONTEXT_NULL_COMMA &err), &err); + ASSERT_TRUE(err.err == CN_CBOR_NO_ERROR); + ASSERT_EQUAL(a->length, 2); +} + +CTEST(cbor, array_errors) +{ + cn_cbor_errback err; + cn_cbor *ci = cn_cbor_int_create(12, CONTEXT_NULL_COMMA &err); + cn_cbor_array_append(NULL, ci, &err); + ASSERT_EQUAL(err.err, CN_CBOR_ERR_INVALID_PARAMETER); + cn_cbor_array_append(ci, NULL, &err); + ASSERT_EQUAL(err.err, CN_CBOR_ERR_INVALID_PARAMETER); +} + +CTEST(cbor, create_encode) +{ + cn_cbor *map; + cn_cbor *cdata; + char data[] = "data"; + unsigned char encoded[1024]; + ssize_t enc_sz; + + map = cn_cbor_map_create(CONTEXT_NULL_COMMA NULL); + ASSERT_NOT_NULL(map); + + cdata = cn_cbor_data_create((uint8_t*)data, sizeof(data)-1, CONTEXT_NULL_COMMA NULL); + ASSERT_NOT_NULL(cdata); + + ASSERT_TRUE(cn_cbor_mapput_int(map, 0, cdata, CONTEXT_NULL_COMMA NULL)); + enc_sz = cn_cbor_encoder_write(encoded, 0, sizeof(encoded), map); + ASSERT_EQUAL(7, enc_sz); +} 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/expected.out b/test/expected.out new file mode 100644 index 0000000..3ed0d13 --- /dev/null +++ b/test/expected.out @@ -0,0 +1,277 @@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +511 +[ + 0 + 1 + 10 + 23 + 24 + 25 + 100 + 1000 + 1000000 + 1000000000000 + 18446744073709551615 + 2( + h'010000000000000000' + ) + OVERFLOW + 3( + h'010000000000000000' + ) + -1 + -10 + -100 + -1000 + 0.000000e+00 + -0.000000e+00 + 1.000000e+00 + 1.100000e+00 + 1.500000e+00 + 6.550400e+04 + 1.000000e+05 + 3.402823e+38 + 1.000000e+300 + 5.960464e-08 + 6.103516e-05 + -4.000000e+00 + -4.100000e+00 + inf + nan + -inf + inf + nan + -inf + inf + nan + -inf + false + true + null + simple(23) + simple(16) + simple(24) + simple(255) + 0( + "2013-03-21T20:04:00Z" + ) + 1( + 1363896240 + ) + 1( + 1.363896e+09 + ) + 23( + h'01020304' + ) + 24( + h'6449455446' + ) + 32( + "http://www.example.com" + ) + h'' + h'01020304' + "" + "a" + "IETF" + ""\" + "ü" + "水" + "𐅑" + [ + ] + [ + 1 + 2 + 3 + ] + [ + 1 + [ + 2 + 3 + ] + [ + 4 + 5 + ] + ] + [ + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + ] + { + } + { + 1 + 2 + 3 + 4 + } + { + "a" + 1 + "b" + [ + 2 + 3 + ] + } + [ + "a" + { + "b" + "c" + } + ] + { + "a" + "A" + "b" + "B" + "c" + "C" + "d" + "D" + "e" + "E" + } + (_ + + h'0102' + h'030405' + ) + (_ + "strea" + "ming" + ) + [ + ] + [ + 1 + [ + 2 + 3 + ] + [ + 4 + 5 + ] + ] + [ + 1 + [ + 2 + 3 + ] + [ + 4 + 5 + ] + ] + [ + 1 + [ + 2 + 3 + ] + [ + 4 + 5 + ] + ] + [ + 1 + [ + 2 + 3 + ] + [ + 4 + 5 + ] + ] + [ + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + ] + { + "a" + 1 + "b" + [ + 2 + 3 + ] + } + [ + "a" + { + "b" + "c" + } + ] + { + "Fun" + true + "Amt" + -2 + } +] + +CN_CBOR_ERR_BREAK_OUTSIDE_INDEF at 1 +CN_CBOR_ERR_MT_UNDEF_FOR_INDEF at 1 +CN_CBOR_ERR_NOT_ALL_DATA_CONSUMED at 1 +CN_CBOR_ERR_OUT_OF_DATA at 1 +CN_CBOR_ERR_RESERVED_AI at 1 +CN_CBOR_ERR_ODD_SIZE_INDEF_MAP at 3 +CN_CBOR_ERR_WRONG_NESTING_IN_INDEF_STRING at 2 + +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ diff --git a/test/test.c b/test/test.c new file mode 100644 index 0000000..d24992f --- /dev/null +++ b/test/test.c @@ -0,0 +1,136 @@ +#include <unistd.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 unsigned char* load_file(const char* filepath, unsigned char **end) { + struct stat st; + if (stat(filepath, &st)==-1) { + ERROR("can't find file", filepath); + return 0; + } + int fd=open(filepath, O_RDONLY); + if (fd==-1) { + ERROR("can't open file", filepath); + return 0; + } + 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); + return 0; + } + close(fd); + text[st.st_size]='\0'; + *end = text + st.st_size; + return text; +} + +static void dump(const cn_cbor* cb, char* out, char** end, int indent) { + if (!cb) + goto done; + int i; + cn_cbor* cp; + char finchar = ')'; /* most likely */ + +#define CPY(s, l) memcpy(out, s, l); out += l; +#define OUT(s) CPY(s, sizeof(s)-1) +#define PRF(f, a) out += sprintf(out, f, a) + + for (i = 0; i < indent; i++) *out++ = ' '; + switch (cb->type) { + case CN_CBOR_TEXT_CHUNKED: OUT("(_\n"); goto sequence; + case CN_CBOR_BYTES_CHUNKED: OUT("(_\n\n"); goto sequence; + case CN_CBOR_TAG: PRF("%ld(\n", cb->v.sint); goto sequence; + case CN_CBOR_ARRAY: finchar = ']'; OUT("[\n"); goto sequence; + case CN_CBOR_MAP: finchar = '}'; OUT("{\n"); goto sequence; + sequence: + for (cp = cb->first_child; cp; cp = cp->next) { + dump(cp, out, &out, indent+2); + } + for (i=0; i<indent; i++) *out++ = ' '; + *out++ = finchar; + break; + case CN_CBOR_BYTES: OUT("h'"); + for (i=0; i<cb->length; i++) + PRF("%02x", cb->v.str[i] & 0xff); + *out++ = '\''; + break; + case CN_CBOR_TEXT: *out++ = '"'; + CPY(cb->v.str, cb->length); /* should escape stuff */ + *out++ = '"'; + break; + case CN_CBOR_NULL: OUT("null"); break; + case CN_CBOR_TRUE: OUT("true"); break; + case CN_CBOR_FALSE: OUT("false"); break; + case CN_CBOR_UNDEF: OUT("simple(23)"); break; + case CN_CBOR_INT: PRF("%ld", cb->v.sint); break; + case CN_CBOR_UINT: PRF("%lu", cb->v.uint); break; + case CN_CBOR_DOUBLE: PRF("%e", cb->v.dbl); break; + case CN_CBOR_SIMPLE: PRF("simple(%ld)", cb->v.sint); break; + default: PRF("???%d???", cb->type); break; + } + *out++ = '\n'; +done: + *end = out; +} + + +const char *err_name[] = { + "CN_CBOR_NO_ERROR", + "CN_CBOR_ERR_OUT_OF_DATA", + "CN_CBOR_ERR_NOT_ALL_DATA_CONSUMED", + "CN_CBOR_ERR_ODD_SIZE_INDEF_MAP", + "CN_CBOR_ERR_BREAK_OUTSIDE_INDEF", + "CN_CBOR_ERR_MT_UNDEF_FOR_INDEF", + "CN_CBOR_ERR_RESERVED_AI", + "CN_CBOR_ERR_WRONG_NESTING_IN_INDEF_STRING", + "CN_CBOR_ERR_OUT_OF_MEMORY", + "CN_CBOR_ERR_FLOAT_NOT_SUPPORTED", +}; + +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 CBOR_CONTEXT_PARAM, &back); + if (ret) + printf("oops 1"); + printf("%s at %d\n", err_name[back.err], back.pos); +} + +int main() { + char buf[100000]; + unsigned char *end; + char *bufend; + unsigned char *s = load_file("cases.cbor", &end); + printf("%zd\n", end-s); + cn_cbor *cb = cn_cbor_decode(s, end-s CBOR_CONTEXT_PARAM, 0); + if (cb) { + dump(cb, buf, &bufend, 0); + *bufend = 0; + printf("%s\n", buf); + cn_cbor_free(cb CBOR_CONTEXT_PARAM); + cb = 0; /* for leaks testing */ + } + 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"); +} + +/* cn-cbor.c:112: CN_CBOR_FAIL("out of memory"); */ |