Merge branch 'main' into dev
diff --git a/.appveyor.yml b/.appveyor.yml
index f673552..46876a8 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -1,4 +1,4 @@
-version: 0.6-build-{build}
+version: build-{build}
 pull_requests:
   do_not_increment_build_number: true
 image:
@@ -13,16 +13,18 @@
     set path=%PATH%;%QTDIR%\bin
 build_script:
 - cmd: >-
-    nmake -f Makefile.nmake -nologo CFLAGS="-W3 -Os -MDd"
+    cmake -S. -Bbuild -GNinja -DWITH_CBOR2JSON=OFF -DBUILD_TESTING=ON -DCMAKE_C_FLAGS="-W3 -Os -MDd" -DCMAKE_CXX_FLAGS="-W3 -O2 -MDd"
 
-    cd tests
+    ninja -C build
 
-    qmake CONFIG-=release CONFIG+=debug
-
-    nmake -nologo -s
 test_script:
 - cmd: >-
-    nmake -s -nologo TESTARGS=-silent check
+    ctest --test-dir build --output-on-failure --output-junit ctest.junitxml
+
+after_test:
+- cmd: >-
+    curl -F file=@build/ctest.junitxml https://ci.appveyor.com/api/testresults/junit/%APPVEYOR_JOB_ID%
+
 artifacts:
-- path: lib\tinycbor.lib
+#- path: build\tinycbor.lib
 deploy: off
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 998935a..1951296 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -27,59 +27,68 @@
       fail-fast: false
       matrix:
         os: [ ubuntu-latest ]
-        build_cfg: [
-          { "name": "gcc-no-math",
-            "flags":
-              '{ "QMAKESPEC": "linux-gcc-no-math",
-                 "EVAL": "export CXX=false && touch math.h float.h",
-                 "CFLAGS": "-ffreestanding -DCBOR_NO_FLOATING_POINT -Os",
-                 "LDFLAGS": "-Wl,--no-undefined",
-                 "LDLIBS": ""
-              }',
-          },
-          { "name": "gcc-freestanding",
-            "flags":
-              '{ "QMAKESPEC": "linux-gcc-freestanding",
-                 "EVAL": "export CXX=false",
-                 "CFLAGS": "-ffreestanding -Os",
-                 "LDFLAGS": "-Wl,--no-undefined -lm"
-              }',
-          },
-          { "name": "clang",
-            "flags":
-              '{ "QMAKESPEC": "linux-clang",
-                 "EVAL": "export CC=clang && export CXX=clang++",
-                 "CFLAGS": "-Oz",
-                 "LDFLAGS": "-Wl,--no-undefined -lm",
-                 "QMAKEFLAGS": "-config release",
-                 "MAKEFLAGS": "-s",
-                 "TESTARGS": "-silent"
-              }',
-          },
-          { "name": "linux-g++",
-            "flags":
-              '{ "QMAKESPEC": "linux-g++",
-                 "EVAL": "export CC=gcc && export CXX=g++",
-                 "CFLAGS": "-Os",
-                 "LDFLAGS": "-Wl,--no-undefined -lm",
-                 "QMAKEFLAGS": "-config release",
-                 "QT_NO_CPU_FEATURE": "rdrnd"
-              }'
-          }
-        ]
+        build_cfg:
+        - name: gcc-no-math
+          cmakeflags: >-
+            -DCMAKE_C_FLAGS="-Os -Werror"
+            -DWITH_FLOATING_POINT=OFF
+            -DWITH_FREESTANDING=ON
+        - name: gcc-freestanding
+          cmakeflags: >-
+            -DCMAKE_C_FLAGS="-Os -Werror"
+            -DWITH_FREESTANDING=ON
+        - name: gcc-small
+          cmakeflags: >-
+            -DBUILD_TESTING=OFF
+            -DCMAKE_C_FLAGS="-Os -Werror"
+        - name: clang-small
+          cmakeflags: >-
+            -DBUILD_TESTING=OFF
+            -DCMAKE_C_COMPILER=clang
+            -DCMAKE_C_FLAGS="-Oz -g -Werror"
+        - name: clang
+          cmakeflags: >-
+            -DBUILD_EXAMPLES=ON
+            -DBUILD_SHARED_LIBS=ON
+            -DCMAKE_BUILD_TYPE=Debug
+            -DCMAKE_C_COMPILER=clang
+            -DCMAKE_C_FLAGS_DEBUG="-Werror"
+            -DCMAKE_CXX_COMPILER=clang++
+            -DCMAKE_CXX_FLAGS_DEBUG="-Werror"
+        - name: linux-g++
+          cmakeflags: >-
+            -DBUILD_EXAMPLES=ON
+            -DBUILD_SHARED_LIBS=ON
+            -DCMAKE_BUILD_TYPE=Debug
+            -DCMAKE_C_COMPILER=gcc
+            -DCMAKE_C_FLAGS_DEBUG="-Werror"
+            -DCMAKE_CXX_COMPILER=g++
+            -DCMAKE_CXX_FLAGS_DEBUG="-Werror"
         include:
-          - os: macos-13
-            build_cfg: { "name": "clang",
-                         "flags":
-                           '{ "QMAKESPEC": "macx-clang",
-                              "EVAL": "export CC=clang && export CXX=clang++",
-                              "CFLAGS": "-Oz",
-                              "QMAKEFLAGS": "-config debug",
-                              "MAKEFLAGS": "-s",
-                              "TESTARGS": "-silent",
-                              "PATH": "/usr/local/opt/qt/bin:$PATH"
-                            }'
-                        }
+        - os: macos-latest
+          build_cfg:
+            name: clang-small
+            cmakeflags: >-
+              -DBUILD_TESTING=OFF
+              -DBUILD_TOOLS=OFF
+              -DCMAKE_C_COMPILER=clang
+              -DCMAKE_C_FLAGS="-Oz -g -Werror"
+              -DCMAKE_CXX_COMPILER=clang++
+              -DCMAKE_CXX_FLAGS="-O2 -g -Werror"
+        - os: macos-15-intel
+          build_cfg:
+            name: clang
+            cmakeflags: >-
+              -DBUILD_EXAMPLES=ON
+              -DBUILD_SHARED_LIBS=ON
+              -DCMAKE_BUILD_TYPE=Debug
+              -DCMAKE_C_COMPILER=clang
+              -DCMAKE_C_FLAGS_DEBUG="-Werror -fsanitize=address"
+              -DCMAKE_CXX_COMPILER=clang++
+              -DCMAKE_CXX_FLAGS_DEBUG="-Werror -fsanitize=address"
+              -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address"
+              -DCMAKE_MODULE_LINKER_FLAGS="-fsanitize=address"
+
 
     # Default job name is too long to be visible in the "Checks" tab.
     name: ${{ matrix.os }}/${{ matrix.build_cfg.name }}
@@ -87,7 +96,7 @@
     runs-on: ${{ matrix.os }}
     steps:
     - name: Clone tinycbor
-      uses: actions/checkout@v4
+      uses: actions/checkout@v6
 
     - name: install Linux software
       if: matrix.os == 'ubuntu-latest'
@@ -97,44 +106,36 @@
         sudo apt-get update
         sudo apt-get install -y --no-install-recommends \
           doxygen \
-          jq \
+          cmake \
           libc6-dbg \
           libcjson-dev \
           libfuntools-dev \
-          qtbase5-dev
+          ninja-build \
+          qt6-base-dev
 
     - name: install macOS software
       if: runner.os == 'macOS'
       run: |
-        # Doxygen 1.9.7 is broken with ifdefs again, install 1.9.4 which works.
-        wget https://raw.githubusercontent.com/Homebrew/homebrew-core/41828ee36b96e35b63b2a4c8cfc2df2c3728944a/Formula/doxygen.rb
-        brew install doxygen.rb
-        rm doxygen.rb
-        brew install qt cjson
+        brew install -q \
+             cjson \
+             cmake \
+             ninja \
+             qt
+
+    - name: Compile
+      run: |
+        set -x
+        cmake -S. -Bbuild -GNinja -DBUILD_TESTING=ON \
+              ${{ matrix.build_cfg.cmakeflags }}
+        ninja -C build -v
+        if [[ -f build/libtinycbor.a ]]; then
+            size build/libtinycbor.a | tee sizes
+        fi
 
     - name: Execute tests
       run: |
-        set -x
-        PATH=`echo /opt/qt*/bin`:$PATH
-        eval $(echo '${{ matrix.build_cfg.flags }}' | jq -r 'to_entries[] | "\(.key)=\"\(.value)\""')
-        eval "$EVAL"
-        # FIXME: remove -Wno-error-line below.
-        export CFLAGS="$CFLAGS -Wno-error=implicit-function-declaration"
-        make OUT=.config V=1 -s -f Makefile.configure configure && cat .config
-        make -k \
-            CFLAGS="$CFLAGS -march=native -g1 -Wall -Wextra -Werror" \
-            CPPFLAGS="-DNDEBUG -DCBOR_ENCODER_WRITER_CONTROL=-1 -DCBOR_PARSER_READER_CONTROL=-1" \
-            lib/libtinycbor.a
-        size lib/libtinycbor.a | tee sizes
-        make -s clean
-        make -k \
-            CFLAGS="$CFLAGS -O0 -g" \
-            LDFLAGS="$LDFLAGS" ${LDLIBS+LDLIBS="$LDLIBS"}
-        grep -q freestanding-pass .config || make \
-            QMAKEFLAGS="$QMAKEFLAGS QMAKE_CXX=$CXX" \
-            tests/Makefile
-        grep -q freestanding-pass .config || \
-            (cd tests && make TESTARGS=-silent check -k \
-            TESTRUNNER=`which valgrind 2>/dev/null`)
-        make -s clean
-        ! [ $BUILD_DOCS ] || ./scripts/update-docs.sh
+        ctest --output-on-failure --test-dir build
+
+    - name: Build docs
+      if: matrix.build_cfg.docs
+      run: ./scripts/update-docs.sh
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 4df21cb..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,92 +0,0 @@
-env:
-  - BUILD_DOCS=false
-jobs:
-  include:
-    - # only build docs on main
-      if: branch = main
-      env: BUILD_DOCS=true
-
-language: cpp
-matrix:
-  include:
-    - os: linux
-      dist: xenial
-      addons:
-        apt:
-          sources:
-            - sourceline: 'ppa:beineri/opt-qt-5.12.1-xenial'
-          packages:
-            - qt512base valgrind
-            - doxygen
-      env:
-        - QMAKESPEC=linux-g++
-        - EVAL="CC=gcc && CXX=g++"
-        - CFLAGS="-Os"
-        - LDFLAGS="-Wl,--no-undefined -lm"
-        - QMAKEFLAGS="-config release"
-        - QT_NO_CPU_FEATURE=rdrnd
-    - os: linux
-      dist: xenial
-      addons:
-        apt:
-          sources:
-            - sourceline: 'ppa:beineri/opt-qt-5.12.1-xenial'
-          packages:
-            - qt512base
-      env:
-        - QMAKESPEC=linux-clang
-        - EVAL="CC=clang && CXX=clang++"
-        - CFLAGS="-Oz"
-        - LDFLAGS="-Wl,--no-undefined -lm"
-        - QMAKEFLAGS="-config release"
-        - MAKEFLAGS=-s
-        - TESTARGS=-silent
-    - os: linux
-      dist: xenial
-      env:
-        - QMAKESPEC=linux-gcc-freestanding
-        - EVAL="CXX=false"
-        - CFLAGS="-ffreestanding -Os"
-        - LDFLAGS="-Wl,--no-undefined -lm"
-    - os: linux
-      dist: xenial
-      env:
-        - QMAKESPEC=linux-gcc-no-math
-        - EVAL="CXX=false && touch src/math.h src/float.h"
-        - CFLAGS="-ffreestanding -DCBOR_NO_FLOATING_POINT -Os"
-        - LDFLAGS="-Wl,--no-undefined"
-        - LDLIBS=""
-    - os: osx
-      env:
-        - QMAKESPEC=macx-clang
-        - CFLAGS="-Oz"
-        - QMAKEFLAGS="-config debug"
-        - MAKEFLAGS=-s
-        - TESTARGS=-silent
-        - PATH=/usr/local/opt/qt5/bin:$PATH
-install:
-  - if [ "${TRAVIS_OS_NAME}" != "linux" ]; then
-        brew update;
-        brew install qt5;
-    fi
-script:
-  - PATH=`echo /opt/qt*/bin`:$PATH
-  - eval "$EVAL"
-  - make -s -f Makefile.configure configure | tee .config
-  - make -k
-        CFLAGS="$CFLAGS -march=native -g1 -Wall -Wextra -Werror"
-        CPPFLAGS="-DNDEBUG -DCBOR_ENCODER_WRITER_CONTROL=-1 -DCBOR_PARSER_READER_CONTROL=-1"
-        lib/libtinycbor.a
-  - size lib/libtinycbor.a | tee sizes
-  - make -s clean
-  - make -k
-        CFLAGS="$CFLAGS -O0 -g"
-        LDFLAGS="$LDFLAGS" ${LDLIBS+LDLIBS="$LDLIBS"}
-  - grep -q freestanding-pass .config || make
-        QMAKEFLAGS="$QMAKEFLAGS QMAKE_CXX=$CXX"
-        tests/Makefile
-  - grep -q freestanding-pass .config ||
-        (cd tests && make TESTARGS=-silent check -k
-        TESTRUNNER=`which valgrind 2>/dev/null`)
-  - make -s clean
-  - ! [ $BUILD_DOCS ] || ./scripts/update-docs.sh
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..e185474
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,190 @@
+# /****************************************************************************
+# **
+# ** Copyright (C) 2015 Intel Corporation
+# **
+# ** 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.
+# **
+# ****************************************************************************/
+
+cmake_minimum_required(VERSION 3.10)
+
+project(tinycbor LANGUAGES C VERSION 7.0)
+
+# Set path to additional cmake scripts
+set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH})
+
+set(TARGETS_EXPORT_NAME "TinyCBOR-targets")
+
+option(WITH_FLOATING_POINT "Use floating point code in TinyCBOR" ON)
+option(WITH_FREESTANDING "Compile TinyCBOR in C freestanding mode" OFF)
+if(WITH_FLOATING_POINT AND NOT WITH_FREESTANDING)
+  option(WITH_CBOR2JSON "Compile code to convert from CBOR to JSON" ON)
+  option(BUILD_EXAMPLES "Compile the TinyCBOR examples" OFF)
+  option(BUILD_TOOLS "Compile the TinyCBOR tools" ON)
+endif()
+
+# Include additional modules that are used unconditionally
+include(GNUInstallDirs)
+include(GenerateExportHeader)
+include(CheckLinkerFlag)
+include(CheckSymbolExists)
+
+add_library(tinycbor
+  src/cborencoder.c
+  src/cborencoder_close_container_checked.c
+  src/cborerrorstrings.c
+  src/cborparser.c
+  src/cborpretty.c
+  src/cborvalidation.c
+  src/cbor.h
+)
+if(WITH_FREESTANDING)
+  target_compile_options(tinycbor PUBLIC
+    $<$<NOT:$<C_COMPILER_ID:MSVC>>:-ffreestanding>
+  )
+else()
+  target_sources(tinycbor PRIVATE
+    src/cborparser_dup_string.c
+    src/cborpretty_stdio.c
+  )
+  if(WITH_CBOR2JSON)
+    target_sources(tinycbor PRIVATE
+      src/cbortojson.c
+    )
+  endif()
+endif()
+if(WITH_FLOATING_POINT)
+  target_sources(tinycbor PRIVATE
+    src/cborencoder_float.c
+    src/cborparser_float.c
+  )
+  if(NOT WIN32)
+    target_link_libraries(tinycbor m)
+  endif()
+else()
+  target_compile_definitions(tinycbor PUBLIC CBOR_NO_FLOATING_POINT)
+endif()
+
+set_target_properties(tinycbor PROPERTIES
+  # Force this library to link as C and compile as C99, to ensure we
+  # don't use something of a newer language level.
+  LINKER_LANGUAGE C
+  C_EXTENSIONS OFF
+  C_STANDARD 99
+
+  # Set version and output name
+  VERSION "0.${PROJECT_VERSION}"
+  SOVERSION "0"
+)
+if(BUILD_SHARED_LIBS)
+  set_target_properties(tinycbor PROPERTIES C_VISIBILITY_PRESET hidden)
+
+  # Check if the linker supports "-z defs" (a.k.a "--no-undefined")
+  check_linker_flag(C "-Wl,-z,defs" HAVE_NO_UNDEFINED)
+  if(HAVE_NO_UNDEFINED)
+    target_link_options(tinycbor PRIVATE "-Wl,-z,defs")
+  endif()
+else()
+  target_compile_definitions(tinycbor PUBLIC CBOR_STATIC_DEFINE)
+endif()
+
+# Enable warnings
+target_compile_options(tinycbor PRIVATE
+  $<$<C_COMPILER_ID:MSVC>:-W3>
+  $<$<NOT:$<C_COMPILER_ID:MSVC>>:
+    -Wall -Wextra
+    -Werror=format-security
+    -Werror=incompatible-pointer-types
+    -Werror=implicit-function-declaration
+    -Werror=int-conversion
+  >
+)
+
+# Generate export macros
+generate_export_header(tinycbor
+  BASE_NAME "cbor"
+  EXPORT_MACRO_NAME "CBOR_API"
+  EXPORT_FILE_NAME "tinycbor-export.h"
+)
+
+# Generate version header
+configure_file(src/tinycbor-version.h.in tinycbor-version.h)
+
+# Generate pkgconfig file
+configure_file(tinycbor.pc.in tinycbor.pc @ONLY)
+
+# Check for open_memstream and store the result in HAVE_OPEN_MEMSTREAM
+check_symbol_exists(open_memstream stdio.h HAVE_OPEN_MEMSTREAM)
+check_symbol_exists(funopen stdio.h HAVE_OPEN_FUNOPEN)
+check_symbol_exists(fopencookie stdio.h HAVE_OPEN_FOPENCOOKIE)
+
+if(NOT HAVE_OPEN_MEMSTREAM)
+  if (HAVE_OPEN_FUNOPEN)
+    message(STATUS "implementing open_memstream using funopen()")
+    target_compile_definitions(tinycbor PRIVATE HAVE_OPEN_FUNOPEN)
+    target_sources(tinycbor PRIVATE src/open_memstream.c)
+  elseif (HAVE_OPEN_FOPENCOOKIE)
+    message(STATUS "implementing open_memstream using fopencookie()")
+    target_compile_definitions(tinycbor PRIVATE HAVE_OPEN_FOPENCOOKIE)
+    target_sources(tinycbor PRIVATE src/open_memstream.c)
+  else()
+    target_compile_definitions(tinycbor PRIVATE WITHOUT_OPEN_MEMSTREAM)
+    message(WARNING "funopen and fopencookie unavailable, open_memstream can not be implemented and conversion to JSON will not work properly!")
+  endif()
+endif()
+
+target_include_directories(tinycbor
+  PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>"
+  PUBLIC "$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}>"
+  PUBLIC "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
+)
+
+install(FILES
+  ${PROJECT_SOURCE_DIR}/src/cbor.h
+  ${PROJECT_BINARY_DIR}/tinycbor-version.h
+  ${PROJECT_BINARY_DIR}/tinycbor-export.h
+  DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/tinycbor
+)
+install(FILES
+  ${CMAKE_BINARY_DIR}/tinycbor.pc
+  DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
+)
+install(
+  TARGETS tinycbor
+  EXPORT "${TARGETS_EXPORT_NAME}"
+  ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}   # import library
+  LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}   # .so files are libraries
+  RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}   # .dll files are binaries
+  INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}  # this does not actually install anything (but used by downstream projects)
+)
+
+set(PROJECT_LIBRARIES TinyCBOR)
+include(PackageConfig)
+
+if(BUILD_EXAMPLES)
+  add_subdirectory(examples)
+endif()
+if(BUILD_TESTING)
+  enable_language(CXX)
+  enable_testing()
+  add_subdirectory(tests)
+endif()
+if(BUILD_TOOLS)
+  add_subdirectory(tools)
+endif()
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 6df1655..0000000
--- a/Makefile
+++ /dev/null
@@ -1,242 +0,0 @@
-# Variables:
-prefix = /usr/local
-exec_prefix = $(prefix)
-bindir = $(exec_prefix)/bin
-libdir = $(exec_prefix)/lib
-includedir = $(prefix)/include
-pkgconfigdir = $(libdir)/pkgconfig
-
-CFLAGS = -Wall -Wextra
-LDFLAGS_GCSECTIONS = -Wl,--gc-sections
-LDFLAGS += $(if $(gc_sections-pass),$(LDFLAGS_GCSECTIONS))
-LDLIBS = -lm
-
-GIT_ARCHIVE = git archive --prefix="$(PACKAGE)/" -9
-INSTALL = install
-INSTALL_DATA = $(INSTALL) -m 644
-INSTALL_PROGRAM = $(INSTALL) -m 755
-QMAKE = qmake
-MKDIR = mkdir -p
-RMDIR = rmdir
-SED = sed
-
-# Our sources
-TINYCBOR_HEADERS = src/cbor.h src/cborjson.h src/tinycbor-version.h
-TINYCBOR_FREESTANDING_SOURCES = \
-	src/cborerrorstrings.c \
-	src/cborencoder.c \
-	src/cborencoder_close_container_checked.c \
-	src/cborencoder_float.c \
-	src/cborparser.c \
-	src/cborparser_float.c \
-	src/cborpretty.c \
-#
-CBORDUMP_SOURCES = tools/cbordump/cbordump.c
-
-BUILD_SHARED = $(shell file -L /bin/sh 2>/dev/null | grep -q ELF && echo 1)
-BUILD_STATIC = 1
-
-ifneq ($(BUILD_STATIC),1)
-ifneq ($(BUILD_SHARED),1)
-  $(error error: BUILD_STATIC and BUILD_SHARED can not be both disabled)
-endif
-endif
-
-INSTALL_TARGETS += $(bindir)/cbordump
-ifeq ($(BUILD_SHARED),1)
-BINLIBRARY=lib/libtinycbor.so
-INSTALL_TARGETS += $(libdir)/libtinycbor.so.$(VERSION)
-endif
-ifeq ($(BUILD_STATIC),1)
-BINLIBRARY=lib/libtinycbor.a
-INSTALL_TARGETS += $(libdir)/libtinycbor.a
-endif
-INSTALL_TARGETS += $(pkgconfigdir)/tinycbor.pc
-INSTALL_TARGETS += $(TINYCBOR_HEADERS:src/%=$(includedir)/tinycbor/%)
-
-# setup VPATH
-MAKEFILE := $(lastword $(MAKEFILE_LIST))
-SRCDIR := $(dir $(MAKEFILE))
-VPATH = $(SRCDIR):$(SRCDIR)/src
-
-# Our version
-GIT_DIR := $(strip $(shell git -C $(SRCDIR). rev-parse --git-dir 2> /dev/null))
-VERSION = $(shell cat $(SRCDIR)VERSION)
-SOVERSION = $(shell cut -f1-2 -d. $(SRCDIR)VERSION)
-PACKAGE = tinycbor-$(VERSION)
-
--include .config
-
-ifeq ($(wildcard .config),)
-    $(info .config file not yet created)
-endif
-
-ifeq ($(freestanding-pass),1)
-TINYCBOR_SOURCES = $(TINYCBOR_FREESTANDING_SOURCES)
-else
-TINYCBOR_SOURCES = \
-	$(TINYCBOR_FREESTANDING_SOURCES) \
-	src/cborparser_dup_string.c \
-	src/cborpretty_stdio.c \
-	src/cbortojson.c \
-	src/cborvalidation.c \
-#
-# if open_memstream is unavailable on the system, try to implement our own
-# version using funopen or fopencookie
-ifeq ($(open_memstream-pass),)
-  ifeq ($(funopen-pass)$(fopencookie-pass),)
-    CFLAGS += -DWITHOUT_OPEN_MEMSTREAM
-    ifeq ($(wildcard .config),.config)
-        $(warning warning: funopen and fopencookie unavailable, open_memstream can not be implemented and conversion to JSON will not work properly!)
-    endif
-  else
-    TINYCBOR_SOURCES += src/open_memstream.c
-  endif
-endif
-endif
-
-# json2cbor depends on an external library (cjson)
-ifneq ($(cjson-pass)$(system-cjson-pass),)
-  JSON2CBOR_SOURCES = tools/json2cbor/json2cbor.c
-  INSTALL_TARGETS += $(bindir)/json2cbor
-  ifeq ($(system-cjson-pass),1)
-    LDFLAGS_CJSON = -lcjson
-  else
-    JSON2CBOR_SOURCES += src/cjson/cJSON.c
-    json2cbor_CCFLAGS = -I$(SRCDIR)src/cjson
-  endif
-endif
-
-# Rules
-all: .config \
-	$(if $(subst 0,,$(BUILD_STATIC)),lib/libtinycbor.a) \
-	$(if $(subst 0,,$(BUILD_SHARED)),lib/libtinycbor.so) \
-	$(if $(freestanding-pass),,bin/cbordump) \
-	tinycbor.pc
-all: $(if $(JSON2CBOR_SOURCES),bin/json2cbor)
-check: tests/Makefile | $(BINLIBRARY)
-	$(MAKE) -C tests check
-silentcheck: | $(BINLIBRARY)
-	TESTARGS=-silent $(MAKE) -f $(MAKEFILE) -s check
-configure: .config
-.config: Makefile.configure
-	$(MAKE) -f $(SRCDIR)Makefile.configure OUT='$@' configure
-
-lib/libtinycbor-freestanding.a: $(TINYCBOR_FREESTANDING_SOURCES:.c=.o)
-	@$(MKDIR) -p lib
-	$(AR) cqs $@ $^
-
-lib/libtinycbor.a: $(TINYCBOR_SOURCES:.c=.o)
-	@$(MKDIR) -p lib
-	$(AR) cqs $@ $^
-
-lib/libtinycbor.so: $(TINYCBOR_SOURCES:.c=.pic.o)
-	@$(MKDIR) -p lib
-	$(CC) -shared -Wl,-soname,libtinycbor.so.$(SOVERSION) -o lib/libtinycbor.so.$(VERSION) $(LDFLAGS) $^ $(LDLIBS)
-	cd lib ; ln -sf libtinycbor.so.$(VERSION) libtinycbor.so ; ln -sf libtinycbor.so.$(VERSION) libtinycbor.so.$(SOVERSION)
-
-bin/cbordump: $(CBORDUMP_SOURCES:.c=.o) $(BINLIBRARY)
-	@$(MKDIR) -p bin
-	$(CC) -o $@ $(LDFLAGS) $^ $(LDLIBS)
-
-bin/json2cbor: $(JSON2CBOR_SOURCES:.c=.o) $(BINLIBRARY)
-	@$(MKDIR) -p bin
-	$(CC) -o $@ $(LDFLAGS) $^ $(LDFLAGS_CJSON) $(LDLIBS)
-
-tinycbor.pc: tinycbor.pc.in
-	$(SED) > $@ < $< \
-		-e 's,@prefix@,$(prefix),' \
-		-e 's,@exec_prefix@,$(exec_prefix),' \
-		-e 's,@libdir@,$(libdir),' \
-		-e 's,@includedir@,$(includedir),' \
-		-e 's,@version@,$(VERSION),'
-
-tests/Makefile: tests/tests.pro
-	$(QMAKE) $(QMAKEFLAGS) -o $@ $<
-
-$(PACKAGE).tar.gz: | .git
-	GIT_DIR=$(SRCDIR).git $(GIT_ARCHIVE) --format=tar.gz -o "$(PACKAGE).tar.gz" HEAD
-$(PACKAGE).zip: | .git
-	GIT_DIR=$(SRCDIR).git $(GIT_ARCHIVE) --format=zip -o "$(PACKAGE).zip" HEAD
-
-$(DESTDIR)$(libdir)/%: lib/%
-	$(INSTALL) -d $(@D)
-	$(INSTALL_DATA) $< $@
-$(DESTDIR)$(bindir)/%: bin/%
-	$(INSTALL) -d $(@D)
-	$(INSTALL_PROGRAM) $< $@
-$(DESTDIR)$(pkgconfigdir)/%: %
-	$(INSTALL) -d $(@D)
-	$(INSTALL_DATA) $< $@
-$(DESTDIR)$(includedir)/tinycbor/%: src/%
-	$(INSTALL) -d $(@D)
-	$(INSTALL_DATA) $< $@
-
-install-strip:
-	$(MAKE) -f $(MAKEFILE) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s' install
-
-install: $(INSTALL_TARGETS:%=$(DESTDIR)%)
-ifeq ($(BUILD_SHARED),1)
-	ln -sf libtinycbor.so.$(VERSION) $(DESTDIR)$(libdir)/libtinycbor.so
-	ln -sf libtinycbor.so.$(VERSION) $(DESTDIR)$(libdir)/libtinycbor.so.$(SOVERSION)
-endif
-
-uninstall:
-	$(RM) $(INSTALL_TARGETS:%=$(DESTDIR)%)
-	$(RM) $(DESTDIR)$(libdir)/libtinycbor.so
-	$(RM) $(DESTDIR)$(libdir)/libtinycbor.so.$(SOVERSION)
-
-mostlyclean:
-	$(RM) $(TINYCBOR_SOURCES:.c=.o)
-	$(RM) $(TINYCBOR_SOURCES:.c=.pic.o)
-	$(RM) $(CBORDUMP_SOURCES:.c=.o)
-
-clean: mostlyclean
-	$(RM) bin/cbordump
-	$(RM) bin/json2cbor
-	$(RM) lib/libtinycbor.a
-	$(RM) lib/libtinycbor-freestanding.a
-	$(RM) tinycbor.pc
-	$(RM) lib/libtinycbor.so*
-	test -e tests/Makefile && $(MAKE) -C tests clean || :
-
-distclean: clean
-	test -e tests/Makefile && $(MAKE) -C tests distclean || :
-
-docs:
-	cd $(SRCDIR)src && VERSION=$(VERSION) doxygen $(SRCDIR)/../Doxyfile
-
-dist: $(PACKAGE).tar.gz $(PACKAGE).zip
-distcheck: .git
-	-$(RM) -r $${TMPDIR-/tmp}/tinycbor-distcheck
-	GIT_DIR=$(SRCDIR).git git archive --prefix=tinycbor-distcheck/ --format=tar HEAD | tar -xf - -C $${TMPDIR-/tmp}
-	cd $${TMPDIR-/tmp}/tinycbor-distcheck && $(MAKE) silentcheck
-	$(RM) -r $${TMPDIR-/tmp}/tinycbor-distcheck
-
-tag: distcheck
-	@cd $(SRCDIR). && perl scripts/maketag.pl
-
-.PHONY: all check silentcheck configure install uninstall
-.PHONY: mostlyclean clean distclean
-.PHONY: docs dist distcheck release
-.SECONDARY:
-
-cflags := $(CPPFLAGS) -I$(SRCDIR)src
-cflags += -std=gnu99 $(CFLAGS)
-
-ifneq ($(DISABLE_WERROR),1)
-cflags += \
-	$(shell $(CC) -S -Werror -Wdiscarded-qualifiers -o - -xc /dev/null >/dev/null 2>&1 && echo -Werror=discarded-qualifiers) \
-	-Werror=incompatible-pointer-types \
-	-Werror=implicit-function-declaration \
-	-Werror=int-conversion
-endif
-
-%.o: %.c
-	@test -d $(@D) || $(MKDIR) $(@D)
-	$(CC) $(cflags) $($(basename $(notdir $@))_CCFLAGS) -c -o $@ $<
-%.pic.o: %.c
-	@test -d $(@D) || $(MKDIR) $(@D)
-	$(CC) $(cflags) -fPIC $($(basename $(notdir $@))_CCFLAGS) -c -o $@ $<
-
--include src/*.d
diff --git a/Makefile.nmake b/Makefile.nmake
index 6afe517..fa79fa2 100644
--- a/Makefile.nmake
+++ b/Makefile.nmake
@@ -1,6 +1,6 @@
 CFLAGS = -W3
 
-TINYCBOR_HEADERS = src\cbor.h src\cborjson.h
+TINYCBOR_HEADERS = src\cbor.h src\cborjson.h src\tinycbor-export.h
 TINYCBOR_SOURCES = \
 	src\cborerrorstrings.c \
 	src\cborencoder.c \
@@ -24,7 +24,7 @@
 	src\cborpretty_stdio.obj \
 	src\cborvalidation.obj
 
-all: lib\tinycbor.lib
+all: src/tinycbor-export.h lib\tinycbor.lib
 check: tests\Makefile lib\tinycbor.lib
 	cd tests & $(MAKE) check
 silentcheck:
@@ -32,12 +32,16 @@
 tests\Makefile: tests\tests.pro
 	qmake -o $@ $**
 
+src\tinycbor-export.h: src\tinycbor-export.h.in
+	copy $** $@
+
 lib\tinycbor.lib: $(TINYCBOR_OBJS)
 	-if not exist lib\NUL md lib
 	lib -nologo /out:$@ $**
 
 mostlyclean:
 	-del $(TINYCBOR_OBJS)
+	-del src\tinycbor-export.h
 clean: mostlyclean
 	-del lib\tinycbor.lib
 	if exist tests\Makefile (cd tests & $(MAKE) clean)
diff --git a/VERSION b/VERSION
deleted file mode 100644
index a918a2a..0000000
--- a/VERSION
+++ /dev/null
@@ -1 +0,0 @@
-0.6.0
diff --git a/cmake/PackageConfig.cmake b/cmake/PackageConfig.cmake
new file mode 100644
index 0000000..73cc913
--- /dev/null
+++ b/cmake/PackageConfig.cmake
@@ -0,0 +1,35 @@
+# This cmake code creates the configuration that is found and used by
+# find_package() of another cmake project
+
+# get lower and upper case project name for the configuration files
+
+# configure and install the configuration files
+include(CMakePackageConfigHelpers)
+
+configure_package_config_file(
+  "${PROJECT_SOURCE_DIR}/cmake/project-config.cmake.in"
+  "${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config.cmake"
+  INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
+  #PATH_VARS CMAKE_INSTALL_DIR
+)
+
+write_basic_package_version_file(
+  "${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake"
+  VERSION ${PROJECT_VERSION}
+  COMPATIBILITY SameMinorVersion
+)
+
+install(FILES
+  "${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config.cmake"
+  "${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake"
+  COMPONENT devel
+  DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
+)
+
+if (PROJECT_LIBRARIES OR PROJECT_STATIC_LIBRARIES)
+  install(
+    EXPORT "${TARGETS_EXPORT_NAME}"
+    FILE ${TARGETS_EXPORT_NAME}.cmake
+    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
+  )
+endif ()
diff --git a/cmake/TinyCBORHelpers.cmake b/cmake/TinyCBORHelpers.cmake
new file mode 100644
index 0000000..bb08117
--- /dev/null
+++ b/cmake/TinyCBORHelpers.cmake
@@ -0,0 +1,38 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: MIT
+function(tinycbor_add_executable target)
+  add_executable(${target} ${ARGN})
+  target_link_libraries(${target} tinycbor)
+  target_compile_options(${target} PRIVATE
+    $<$<CXX_COMPILER_ID:MSVC>:-W3>
+    $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall -Wextra>
+  )
+endfunction()
+
+function(tinycbor_add_test target)
+  tinycbor_add_executable(${target} ${ARGN})
+  set(memcheck_command ${CMAKE_MEMORYCHECK_COMMAND} ${CMAKE_MEMORYCHECK_COMMAND_OPTIONS})
+  separate_arguments(memcheck_command)
+  add_test(NAME ${target} COMMAND ${memcheck_command} $<TARGET_FILE:${target}>)
+endfunction()
+
+function(tinycbor_add_qtest target)
+  tinycbor_add_test(${target} ${ARGN})
+  target_link_libraries(${target} Qt::Core Qt::Test)
+endfunction()
+
+option(WITH_VALGRIND "Use Valgrind (if found) to run tests" ON)
+if(WITH_VALGRIND AND NOT DEFINED CMAKE_MEMORYCHECK_COMMAND)
+  find_program(VALGRIND "valgrind")
+  if(VALGRIND)
+    set(CMAKE_MEMORYCHECK_COMMAND ${VALGRIND} --tool=memcheck)
+    set(CMAKE_MEMORYCHECK_COMMAND_OPTIONS}
+        --error-exitcode=255
+        --errors-for-leak-kinds=definite
+        --leak-check=yes
+        --num-callers=20
+    )
+  endif()
+else()
+  set(VALGRIND OFF)
+endif()
diff --git a/cmake/project-config.cmake.in b/cmake/project-config.cmake.in
new file mode 100644
index 0000000..6729c5c
--- /dev/null
+++ b/cmake/project-config.cmake.in
@@ -0,0 +1,16 @@
+# Config file for @PROJECT_NAME_LOWER@
+#
+# It defines the following variables:
+#
+# @PROJECT_NAME_UPPER@_INCLUDE_DIRS     - include directory
+# @PROJECT_NAME_UPPER@_LIBRARIES        - all dynamic libraries
+# @PROJECT_NAME_UPPER@_STATIC_LIBRARIES - all static libraries
+
+@PACKAGE_INIT@
+
+include(CMakeFindDependencyMacro)
+
+# Add optional dependencies here
+
+include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake")
+check_required_components("@PROJECT_NAME@")
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
new file mode 100644
index 0000000..3f567e1
--- /dev/null
+++ b/examples/CMakeLists.txt
@@ -0,0 +1,5 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: MIT
+include(TinyCBORHelpers)
+
+tinycbor_add_executable(simplereader simplereader.c)
diff --git a/examples/examples.pro b/examples/examples.pro
deleted file mode 100644
index 22071ac..0000000
--- a/examples/examples.pro
+++ /dev/null
@@ -1,2 +0,0 @@
-TEMPLATE = subdirs
-SUBDIRS = simplereader.pro
diff --git a/examples/simplereader.c b/examples/simplereader.c
index a815a36..13473ef 100644
--- a/examples/simplereader.c
+++ b/examples/simplereader.c
@@ -174,12 +174,13 @@
     CborError err = cbor_parser_init(buf, length, 0, &parser, &it);
     if (!err)
         err = dumprecursive(&it, 0);
-    free(buf);
 
     if (err) {
         fprintf(stderr, "CBOR parsing failure at offset %ld: %s\n",
                 cbor_value_get_next_byte(&it) - buf, cbor_error_string(err));
+        free(buf);
         return 1;
     }
+    free(buf);
     return 0;
 }
diff --git a/examples/simplereader.pro b/examples/simplereader.pro
deleted file mode 100644
index 07fdc6a..0000000
--- a/examples/simplereader.pro
+++ /dev/null
@@ -1,3 +0,0 @@
-CONFIG -= qt
-SOURCES = simplereader.c
-include(../src/src.pri)
diff --git a/src/cbor.h b/src/cbor.h
index 86909d5..d557026 100644
--- a/src/cbor.h
+++ b/src/cbor.h
@@ -38,6 +38,7 @@
 #include "cbor_cfg.h"
 #endif
 
+#include "tinycbor-export.h"
 #include "tinycbor-version.h"
 
 #define TINYCBOR_VERSION            ((TINYCBOR_VERSION_MAJOR << 16) | (TINYCBOR_VERSION_MINOR << 8) | TINYCBOR_VERSION_PATCH)
@@ -59,11 +60,8 @@
 #  define SIZE_MAX ((size_t)-1)
 #endif
 
-#ifndef CBOR_API
-#  define CBOR_API
-#endif
 #ifndef CBOR_PRIVATE_API
-#  define CBOR_PRIVATE_API
+#  define CBOR_PRIVATE_API  CBOR_API
 #endif
 #ifndef CBOR_INLINE_API
 #  if defined(__cplusplus)
@@ -213,7 +211,7 @@
 {
     CborEncoderAppendCborData = 0,
     CborEncoderAppendStringData = 1,
-    CborEncoderApendRawData = 2
+    CborEncoderAppendRawData = 2
 } CborEncoderAppendType;
 
 typedef CborError (*CborEncoderWriteFunction)(void *, const void *, size_t, CborEncoderAppendType);
@@ -727,4 +725,3 @@
 #endif
 
 #endif /* CBOR_H */
-
diff --git a/src/cborencoder.c b/src/cborencoder.c
index 69aebf4..b21c1da 100644
--- a/src/cborencoder.c
+++ b/src/cborencoder.c
@@ -22,16 +22,7 @@
 **
 ****************************************************************************/
 
-#ifndef _BSD_SOURCE
-#define _BSD_SOURCE 1
-#endif
-#ifndef _DEFAULT_SOURCE
-#define _DEFAULT_SOURCE 1
-#endif
-#ifndef __STDC_LIMIT_MACROS
-#  define __STDC_LIMIT_MACROS 1
-#endif
-#define __STDC_WANT_IEC_60559_TYPES_EXT__
+#include "cborinternalmacros_p.h"
 
 #include "cbor.h"
 #include "cborinternal_p.h"
@@ -213,6 +204,7 @@
 {
 #ifdef CBOR_ENCODER_WRITE_FUNCTION
     (void) writer;
+    encoder->data.writer = CBOR_NULLPTR;
 #else
     encoder->data.writer = writer;
 #endif
@@ -484,7 +476,7 @@
  */
 CborError cbor_encode_raw(CborEncoder *encoder, const uint8_t *raw, size_t length)
 {
-    return append_to_buffer(encoder, raw, length, CborEncoderApendRawData);
+    return append_to_buffer(encoder, raw, length, CborEncoderAppendRawData);
 }
 
 /**
diff --git a/src/cborencoder_close_container_checked.c b/src/cborencoder_close_container_checked.c
index b88f06c..fd203a8 100644
--- a/src/cborencoder_close_container_checked.c
+++ b/src/cborencoder_close_container_checked.c
@@ -22,12 +22,7 @@
 **
 ****************************************************************************/
 
-#define _BSD_SOURCE 1
-#define _DEFAULT_SOURCE 1
-#ifndef __STDC_LIMIT_MACROS
-#  define __STDC_LIMIT_MACROS 1
-#endif
-#define __STDC_WANT_IEC_60559_TYPES_EXT__
+#include "cborinternalmacros_p.h"
 
 #include "cbor.h"
 
diff --git a/src/cborencoder_float.c b/src/cborencoder_float.c
index d87919e..ff87942 100644
--- a/src/cborencoder_float.c
+++ b/src/cborencoder_float.c
@@ -22,12 +22,7 @@
 **
 ****************************************************************************/
 
-#define _BSD_SOURCE 1
-#define _DEFAULT_SOURCE 1
-#ifndef __STDC_LIMIT_MACROS
-#  define __STDC_LIMIT_MACROS 1
-#endif
-#define __STDC_WANT_IEC_60559_TYPES_EXT__
+#include "cborinternalmacros_p.h"
 
 #include "cbor.h"
 
diff --git a/src/cborinternal_p.h b/src/cborinternal_p.h
index 19273ac..ee9c117 100644
--- a/src/cborinternal_p.h
+++ b/src/cborinternal_p.h
@@ -48,8 +48,12 @@
 /* Check for FLT16_MANT_DIG using integer comparison. Clang headers incorrectly
  * define this macro unconditionally when __STDC_WANT_IEC_60559_TYPES_EXT__
  * is defined (regardless of actual support for _Float16).
+ *
+ * GCC defines these macros but doesn't support arithmetic including
+ * conversions on x86 without SSE2.
  */
-#  if FLT16_MANT_DIG > 0 || __FLT16_MANT_DIG__ > 0
+#  if (FLT16_MANT_DIG > 0 || __FLT16_MANT_DIG__ > 0) && \
+      !(defined(__i386__) && !defined(__SSE2__))
 static inline unsigned short encode_half(float x)
 {
     unsigned short h;
diff --git a/src/cborinternalmacros_p.h b/src/cborinternalmacros_p.h
new file mode 100644
index 0000000..8450a3b
--- /dev/null
+++ b/src/cborinternalmacros_p.h
@@ -0,0 +1,36 @@
+/****************************************************************************
+**
+** Copyright (C) 2025 Intel Corporation
+**
+** 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.
+**
+****************************************************************************/
+
+#ifndef _BSD_SOURCE
+#  define _BSD_SOURCE 1
+#endif
+#ifndef _DEFAULT_SOURCE
+#  define _DEFAULT_SOURCE 1
+#endif
+#ifndef __STDC_LIMIT_MACROS
+#  define __STDC_LIMIT_MACROS 1
+#endif
+#ifndef __STDC_WANT_IEC_60559_TYPES_EXT__
+#  define __STDC_WANT_IEC_60559_TYPES_EXT__
+#endif
diff --git a/src/cborparser.c b/src/cborparser.c
index 75a4088..31c8d8b 100644
--- a/src/cborparser.c
+++ b/src/cborparser.c
@@ -22,16 +22,7 @@
 **
 ****************************************************************************/
 
-#ifndef _BSD_SOURCE
-#define _BSD_SOURCE 1
-#endif
-#ifndef _DEFAULT_SOURCE
-#define _DEFAULT_SOURCE 1
-#endif
-#ifndef __STDC_LIMIT_MACROS
-#  define __STDC_LIMIT_MACROS 1
-#endif
-#define __STDC_WANT_IEC_60559_TYPES_EXT__
+#include "cborinternalmacros_p.h"
 
 #include "cbor.h"
 #include "cborinternal_p.h"
@@ -231,7 +222,7 @@
         case SinglePrecisionFloat:
         case DoublePrecisionFloat:
             it->flags |= CborIteratorFlag_IntegerValueTooLarge;
-            /* fall through */
+            CBOR_FALLTHROUGH;
         case TrueValue:
         case NullValue:
         case UndefinedValue:
diff --git a/src/cborparser_dup_string.c b/src/cborparser_dup_string.c
index b634617..21d88c2 100644
--- a/src/cborparser_dup_string.c
+++ b/src/cborparser_dup_string.c
@@ -22,16 +22,7 @@
 **
 ****************************************************************************/
 
-#ifndef _BSD_SOURCE
-#define _BSD_SOURCE 1
-#endif
-#ifndef _DEFAULT_SOURCE
-#define _DEFAULT_SOURCE 1
-#endif
-#ifndef __STDC_LIMIT_MACROS
-#  define __STDC_LIMIT_MACROS 1
-#endif
-#define __STDC_WANT_IEC_60559_TYPES_EXT__
+#include "cborinternalmacros_p.h"
 
 #include "cbor.h"
 #include "compilersupport_p.h"
@@ -49,16 +40,19 @@
  * undefined, so checking with \ref cbor_value_get_type or \ref
  * cbor_value_is_text_string is recommended.
  *
- * If \c malloc returns a NULL pointer, this function will return error
- * condition \ref CborErrorOutOfMemory.
- *
  * On success, \c *buffer will contain a valid pointer that must be freed by
- * calling \c free(). This is the case even for zero-length strings.
- *
- * The \a next pointer, if not null, will be updated to point to the next item
- * after this string. If \a value points to the last item, then \a next will be
+ * calling \c free(). This is the case even for zero-length strings. The \a
+ * next pointer, if not null, will be updated to point to the next item after
+ * this string. If \a value points to the last item, then \a next will be
  * invalid.
  *
+ * If \c malloc returns a NULL pointer, this function will return error
+ * condition \ref CborErrorOutOfMemory. In this case, \c *buflen should contain
+ * the number of bytes necessary to copy this string and \a value will be
+ * updated to point to the next element. On all other failure cases, the values
+ * contained in \c *buffer, \c *buflen and \a next are undefined and mustn't be
+ * used (for example, calling \c{free()}).
+ *
  * This function may not run in constant time (it will run in O(n) time on the
  * number of chunks). It requires constant memory (O(1)) in addition to the
  * malloc'ed block.
@@ -80,16 +74,19 @@
  * undefined, so checking with \ref cbor_value_get_type or \ref
  * cbor_value_is_byte_string is recommended.
  *
- * If \c malloc returns a NULL pointer, this function will return error
- * condition \ref CborErrorOutOfMemory.
- *
  * On success, \c *buffer will contain a valid pointer that must be freed by
- * calling \c free(). This is the case even for zero-length strings.
- *
- * The \a next pointer, if not null, will be updated to point to the next item
- * after this string. If \a value points to the last item, then \a next will be
+ * calling \c free(). This is the case even for zero-length strings. The \a
+ * next pointer, if not null, will be updated to point to the next item after
+ * this string. If \a value points to the last item, then \a next will be
  * invalid.
  *
+ * If \c malloc returns a NULL pointer, this function will return error
+ * condition \ref CborErrorOutOfMemory. In this case, \c *buflen should contain
+ * the number of bytes necessary to copy this string and \a value will be
+ * updated to point to the next element. On all other failure cases, the values
+ * contained in \c *buffer, \c *buflen and \a next are undefined and mustn't be
+ * used (for example, calling \c{free()}).
+ *
  * This function may not run in constant time (it will run in O(n) time on the
  * number of chunks). It requires constant memory (O(1)) in addition to the
  * malloc'ed block.
@@ -98,24 +95,28 @@
  */
 CborError _cbor_value_dup_string(const CborValue *value, void **buffer, size_t *buflen, CborValue *next)
 {
+    const CborValue it = *value;    // often value == next
     CborError err;
+    void *tmpbuf;
     cbor_assert(buffer);
     cbor_assert(buflen);
     *buflen = SIZE_MAX;
-    err = _cbor_value_copy_string(value, NULL, buflen, NULL);
+    err = _cbor_value_copy_string(&it, NULL, buflen, next);
     if (err)
         return err;
 
     ++*buflen;
-    *buffer = cbor_malloc(*buflen);
-    if (!*buffer) {
+    tmpbuf = cbor_malloc(*buflen);
+    if (!tmpbuf) {
         /* out of memory */
         return CborErrorOutOfMemory;
     }
-    err = _cbor_value_copy_string(value, *buffer, buflen, next);
+    err = _cbor_value_copy_string(&it, tmpbuf, buflen, next);
     if (err) {
-        cbor_free(*buffer);
+        /* This shouldn't have happened! We've iterated once. */
+        cbor_free(tmpbuf);
         return err;
     }
+    *buffer = tmpbuf;
     return CborNoError;
 }
diff --git a/src/cborparser_float.c b/src/cborparser_float.c
index 268239e..f7d0189 100644
--- a/src/cborparser_float.c
+++ b/src/cborparser_float.c
@@ -22,12 +22,7 @@
 **
 ****************************************************************************/
 
-#define _BSD_SOURCE 1
-#define _DEFAULT_SOURCE 1
-#ifndef __STDC_LIMIT_MACROS
-#  define __STDC_LIMIT_MACROS 1
-#endif
-#define __STDC_WANT_IEC_60559_TYPES_EXT__
+#include "cborinternalmacros_p.h"
 
 #include "cbor.h"
 
diff --git a/src/cborpretty.c b/src/cborpretty.c
index a07741f..02b5706 100644
--- a/src/cborpretty.c
+++ b/src/cborpretty.c
@@ -22,12 +22,7 @@
 **
 ****************************************************************************/
 
-#define _BSD_SOURCE 1
-#define _DEFAULT_SOURCE 1
-#ifndef __STDC_LIMIT_MACROS
-#  define __STDC_LIMIT_MACROS 1
-#endif
-#define __STDC_WANT_IEC_60559_TYPES_EXT__
+#include "cborinternalmacros_p.h"
 
 #include "cbor.h"
 #include "cborinternal_p.h"
@@ -169,7 +164,7 @@
      *    value, chosen in an implementation-defined manner.
      */
     supremum = -2.0 * INT64_MIN;     /* -2 * (- 2^63) == 2^64 */
-    if (v >= supremum)
+    if (!(v < supremum)) /* out of range or NaN */
         return false;
 
     /* Now we can convert, these two conversions cannot be UB */
@@ -313,7 +308,7 @@
     const char *comma = "";
     CborError err = CborNoError;
 
-    if (!recursionsLeft) {
+    if (recursionsLeft <= 0) {
         printRecursionLimit(stream, out);
         while (!cbor_value_at_end(it) && !err) {
             err = cbor_value_advance(it);
@@ -361,6 +356,9 @@
             copy_current_position(it, &recursed);
             return err;       /* parse error */
         }
+        /* N.B. recursionsLeft can be zero, in which case container_to_pretty is called with
+         * recursionsLeft = -1 and reports that nesting is too deep.
+         */
         err = container_to_pretty(stream, out, &recursed, type, flags, recursionsLeft - 1);
         if (err) {
             copy_current_position(it, &recursed);
@@ -457,7 +455,7 @@
         err = stream(out, "%" PRIu64 "%s(", tag, get_indicator(it, flags));
         if (!err)
             err = cbor_value_advance_fixed(it);
-        if (!err && recursionsLeft)
+        if (!err && recursionsLeft > 0)
             err = value_to_pretty(stream, out, it, flags, recursionsLeft - 1);
         else if (!err)
             printRecursionLimit(stream, out);
diff --git a/src/cbortojson.c b/src/cbortojson.c
index 890769c..9a1cccf 100644
--- a/src/cbortojson.c
+++ b/src/cbortojson.c
@@ -22,13 +22,7 @@
 **
 ****************************************************************************/
 
-#define _BSD_SOURCE 1
-#define _DEFAULT_SOURCE 1
-#define _GNU_SOURCE 1
-#ifndef __STDC_LIMIT_MACROS
-#  define __STDC_LIMIT_MACROS 1
-#endif
-#define __STDC_WANT_IEC_60559_TYPES_EXT__
+#include "cborinternalmacros_p.h"
 
 #include "cbor.h"
 #include "cborjson.h"
@@ -167,11 +161,19 @@
     int flags;
 } ConversionStatus;
 
-static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType type, ConversionStatus *status);
+static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType type,
+                               int nestingLevel, ConversionStatus *status);
+
+static void append_hex(void *buffer, uint8_t byte)
+{
+    static const char characters[] = "0123456789abcdef";
+    char *str = buffer;
+    str[0] = characters[byte >> 4];
+    str[1] = characters[byte & 0xf];
+}
 
 static CborError dump_bytestring_base16(char **result, CborValue *it)
 {
-    static const char characters[] = "0123456789abcdef";
     size_t i;
     size_t n = 0;
     uint8_t *buffer;
@@ -180,7 +182,10 @@
         return err;
 
     /* a Base16 (hex) output is twice as big as our buffer */
-    buffer = (uint8_t *)cbor_malloc(n * 2 + 1);
+    size_t needed;
+    if (mul_check_overflow(n, 2, &needed) || add_check_overflow(needed, 1, &needed))
+        return CborErrorDataTooLarge;
+    buffer = (uint8_t *)cbor_malloc(needed);
     if (buffer == NULL)
         /* out of memory */
         return CborErrorOutOfMemory;
@@ -194,8 +199,7 @@
 
     for (i = 0; i < n; ++i) {
         uint8_t byte = buffer[n + i];
-        buffer[2*i]     = characters[byte >> 4];
-        buffer[2*i + 1] = characters[byte & 0xf];
+        append_hex(buffer + 2 * i, byte);
     }
     return CborNoError;
 }
@@ -209,8 +213,12 @@
         return err;
 
     /* a Base64 output (untruncated) has 4 bytes for every 3 in the input */
-    size_t len = (n + 5) / 3 * 4;
-    buffer = (uint8_t *)cbor_malloc(len + 1);
+    size_t len, needed;
+    if (add_check_overflow(n, 5, &len) || mul_check_overflow(len / 3, 4, &len)
+            || add_check_overflow(len, 1, &needed)) {
+        return CborErrorDataTooLarge;
+    }
+    buffer = (uint8_t *)cbor_malloc(needed);
     if (buffer == NULL)
         /* out of memory */
         return CborErrorOutOfMemory;
@@ -292,6 +300,98 @@
     return generic_dump_base64(result, it, alphabet);
 }
 
+static CborError escape_text_string(char **str, size_t *alloc, size_t *offsetp, const char *input, size_t len)
+{
+    /* JSON requires escaping some characters in strings, so we iterate and
+     * escape as necessary
+     * https://www.rfc-editor.org/rfc/rfc8259#section-7:
+     *  All Unicode characters may be placed within the
+     *  quotation marks, except for the characters that MUST be escaped:
+     *  quotation mark, reverse solidus, and the control characters (U+0000
+     *  through U+001F).
+     * We additionally choose to escape BS, HT, CR, LF and FF.
+     */
+    char *buf = *str;
+
+    /* Ensure we have enough space for this chunk. In the worst case, we
+     * have 6 escaped characters per input character.
+     *
+     * The overflow checking here is only practically useful for 32-bit
+     * machines, as SIZE_MAX/6 for a 64-bit machine is 2.6667 exabytes.
+     * That is much more than any current architecture can even address and
+     * cbor_value_get_text_string_chunk() only works for data already
+     * loaded into memory.
+     */
+    size_t needed;
+    size_t offset = offsetp ? *offsetp : 0;
+    if (mul_check_overflow(len, 6, &needed) || add_check_overflow(needed, offset, &needed)
+            || add_check_overflow(needed, 1, &needed)) {
+        return CborErrorDataTooLarge;
+    }
+    if (!alloc || needed > *alloc) {
+        buf = cbor_realloc(buf, needed);
+        if (!buf)
+            return CborErrorOutOfMemory;
+        if (alloc)
+            *alloc = needed;
+    }
+
+    for (size_t i = 0; i < len; ++i) {
+        static const char escapeChars[] = "\b\t\n\r\f\"\\";
+        static const char escapedChars[] = "btnrf\"\\";
+        unsigned char c = input[i];
+
+        char *esc = c > 0 ? strchr(escapeChars, c) : NULL;
+        if (esc) {
+            buf[offset++] = '\\';
+            buf[offset++] = escapedChars[esc - escapeChars];
+        } else if (c <= 0x1F) {
+            buf[offset++] = '\\';
+            buf[offset++] = 'u';
+            buf[offset++] = '0';
+            buf[offset++] = '0';
+            append_hex(buf + offset, c);
+            offset += 2;
+        } else {
+            buf[offset++] = c;
+        }
+    }
+    buf[offset] = '\0';
+    *str = buf;
+    if (offsetp)
+        *offsetp = offset;
+    return CborNoError;
+}
+
+static CborError text_string_to_escaped(char **str, CborValue *it)
+{
+    size_t alloc = 0, offset = 0;
+    CborError err;
+
+    *str = NULL;
+    err = cbor_value_begin_string_iteration(it);
+    while (err == CborNoError) {
+        const char *chunk;
+        size_t len;
+        err = cbor_value_get_text_string_chunk(it, &chunk, &len, it);
+        if (err == CborNoError)
+            err = escape_text_string(str, &alloc, &offset, chunk, len);
+    }
+
+    if (likely(err == CborErrorNoMoreStringChunks)) {
+        /* success */
+        if (!*str)
+            *str = strdup("");  // wasteful, but very atypical
+        err = cbor_value_finish_string_iteration(it);
+        if (likely(err == CborNoError))
+            return CborNoError;
+    }
+
+    cbor_free(*str);
+    *str = NULL;
+    return err;
+}
+
 static CborError add_value_metadata(FILE *out, CborType type, const ConversionStatus *status)
 {
     int flags = status->flags;
@@ -328,11 +428,13 @@
     return CborNoError;
 }
 
-static CborError find_tagged_type(CborValue *it, CborTag *tag, CborType *type)
+static CborError find_tagged_type(CborValue *it, CborTag *tag, CborType *type, int nestingLevel)
 {
     CborError err = CborNoError;
     *type = cbor_value_get_type(it);
     while (*type == CborTagType) {
+        if (nestingLevel-- == 0)
+            return CborErrorNestingTooDeep;
         cbor_value_get_tag(it, tag);    /* can't fail */
         err = cbor_value_advance_fixed(it);
         if (err)
@@ -343,7 +445,7 @@
     return err;
 }
 
-static CborError tagged_value_to_json(FILE *out, CborValue *it, int flags, ConversionStatus *status)
+static CborError tagged_value_to_json(FILE *out, CborValue *it, int flags, int nestingLevel, ConversionStatus *status)
 {
     CborTag tag;
     CborError err;
@@ -358,7 +460,7 @@
             return CborErrorIO;
 
         CborType type = cbor_value_get_type(it);
-        err = value_to_json(out, it, flags, type, status);
+        err = value_to_json(out, it, flags, type, nestingLevel, status);
         if (err)
             return err;
         if (flags & CborConvertAddMetadata && status->flags) {
@@ -374,7 +476,7 @@
     }
 
     CborType type;
-    err = find_tagged_type(it, &status->lastTag, &type);
+    err = find_tagged_type(it, &status->lastTag, &type, nestingLevel);
     if (err)
         return err;
     tag = status->lastTag;
@@ -402,7 +504,7 @@
     }
 
     /* no special handling */
-    err = value_to_json(out, it, flags, type, status);
+    err = value_to_json(out, it, flags, type, nestingLevel, status);
     status->flags |= TypeWasTagged | type;
     return err;
 }
@@ -417,19 +519,25 @@
     return CborErrorJsonNotImplemented;
 #else
     size_t size;
+    char *stringified;
 
-    FILE *memstream = open_memstream(key, &size);
+    FILE *memstream = open_memstream(&stringified, &size);
     if (memstream == NULL)
         return CborErrorOutOfMemory;        /* could also be EMFILE, but it's unlikely */
     CborError err = cbor_value_to_pretty_advance(memstream, it);
 
-    if (unlikely(fclose(memstream) < 0 || *key == NULL))
+    if (unlikely(fclose(memstream) < 0 || stringified == NULL))
         return CborErrorInternalError;
+    if (err == CborNoError) {
+        /* escape the stringified CBOR stream */
+        err = escape_text_string(key, NULL, NULL, stringified, size);
+    }
+    cbor_free(stringified);
     return err;
 #endif
 }
 
-static CborError array_to_json(FILE *out, CborValue *it, int flags, ConversionStatus *status)
+static CborError array_to_json(FILE *out, CborValue *it, int flags, int nestingLevel, ConversionStatus *status)
 {
     const char *comma = "";
     while (!cbor_value_at_end(it)) {
@@ -437,27 +545,26 @@
             return CborErrorIO;
         comma = ",";
 
-        CborError err = value_to_json(out, it, flags, cbor_value_get_type(it), status);
+        CborError err = value_to_json(out, it, flags, cbor_value_get_type(it), nestingLevel, status);
         if (err)
             return err;
     }
     return CborNoError;
 }
 
-static CborError map_to_json(FILE *out, CborValue *it, int flags, ConversionStatus *status)
+static CborError map_to_json(FILE *out, CborValue *it, int flags, int nestingLevel, ConversionStatus *status)
 {
     const char *comma = "";
     CborError err;
     while (!cbor_value_at_end(it)) {
-        char *key;
+        char *key = NULL;
         if (fprintf(out, "%s", comma) < 0)
             return CborErrorIO;
         comma = ",";
 
         CborType keyType = cbor_value_get_type(it);
         if (likely(keyType == CborTextStringType)) {
-            size_t n = 0;
-            err = cbor_value_dup_text_string(it, &key, &n, it);
+            err = text_string_to_escaped(&key, it);
         } else if (flags & CborConvertStringifyMapKeys) {
             err = stringify_map_key(&key, it, flags, keyType);
         } else {
@@ -474,7 +581,7 @@
 
         /* then, print the value */
         CborType valueType = cbor_value_get_type(it);
-        err = value_to_json(out, it, flags, valueType, status);
+        err = value_to_json(out, it, flags, valueType, nestingLevel, status);
 
         /* finally, print any metadata we may have */
         if (flags & CborConvertAddMetadata) {
@@ -497,11 +604,15 @@
     return CborNoError;
 }
 
-static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType type, ConversionStatus *status)
+static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType type,
+                               int nestingLevel, ConversionStatus *status)
 {
     CborError err;
     status->flags = 0;
 
+    if (nestingLevel == 0)
+        return CborErrorNestingTooDeep;
+
     switch (type) {
     case CborArrayType:
     case CborMapType: {
@@ -516,8 +627,8 @@
             return CborErrorIO;
 
         err = (type == CborArrayType) ?
-                  array_to_json(out, &recursed, flags, status) :
-                  map_to_json(out, &recursed, flags, status);
+                  array_to_json(out, &recursed, flags, nestingLevel - 1, status) :
+                  map_to_json(out, &recursed, flags, nestingLevel - 1, status);
         if (err) {
             copy_current_position(it, &recursed);
             return err;       /* parse error */
@@ -533,28 +644,11 @@
         return CborNoError;
     }
 
-    case CborIntegerType: {
-        double num;     /* JS numbers are IEEE double precision */
-        uint64_t val;
-        cbor_value_get_raw_integer(it, &val);    /* can't fail */
-        num = (double)val;
-
-        if (cbor_value_is_negative_integer(it)) {
-            num = -num - 1;                     /* convert to negative */
-            if ((uint64_t)(-num - 1) != val) {
-                status->flags = NumberPrecisionWasLost | NumberWasNegative;
-                status->originalNumber = val;
-            }
-        } else {
-            if ((uint64_t)num != val) {
-                status->flags = NumberPrecisionWasLost;
-                status->originalNumber = val;
-            }
-        }
-        if (fprintf(out, "%.0f", num) < 0)  /* this number has no fraction, so no decimal points please */
-            return CborErrorIO;
-        break;
-    }
+    case CborIntegerType:
+    case CborNullType:
+    case CborBooleanType:
+        /* just use cborpretty.c */
+        return cbor_value_to_pretty_advance(out, it);
 
     case CborByteStringType:
     case CborTextStringType: {
@@ -563,8 +657,7 @@
             err = dump_bytestring_base64url(&str, it);
             status->flags = TypeWasNotNative;
         } else {
-            size_t n = 0;
-            err = cbor_value_dup_text_string(it, &str, &n, it);
+            err = text_string_to_escaped(&str, it);
         }
         if (err)
             return err;
@@ -574,7 +667,7 @@
     }
 
     case CborTagType:
-        return tagged_value_to_json(out, it, flags, status);
+        return tagged_value_to_json(out, it, flags, nestingLevel - 1, status);
 
     case CborSimpleType: {
         uint8_t simple_type;
@@ -586,25 +679,12 @@
         break;
     }
 
-    case CborNullType:
-        if (fprintf(out, "null") < 0)
-            return CborErrorIO;
-        break;
-
     case CborUndefinedType:
         status->flags = TypeWasNotNative;
         if (fprintf(out, "\"undefined\"") < 0)
             return CborErrorIO;
         break;
 
-    case CborBooleanType: {
-        bool val;
-        cbor_value_get_boolean(it, &val);       /* can't fail */
-        if (fprintf(out, val ? "true" : "false") < 0)
-            return CborErrorIO;
-        break;
-    }
-
 #ifndef CBOR_NO_FLOATING_POINT
     case CborDoubleType: {
         double val;
@@ -637,8 +717,10 @@
             status->flags |= r == FP_NAN ? NumberWasNaN :
                                            NumberWasInfinite | (val < 0 ? NumberWasNegative : 0);
         } else {
-            uint64_t ival = (uint64_t)fabs(val);
-            if ((double)ival == fabs(val)) {
+            const double limit = (UINT32_MAX + 1.0) * (UINT32_MAX + 1.0);  /* 2^64 */
+            uint64_t ival = 0;
+            double aval = fabs(val);
+            if (aval < limit && (double)(ival = (uint64_t)aval) == aval) {
                 /* print as integer so we get the full precision */
                 r = fprintf(out, "%s%" PRIu64, val < 0 ? "-" : "", ival);
                 status->flags |= TypeWasNotNative;   /* mark this integer number as a double */
@@ -704,7 +786,8 @@
 CborError cbor_value_to_json_advance(FILE *out, CborValue *value, int flags)
 {
     ConversionStatus status;
-    return value_to_json(out, value, flags, cbor_value_get_type(value), &status);
+    return value_to_json(out, value, flags, cbor_value_get_type(value), CBOR_PARSER_MAX_RECURSIONS,
+                         &status);
 }
 
 /** @} */
diff --git a/src/cborvalidation.c b/src/cborvalidation.c
index 6e13138..2ad0c18 100644
--- a/src/cborvalidation.c
+++ b/src/cborvalidation.c
@@ -22,12 +22,7 @@
 **
 ****************************************************************************/
 
-#define _BSD_SOURCE 1
-#define _DEFAULT_SOURCE 1
-#ifndef __STDC_LIMIT_MACROS
-#  define __STDC_LIMIT_MACROS 1
-#endif
-#define __STDC_WANT_IEC_60559_TYPES_EXT__
+#include "cborinternalmacros_p.h"
 
 #include "cbor.h"
 #include "cborinternal_p.h"
diff --git a/src/compilersupport_p.h b/src/compilersupport_p.h
index 1b826b8..670a291 100644
--- a/src/compilersupport_p.h
+++ b/src/compilersupport_p.h
@@ -51,6 +51,24 @@
 #else
 #  define cbor_static_assert(x)         ((void)sizeof(char[2*!!(x) - 1]))
 #endif
+
+#if defined(__has_cpp_attribute) && defined(__cplusplus)    // C++17
+#  if __has_cpp_attribute(fallthrough)
+#    define CBOR_FALLTHROUGH            [[fallthrough]]
+#  endif
+#elif defined(__has_c_attribute) && !defined(__cplusplus)   // C23
+#  if __has_c_attribute(fallthrough)
+#    define CBOR_FALLTHROUGH            [[fallthrough]]
+#  endif
+#endif
+#ifndef CBOR_FALLTHROUGH
+#  ifdef __GNUC__
+#    define CBOR_FALLTHROUGH            __attribute__((fallthrough))
+#  else
+#    define CBOR_FALLTHROUGH            do { } while (0)
+#  endif
+#endif
+
 #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__cplusplus)
 /* inline is a keyword */
 #else
@@ -189,22 +207,28 @@
 #  define CONST_CAST(t, v)  (t)(uintptr_t)(v)
 #endif
 
-#ifdef __GNUC__
-#ifndef likely
+#if defined(__cplusplus) || __STDC_VERSION__ >= 202311
+#  define CBOR_NULLPTR nullptr
+#else
+#  define CBOR_NULLPTR NULL
+#endif
+
+#ifdef likely
+/* something has already defined likely(), accept it */
+#elif defined(__GNUC__)
 #  define likely(x)     __builtin_expect(!!(x), 1)
-#endif
-#ifndef unlikely
 #  define unlikely(x)   __builtin_expect(!!(x), 0)
-#endif
-#  define unreachable() __builtin_unreachable()
-#elif defined(_MSC_VER)
-#  define likely(x)     (x)
-#  define unlikely(x)   (x)
-#  define unreachable() __assume(0)
 #else
 #  define likely(x)     (x)
 #  define unlikely(x)   (x)
-#  define unreachable() do {} while (0)
+#endif
+
+#ifdef unreachable
+/* C23 has unreachable() */
+#elif defined(__GNUC__)
+#  define unreachable() __builtin_unreachable()
+#elif defined(_MSC_VER)
+#  define unreachable() __assume(0)
 #endif
 
 static inline bool add_check_overflow(size_t v1, size_t v2, size_t *r)
@@ -218,4 +242,15 @@
 #endif
 }
 
+static inline bool mul_check_overflow(size_t v1, size_t v2, size_t *r)
+{
+#if ((defined(__GNUC__) && (__GNUC__ >= 5)) && !defined(__INTEL_COMPILER)) || __has_builtin(__builtin_add_overflow)
+    return __builtin_mul_overflow(v1, v2, r);
+#else
+    /* unsigned multiplications are well-defined */
+    *r = v1 * v2;
+    return *r > v1 && *r > v2;
+#endif
+}
+
 #endif /* COMPILERSUPPORT_H */
diff --git a/src/memory.h b/src/memory.h
index 0032b93..6686eb2 100644
--- a/src/memory.h
+++ b/src/memory.h
@@ -26,6 +26,7 @@
 #  include CBOR_CUSTOM_ALLOC_INCLUDE
 #else
 #  include <stdlib.h>
-#  define cbor_malloc malloc
-#  define cbor_free   free
+#  define cbor_malloc   malloc
+#  define cbor_realloc  realloc
+#  define cbor_free     free
 #endif
diff --git a/src/open_memstream.c b/src/open_memstream.c
index 3365378..ed94e89 100644
--- a/src/open_memstream.c
+++ b/src/open_memstream.c
@@ -35,10 +35,10 @@
 #if defined(__unix__) || defined(__APPLE__)
 #  include <unistd.h>
 #endif
-#ifdef __APPLE__
+#if defined(HAVE_OPEN_FUNOPEN)
 typedef int RetType;
 typedef int LenType;
-#elif __linux__
+#elif defined(HAVE_OPEN_FOPENCOOKIE)
 typedef ssize_t RetType;
 typedef size_t LenType;
 #else
@@ -99,9 +99,9 @@
     *bufptr = NULL;
     *lenptr = 0;
 
-#ifdef __APPLE__
+#if defined(HAVE_OPEN_FUNOPEN)
     return funopen(b, NULL, write_to_buffer, NULL, close_buffer);
-#elif __linux__
+#elif defined(HAVE_OPEN_FOPENCOOKIE)
     static const cookie_io_functions_t vtable = {
         NULL,
         write_to_buffer,
diff --git a/src/tinycbor-export.h.in b/src/tinycbor-export.h.in
new file mode 100644
index 0000000..a181cce
--- /dev/null
+++ b/src/tinycbor-export.h.in
@@ -0,0 +1,3 @@
+#ifndef CBOR_API
+#define CBOR_API
+#endif
diff --git a/src/tinycbor-version.h b/src/tinycbor-version.h
deleted file mode 100644
index c26560c..0000000
--- a/src/tinycbor-version.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#define TINYCBOR_VERSION_MAJOR      0
-#define TINYCBOR_VERSION_MINOR      6
-#define TINYCBOR_VERSION_PATCH      0
diff --git a/src/tinycbor-version.h.in b/src/tinycbor-version.h.in
new file mode 100644
index 0000000..5ea04e6
--- /dev/null
+++ b/src/tinycbor-version.h.in
@@ -0,0 +1,7 @@
+/* Copyright (C) 2025 Intel Corporation
+ * SPDX-License-Identifier: MIT
+ */
+/* This is a generated file */
+#define TINYCBOR_VERSION_MAJOR      @PROJECT_VERSION_MAJOR@
+#define TINYCBOR_VERSION_MINOR      @PROJECT_VERSION_MINOR@
+#define TINYCBOR_VERSION_PATCH      0
diff --git a/src/tinycbor.pro b/src/tinycbor.pro
deleted file mode 100644
index 2ba508a..0000000
--- a/src/tinycbor.pro
+++ /dev/null
@@ -1,10 +0,0 @@
-TEMPLATE = lib
-CONFIG += static warn_on
-CONFIG -= qt
-DESTDIR = ../lib
-
-!msvc:QMAKE_CFLAGS += \
-    -Werror=incompatible-pointer-types \
-    -Werror=implicit-function-declaration \
-    -Werror=int-conversion
-include(src.pri)
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644
index 0000000..1747927
--- /dev/null
+++ b/tests/CMakeLists.txt
@@ -0,0 +1,20 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: MIT
+include(TinyCBORHelpers)
+add_subdirectory(c90)
+
+if(WITH_FREESTANDING OR NOT WITH_FLOATING_POINT)
+  return()
+endif()
+
+find_package(Qt6 COMPONENTS Core Test)
+if(Qt6_FOUND)
+  set(CMAKE_CXX_STANDARD 20)
+  set(CMAKE_AUTOMOC ON)
+  add_subdirectory(cpp)
+  add_subdirectory(encoder)
+  add_subdirectory(parser)
+  if(WITH_CBOR2JSON)
+    add_subdirectory(tojson)
+  endif()
+endif()
diff --git a/tests/c90/CMakeLists.txt b/tests/c90/CMakeLists.txt
new file mode 100644
index 0000000..17f8cfa
--- /dev/null
+++ b/tests/c90/CMakeLists.txt
@@ -0,0 +1,8 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: MIT
+tinycbor_add_test(tst_c90 tst_c90.c)
+set_target_properties(tst_c90 PROPERTIES
+  LINKER_LANGUAGE C
+  C_STANDARD 90
+  C_STANDARD_REQUIRED ON
+)
diff --git a/tests/c90/c90.pro b/tests/c90/c90.pro
deleted file mode 100644
index 59166b4..0000000
--- a/tests/c90/c90.pro
+++ /dev/null
@@ -1,7 +0,0 @@
-CONFIG += testcase parallel_test console
-CONFIG -= qt app_bundle
-gcc: QMAKE_CFLAGS += -std=c90 -pedantic-errors -Wall -Wextra -Werror
-darwin: QMAKE_CFLAGS += -Wno-long-long
-
-SOURCES += tst_c90.c
-INCLUDEPATH += ../../src
diff --git a/tests/cpp/CMakeLists.txt b/tests/cpp/CMakeLists.txt
new file mode 100644
index 0000000..9d69849
--- /dev/null
+++ b/tests/cpp/CMakeLists.txt
@@ -0,0 +1,4 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: MIT
+
+tinycbor_add_qtest(tst_cpp tst_cpp.cpp)
diff --git a/tests/cpp/cpp.pro b/tests/cpp/cpp.pro
deleted file mode 100644
index 5e9e608..0000000
--- a/tests/cpp/cpp.pro
+++ /dev/null
@@ -1,5 +0,0 @@
-CONFIG += testcase parallel_test c++11
-QT = core testlib
-
-SOURCES = tst_cpp.cpp
-INCLUDEPATH += ../../src
diff --git a/tests/encoder/CMakeLists.txt b/tests/encoder/CMakeLists.txt
new file mode 100644
index 0000000..834fd4d
--- /dev/null
+++ b/tests/encoder/CMakeLists.txt
@@ -0,0 +1,3 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: MIT
+tinycbor_add_qtest(tst_encoder tst_encoder.cpp)
diff --git a/tests/encoder/encoder.pro b/tests/encoder/encoder.pro
deleted file mode 100644
index 180f0d7..0000000
--- a/tests/encoder/encoder.pro
+++ /dev/null
@@ -1,11 +0,0 @@
-SOURCES += tst_encoder.cpp
-
-CONFIG += testcase parallel_test c++11
-QT = core testlib
-
-DEFINES += QT_NO_FOREACH QT_NO_AS_CONST
-
-INCLUDEPATH += ../../src
-msvc: POST_TARGETDEPS = ../../lib/tinycbor.lib
-else: POST_TARGETDEPS += ../../lib/libtinycbor.a
-LIBS += $$POST_TARGETDEPS
diff --git a/tests/encoder/tst_encoder.cpp b/tests/encoder/tst_encoder.cpp
index 1efc6e9..0708064 100644
--- a/tests/encoder/tst_encoder.cpp
+++ b/tests/encoder/tst_encoder.cpp
@@ -107,35 +107,35 @@
 {
     int type = v.userType();
     switch (type) {
-    case QVariant::Int:
-    case QVariant::LongLong:
+    case QMetaType::Int:
+    case QMetaType::LongLong:
         return cbor_encode_int(encoder, v.toLongLong());
 
-    case QVariant::UInt:
-    case QVariant::ULongLong:
+    case QMetaType::UInt:
+    case QMetaType::ULongLong:
         return cbor_encode_uint(encoder, v.toULongLong());
 
-    case QVariant::Bool:
+    case QMetaType::Bool:
         return cbor_encode_boolean(encoder, v.toBool());
 
-    case QVariant::Invalid:
+    case QMetaType::UnknownType:
         return cbor_encode_undefined(encoder);
 
     case QMetaType::VoidStar:
         return cbor_encode_null(encoder);
 
-    case QVariant::Double:
+    case QMetaType::Double:
         return cbor_encode_double(encoder, v.toDouble());
 
     case QMetaType::Float:
         return cbor_encode_float(encoder, v.toFloat());
 
-    case QVariant::String: {
+    case QMetaType::QString: {
         QByteArray string = v.toString().toUtf8();
         return cbor_encode_text_string(encoder, string.constData(), string.length());
     }
 
-    case QVariant::ByteArray: {
+    case QMetaType::QByteArray: {
         QByteArray string = v.toByteArray();
         return cbor_encode_byte_string(encoder, reinterpret_cast<const quint8 *>(string.constData()), string.length());
     }
@@ -158,7 +158,7 @@
                 return err;
             return static_cast<CborError>(err | encodeVariant(encoder, v.value<Tag>().tagged));
         }
-        if (type == QVariant::List || type == qMetaTypeId<IndeterminateLengthArray>()) {
+        if (type == QMetaType::QVariantList || type == qMetaTypeId<IndeterminateLengthArray>()) {
             CborEncoder sub;
             QVariantList list = v.toList();
             size_t len = list.length();
diff --git a/tests/parser/CMakeLists.txt b/tests/parser/CMakeLists.txt
new file mode 100644
index 0000000..af59aa9
--- /dev/null
+++ b/tests/parser/CMakeLists.txt
@@ -0,0 +1,3 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: MIT
+tinycbor_add_qtest(tst_parser tst_parser.cpp)
diff --git a/tests/parser/parser.pro b/tests/parser/parser.pro
deleted file mode 100644
index a61291a..0000000
--- a/tests/parser/parser.pro
+++ /dev/null
@@ -1,10 +0,0 @@
-SOURCES += tst_parser.cpp ../../src/cborparser.c
-
-CONFIG += testcase parallel_test c++11
-QT = core testlib
-DEFINES += CBOR_PARSER_MAX_RECURSIONS=16
-
-INCLUDEPATH += ../../src
-msvc: POST_TARGETDEPS = ../../lib/tinycbor.lib
-else: POST_TARGETDEPS += ../../lib/libtinycbor.a
-LIBS += $$POST_TARGETDEPS
diff --git a/tests/parser/tst_parser.cpp b/tests/parser/tst_parser.cpp
index e38a208..f937458 100644
--- a/tests/parser/tst_parser.cpp
+++ b/tests/parser/tst_parser.cpp
@@ -38,6 +38,10 @@
 #  include <windows.h>
 #endif
 
+#ifndef CBOR_PARSER_MAX_RECURSIONS
+#  define CBOR_PARSER_MAX_RECURSIONS 1024
+#endif
+
 #ifndef QCOMPARE_EQ
 // added for Qt 6.4
 #  define QCOMPARE_EQ QCOMPARE
@@ -264,7 +268,7 @@
         if (text)
             *parsed = '"' + QString::fromUtf8(text, len) + '"';
     } else {
-        Q_ASSERT(false);
+        Q_UNREACHABLE();
     }
     return err;
 }
diff --git a/tests/tests.pro b/tests/tests.pro
deleted file mode 100644
index 627ffbc..0000000
--- a/tests/tests.pro
+++ /dev/null
@@ -1,3 +0,0 @@
-TEMPLATE = subdirs
-SUBDIRS = parser encoder cpp tojson
-msvc: SUBDIRS -= tojson
diff --git a/tests/tojson/CMakeLists.txt b/tests/tojson/CMakeLists.txt
new file mode 100644
index 0000000..ade1a8b
--- /dev/null
+++ b/tests/tojson/CMakeLists.txt
@@ -0,0 +1,3 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: MIT
+tinycbor_add_qtest(tst_tojson tst_tojson.cpp)
diff --git a/tests/tojson/tojson.pro b/tests/tojson/tojson.pro
deleted file mode 100644
index b422652..0000000
--- a/tests/tojson/tojson.pro
+++ /dev/null
@@ -1,8 +0,0 @@
-CONFIG += testcase parallel_test c++11
-QT = core testlib
-
-SOURCES += tst_tojson.cpp
-INCLUDEPATH += ../../src
-msvc: POST_TARGETDEPS = ../../lib/tinycbor.lib
-else: POST_TARGETDEPS += ../../lib/libtinycbor.a
-LIBS += $$POST_TARGETDEPS
diff --git a/tests/tojson/tst_tojson.cpp b/tests/tojson/tst_tojson.cpp
index d91aee1..d0eb90b 100644
--- a/tests/tojson/tst_tojson.cpp
+++ b/tests/tojson/tst_tojson.cpp
@@ -22,8 +22,10 @@
 **
 ****************************************************************************/
 
+#define __STDC_WANT_IEC_60559_TYPES_EXT__
 #include <QtTest>
 #include "cbor.h"
+#include "cborinternal_p.h"
 #include "cborjson.h"
 #include <locale.h>
 
@@ -75,6 +77,9 @@
     void metaDataAndTagsToObjects();
     void metaDataForKeys_data();
     void metaDataForKeys();
+
+    void recursionLimit_data();
+    void recursionLimit();
 };
 #include "tst_tojson.moc"
 
@@ -95,13 +100,20 @@
     QTest::newRow("0") << raw("\x00") << "0";
     QTest::newRow("1") << raw("\x01") << "1";
     QTest::newRow("2^53-1") << raw("\x1b\0\x1f\xff\xff""\xff\xff\xff\xff") << "9007199254740991";
-    QTest::newRow("2^64-epsilon") << raw("\x1b\xff\xff\xff\xff""\xff\xff\xf8\x00") << "18446744073709549568";
+    QTest::newRow("2^53+1") << raw("\x1b\0\x20\0\0""\0\0\0\1") << "9007199254740993";
+    QTest::newRow("2^63-1") << raw("\x1b\x7f\xff\xff\xff""\xff\xff\xff\xff") << "9223372036854775807";
+    QTest::newRow("2^64-1") << raw("\x1b\xff\xff\xff\xff""\xff\xff\xff\xff") << "18446744073709551615";
 
     // negative integers
     QTest::newRow("-1") << raw("\x20") << "-1";
     QTest::newRow("-2") << raw("\x21") << "-2";
     QTest::newRow("-2^53+1") << raw("\x3b\0\x1f\xff\xff""\xff\xff\xff\xfe") << "-9007199254740991";
-    QTest::newRow("-2^64+epsilon") << raw("\x3b\xff\xff\xff\xff""\xff\xff\xf8\x00") << "-18446744073709549568";
+    QTest::newRow("-2^53-1") << raw("\x3b\0\x20\0\0""\0\0\0\0") << "-9007199254740993";
+    QTest::newRow("-2^63+1") << raw("\x3b\x7f\xff\xff\xff""\xff\xff\xff\xfe") << "-9223372036854775807";
+    QTest::newRow("-2^63") << raw("\x3b\x7f\xff\xff\xff""\xff\xff\xff\xff") << "-9223372036854775808";
+    QTest::newRow("-2^63-1") << raw("\x3b\x80\0\0\0""\0\0\0\0") << "-9223372036854775809";
+    QTest::newRow("-2^64+1") << raw("\x3b\xff\xff\xff\xff""\xff\xff\xff\xfe") << "-18446744073709551615";
+    QTest::newRow("-2^64") << raw("\x3b\xff\xff\xff\xff""\xff\xff\xff\xff") << "-18446744073709551616";
 
     QTest::newRow("false") << raw("\xf4") << "false";
     QTest::newRow("true") << raw("\xf5") << "true";
@@ -157,6 +169,18 @@
     QTest::newRow("_textstring5*2") << raw("\x7f\x63Hel\x62lo\xff") << "\"Hello\"";
     QTest::newRow("_textstring5*5") << raw("\x7f\x61H\x61""e\x61l\x61l\x61o\xff") << "\"Hello\"";
     QTest::newRow("_textstring5*6") << raw("\x7f\x61H\x61""e\x61l\x60\x61l\x61o\xff") << "\"Hello\"";
+
+    // strings containing characters that are escaped in JSON
+    QTest::newRow("null") << raw("\x61\0") << R"("\u0000")";
+    QTest::newRow("bell") << raw("\x61\7") << R"("\u0007")";    // not \\a
+    QTest::newRow("backspace") << raw("\x61\b") << R"("\b")";
+    QTest::newRow("tab") << raw("\x61\t") << R"("\t")";
+    QTest::newRow("carriage-return") << raw("\x61\r") << R"("\r")";
+    QTest::newRow("line-feed") << raw("\x61\n") << R"("\n")";
+    QTest::newRow("form-feed") << raw("\x61\f") << R"("\f")";
+    QTest::newRow("esc") << raw("\x61\x1f") << R"("\u001f")";
+    QTest::newRow("quote") << raw("\x61\"") << R"("\"")";
+    QTest::newRow("backslash") << raw("\x61\\") << R"("\\")";
 }
 
 void addNonJsonData()
@@ -409,6 +433,15 @@
     QTest::newRow("map-24-0") << raw("\xa1\x18\x18\0") << "{24: 0}";
     QTest::newRow("_map-0-24") << raw("\xbf\0\x18\x18\xff") << "{_ 0: 24}";
     QTest::newRow("_map-24-0") << raw("\xbf\x18\x18\0\xff") << "{_ 24: 0}";
+
+    // nested strings ought to be escaped
+    QTest::newRow("array-emptystring") << raw("\x81\x60") << R"([\"\"])";
+    QTest::newRow("array-string1") << raw("\x81\x61 ") << R"([\" \"])";
+
+    // and escaped chracters in strings end up doubly escaped
+    QTest::newRow("array-string-null") << raw("\x81\x61\0") << R"([\"\\u0000\"])";
+    QTest::newRow("array-string-quote") << raw("\x81\x61\"") << R"([\"\\\"\"])";
+    QTest::newRow("array-string-backslash") << raw("\x81\x61\\") << R"([\"\\\\\"])";
 }
 
 void tst_ToJson::nonStringKeyMaps()
@@ -592,16 +625,6 @@
     QTest::newRow("2.^53-1") << raw("\xfb\x43\x3f\xff\xff""\xff\xff\xff\xff") << "\"t\":251";
     QTest::newRow("2.^64-epsilon") << raw("\xfb\x43\xef\xff\xff""\xff\xff\xff\xff") << "\"t\":251";
 
-    // integers that are too precise for double
-    QTest::newRow("2^53+1") << raw("\x1b\0\x20\0\0""\0\0\0\1")
-                            << "\"t\":0,\"v\":\"+20000000000001\"";
-    QTest::newRow("INT64_MAX-1") << raw("\x1b\x7f\xff\xff\xff""\xff\xff\xff\xfe")
-                                 << "\"t\":0,\"v\":\"+7ffffffffffffffe\"";
-    QTest::newRow("INT64_MAX+1") << raw("\x1b\x80\0\0\0""\0\0\0\1")
-                                 << "\"t\":0,\"v\":\"+8000000000000001\"";
-    QTest::newRow("-2^53-1") << raw("\x3b\0\x20\0\0""\0\0\0\0")
-                             << "\"t\":0,\"v\":\"-20000000000000\"";
-
     // simple values
     QTest::newRow("simple0") << raw("\xe0") << "\"t\":224,\"v\":0";
     QTest::newRow("simple19") << raw("\xf3") << "\"t\":224,\"v\":19";
@@ -728,4 +751,33 @@
                CborConvertAddMetadata | CborConvertStringifyMapKeys);
 }
 
+void tst_ToJson::recursionLimit_data()
+{
+    static const int recursions = CBOR_PARSER_MAX_RECURSIONS + 2;
+    QTest::addColumn<QByteArray>("data");
+
+    QTest::newRow("arrays") << QByteArray(recursions, '\x81');
+    QTest::newRow("tags") << QByteArray(recursions, '\xc1');
+
+    QByteArray mapData;
+    mapData.reserve(2 * recursions);
+    for (int i = 0; i < recursions; ++i)
+        mapData.append("\xa1\x60", 2);
+    QTest::newRow("maps") << mapData;
+}
+
+void tst_ToJson::recursionLimit()
+{
+    QFETCH(QByteArray, data);
+    CborParser parser;
+    CborValue first;
+    CborError err = cbor_parser_init(reinterpret_cast<const quint8 *>(data.constData()), data.length(), 0, &parser, &first);
+    QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\"");
+
+    QString parsed;
+    err = parseOne(&first, &parsed, 0);
+
+    QCOMPARE(err, CborErrorNestingTooDeep);
+}
+
 QTEST_MAIN(tst_ToJson)
diff --git a/tinycbor.pc.in b/tinycbor.pc.in
index 382779a..ef16fc8 100644
--- a/tinycbor.pc.in
+++ b/tinycbor.pc.in
@@ -1,11 +1,11 @@
-prefix=@prefix@
-exec_prefix=@exec_prefix@
-libdir=@libdir@
-includedir=@includedir@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=@CMAKE_INSTALL_PREFIX@
+libdir=@CMAKE_INSTALL_LIBDIR@
+includedir=@CMAKE_INSTALL_INCLUDEDIR@
 
 Name: TinyCBOR
 Description: A tiny CBOR encoder and decoder library
-Version: @version@
+Version: @PROJECT_VERSION@
 Libs: -L${libdir} -ltinycbor
 Libs.private: -lm
 Cflags: -I${includedir}/tinycbor
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
new file mode 100644
index 0000000..8ad12e2
--- /dev/null
+++ b/tools/CMakeLists.txt
@@ -0,0 +1,5 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: MIT
+include(TinyCBORHelpers)
+add_subdirectory(cbordump)
+add_subdirectory(json2cbor)
diff --git a/tools/Makefile b/tools/Makefile
deleted file mode 100644
index fed6108..0000000
--- a/tools/Makefile
+++ /dev/null
@@ -1,12 +0,0 @@
-CFLAGS = -O2 -g
-CPPFLAGS = -I../src
-VPATH = cbordump:../src
-
-all: ../bin ../bin/cbordump
-../bin:
-	@-mkdir ../bin
-
-../bin/cbordump: cbordump.o cborparser.o cborparser_dup_string.o cbortojson.o cborerrorstrings.o cborpretty.o
-	$(CC) -o $@ $^
-	$(RM) $^
-
diff --git a/tools/cbordump/CMakeLists.txt b/tools/cbordump/CMakeLists.txt
new file mode 100644
index 0000000..c03fc5e
--- /dev/null
+++ b/tools/cbordump/CMakeLists.txt
@@ -0,0 +1,6 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: MIT
+if(NOT WIN32)
+  tinycbor_add_executable(cbordump cbordump.c)
+  install(TARGETS cbordump)
+endif()
diff --git a/tools/cbordump/cbordump.pro b/tools/cbordump/cbordump.pro
deleted file mode 100644
index 71ae6f7..0000000
--- a/tools/cbordump/cbordump.pro
+++ /dev/null
@@ -1,10 +0,0 @@
-TEMPLATE = app
-CONFIG += console
-CONFIG -= app_bundle
-CONFIG -= qt
-DESTDIR = ../../bin
-
-CBORDIR = $$PWD/../../src
-INCLUDEPATH += $$CBORDIR
-SOURCES += cbordump.c
-LIBS += ../../lib/libtinycbor.a
diff --git a/tools/json2cbor/CMakeLists.txt b/tools/json2cbor/CMakeLists.txt
new file mode 100644
index 0000000..a873114
--- /dev/null
+++ b/tools/json2cbor/CMakeLists.txt
@@ -0,0 +1,10 @@
+# Copyright (C) 2025 Intel Corporation
+# SPDX-License-Identifier: MIT
+include(FindPkgConfig)
+pkg_check_modules(LIBCJSON libcjson)
+if(LIBCJSON_FOUND)
+  tinycbor_add_executable(json2cbor json2cbor.c)
+  target_include_directories(json2cbor SYSTEM PUBLIC ${LIBCJSON_INCLUDE_DIRS})
+  target_link_libraries(json2cbor ${LIBCJSON_LIBRARIES})
+  install(TARGETS json2cbor)
+endif()
diff --git a/tools/json2cbor/json2cbor.c b/tools/json2cbor/json2cbor.c
index 090ee15..bb35108 100644
--- a/tools/json2cbor/json2cbor.c
+++ b/tools/json2cbor/json2cbor.c
@@ -328,15 +328,17 @@
         err = cbor_encode_double(encoder, json->valuedouble);
 
         if (err == CborErrorOutOfMemory) {
-            buffersize += 1024;
-            uint8_t *newbuffer = realloc(buffer, buffersize);
+            ptrdiff_t offset = cbor_encoder_get_buffer_size(&container, buffer);
+            size_t newbuffersize = buffersize + 1024;
+            uint8_t *newbuffer = realloc(buffer, newbuffersize);
             if (newbuffer == NULL)
                 return err;
 
             *encoder = container;   // restore state
-            encoder->data.ptr = newbuffer + (container.data.ptr - buffer);
-            encoder->end = newbuffer + buffersize;
+            encoder->data.ptr = newbuffer + offset;
+            encoder->end = newbuffer + newbuffersize;
             buffer = newbuffer;
+            buffersize = newbuffersize;
             goto encode_double;
         }
         return err;
diff --git a/tools/json2cbor/json2cbor.pro b/tools/json2cbor/json2cbor.pro
deleted file mode 100644
index fd6bcd0..0000000
--- a/tools/json2cbor/json2cbor.pro
+++ /dev/null
@@ -1,20 +0,0 @@
-TEMPLATE = app
-CONFIG += console
-CONFIG -= app_bundle
-CONFIG -= qt
-DESTDIR = ../../bin
-
-CBORDIR = $$PWD/../../src
-INCLUDEPATH += $$CBORDIR
-SOURCES += json2cbor.c
-LIBS += ../../lib/libtinycbor.a
-
-CJSONDIR = .
-!exists($$CJSONDIR/cJSON.h): CJSONDIR = $$CBORDIR/cjson
-exists($$CJSONDIR/cJSON.h) {
-    INCLUDEPATH += $$CJSONDIR
-    SOURCES += $$CJSONDIR/cJSON.c
-} else {
-    message("cJSON not found, not building json2cbor.")
-    TEMPLATE = aux
-}