diff options
| author | jacqueline <me@jacqueline.id.au> | 2023-01-21 14:04:56 +1100 |
|---|---|---|
| committer | jacqueline <me@jacqueline.id.au> | 2023-01-21 14:09:00 +1100 |
| commit | 393b268e159a40b23bc63464f4d04d5be09e070f (patch) | |
| tree | d78167662cd68de3e08d90bb1b44070ab542e433 /lib/cbor/tinycbor | |
| parent | bb50cf52cd8984704b757b1e5f9e11a126b24eba (diff) | |
| download | tangara-fw-393b268e159a40b23bc63464f4d04d5be09e070f.tar.gz | |
Vendor tinycbor, since v5 no longer includes it
Diffstat (limited to 'lib/cbor/tinycbor')
58 files changed, 12434 insertions, 0 deletions
diff --git a/lib/cbor/tinycbor/.appveyor.yml b/lib/cbor/tinycbor/.appveyor.yml new file mode 100644 index 00000000..ef47797e --- /dev/null +++ b/lib/cbor/tinycbor/.appveyor.yml @@ -0,0 +1,28 @@ +version: 0.6-build-{build} +pull_requests: + do_not_increment_build_number: true +image: +- Visual Studio 2017 +- Visual Studio 2019 +install: +- cmd: >- + if /i "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" (call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" x86) & (set QTDIR=C:\Qt\5.13\msvc2017) + + if /i "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2019" (call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" x64) & (set QTDIR=C:\Qt\6.1\msvc2019_64) + + set path=%PATH%;%QTDIR%\bin +build_script: +- cmd: >- + nmake -f Makefile.nmake -nologo CFLAGS="-W3 -Os -MDd" + + cd tests + + qmake CONFIG-=release CONFIG+=debug + + nmake -nologo -s +test_script: +- cmd: >- + nmake -s -nologo TESTARGS=-silent check +artifacts: +- path: lib\tinycbor.lib +deploy: off diff --git a/lib/cbor/tinycbor/.gitattributes b/lib/cbor/tinycbor/.gitattributes new file mode 100644 index 00000000..76ed2567 --- /dev/null +++ b/lib/cbor/tinycbor/.gitattributes @@ -0,0 +1,4 @@ +.tag export-subst +.gitignore export-ignore +.gitattributes export-ignore +.appveyor.yml text diff --git a/lib/cbor/tinycbor/.gitignore b/lib/cbor/tinycbor/.gitignore new file mode 100644 index 00000000..3272de33 --- /dev/null +++ b/lib/cbor/tinycbor/.gitignore @@ -0,0 +1,81 @@ +# Frequent generated files +callgrind.out.* +pcviewer.cfg +*~ +*.a +*.la +*.core +*.d +*.dylib +*.moc +*.o +*.obj +*.orig +*.swp +*.rej +*.so +*.so.* +*.pbxuser +*.mode1 +*.mode1v3 +*_pch.h.cpp +*_resource.rc +.#* +*.*# +core +.qmake.cache +.qmake.stash +.qmake.vars +.device.vars +tags +.DS_Store +*.debug +Makefile* +*.prl +*.app +*.pro.user* +*.qmlproject.user* +*.gcov +*.gcda +*.gcno +*.flc +.*.swp +tinycbor.pc + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.vcxproj +*.vcxproj.filters +*.vcxproj.user +*.exe.embed.manifest +*.exe_manifest.rc +*.exe_manifest.res + +# MinGW generated files +*.Debug +*.Release + +# INTEGRITY generated files +*.gpj +*.int +*.ael +*.dla +*.dnm +*.dep +*.map + +bin +doc +lib +src/cjson +src/doxygen.log +!/Makefile +.config diff --git a/lib/cbor/tinycbor/.tag b/lib/cbor/tinycbor/.tag new file mode 100644 index 00000000..6828f88d --- /dev/null +++ b/lib/cbor/tinycbor/.tag @@ -0,0 +1 @@ +$Format:%H$ diff --git a/lib/cbor/tinycbor/.travis.yml b/lib/cbor/tinycbor/.travis.yml new file mode 100644 index 00000000..4df21cb7 --- /dev/null +++ b/lib/cbor/tinycbor/.travis.yml @@ -0,0 +1,92 @@ +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/lib/cbor/tinycbor/Doxyfile b/lib/cbor/tinycbor/Doxyfile new file mode 100644 index 00000000..a7263c2f --- /dev/null +++ b/lib/cbor/tinycbor/Doxyfile @@ -0,0 +1,49 @@ +PROJECT_NAME = "TinyCBOR $(VERSION) API" +OUTPUT_DIRECTORY = ../doc +ABBREVIATE_BRIEF = +SHORT_NAMES = YES +JAVADOC_AUTOBRIEF = YES +QT_AUTOBRIEF = YES +TAB_SIZE = 8 +ALIASES = "value=\arg \c" +OPTIMIZE_OUTPUT_FOR_C = YES +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = NO +HIDE_UNDOC_MEMBERS = YES +HIDE_UNDOC_CLASSES = YES +GENERATE_TODOLIST = NO +GENERATE_TESTLIST = NO +GENERATE_BUGLIST = NO +GENERATE_DEPRECATEDLIST= NO +SHOW_USED_FILES = NO +WARN_IF_UNDOCUMENTED = NO +WARN_LOGFILE = doxygen.log +INPUT = . +FILE_PATTERNS = *.h \ + *.c \ + *.dox +EXCLUDE_PATTERNS = *_p.h +STRIP_CODE_COMMENTS = NO +REFERENCED_BY_RELATION = YES +IGNORE_PREFIX = cbor_ \ + Cbor +HTML_TIMESTAMP = NO +GENERATE_HTMLHELP = YES +GENERATE_CHI = YES +BINARY_TOC = YES +TOC_EXPAND = YES +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest +SEARCHENGINE = NO +GENERATE_LATEX = NO +COMPACT_LATEX = YES +MACRO_EXPANSION = YES +PREDEFINED = DOXYGEN \ + CBOR_INLINE_API= +CLASS_DIAGRAMS = NO +CLASS_GRAPH = NO +COLLABORATION_GRAPH = NO +GROUP_GRAPHS = NO +INCLUDE_GRAPH = NO +INCLUDED_BY_GRAPH = NO +GRAPHICAL_HIERARCHY = NO +DIRECTORY_GRAPH = NO diff --git a/lib/cbor/tinycbor/LICENSE b/lib/cbor/tinycbor/LICENSE new file mode 100644 index 00000000..4aad977c --- /dev/null +++ b/lib/cbor/tinycbor/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 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. diff --git a/lib/cbor/tinycbor/Makefile b/lib/cbor/tinycbor/Makefile new file mode 100644 index 00000000..948e3faf --- /dev/null +++ b/lib/cbor/tinycbor/Makefile @@ -0,0 +1,250 @@ +# 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) + +# Check that QMAKE is Qt 5 +ifeq ($(origin QMAKE),file) + check_qmake = $(strip $(shell $(1) -query QT_VERSION 2>/dev/null | cut -b1)) + ifneq ($(call check_qmake,$(QMAKE)),5) + QMAKE := qmake -qt5 + ifneq ($(call check_qmake,$(QMAKE)),5) + QMAKE := qmake-qt5 + ifneq ($(call check_qmake,$(QMAKE)),5) + QMAKE := @echo >&2 $(MAKEFILE): Cannot find a Qt 5 qmake; false + endif + endif + endif +endif + +-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='>&9' configure 9> $@ + +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) \ + -Werror=incompatible-pointer-types \ + -Werror=implicit-function-declaration \ + -Werror=int-conversion +%.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/lib/cbor/tinycbor/README b/lib/cbor/tinycbor/README new file mode 100644 index 00000000..167efa06 --- /dev/null +++ b/lib/cbor/tinycbor/README @@ -0,0 +1,13 @@ +Concise Binary Object Representation (CBOR) Library +--------------------------------------------------- + +To build TinyCBOR: + + make + +If you want to change the compiler or pass extra compiler flags: + + make CC=clang CFLAGS="-m32 -Oz" LDFLAGS="-m32" + +Documentation: https://intel.github.io/tinycbor/current/ + diff --git a/lib/cbor/tinycbor/TODO b/lib/cbor/tinycbor/TODO new file mode 100644 index 00000000..e9103ee6 --- /dev/null +++ b/lib/cbor/tinycbor/TODO @@ -0,0 +1,25 @@ +==== To Do list for libcbor ==== +=== General === +* API review +* Benchmark +* Write examples +** Simple decoder +** Decoder to JSON +** Windowed encoding/decoding (limited memory) + +=== Encoder === +* Write API docs +* Add API for creating indeterminate-length arrays and maps +* Add API for creating indeterminate-length strings +* Add API for relaxing doubles to floats and to integers +* Add length-checking of the sub-containers (#ifndef CBOR_ENCODER_NO_USER_CHECK) +* Decide how to indicate number of bytes needed +** Suggestion: return negative number from the functions + +=== Decoder === +* Write functions not yet implemented +* Add API for stream-decoding strings +* Add API for checking known tags and simple types +* (unlikely) Add API for checking the pairing of a tag and the tagged type +* Write tests for error conditions +* Fuzzy-test the decoder diff --git a/lib/cbor/tinycbor/VERSION b/lib/cbor/tinycbor/VERSION new file mode 100644 index 00000000..a918a2aa --- /dev/null +++ b/lib/cbor/tinycbor/VERSION @@ -0,0 +1 @@ +0.6.0 diff --git a/lib/cbor/tinycbor/examples/examples.pro b/lib/cbor/tinycbor/examples/examples.pro new file mode 100644 index 00000000..22071ac3 --- /dev/null +++ b/lib/cbor/tinycbor/examples/examples.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS = simplereader.pro diff --git a/lib/cbor/tinycbor/examples/simplereader.c b/lib/cbor/tinycbor/examples/simplereader.c new file mode 100644 index 00000000..0612ba44 --- /dev/null +++ b/lib/cbor/tinycbor/examples/simplereader.c @@ -0,0 +1,185 @@ +#include "../src/cbor.h" + +#include <sys/stat.h> +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +static uint8_t *readfile(const char *fname, size_t *size) +{ + struct stat st; + FILE *f = fopen(fname, "rb"); + if (!f) + return NULL; + if (fstat(fileno(f), &st) == -1) + return NULL; + uint8_t *buf = malloc(st.st_size); + if (buf == NULL) + return NULL; + *size = fread(buf, st.st_size, 1, f) == 1 ? st.st_size : 0; + fclose(f); + return buf; +} + +static void indent(int nestingLevel) +{ + while (nestingLevel--) + printf(" "); +} + +static void dumpbytes(const uint8_t *buf, size_t len) +{ + printf("\""); + while (len--) + printf("\\x%02X", *buf++); + printf("\""); +} + +static CborError dumprecursive(CborValue *it, int nestingLevel) +{ + while (!cbor_value_at_end(it)) { + CborError err; + CborType type = cbor_value_get_type(it); + + indent(nestingLevel); + switch (type) { + case CborArrayType: + case CborMapType: { + // recursive type + CborValue recursed; + assert(cbor_value_is_container(it)); + puts(type == CborArrayType ? "Array[" : "Map["); + err = cbor_value_enter_container(it, &recursed); + if (err) + return err; // parse error + err = dumprecursive(&recursed, nestingLevel + 1); + if (err) + return err; // parse error + err = cbor_value_leave_container(it, &recursed); + if (err) + return err; // parse error + indent(nestingLevel); + puts("]"); + continue; + } + + case CborIntegerType: { + int64_t val; + cbor_value_get_int64(it, &val); // can't fail + printf("%lld\n", (long long)val); + break; + } + + case CborByteStringType: { + uint8_t *buf; + size_t n; + err = cbor_value_dup_byte_string(it, &buf, &n, it); + if (err) + return err; // parse error + dumpbytes(buf, n); + puts(""); + free(buf); + continue; + } + + case CborTextStringType: { + char *buf; + size_t n; + err = cbor_value_dup_text_string(it, &buf, &n, it); + if (err) + return err; // parse error + printf("\"%s\"\n", buf); + free(buf); + continue; + } + + case CborTagType: { + CborTag tag; + cbor_value_get_tag(it, &tag); // can't fail + printf("Tag(%lld)\n", (long long)tag); + break; + } + + case CborSimpleType: { + uint8_t type; + cbor_value_get_simple_type(it, &type); // can't fail + printf("simple(%u)\n", type); + break; + } + + case CborNullType: + puts("null"); + break; + + case CborUndefinedType: + puts("undefined"); + break; + + case CborBooleanType: { + bool val; + cbor_value_get_boolean(it, &val); // can't fail + puts(val ? "true" : "false"); + break; + } + + case CborDoubleType: { + double val; + if (false) { + float f; + case CborFloatType: + cbor_value_get_float(it, &f); + val = f; + } else { + cbor_value_get_double(it, &val); + } + printf("%g\n", val); + break; + } + case CborHalfFloatType: { + uint16_t val; + cbor_value_get_half_float(it, &val); + printf("__f16(%04x)\n", val); + break; + } + + case CborInvalidType: + assert(false); // can't happen + break; + } + + err = cbor_value_advance_fixed(it); + if (err) + return err; + } + return CborNoError; +} + +int main(int argc, char **argv) +{ + if (argc != 2) { + puts("simplereader <filename>"); + return 1; + } + + size_t length; + uint8_t *buf = readfile(argv[1], &length); + if (!buf) { + perror("readfile"); + return 1; + } + + CborParser parser; + CborValue it; + 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", + it.ptr - buf, cbor_error_string(err)); + return 1; + } + return 0; +} diff --git a/lib/cbor/tinycbor/examples/simplereader.pro b/lib/cbor/tinycbor/examples/simplereader.pro new file mode 100644 index 00000000..07fdc6ac --- /dev/null +++ b/lib/cbor/tinycbor/examples/simplereader.pro @@ -0,0 +1,3 @@ +CONFIG -= qt +SOURCES = simplereader.c +include(../src/src.pri) diff --git a/lib/cbor/tinycbor/scripts/maketag.pl b/lib/cbor/tinycbor/scripts/maketag.pl new file mode 100644 index 00000000..5b1a8b79 --- /dev/null +++ b/lib/cbor/tinycbor/scripts/maketag.pl @@ -0,0 +1,91 @@ +#!perl +use strict; +sub run(@) { + open PROC, "-|", @_ or die("Cannot run $_[0]: $!"); + my @out; + while (<PROC>) { + chomp; + push @out, $_; + } + close PROC; + return @out; +} + +my @tags = run("git", "tag"); +my @v = run("git", "show", "HEAD:VERSION"); +my $v = $v[0]; + +my $tagfile = ".git/TAG_EDITMSG"; +open TAGFILE, ">", $tagfile + or die("Cannot create file for editing tag message: $!"); +select TAGFILE; +print "TinyCBOR release $v\n"; +print "\n"; +print "# Write something nice about this release here\n"; + +# Do we have a commit template? +my @result = run("git", "config", "--get", "commit.template"); +if (scalar @result) { + open TEMPLATE, "<", $result[0]; + map { print $_; } <TEMPLATE>; + close TEMPLATE; +} + +print "\n"; +print "# Commit log\n"; +open LOG, "-|", "git", "shortlog", "-e", "--no-merges", "--not", @tags; +map { print "# $_"; } <LOG>; +close LOG; + +print "# Header diff:\n"; +open DIFF, "-|", "git", "diff", "HEAD", "--not", @tags, "--", 'src/*.h', ':!*_p.h'; +map { print "# $_"; } <DIFF>; +close DIFF; + +select STDOUT; +close TAGFILE; + +# Run the editor. +# We use system so that stdin, stdout and stderr are forwarded. +@result = run("git", "var", "GIT_EDITOR"); +@result = ($result[0], $tagfile); +system @result; +exit ($? >> 8) if $?; + +# Create the tag +# Also using system so that hte user can see the output. +system("git", "tag", "-a", "-F", $tagfile, split(' ', $ENV{GITTAGFLAGS}), "v$v"); +exit ($? >> 8) if $?; + +# Update the version files for the next patch release +@v = split(/\./, $v); +if (scalar @v < 3) { + push @v, '1'; +} else { + ++$v[-1]; +} +$v = join('.', @v); +open VERSION, ">", "VERSION" or die("Cannot open VERSION file: $!"); +print VERSION "$v\n"; +close VERSION; + +open VERSION, ">", "src/tinycbor-version.h" or die("Cannot open src/tinycbor-version.h: $!"); +print VERSION "#define TINYCBOR_VERSION_MAJOR ", $v[0], "\n"; +print VERSION "#define TINYCBOR_VERSION_MINOR ", $v[1], "\n"; +print VERSION "#define TINYCBOR_VERSION_PATCH ", $v[2], "\n"; +close VERSION; + +if (open APPVEYORYML, "<", ".appveyor.yml") { + my @contents = map { + s/^version:.*/version: $v[0].$v[1].$v[2]-build-{build}/; + $_; + } <APPVEYORYML>; + close APPVEYORYML; + open APPVEYORYML, ">", ".appveyor.yml"; + print APPVEYORYML join('', @contents); + close APPVEYORYML; +} + +# Print summary +print "Tag created and next versions updated.\n"; +print "Don't forget to create the docs.\n" if $v[2] == 1; diff --git a/lib/cbor/tinycbor/scripts/update-docs.sh b/lib/cbor/tinycbor/scripts/update-docs.sh new file mode 100755 index 00000000..19acfaea --- /dev/null +++ b/lib/cbor/tinycbor/scripts/update-docs.sh @@ -0,0 +1,52 @@ +#!/bin/sh -ex +tuple="$TRAVIS_BRANCH${TRAVIS_TAG:+tag:$TRAVIS_TAG},$TRAVIS_PULL_REQUEST" +case "$tuple" in + dev,false|main,false|tag:*) + ;; + *) + exit 0 + ;; +esac +V=`cut -f1-2 -d. <VERSION` +git fetch origin gh-pages + +# Fail if the library sizes file isn't present +test -r sizes + +# Run doxygen (maybe) +if [ -n "${TRAVIS_TAG-$FORCE_DOCS}" ] && make -s docs 2>/dev/null; then + git checkout -b gh-pages FETCH_HEAD + if [ -d "$V" ]; then + mv "$V" "old-$V" + fi + mv doc/html "$V" + git add -A "$V" +else + git checkout -b gh-pages FETCH_HEAD + mkdir -p "$V" +fi + +# Update the symlink for the branch name +rm -f "./$TRAVIS_BRANCH" +ln -s "$V" "$TRAVIS_BRANCH" +git add "./$TRAVIS_BRANCH" + +# Update the library sizes file +# (will fail if the release build failed) +mkdir -p "library_sizes/$TRAVIS_BRANCH" +mv sizes "library_sizes/$TRAVIS_BRANCH/$QMAKESPEC" +(cd "library_sizes/$TRAVIS_BRANCH/"; + for f in *; do echo "$f:"; cat "$f" ; done) > "$V/library_sizes.txt" +git add "library_sizes/$TRAVIS_BRANCH" "$V/library_sizes.txt" +git diff --cached -U0 "$V/library_sizes.txt" + +# Commit everything +if git commit -m "Update docs for $V (Travis build $TRAVIS_BUILD_NUMBER) + +Matching commit $TRAVIS_COMMIT: +$TRAVIS_COMMIT_MESSAGE"; then + # We've made a commit, push it + set +x + url=`git config --get remote.origin.url | sed -e s,://github,://$GITHUB_AUTH@github,` + git push "$url" @:gh-pages +fi diff --git a/lib/cbor/tinycbor/src/cbor.dox b/lib/cbor/tinycbor/src/cbor.dox new file mode 100644 index 00000000..0bf5c54c --- /dev/null +++ b/lib/cbor/tinycbor/src/cbor.dox @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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. +** +****************************************************************************/ + +/** + * \mainpage + * The TinyCBOR $(VERSION) library is a small CBOR encoder and decoder library, + * optimized for very fast operation with very small footprint. The main encoder + * and decoder functions do not allocate memory. + * + * TinyCBOR is divided into the following groups of functions and structures: + * - \ref CborGlobals + * - \ref CborEncoding + * - \ref CborParsing + * - \ref CborPretty + * - \ref CborToJson + */ + +/** + * \file <cbor.h> + * The <cbor.h> is the main header in TinyCBOR and defines the constants used by most functions + * as well as the structures for encoding (CborEncoder) and decoding (CborValue). + * + * \sa <cborjson.h> + */ + +/** + * \file <cborjson.h> + * The <cborjson.h> file contains the routines that are used to convert a CBOR + * data stream into JSON. + * + * \sa <cbor.h> + */ + +/** + * \defgroup CborGlobals Global constants + * \brief Constants used by all TinyCBOR function groups. + */ + +/** + * \addtogroup CborGlobals + * @{ + */ + +/** + * \var size_t CborIndefiniteLength + * + * This variable is a constant used to indicate that the length of the map or + * array is not yet determined. It is used in functions + * cbor_encoder_create_map() and cbor_encoder_create_array() + */ + +/** + * \enum CborType + * The CborType enum contains the types known to TinyCBOR. + * + * \value CborIntegerType Type is an integer value, positive, negative or zero + * \value CborByteStringType Type is a string of arbitrary raw bytes + * \value CborTextStringType Type is a text string encoded in UTF-8 + * \value CborArrayType Type is a CBOR array + * \value CborMapType Type is a CBOR map (an associative container with key and value pairs) + * \value CborTagType Type is a CBOR tag (a 64-bit integer describing the item that follows, see CborKnownTags) + * \value CborSimpleType Type is one of the CBOR Simple Types + * \value CborBooleanType Type is a boolean (true or false) + * \value CborNullType Type encodes a null + * \value CborUndefinedType Type encodes an undefined value + * \value CborHalfFloatType Type is an IEEE 754 half precision (16-bit) floating point type + * \value CborFloatType Type is an IEEE 754 single precision (32-bit) floating point type + * \value CborDoubleType Type is an IEEE 754 double precision (64-bit) floating point type + * \value CborInvalidType Type is not valid (this value is used to indicate error conditions) + */ + +/** + * \enum CborKnownTags + * The CborKnownTags enum contains known tags specified in RFC 7049, for use by the application. + * TinyCBOR does not usually interpret the meaning of these tags and does not add them to the + * output stream, unless specifically instructed to do so in functions for that effect. + * + * \value CborDateTimeStringTag Text string contains a date-time encoded in RFC 3339 format, "YYYY-MM-DD hh:mm:ss+zzzz" + * \value CborUnixTime_tTag Number is a Unix time_t quantity, the number of seconds since 1970-01-01 midnight UTC + * \value CborPositiveBignumTag Item is a CBOR byte string encoding a positive integer of arbitrary precision + * \value CborNegativeBignumTag Item is a CBOR byte string encoding a negative integer of arbitrary precision + * \value CborDecimalTag Item is a CBOR array of two integers encoding a fixed-point decimal + * \value CborBigfloatTag Item is a bigfloat + * \value CborExpectedBase64urlTag Item is a CBOR byte string that is expected to be encoded as Base64Url + * \value CborExpectedBase64Tag Item is a CBOR byte string that is expected to be encoded as Base64 + * \value CborExpectedBase16Tag Item is a CBOR byte string that is expected to be encoded as Base16 (also known as "hexdump") + * \value CborUriTag Item is a CBOR text string containing a URI (RFC 3986) or IRI (RFC 3987) + * \value CborBase64urlTag Item is a CBOR text string that was encoded as Base64Url + * \value CborBase64Tag Item is a CBOR text string that was encoded as Base64 + * \value CborRegularExpressionTag Item is a CBOR text string containing a regular expression + * \value CborMimeMessageTag Item is a CBOR text string containing a MIME message (RFC 2045, 2046, 2047, 2822) + * \value CborSignatureTag Item contains CBOR-encoded data. + * This tag is also used as "file magic," marking a file as containing CBOR + */ + +/** + * \typedef CborTag + * This typedef is an unsigned 64-bit integer. Known CBOR tags can be used from the CborKnownTags enum + * but the user application may use other tag values than the ones specified in RFC 7049. + */ + +/** @} */ diff --git a/lib/cbor/tinycbor/src/cbor.h b/lib/cbor/tinycbor/src/cbor.h new file mode 100644 index 00000000..be5bbc77 --- /dev/null +++ b/lib/cbor/tinycbor/src/cbor.h @@ -0,0 +1,724 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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 CBOR_H +#define CBOR_H + +#ifndef assert +#include <assert.h> +#endif +#include <limits.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> +#include <stdio.h> + +#include "tinycbor-version.h" + +#define TINYCBOR_VERSION ((TINYCBOR_VERSION_MAJOR << 16) | (TINYCBOR_VERSION_MINOR << 8) | TINYCBOR_VERSION_PATCH) + +#ifdef __cplusplus +extern "C" { +#else +#include <stdbool.h> +#endif + +#ifndef SIZE_MAX +/* Some systems fail to define SIZE_MAX in <stdint.h>, even though C99 requires it... + * Conversion from signed to unsigned is defined in 6.3.1.3 (Signed and unsigned integers) p2, + * which says: "the value is converted by repeatedly adding or subtracting one more than the + * maximum value that can be represented in the new type until the value is in the range of the + * new type." + * So -1 gets converted to size_t by adding SIZE_MAX + 1, which results in SIZE_MAX. + */ +# define SIZE_MAX ((size_t)-1) +#endif + +#ifndef CBOR_API +# define CBOR_API +#endif +#ifndef CBOR_PRIVATE_API +# define CBOR_PRIVATE_API +#endif +#ifndef CBOR_INLINE_API +# if defined(__cplusplus) +# define CBOR_INLINE inline +# define CBOR_INLINE_API inline +# else +# define CBOR_INLINE_API static CBOR_INLINE +# if defined(_MSC_VER) +# define CBOR_INLINE __inline +# elif defined(__GNUC__) +# define CBOR_INLINE __inline__ +# elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +# define CBOR_INLINE inline +# else +# define CBOR_INLINE +# endif +# endif +#endif + +typedef enum CborType { + CborIntegerType = 0x00, + CborByteStringType = 0x40, + CborTextStringType = 0x60, + CborArrayType = 0x80, + CborMapType = 0xa0, + CborTagType = 0xc0, + CborSimpleType = 0xe0, + CborBooleanType = 0xf5, + CborNullType = 0xf6, + CborUndefinedType = 0xf7, + CborHalfFloatType = 0xf9, + CborFloatType = 0xfa, + CborDoubleType = 0xfb, + + CborInvalidType = 0xff /* equivalent to the break byte, so it will never be used */ +} CborType; + +typedef uint64_t CborTag; +typedef enum CborKnownTags { + CborDateTimeStringTag = 0, + CborUnixTime_tTag = 1, + CborPositiveBignumTag = 2, + CborNegativeBignumTag = 3, + CborDecimalTag = 4, + CborBigfloatTag = 5, + CborCOSE_Encrypt0Tag = 16, + CborCOSE_Mac0Tag = 17, + CborCOSE_Sign1Tag = 18, + CborExpectedBase64urlTag = 21, + CborExpectedBase64Tag = 22, + CborExpectedBase16Tag = 23, + CborEncodedCborTag = 24, + CborUrlTag = 32, + CborBase64urlTag = 33, + CborBase64Tag = 34, + CborRegularExpressionTag = 35, + CborMimeMessageTag = 36, + CborCOSE_EncryptTag = 96, + CborCOSE_MacTag = 97, + CborCOSE_SignTag = 98, + CborSignatureTag = 55799 +} CborKnownTags; + +/* #define the constants so we can check with #ifdef */ +#define CborDateTimeStringTag CborDateTimeStringTag +#define CborUnixTime_tTag CborUnixTime_tTag +#define CborPositiveBignumTag CborPositiveBignumTag +#define CborNegativeBignumTag CborNegativeBignumTag +#define CborDecimalTag CborDecimalTag +#define CborBigfloatTag CborBigfloatTag +#define CborCOSE_Encrypt0Tag CborCOSE_Encrypt0Tag +#define CborCOSE_Mac0Tag CborCOSE_Mac0Tag +#define CborCOSE_Sign1Tag CborCOSE_Sign1Tag +#define CborExpectedBase64urlTag CborExpectedBase64urlTag +#define CborExpectedBase64Tag CborExpectedBase64Tag +#define CborExpectedBase16Tag CborExpectedBase16Tag +#define CborEncodedCborTag CborEncodedCborTag +#define CborUrlTag CborUrlTag +#define CborBase64urlTag CborBase64urlTag +#define CborBase64Tag CborBase64Tag +#define CborRegularExpressionTag CborRegularExpressionTag +#define CborMimeMessageTag CborMimeMessageTag +#define CborCOSE_EncryptTag CborCOSE_EncryptTag +#define CborCOSE_MacTag CborCOSE_MacTag +#define CborCOSE_SignTag CborCOSE_SignTag +#define CborSignatureTag CborSignatureTag + +/* Error API */ + +typedef enum CborError { + CborNoError = 0, + + /* errors in all modes */ + CborUnknownError, + CborErrorUnknownLength, /* request for length in array, map, or string with indeterminate length */ + CborErrorAdvancePastEOF, + CborErrorIO, + + /* parser errors streaming errors */ + CborErrorGarbageAtEnd = 256, + CborErrorUnexpectedEOF, + CborErrorUnexpectedBreak, + CborErrorUnknownType, /* can only happen in major type 7 */ + CborErrorIllegalType, /* type not allowed here */ + CborErrorIllegalNumber, + CborErrorIllegalSimpleType, /* types of value less than 32 encoded in two bytes */ + CborErrorNoMoreStringChunks, + + /* parser errors in strict mode parsing only */ + CborErrorUnknownSimpleType = 512, + CborErrorUnknownTag, + CborErrorInappropriateTagForType, + CborErrorDuplicateObjectKeys, + CborErrorInvalidUtf8TextString, + CborErrorExcludedType, + CborErrorExcludedValue, + CborErrorImproperValue, + CborErrorOverlongEncoding, + CborErrorMapKeyNotString, + CborErrorMapNotSorted, + CborErrorMapKeysNotUnique, + + /* encoder errors */ + CborErrorTooManyItems = 768, + CborErrorTooFewItems, + + /* internal implementation errors */ + CborErrorDataTooLarge = 1024, + CborErrorNestingTooDeep, + CborErrorUnsupportedType, + CborErrorUnimplementedValidation, + + /* errors in converting to JSON */ + CborErrorJsonObjectKeyIsAggregate = 1280, + CborErrorJsonObjectKeyNotString, + CborErrorJsonNotImplemented, + + CborErrorOutOfMemory = (int) (~0U / 2 + 1), + CborErrorInternalError = (int) (~0U / 2) /* INT_MAX on two's complement machines */ +} CborError; + +CBOR_API const char *cbor_error_string(CborError error); + +/* Encoder API */ + +typedef enum CborEncoderAppendType +{ + CborEncoderAppendCborData = 0, + CborEncoderAppendStringData = 1 +} CborEncoderAppendType; + +typedef CborError (*CborEncoderWriteFunction)(void *, const void *, size_t, CborEncoderAppendType); + +enum CborEncoderFlags +{ + CborIteratorFlag_WriterFunction = 0x01, + CborIteratorFlag_ContainerIsMap_ = 0x20 +}; + +struct CborEncoder +{ + union { + uint8_t *ptr; + ptrdiff_t bytes_needed; + CborEncoderWriteFunction writer; + } data; + uint8_t *end; + size_t remaining; + int flags; +}; +typedef struct CborEncoder CborEncoder; + +static const size_t CborIndefiniteLength = SIZE_MAX; + +#ifndef CBOR_NO_ENCODER_API +CBOR_API void cbor_encoder_init(CborEncoder *encoder, uint8_t *buffer, size_t size, int flags); +CBOR_API void cbor_encoder_init_writer(CborEncoder *encoder, CborEncoderWriteFunction writer, void *); +CBOR_API CborError cbor_encode_uint(CborEncoder *encoder, uint64_t value); +CBOR_API CborError cbor_encode_int(CborEncoder *encoder, int64_t value); +CBOR_API CborError cbor_encode_negative_int(CborEncoder *encoder, uint64_t absolute_value); +CBOR_API CborError cbor_encode_simple_value(CborEncoder *encoder, uint8_t value); +CBOR_API CborError cbor_encode_tag(CborEncoder *encoder, CborTag tag); +CBOR_API CborError cbor_encode_text_string(CborEncoder *encoder, const char *string, size_t length); +CBOR_INLINE_API CborError cbor_encode_text_stringz(CborEncoder *encoder, const char *string) +{ return cbor_encode_text_string(encoder, string, strlen(string)); } +CBOR_API CborError cbor_encode_byte_string(CborEncoder *encoder, const uint8_t *string, size_t length); +CBOR_API CborError cbor_encode_floating_point(CborEncoder *encoder, CborType fpType, const void *value); + +CBOR_INLINE_API CborError cbor_encode_boolean(CborEncoder *encoder, bool value) +{ return cbor_encode_simple_value(encoder, (int)value - 1 + (CborBooleanType & 0x1f)); } +CBOR_INLINE_API CborError cbor_encode_null(CborEncoder *encoder) +{ return cbor_encode_simple_value(encoder, CborNullType & 0x1f); } +CBOR_INLINE_API CborError cbor_encode_undefined(CborEncoder *encoder) +{ return cbor_encode_simple_value(encoder, CborUndefinedType & 0x1f); } + +CBOR_INLINE_API CborError cbor_encode_half_float(CborEncoder *encoder, const void *value) +{ return cbor_encode_floating_point(encoder, CborHalfFloatType, value); } +CBOR_API CborError cbor_encode_float_as_half_float(CborEncoder *encoder, float value); +CBOR_INLINE_API CborError cbor_encode_float(CborEncoder *encoder, float value) +{ return cbor_encode_floating_point(encoder, CborFloatType, &value); } +CBOR_INLINE_API CborError cbor_encode_double(CborEncoder *encoder, double value) +{ return cbor_encode_floating_point(encoder, CborDoubleType, &value); } + +CBOR_API CborError cbor_encoder_create_array(CborEncoder *parentEncoder, CborEncoder *arrayEncoder, size_t length); +CBOR_API CborError cbor_encoder_create_map(CborEncoder *parentEncoder, CborEncoder *mapEncoder, size_t length); +CBOR_API CborError cbor_encoder_close_container(CborEncoder *parentEncoder, const CborEncoder *containerEncoder); +CBOR_API CborError cbor_encoder_close_container_checked(CborEncoder *parentEncoder, const CborEncoder *containerEncoder); + +CBOR_INLINE_API uint8_t *_cbor_encoder_get_buffer_pointer(const CborEncoder *encoder) +{ + return encoder->data.ptr; +} + +CBOR_INLINE_API size_t cbor_encoder_get_buffer_size(const CborEncoder *encoder, const uint8_t *buffer) +{ + return (size_t)(encoder->data.ptr - buffer); +} + +CBOR_INLINE_API size_t cbor_encoder_get_extra_bytes_needed(const CborEncoder *encoder) +{ + return encoder->end ? 0 : (size_t)encoder->data.bytes_needed; +} +#endif /* CBOR_NO_ENCODER_API */ + +/* Parser API */ + +enum CborParserGlobalFlags +{ + CborParserFlag_ExternalSource = 0x01 +}; + +enum CborParserIteratorFlags +{ + /* used for all types, but not during string chunk iteration + * (values are static-asserted, don't change) */ + CborIteratorFlag_IntegerValueIs64Bit = 0x01, + CborIteratorFlag_IntegerValueTooLarge = 0x02, + + /* used only for CborIntegerType */ + CborIteratorFlag_NegativeInteger = 0x04, + + /* used only during string iteration */ + CborIteratorFlag_BeforeFirstStringChunk = 0x04, + CborIteratorFlag_IteratingStringChunks = 0x08, + + /* used for arrays, maps and strings, including during chunk iteration */ + CborIteratorFlag_UnknownLength = 0x10, + + /* used for maps, but must be kept for all types + * (ContainerIsMap value must be CborMapType - CborArrayType) */ + CborIteratorFlag_ContainerIsMap = 0x20, + CborIteratorFlag_NextIsMapKey = 0x40 +}; + +struct CborValue; +struct CborParserOperations +{ + bool (*can_read_bytes)(void *token, size_t len); + void *(*read_bytes)(void *token, void *dst, size_t offset, size_t len); + void (*advance_bytes)(void *token, size_t len); + CborError (*transfer_string)(void *token, const void **userptr, size_t offset, size_t len); +}; + +struct CborParser +{ + union { + const uint8_t *end; + const struct CborParserOperations *ops; + } source; + enum CborParserGlobalFlags flags; +}; +typedef struct CborParser CborParser; + +struct CborValue +{ + const CborParser *parser; + union { + const uint8_t *ptr; + void *token; + } source; + uint32_t remaining; + uint16_t extra; + uint8_t type; + uint8_t flags; +}; +typedef struct CborValue CborValue; + +#ifndef CBOR_NO_PARSER_API +CBOR_API CborError cbor_parser_init(const uint8_t *buffer, size_t size, uint32_t flags, CborParser *parser, CborValue *it); +CBOR_API CborError cbor_parser_init_reader(const struct CborParserOperations *ops, CborParser *parser, CborValue *it, void *token); + +CBOR_API CborError cbor_value_validate_basic(const CborValue *it); + +CBOR_INLINE_API bool cbor_value_at_end(const CborValue *it) +{ return it->remaining == 0; } +CBOR_INLINE_API const uint8_t *cbor_value_get_next_byte(const CborValue *it) +{ return it->source.ptr; } +CBOR_API CborError cbor_value_reparse(CborValue *it); +CBOR_API CborError cbor_value_advance_fixed(CborValue *it); +CBOR_API CborError cbor_value_advance(CborValue *it); +CBOR_INLINE_API bool cbor_value_is_container(const CborValue *it) +{ return it->type == CborArrayType || it->type == CborMapType; } +CBOR_API CborError cbor_value_enter_container(const CborValue *it, CborValue *recursed); +CBOR_API CborError cbor_value_leave_container(CborValue *it, const CborValue *recursed); + +CBOR_PRIVATE_API uint64_t _cbor_value_decode_int64_internal(const CborValue *value); +CBOR_INLINE_API uint64_t _cbor_value_extract_int64_helper(const CborValue *value) +{ + return value->flags & CborIteratorFlag_IntegerValueTooLarge ? + _cbor_value_decode_int64_internal(value) : value->extra; +} + +CBOR_INLINE_API bool cbor_value_is_valid(const CborValue *value) +{ return value && value->type != CborInvalidType; } +CBOR_INLINE_API CborType cbor_value_get_type(const CborValue *value) +{ return (CborType)value->type; } + +/* Null & undefined type */ +CBOR_INLINE_API bool cbor_value_is_null(const CborValue *value) +{ return value->type == CborNullType; } +CBOR_INLINE_API bool cbor_value_is_undefined(const CborValue *value) +{ return value->type == CborUndefinedType; } + +/* Booleans */ +CBOR_INLINE_API bool cbor_value_is_boolean(const CborValue *value) +{ return value->type == CborBooleanType; } +CBOR_INLINE_API CborError cbor_value_get_boolean(const CborValue *value, bool *result) +{ + assert(cbor_value_is_boolean(value)); + *result = !!value->extra; + return CborNoError; +} + +/* Simple types */ +CBOR_INLINE_API bool cbor_value_is_simple_type(const CborValue *value) +{ return value->type == CborSimpleType; } +CBOR_INLINE_API CborError cbor_value_get_simple_type(const CborValue *value, uint8_t *result) +{ + assert(cbor_value_is_simple_type(value)); + *result = (uint8_t)value->extra; + return CborNoError; +} + +/* Integers */ +CBOR_INLINE_API bool cbor_value_is_integer(const CborValue *value) +{ return value->type == CborIntegerType; } +CBOR_INLINE_API bool cbor_value_is_unsigned_integer(const CborValue *value) +{ return cbor_value_is_integer(value) && (value->flags & CborIteratorFlag_NegativeInteger) == 0; } +CBOR_INLINE_API bool cbor_value_is_negative_integer(const CborValue *value) +{ return cbor_value_is_integer(value) && (value->flags & CborIteratorFlag_NegativeInteger); } + +CBOR_INLINE_API CborError cbor_value_get_raw_integer(const CborValue *value, uint64_t *result) +{ + assert(cbor_value_is_integer(value)); + *result = _cbor_value_extract_int64_helper(value); + return CborNoError; +} + +CBOR_INLINE_API CborError cbor_value_get_uint64(const CborValue *value, uint64_t *result) +{ + assert(cbor_value_is_unsigned_integer(value)); + *result = _cbor_value_extract_int64_helper(value); + return CborNoError; +} + +CBOR_INLINE_API CborError cbor_value_get_int64(const CborValue *value, int64_t *result) +{ + assert(cbor_value_is_integer(value)); + *result = (int64_t) _cbor_value_extract_int64_helper(value); + if (value->flags & CborIteratorFlag_NegativeInteger) + *result = -*result - 1; + return CborNoError; +} + +CBOR_INLINE_API CborError cbor_value_get_int(const CborValue *value, int *result) +{ + assert(cbor_value_is_integer(value)); + *result = (int) _cbor_value_extract_int64_helper(value); + if (value->flags & CborIteratorFlag_NegativeInteger) + *result = -*result - 1; + return CborNoError; +} + +CBOR_API CborError cbor_value_get_int64_checked(const CborValue *value, int64_t *result); +CBOR_API CborError cbor_value_get_int_checked(const CborValue *value, int *result); + +CBOR_INLINE_API bool cbor_value_is_length_known(const CborValue *value) +{ return (value->flags & CborIteratorFlag_UnknownLength) == 0; } + +/* Tags */ +CBOR_INLINE_API bool cbor_value_is_tag(const CborValue *value) +{ return value->type == CborTagType; } +CBOR_INLINE_API CborError cbor_value_get_tag(const CborValue *value, CborTag *result) +{ + assert(cbor_value_is_tag(value)); + *result = _cbor_value_extract_int64_helper(value); + return CborNoError; +} +CBOR_API CborError cbor_value_skip_tag(CborValue *it); + +/* Strings */ +CBOR_INLINE_API bool cbor_value_is_byte_string(const CborValue *value) +{ return value->type == CborByteStringType; } +CBOR_INLINE_API bool cbor_value_is_text_string(const CborValue *value) +{ return value->type == CborTextStringType; } + +CBOR_INLINE_API CborError cbor_value_get_string_length(const CborValue *value, size_t *length) +{ + uint64_t v; + assert(cbor_value_is_byte_string(value) || cbor_value_is_text_string(value)); + if (!cbor_value_is_length_known(value)) + return CborErrorUnknownLength; + v = _cbor_value_extract_int64_helper(value); + *length = (size_t)v; + if (*length != v) + return CborErrorDataTooLarge; + return CborNoError; +} + +CBOR_PRIVATE_API CborError _cbor_value_copy_string(const CborValue *value, void *buffer, + size_t *buflen, CborValue *next); +CBOR_PRIVATE_API CborError _cbor_value_dup_string(const CborValue *value, void **buffer, + size_t *buflen, CborValue *next); + +CBOR_API CborError cbor_value_calculate_string_length(const CborValue *value, size_t *length); + +CBOR_INLINE_API CborError cbor_value_copy_text_string(const CborValue *value, char *buffer, + size_t *buflen, CborValue *next) +{ + assert(cbor_value_is_text_string(value)); + return _cbor_value_copy_string(value, buffer, buflen, next); +} +CBOR_INLINE_API CborError cbor_value_copy_byte_string(const CborValue *value, uint8_t *buffer, + size_t *buflen, CborValue *next) +{ + assert(cbor_value_is_byte_string(value)); + return _cbor_value_copy_string(value, buffer, buflen, next); +} + +CBOR_INLINE_API CborError cbor_value_dup_text_string(const CborValue *value, char **buffer, + size_t *buflen, CborValue *next) +{ + assert(cbor_value_is_text_string(value)); + return _cbor_value_dup_string(value, (void **)buffer, buflen, next); +} +CBOR_INLINE_API CborError cbor_value_dup_byte_string(const CborValue *value, uint8_t **buffer, + size_t *buflen, CborValue *next) +{ + assert(cbor_value_is_byte_string(value)); + return _cbor_value_dup_string(value, (void **)buffer, buflen, next); +} + +CBOR_PRIVATE_API CborError _cbor_value_get_string_chunk_size(const CborValue *value, size_t *len); +CBOR_INLINE_API CborError cbor_value_get_string_chunk_size(const CborValue *value, size_t *len) +{ + assert(value->flags & CborIteratorFlag_IteratingStringChunks); + return _cbor_value_get_string_chunk_size(value, len); +} + +CBOR_INLINE_API bool cbor_value_string_iteration_at_end(const CborValue *value) +{ + size_t dummy; + return cbor_value_get_string_chunk_size(value, &dummy) == CborErrorNoMoreStringChunks; +} + +CBOR_PRIVATE_API CborError _cbor_value_begin_string_iteration(CborValue *value); +CBOR_INLINE_API CborError cbor_value_begin_string_iteration(CborValue *value) +{ + assert(cbor_value_is_text_string(value) || cbor_value_is_byte_string(value)); + assert(!(value->flags & CborIteratorFlag_IteratingStringChunks)); + return _cbor_value_begin_string_iteration(value); +} + +CBOR_PRIVATE_API CborError _cbor_value_finish_string_iteration(CborValue *value); +CBOR_INLINE_API CborError cbor_value_finish_string_iteration(CborValue *value) +{ + assert(cbor_value_string_iteration_at_end(value)); + return _cbor_value_finish_string_iteration(value); +} + +CBOR_PRIVATE_API CborError _cbor_value_get_string_chunk(const CborValue *value, const void **bufferptr, + size_t *len, CborValue *next); +CBOR_INLINE_API CborError cbor_value_get_text_string_chunk(const CborValue *value, const char **bufferptr, + size_t *len, CborValue *next) +{ + assert(cbor_value_is_text_string(value)); + return _cbor_value_get_string_chunk(value, (const void **)bufferptr, len, next); +} +CBOR_INLINE_API CborError cbor_value_get_byte_string_chunk(const CborValue *value, const uint8_t **bufferptr, + size_t *len, CborValue *next) +{ + assert(cbor_value_is_byte_string(value)); + return _cbor_value_get_string_chunk(value, (const void **)bufferptr, len, next); +} + +CBOR_API CborError cbor_value_text_string_equals(const CborValue *value, const char *string, bool *result); + +/* Maps and arrays */ +CBOR_INLINE_API bool cbor_value_is_array(const CborValue *value) +{ return value->type == CborArrayType; } +CBOR_INLINE_API bool cbor_value_is_map(const CborValue *value) +{ return value->type == CborMapType; } + +CBOR_INLINE_API CborError cbor_value_get_array_length(const CborValue *value, size_t *length) +{ + uint64_t v; + assert(cbor_value_is_array(value)); + if (!cbor_value_is_length_known(value)) + return CborErrorUnknownLength; + v = _cbor_value_extract_int64_helper(value); + *length = (size_t)v; + if (*length != v) + return CborErrorDataTooLarge; + return CborNoError; +} + +CBOR_INLINE_API CborError cbor_value_get_map_length(const CborValue *value, size_t *length) +{ + uint64_t v; + assert(cbor_value_is_map(value)); + if (!cbor_value_is_length_known(value)) + return CborErrorUnknownLength; + v = _cbor_value_extract_int64_helper(value); + *length = (size_t)v; + if (*length != v) + return CborErrorDataTooLarge; + return CborNoError; +} + +CBOR_API CborError cbor_value_map_find_value(const CborValue *map, const char *string, CborValue *element); + +/* Floating point */ +CBOR_INLINE_API bool cbor_value_is_half_float(const CborValue *value) +{ return value->type == CborHalfFloatType; } +CBOR_API CborError cbor_value_get_half_float_as_float(const CborValue *value, float *result); +CBOR_INLINE_API CborError cbor_value_get_half_float(const CborValue *value, void *result) +{ + assert(cbor_value_is_half_float(value)); + assert((value->flags & CborIteratorFlag_IntegerValueTooLarge) == 0); + + /* size has already been computed */ + memcpy(result, &value->extra, sizeof(value->extra)); + return CborNoError; +} + +CBOR_INLINE_API bool cbor_value_is_float(const CborValue *value) +{ return value->type == CborFloatType; } +CBOR_INLINE_API CborError cbor_value_get_float(const CborValue *value, float *result) +{ + uint32_t data; + assert(cbor_value_is_float(value)); + assert(value->flags & CborIteratorFlag_IntegerValueTooLarge); + data = (uint32_t)_cbor_value_decode_int64_internal(value); + memcpy(result, &data, sizeof(*result)); + return CborNoError; +} + +CBOR_INLINE_API bool cbor_value_is_double(const CborValue *value) +{ return value->type == CborDoubleType; } +CBOR_INLINE_API CborError cbor_value_get_double(const CborValue *value, double *result) +{ + uint64_t data; + assert(cbor_value_is_double(value)); + assert(value->flags & CborIteratorFlag_IntegerValueTooLarge); + data = _cbor_value_decode_int64_internal(value); + memcpy(result, &data, sizeof(*result)); + return CborNoError; +} + +/* Validation API */ +#ifndef CBOR_NO_VALIDATION_API + +enum CborValidationFlags { + /* Bit mapping: + * bits 0-7 (8 bits): canonical format + * bits 8-11 (4 bits): canonical format & strict mode + * bits 12-20 (8 bits): strict mode + * bits 21-31 (10 bits): other + */ + + CborValidateShortestIntegrals = 0x0001, + CborValidateShortestFloatingPoint = 0x0002, + CborValidateShortestNumbers = CborValidateShortestIntegrals | CborValidateShortestFloatingPoint, + CborValidateNoIndeterminateLength = 0x0100, + CborValidateMapIsSorted = 0x0200 | CborValidateNoIndeterminateLength, + + CborValidateCanonicalFormat = 0x0fff, + + CborValidateMapKeysAreUnique = 0x1000 | CborValidateMapIsSorted, + CborValidateTagUse = 0x2000, + CborValidateUtf8 = 0x4000, + + CborValidateStrictMode = 0xfff00, + + CborValidateMapKeysAreString = 0x100000, + CborValidateNoUndefined = 0x200000, + CborValidateNoTags = 0x400000, + CborValidateFiniteFloatingPoint = 0x800000, + /* unused = 0x1000000, */ + /* unused = 0x2000000, */ + + CborValidateNoUnknownSimpleTypesSA = 0x4000000, + CborValidateNoUnknownSimpleTypes = 0x8000000 | CborValidateNoUnknownSimpleTypesSA, + CborValidateNoUnknownTagsSA = 0x10000000, + CborValidateNoUnknownTagsSR = 0x20000000 | CborValidateNoUnknownTagsSA, + CborValidateNoUnknownTags = 0x40000000 | CborValidateNoUnknownTagsSR, + + CborValidateCompleteData = (int)0x80000000, + + CborValidateStrictest = (int)~0U, + CborValidateBasic = 0 +}; + +CBOR_API CborError cbor_value_validate(const CborValue *it, uint32_t flags); +#endif /* CBOR_NO_VALIDATION_API */ + +/* Human-readable (dump) API */ +#ifndef CBOR_NO_PRETTY_API + +enum CborPrettyFlags { + CborPrettyNumericEncodingIndicators = 0x01, + CborPrettyTextualEncodingIndicators = 0, + + CborPrettyIndicateIndeterminateLength = 0x02, + CborPrettyIndicateIndetermineLength = CborPrettyIndicateIndeterminateLength, /* deprecated */ + CborPrettyIndicateOverlongNumbers = 0x04, + + CborPrettyShowStringFragments = 0x100, + CborPrettyMergeStringFragments = 0, + + CborPrettyDefaultFlags = CborPrettyIndicateIndeterminateLength +}; + +typedef CborError (*CborStreamFunction)(void *token, const char *fmt, ...) +#ifdef __GNUC__ + __attribute__((__format__(printf, 2, 3))) +#endif +; + +CBOR_API CborError cbor_value_to_pretty_stream(CborStreamFunction streamFunction, void *token, CborValue *value, int flags); + +/* The following API requires a hosted C implementation (uses FILE*) */ +#if !defined(__STDC_HOSTED__) || __STDC_HOSTED__-0 == 1 +CBOR_API CborError cbor_value_to_pretty_advance_flags(FILE *out, CborValue *value, int flags); +CBOR_API CborError cbor_value_to_pretty_advance(FILE *out, CborValue *value); +CBOR_INLINE_API CborError cbor_value_to_pretty(FILE *out, const CborValue *value) +{ + CborValue copy = *value; + return cbor_value_to_pretty_advance_flags(out, ©, CborPrettyDefaultFlags); +} +#endif /* __STDC_HOSTED__ check */ + +#endif /* CBOR_NO_PRETTY_API */ + +#endif /* CBOR_NO_PARSER_API */ + +#ifdef __cplusplus +} +#endif + +#endif /* CBOR_H */ + diff --git a/lib/cbor/tinycbor/src/cborencoder.c b/lib/cbor/tinycbor/src/cborencoder.c new file mode 100644 index 00000000..a51f4451 --- /dev/null +++ b/lib/cbor/tinycbor/src/cborencoder.c @@ -0,0 +1,689 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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 + +#include "cbor.h" +#include "cborinternal_p.h" +#include "compilersupport_p.h" + +#include <stdlib.h> +#include <string.h> + +/** + * \defgroup CborEncoding Encoding to CBOR + * \brief Group of functions used to encode data to CBOR. + * + * CborEncoder is used to encode data into a CBOR stream. The outermost + * CborEncoder is initialized by calling cbor_encoder_init(), with the buffer + * where the CBOR stream will be stored. The outermost CborEncoder is usually + * used to encode exactly one item, most often an array or map. It is possible + * to encode more than one item, but care must then be taken on the decoder + * side to ensure the state is reset after each item was decoded. + * + * Nested CborEncoder objects are created using cbor_encoder_create_array() and + * cbor_encoder_create_map(), later closed with cbor_encoder_close_container() + * or cbor_encoder_close_container_checked(). The pairs of creation and closing + * must be exactly matched and their parameters are always the same. + * + * CborEncoder writes directly to the user-supplied buffer, without extra + * buffering. CborEncoder does not allocate memory and CborEncoder objects are + * usually created on the stack of the encoding functions. + * + * The example below initializes a CborEncoder object with a buffer and encodes + * a single integer. + * + * \code + * uint8_t buf[16]; + * CborEncoder encoder; + * cbor_encoder_init(&encoder, buf, sizeof(buf), 0); + * cbor_encode_int(&encoder, some_value); + * \endcode + * + * As explained before, usually the outermost CborEncoder object is used to add + * one array or map, which in turn contains multiple elements. The example + * below creates a CBOR map with one element: a key "foo" and a boolean value. + * + * \code + * uint8_t buf[16]; + * CborEncoder encoder, mapEncoder; + * cbor_encoder_init(&encoder, buf, sizeof(buf), 0); + * cbor_encoder_create_map(&encoder, &mapEncoder, 1); + * cbor_encode_text_stringz(&mapEncoder, "foo"); + * cbor_encode_boolean(&mapEncoder, some_value); + * cbor_encoder_close_container(&encoder, &mapEncoder); + * \endcode + * + * <h3 class="groupheader">Error checking and buffer size</h3> + * + * All functions operating on CborEncoder return a condition of type CborError. + * If the encoding was successful, they return CborNoError. Some functions do + * extra checking on the input provided and may return some other error + * conditions (for example, cbor_encode_simple_value() checks that the type is + * of the correct type). + * + * In addition, all functions check whether the buffer has enough bytes to + * encode the item being appended. If that is not possible, they return + * CborErrorOutOfMemory. + * + * It is possible to continue with the encoding of data past the first function + * that returns CborErrorOutOfMemory. CborEncoder functions will not overrun + * the buffer, but will instead count how many more bytes are needed to + * complete the encoding. At the end, you can obtain that count by calling + * cbor_encoder_get_extra_bytes_needed(). + * + * \section1 Finalizing the encoding + * + * Once all items have been appended and the containers have all been properly + * closed, the user-supplied buffer will contain the CBOR stream and may be + * immediately used. To obtain the size of the buffer, call + * cbor_encoder_get_buffer_size() with the original buffer pointer. + * + * The example below illustrates how one can encode an item with error checking + * and then pass on the buffer for network sending. + * + * \code + * uint8_t buf[16]; + * CborError err; + * CborEncoder encoder, mapEncoder; + * cbor_encoder_init(&encoder, buf, sizeof(buf), 0); + * err = cbor_encoder_create_map(&encoder, &mapEncoder, 1); + * if (err) + * return err; + * err = cbor_encode_text_stringz(&mapEncoder, "foo"); + * if (err) + * return err; + * err = cbor_encode_boolean(&mapEncoder, some_value); + * if (err) + * return err; + * err = cbor_encoder_close_container_checked(&encoder, &mapEncoder); + * if (err) + * return err; + * + * size_t len = cbor_encoder_get_buffer_size(&encoder, buf); + * send_payload(buf, len); + * return CborNoError; + * \endcode + * + * Finally, the example below expands on the one above and also + * deals with dynamically growing the buffer if the initial allocation wasn't + * big enough. Note the two places where the error checking was replaced with + * an cbor_assertion, showing where the author assumes no error can occur. + * + * \code + * uint8_t *encode_string_array(const char **strings, int n, size_t *bufsize) + * { + * CborError err; + * CborEncoder encoder, arrayEncoder; + * size_t size = 256; + * uint8_t *buf = NULL; + * + * while (1) { + * int i; + * size_t more_bytes; + * uint8_t *nbuf = realloc(buf, size); + * if (nbuf == NULL) + * goto error; + * buf = nbuf; + * + * cbor_encoder_init(&encoder, buf, size, 0); + * err = cbor_encoder_create_array(&encoder, &arrayEncoder, n); + * cbor_assert(!err); // can't fail, the buffer is always big enough + * + * for (i = 0; i < n; ++i) { + * err = cbor_encode_text_stringz(&arrayEncoder, strings[i]); + * if (err && err != CborErrorOutOfMemory) + * goto error; + * } + * + * err = cbor_encoder_close_container_checked(&encoder, &arrayEncoder); + * cbor_assert(!err); // shouldn't fail! + * + * more_bytes = cbor_encoder_get_extra_bytes_needed(encoder); + * if (more_size) { + * // buffer wasn't big enough, try again + * size += more_bytes; + * continue; + * } + * + * *bufsize = cbor_encoder_get_buffer_size(encoder, buf); + * return buf; + * } + * error: + * free(buf); + * return NULL; + * } + * \endcode + */ + +/** + * \addtogroup CborEncoding + * @{ + */ + +/** + * \struct CborEncoder + * Structure used to encode to CBOR. + */ + +/** + * Initializes a CborEncoder structure \a encoder by pointing it to buffer \a + * buffer of size \a size. The \a flags field is currently unused and must be + * zero. + */ +void cbor_encoder_init(CborEncoder *encoder, uint8_t *buffer, size_t size, int flags) +{ + encoder->data.ptr = buffer; + encoder->end = buffer + size; + encoder->remaining = 2; + encoder->flags = flags; +} + +void cbor_encoder_init_writer(CborEncoder *encoder, CborEncoderWriteFunction writer, void *token) +{ +#ifdef CBOR_ENCODER_WRITE_FUNCTION + (void) writer; +#else + encoder->data.writer = writer; +#endif + encoder->end = (uint8_t *)token; + encoder->remaining = 2; + encoder->flags = CborIteratorFlag_WriterFunction; +} + +static inline void put16(void *where, uint16_t v) +{ + uint16_t v_be = cbor_htons(v); + memcpy(where, &v_be, sizeof(v_be)); +} + +/* Note: Since this is currently only used in situations where OOM is the only + * valid error, we KNOW this to be true. Thus, this function now returns just 'true', + * but if in the future, any function starts returning a non-OOM error, this will need + * to be changed to the test. At the moment, this is done to prevent more branches + * being created in the tinycbor output */ +static inline bool isOomError(CborError err) +{ + if (CBOR_ENCODER_WRITER_CONTROL < 0) + return true; + + /* CborErrorOutOfMemory is the only negative error code, intentionally + * so we can write the test like this */ + return (int)err < 0; +} + +static inline void put32(void *where, uint32_t v) +{ + uint32_t v_be = cbor_htonl(v); + memcpy(where, &v_be, sizeof(v_be)); +} + +static inline void put64(void *where, uint64_t v) +{ + uint64_t v_be = cbor_htonll(v); + memcpy(where, &v_be, sizeof(v_be)); +} + +static inline bool would_overflow(CborEncoder *encoder, size_t len) +{ + ptrdiff_t remaining = (ptrdiff_t)encoder->end; + remaining -= remaining ? (ptrdiff_t)encoder->data.ptr : encoder->data.bytes_needed; + remaining -= (ptrdiff_t)len; + return unlikely(remaining < 0); +} + +static inline void advance_ptr(CborEncoder *encoder, size_t n) +{ + if (encoder->end) + encoder->data.ptr += n; + else + encoder->data.bytes_needed += n; +} + +static inline CborError append_to_buffer(CborEncoder *encoder, const void *data, size_t len, + CborEncoderAppendType appendType) +{ + if (CBOR_ENCODER_WRITER_CONTROL >= 0) { + if (encoder->flags & CborIteratorFlag_WriterFunction || CBOR_ENCODER_WRITER_CONTROL != 0) { +# ifdef CBOR_ENCODER_WRITE_FUNCTION + return CBOR_ENCODER_WRITE_FUNCTION(encoder->end, data, len, appendType); +# else + return encoder->data.writer(encoder->end, data, len, appendType); +# endif + } + } + +#if CBOR_ENCODER_WRITER_CONTROL <= 0 + if (would_overflow(encoder, len)) { + if (encoder->end != NULL) { + len -= encoder->end - encoder->data.ptr; + encoder->end = NULL; + encoder->data.bytes_needed = 0; + } + + advance_ptr(encoder, len); + return CborErrorOutOfMemory; + } + + memcpy(encoder->data.ptr, data, len); + encoder->data.ptr += len; +#endif + return CborNoError; +} + +static inline CborError append_byte_to_buffer(CborEncoder *encoder, uint8_t byte) +{ + return append_to_buffer(encoder, &byte, 1, CborEncoderAppendCborData); +} + +static inline CborError encode_number_no_update(CborEncoder *encoder, uint64_t ui, uint8_t shiftedMajorType) +{ + /* Little-endian would have been so much more convenient here: + * We could just write at the beginning of buf but append_to_buffer + * only the necessary bytes. + * Since it has to be big endian, do it the other way around: + * write from the end. */ + uint64_t buf[2]; + uint8_t *const bufend = (uint8_t *)buf + sizeof(buf); + uint8_t *bufstart = bufend - 1; + put64(buf + 1, ui); /* we probably have a bunch of zeros in the beginning */ + + if (ui < Value8Bit) { + *bufstart += shiftedMajorType; + } else { + uint8_t more = 0; + if (ui > 0xffU) + ++more; + if (ui > 0xffffU) + ++more; + if (ui > 0xffffffffU) + ++more; + bufstart -= (size_t)1 << more; + *bufstart = shiftedMajorType + Value8Bit + more; + } + + return append_to_buffer(encoder, bufstart, bufend - bufstart, CborEncoderAppendCborData); +} + +static inline void saturated_decrement(CborEncoder *encoder) +{ + if (encoder->remaining) + --encoder->remaining; +} + +static inline CborError encode_number(CborEncoder *encoder, uint64_t ui, uint8_t shiftedMajorType) +{ + saturated_decrement(encoder); + return encode_number_no_update(encoder, ui, shiftedMajorType); +} + +/** + * Appends the unsigned 64-bit integer \a value to the CBOR stream provided by + * \a encoder. + * + * \sa cbor_encode_negative_int, cbor_encode_int + */ +CborError cbor_encode_uint(CborEncoder *encoder, uint64_t value) +{ + return encode_number(encoder, value, UnsignedIntegerType << MajorTypeShift); +} + +/** + * Appends the negative 64-bit integer whose absolute value is \a + * absolute_value to the CBOR stream provided by \a encoder. + * + * If the value \a absolute_value is zero, this function encodes -2^64. + * + * \sa cbor_encode_uint, cbor_encode_int + */ +CborError cbor_encode_negative_int(CborEncoder *encoder, uint64_t absolute_value) +{ + return encode_number(encoder, absolute_value - 1, NegativeIntegerType << MajorTypeShift); +} + +/** + * Appends the signed 64-bit integer \a value to the CBOR stream provided by + * \a encoder. + * + * \sa cbor_encode_negative_int, cbor_encode_uint + */ +CborError cbor_encode_int(CborEncoder *encoder, int64_t value) +{ + /* adapted from code in RFC 7049 appendix C (pseudocode) */ + uint64_t ui = value >> 63; /* extend sign to whole length */ + uint8_t majorType = ui & 0x20; /* extract major type */ + ui ^= value; /* complement negatives */ + return encode_number(encoder, ui, majorType); +} + +/** + * Appends the CBOR Simple Type of value \a value to the CBOR stream provided by + * \a encoder. + * + * This function may return error CborErrorIllegalSimpleType if the \a value + * variable contains a number that is not a valid simple type. + */ +CborError cbor_encode_simple_value(CborEncoder *encoder, uint8_t value) +{ +#ifndef CBOR_ENCODER_NO_CHECK_USER + /* check if this is a valid simple type */ + if (value >= HalfPrecisionFloat && value <= Break) + return CborErrorIllegalSimpleType; +#endif + return encode_number(encoder, value, SimpleTypesType << MajorTypeShift); +} + +/** + * Appends the floating-point value of type \a fpType and pointed to by \a + * value to the CBOR stream provided by \a encoder. The value of \a fpType must + * be one of CborHalfFloatType, CborFloatType or CborDoubleType, otherwise the + * behavior of this function is undefined. + * + * This function is useful for code that needs to pass through floating point + * values but does not wish to have the actual floating-point code. + * + * \sa cbor_encode_half_float, cbor_encode_float_as_half_float, cbor_encode_float, cbor_encode_double + */ +CborError cbor_encode_floating_point(CborEncoder *encoder, CborType fpType, const void *value) +{ + unsigned size; + uint8_t buf[1 + sizeof(uint64_t)]; + cbor_assert(fpType == CborHalfFloatType || fpType == CborFloatType || fpType == CborDoubleType); + buf[0] = fpType; + + size = 2U << (fpType - CborHalfFloatType); + if (size == 8) + put64(buf + 1, *(const uint64_t*)value); + else if (size == 4) + put32(buf + 1, *(const uint32_t*)value); + else + put16(buf + 1, *(const uint16_t*)value); + saturated_decrement(encoder); + return append_to_buffer(encoder, buf, size + 1, CborEncoderAppendCborData); +} + +/** + * Appends the CBOR tag \a tag to the CBOR stream provided by \a encoder. + * + * \sa CborTag + */ +CborError cbor_encode_tag(CborEncoder *encoder, CborTag tag) +{ + /* tags don't count towards the number of elements in an array or map */ + return encode_number_no_update(encoder, tag, TagType << MajorTypeShift); +} + +static CborError encode_string(CborEncoder *encoder, size_t length, uint8_t shiftedMajorType, const void *string) +{ + CborError err = encode_number(encoder, length, shiftedMajorType); + if (err && !isOomError(err)) + return err; + return append_to_buffer(encoder, string, length, CborEncoderAppendStringData); +} + +/** + * \fn CborError cbor_encode_text_stringz(CborEncoder *encoder, const char *string) + * + * Appends the null-terminated text string \a string to the CBOR stream + * provided by \a encoder. CBOR requires that \a string be valid UTF-8, but + * TinyCBOR makes no verification of correctness. The terminating null is not + * included in the stream. + * + * \sa cbor_encode_text_string, cbor_encode_byte_string + */ + +/** + * Appends the byte string \a string of length \a length to the CBOR stream + * provided by \a encoder. CBOR byte strings are arbitrary raw data. + * + * \sa cbor_encode_text_stringz, cbor_encode_text_string + */ +CborError cbor_encode_byte_string(CborEncoder *encoder, const uint8_t *string, size_t length) +{ + return encode_string(encoder, length, ByteStringType << MajorTypeShift, string); +} + +/** + * Appends the text string \a string of length \a length to the CBOR stream + * provided by \a encoder. CBOR requires that \a string be valid UTF-8, but + * TinyCBOR makes no verification of correctness. + * + * \sa CborError cbor_encode_text_stringz, cbor_encode_byte_string + */ +CborError cbor_encode_text_string(CborEncoder *encoder, const char *string, size_t length) +{ + return encode_string(encoder, length, TextStringType << MajorTypeShift, string); +} + +#ifdef __GNUC__ +__attribute__((noinline)) +#endif +static CborError create_container(CborEncoder *encoder, CborEncoder *container, size_t length, uint8_t shiftedMajorType) +{ + CborError err; + container->data.ptr = encoder->data.ptr; + container->end = encoder->end; + saturated_decrement(encoder); + container->remaining = length + 1; /* overflow ok on CborIndefiniteLength */ + + cbor_static_assert((int)CborIteratorFlag_ContainerIsMap_ == (int)CborIteratorFlag_ContainerIsMap); + cbor_static_assert(((MapType << MajorTypeShift) & CborIteratorFlag_ContainerIsMap) == CborIteratorFlag_ContainerIsMap); + cbor_static_assert(((ArrayType << MajorTypeShift) & CborIteratorFlag_ContainerIsMap) == 0); + container->flags = shiftedMajorType & CborIteratorFlag_ContainerIsMap; + if (CBOR_ENCODER_WRITER_CONTROL == 0) + container->flags |= encoder->flags & CborIteratorFlag_WriterFunction; + + if (length == CborIndefiniteLength) { + container->flags |= CborIteratorFlag_UnknownLength; + err = append_byte_to_buffer(container, shiftedMajorType + IndefiniteLength); + } else { + if (shiftedMajorType & CborIteratorFlag_ContainerIsMap) + container->remaining += length; + err = encode_number_no_update(container, length, shiftedMajorType); + } + return err; +} + +/** + * Creates a CBOR array in the CBOR stream provided by \a parentEncoder and + * initializes \a arrayEncoder so that items can be added to the array using + * the CborEncoder functions. The array must be terminated by calling either + * cbor_encoder_close_container() or cbor_encoder_close_container_checked() + * with the same \a encoder and \a arrayEncoder parameters. + * + * The number of items inserted into the array must be exactly \a length items, + * otherwise the stream is invalid. If the number of items is not known when + * creating the array, the constant \ref CborIndefiniteLength may be passed as + * length instead, and an indefinite length array is created. + * + * \sa cbor_encoder_create_map + */ +CborError cbor_encoder_create_array(CborEncoder *parentEncoder, CborEncoder *arrayEncoder, size_t length) +{ + return create_container(parentEncoder, arrayEncoder, length, ArrayType << MajorTypeShift); +} + +/** + * Creates a CBOR map in the CBOR stream provided by \a parentEncoder and + * initializes \a mapEncoder so that items can be added to the map using + * the CborEncoder functions. The map must be terminated by calling either + * cbor_encoder_close_container() or cbor_encoder_close_container_checked() + * with the same \a encoder and \a mapEncoder parameters. + * + * The number of pair of items inserted into the map must be exactly \a length + * items, otherwise the stream is invalid. If the number is not known + * when creating the map, the constant \ref CborIndefiniteLength may be passed as + * length instead, and an indefinite length map is created. + * + * \b{Implementation limitation:} TinyCBOR cannot encode more than SIZE_MAX/2 + * key-value pairs in the stream. If the length \a length is larger than this + * value (and is not \ref CborIndefiniteLength), this function returns error + * CborErrorDataTooLarge. + * + * \sa cbor_encoder_create_array + */ +CborError cbor_encoder_create_map(CborEncoder *parentEncoder, CborEncoder *mapEncoder, size_t length) +{ + if (length != CborIndefiniteLength && length > SIZE_MAX / 2) + return CborErrorDataTooLarge; + return create_container(parentEncoder, mapEncoder, length, MapType << MajorTypeShift); +} + +/** + * Closes the CBOR container (array or map) provided by \a containerEncoder and + * updates the CBOR stream provided by \a encoder. Both parameters must be the + * same as were passed to cbor_encoder_create_array() or + * cbor_encoder_create_map(). + * + * Since version 0.5, this function verifies that the number of items (or pairs + * of items, in the case of a map) was correct. It is no longer necessary to call + * cbor_encoder_close_container_checked() instead. + * + * \sa cbor_encoder_create_array(), cbor_encoder_create_map() + */ +CborError cbor_encoder_close_container(CborEncoder *parentEncoder, const CborEncoder *containerEncoder) +{ + // synchronise buffer state with that of the container + parentEncoder->end = containerEncoder->end; + parentEncoder->data = containerEncoder->data; + + if (containerEncoder->flags & CborIteratorFlag_UnknownLength) + return append_byte_to_buffer(parentEncoder, BreakByte); + + if (containerEncoder->remaining != 1) + return containerEncoder->remaining == 0 ? CborErrorTooManyItems : CborErrorTooFewItems; + + if (!parentEncoder->end) + return CborErrorOutOfMemory; /* keep the state */ + + return CborNoError; +} + +/** + * \fn CborError cbor_encode_boolean(CborEncoder *encoder, bool value) + * + * Appends the boolean value \a value to the CBOR stream provided by \a encoder. + */ + +/** + * \fn CborError cbor_encode_null(CborEncoder *encoder) + * + * Appends the CBOR type representing a null value to the CBOR stream provided + * by \a encoder. + * + * \sa cbor_encode_undefined() + */ + +/** + * \fn CborError cbor_encode_undefined(CborEncoder *encoder) + * + * Appends the CBOR type representing an undefined value to the CBOR stream + * provided by \a encoder. + * + * \sa cbor_encode_null() + */ + +/** + * \fn CborError cbor_encode_half_float(CborEncoder *encoder, const void *value) + * + * Appends the IEEE 754 half-precision (16-bit) floating point value pointed to + * by \a value to the CBOR stream provided by \a encoder. + * + * \sa cbor_encode_floating_point(), cbor_encode_float(), cbor_encode_double() + */ + +/** + * \fn CborError cbor_encode_float_as_half_float(CborEncoder *encoder, float value) + * + * Convert the IEEE 754 single-precision (32-bit) floating point value \a value + * to the IEEE 754 half-precision (16-bit) floating point value and append it + * to the CBOR stream provided by \a encoder. + * The \a value should be in the range of the IEEE 754 half-precision floating point type, + * INFINITY, -INFINITY, or NAN, otherwise the behavior of this function is undefined. + * + * \sa cbor_encode_floating_point(), cbor_encode_float(), cbor_encode_double() + */ + +/** + * \fn CborError cbor_encode_float(CborEncoder *encoder, float value) + * + * Appends the IEEE 754 single-precision (32-bit) floating point value \a value + * to the CBOR stream provided by \a encoder. + * + * \sa cbor_encode_floating_point(), cbor_encode_half_float(), cbor_encode_float_as_half_float(), cbor_encode_double() + */ + +/** + * \fn CborError cbor_encode_double(CborEncoder *encoder, double value) + * + * Appends the IEEE 754 double-precision (64-bit) floating point value \a value + * to the CBOR stream provided by \a encoder. + * + * \sa cbor_encode_floating_point(), cbor_encode_half_float(), cbor_encode_float_as_half_float(), cbor_encode_float() + */ + +/** + * \fn size_t cbor_encoder_get_buffer_size(const CborEncoder *encoder, const uint8_t *buffer) + * + * Returns the total size of the buffer starting at \a buffer after the + * encoding finished without errors. The \a encoder and \a buffer arguments + * must be the same as supplied to cbor_encoder_init(). + * + * If the encoding process had errors, the return value of this function is + * meaningless. If the only errors were CborErrorOutOfMemory, instead use + * cbor_encoder_get_extra_bytes_needed() to find out by how much to grow the + * buffer before encoding again. + * + * See \ref CborEncoding for an example of using this function. + * + * \sa cbor_encoder_init(), cbor_encoder_get_extra_bytes_needed(), CborEncoding + */ + +/** + * \fn size_t cbor_encoder_get_extra_bytes_needed(const CborEncoder *encoder) + * + * Returns how many more bytes the original buffer supplied to + * cbor_encoder_init() needs to be extended by so that no CborErrorOutOfMemory + * condition will happen for the encoding. If the buffer was big enough, this + * function returns 0. The \a encoder must be the original argument as passed + * to cbor_encoder_init(). + * + * This function is usually called after an encoding sequence ended with one or + * more CborErrorOutOfMemory errors, but no other error. If any other error + * happened, the return value of this function is meaningless. + * + * See \ref CborEncoding for an example of using this function. + * + * \sa cbor_encoder_init(), cbor_encoder_get_buffer_size(), CborEncoding + */ + +/** @} */ diff --git a/lib/cbor/tinycbor/src/cborencoder_close_container_checked.c b/lib/cbor/tinycbor/src/cborencoder_close_container_checked.c new file mode 100644 index 00000000..5661e4d5 --- /dev/null +++ b/lib/cbor/tinycbor/src/cborencoder_close_container_checked.c @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" + +/** + * \addtogroup CborEncoding + * @{ + */ + +/** + * @deprecated + * + * Closes the CBOR container (array or map) provided by \a containerEncoder and + * updates the CBOR stream provided by \a encoder. Both parameters must be the + * same as were passed to cbor_encoder_create_array() or + * cbor_encoder_create_map(). + * + * Prior to version 0.5, cbor_encoder_close_container() did not check the + * number of items added. Since that version, it does and now + * cbor_encoder_close_container_checked() is no longer needed. + * + * \sa cbor_encoder_create_array(), cbor_encoder_create_map() + */ +CborError cbor_encoder_close_container_checked(CborEncoder *encoder, const CborEncoder *containerEncoder) +{ + return cbor_encoder_close_container(encoder, containerEncoder); +} + +/** @} */ diff --git a/lib/cbor/tinycbor/src/cborencoder_float.c b/lib/cbor/tinycbor/src/cborencoder_float.c new file mode 100644 index 00000000..6f168379 --- /dev/null +++ b/lib/cbor/tinycbor/src/cborencoder_float.c @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** Copyright (C) 2019 S.Phirsov +** +** 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. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" + +#include "cborinternal_p.h" + +#ifndef CBOR_NO_HALF_FLOAT_TYPE +CborError cbor_encode_float_as_half_float(CborEncoder *encoder, float value) +{ + uint16_t v = (uint16_t)encode_half(value); + + return cbor_encode_floating_point(encoder, CborHalfFloatType, &v); +} +#endif diff --git a/lib/cbor/tinycbor/src/cborerrorstrings.c b/lib/cbor/tinycbor/src/cborerrorstrings.c new file mode 100644 index 00000000..44f766a3 --- /dev/null +++ b/lib/cbor/tinycbor/src/cborerrorstrings.c @@ -0,0 +1,188 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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. +** +****************************************************************************/ + +#include "cbor.h" + +#ifndef _ +# define _(msg) msg +#endif + +/** + * \enum CborError + * \ingroup CborGlobals + * The CborError enum contains the possible error values used by the CBOR encoder and decoder. + * + * TinyCBOR functions report success by returning CborNoError, or one error + * condition by returning one of the values below. One exception is the + * out-of-memory condition (CborErrorOutOfMemory), which the functions for \ref + * CborEncoding may report in bit-wise OR with other conditions. + * + * This technique allows code to determine whether the only error condition was + * a lack of buffer space, which may not be a fatal condition if the buffer can + * be resized. Additionally, the functions for \ref CborEncoding may continue + * to be used even after CborErrorOutOfMemory is returned, and instead they + * will simply calculate the extra space needed. + * + * \value CborNoError No error occurred + * \omitvalue CborUnknownError + * \value CborErrorUnknownLength Request for the length of an array, map or string whose length is not provided in the CBOR stream + * \value CborErrorAdvancePastEOF Not enough data in the stream to decode item (decoding would advance past end of stream) + * \value CborErrorIO An I/O error occurred, probably due to an out-of-memory situation + * \value CborErrorGarbageAtEnd Bytes exist past the end of the CBOR stream + * \value CborErrorUnexpectedEOF End of stream reached unexpectedly + * \value CborErrorUnexpectedBreak A CBOR break byte was found where not expected + * \value CborErrorUnknownType An unknown type (future extension to CBOR) was found in the stream + * \value CborErrorIllegalType An invalid type was found while parsing a chunked CBOR string + * \value CborErrorIllegalNumber An illegal initial byte (encoding unspecified additional information) was found + * \value CborErrorIllegalSimpleType An illegal encoding of a CBOR Simple Type of value less than 32 was found + * \omitvalue CborErrorUnknownSimpleType + * \omitvalue CborErrorUnknownTag + * \omitvalue CborErrorInappropriateTagForType + * \omitvalue CborErrorDuplicateObjectKeys + * \value CborErrorInvalidUtf8TextString Illegal UTF-8 encoding found while parsing CBOR Text String + * \value CborErrorTooManyItems Too many items were added to CBOR map or array of pre-determined length + * \value CborErrorTooFewItems Too few items were added to CBOR map or array of pre-determined length + * \value CborErrorDataTooLarge Data item size exceeds TinyCBOR's implementation limits + * \value CborErrorNestingTooDeep Data item nesting exceeds TinyCBOR's implementation limits + * \omitvalue CborErrorUnsupportedType + * \value CborErrorJsonObjectKeyIsAggregate Conversion to JSON failed because the key in a map is a CBOR map or array + * \value CborErrorJsonObjectKeyNotString Conversion to JSON failed because the key in a map is not a text string + * \value CborErrorOutOfMemory During CBOR encoding, the buffer provided is insufficient for encoding the data item; + * in other situations, TinyCBOR failed to allocate memory + * \value CborErrorInternalError An internal error occurred in TinyCBOR + */ + +/** + * \ingroup CborGlobals + * Returns the error string corresponding to the CBOR error condition \a error. + */ +const char *cbor_error_string(CborError error) +{ + switch (error) { + case CborNoError: + return ""; + + case CborUnknownError: + return _("unknown error"); + + case CborErrorOutOfMemory: + return _("out of memory/need more memory"); + + case CborErrorUnknownLength: + return _("unknown length (attempted to get the length of a map/array/string of indeterminate length"); + + case CborErrorAdvancePastEOF: + return _("attempted to advance past EOF"); + + case CborErrorIO: + return _("I/O error"); + + case CborErrorGarbageAtEnd: + return _("garbage after the end of the content"); + + case CborErrorUnexpectedEOF: + return _("unexpected end of data"); + + case CborErrorUnexpectedBreak: + return _("unexpected 'break' byte"); + + case CborErrorUnknownType: + return _("illegal byte (encodes future extension type)"); + + case CborErrorIllegalType: + return _("mismatched string type in chunked string"); + + case CborErrorIllegalNumber: + return _("illegal initial byte (encodes unspecified additional information)"); + + case CborErrorIllegalSimpleType: + return _("illegal encoding of simple type smaller than 32"); + + case CborErrorNoMoreStringChunks: + return _("no more byte or text strings available"); + + case CborErrorUnknownSimpleType: + return _("unknown simple type"); + + case CborErrorUnknownTag: + return _("unknown tag"); + + case CborErrorInappropriateTagForType: + return _("inappropriate tag for type"); + + case CborErrorDuplicateObjectKeys: + return _("duplicate keys in object"); + + case CborErrorInvalidUtf8TextString: + return _("invalid UTF-8 content in string"); + + case CborErrorExcludedType: + return _("excluded type found"); + + case CborErrorExcludedValue: + return _("excluded value found"); + + case CborErrorImproperValue: + case CborErrorOverlongEncoding: + return _("value encoded in non-canonical form"); + + case CborErrorMapKeyNotString: + case CborErrorJsonObjectKeyNotString: + return _("key in map is not a string"); + + case CborErrorMapNotSorted: + return _("map is not sorted"); + + case CborErrorMapKeysNotUnique: + return _("map keys are not unique"); + + case CborErrorTooManyItems: + return _("too many items added to encoder"); + + case CborErrorTooFewItems: + return _("too few items added to encoder"); + + case CborErrorDataTooLarge: + return _("internal error: data too large"); + + case CborErrorNestingTooDeep: + return _("internal error: too many nested containers found in recursive function"); + + case CborErrorUnsupportedType: + return _("unsupported type"); + + case CborErrorUnimplementedValidation: + return _("validation not implemented for the current parser state"); + + case CborErrorJsonObjectKeyIsAggregate: + return _("conversion to JSON failed: key in object is an array or map"); + + case CborErrorJsonNotImplemented: + return _("conversion to JSON failed: open_memstream unavailable"); + + case CborErrorInternalError: + return _("internal error"); + } + return cbor_error_string(CborUnknownError); +} diff --git a/lib/cbor/tinycbor/src/cborinternal_p.h b/lib/cbor/tinycbor/src/cborinternal_p.h new file mode 100644 index 00000000..16269e63 --- /dev/null +++ b/lib/cbor/tinycbor/src/cborinternal_p.h @@ -0,0 +1,316 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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 CBORINTERNAL_P_H +#define CBORINTERNAL_P_H + +#include "compilersupport_p.h" + +#ifndef CBOR_NO_FLOATING_POINT +# include <float.h> +# include <math.h> +#else +# ifndef CBOR_NO_HALF_FLOAT_TYPE +# define CBOR_NO_HALF_FLOAT_TYPE 1 +# endif +#endif + +#ifndef CBOR_NO_HALF_FLOAT_TYPE +# if defined(__F16C__) || defined(__AVX2__) +# include <immintrin.h> +static inline unsigned short encode_half(float val) +{ + __m128i m = _mm_cvtps_ph(_mm_set_ss(val), _MM_FROUND_CUR_DIRECTION); + return _mm_extract_epi16(m, 0); +} +static inline float decode_half(unsigned short half) +{ + __m128i m = _mm_cvtsi32_si128(half); + return _mm_cvtss_f32(_mm_cvtph_ps(m)); +} +# else +/* software implementation of float-to-fp16 conversions */ +static inline unsigned short encode_half(double val) +{ + uint64_t v; + int sign, exp, mant; + memcpy(&v, &val, sizeof(v)); + sign = v >> 63 << 15; + exp = (v >> 52) & 0x7ff; + mant = v << 12 >> 12 >> (53-11); /* keep only the 11 most significant bits of the mantissa */ + exp -= 1023; + if (exp == 1024) { + /* infinity or NaN */ + exp = 16; + mant >>= 1; + } else if (exp >= 16) { + /* overflow, as largest number */ + exp = 15; + mant = 1023; + } else if (exp >= -14) { + /* regular normal */ + } else if (exp >= -24) { + /* subnormal */ + mant |= 1024; + mant >>= -(exp + 14); + exp = -15; + } else { + /* underflow, make zero */ + return 0; + } + + /* safe cast here as bit operations above guarantee not to overflow */ + return (unsigned short)(sign | ((exp + 15) << 10) | mant); +} + +/* this function was copied & adapted from RFC 7049 Appendix D */ +static inline double decode_half(unsigned short 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 +#endif /* CBOR_NO_HALF_FLOAT_TYPE */ + +#ifndef CBOR_INTERNAL_API +# define CBOR_INTERNAL_API +#endif + +#ifndef CBOR_PARSER_MAX_RECURSIONS +# define CBOR_PARSER_MAX_RECURSIONS 1024 +#endif + +#ifndef CBOR_ENCODER_WRITER_CONTROL +# define CBOR_ENCODER_WRITER_CONTROL 0 +#endif +#ifndef CBOR_PARSER_READER_CONTROL +# define CBOR_PARSER_READER_CONTROL 0 +#endif + +/* + * CBOR Major types + * Encoded in the high 3 bits of the descriptor byte + * See http://tools.ietf.org/html/rfc7049#section-2.1 + */ +typedef enum CborMajorTypes { + UnsignedIntegerType = 0U, + NegativeIntegerType = 1U, + ByteStringType = 2U, + TextStringType = 3U, + ArrayType = 4U, + MapType = 5U, /* a.k.a. object */ + TagType = 6U, + SimpleTypesType = 7U +} CborMajorTypes; + +/* + * CBOR simple and floating point types + * Encoded in the low 8 bits of the descriptor byte when the + * Major Type is 7. + */ +typedef enum CborSimpleTypes { + FalseValue = 20, + TrueValue = 21, + NullValue = 22, + UndefinedValue = 23, + SimpleTypeInNextByte = 24, /* not really a simple type */ + HalfPrecisionFloat = 25, /* ditto */ + SinglePrecisionFloat = 26, /* ditto */ + DoublePrecisionFloat = 27, /* ditto */ + Break = 31 +} CborSimpleTypes; + +enum { + SmallValueBitLength = 5U, + SmallValueMask = (1U << SmallValueBitLength) - 1, /* 31 */ + Value8Bit = 24U, + Value16Bit = 25U, + Value32Bit = 26U, + Value64Bit = 27U, + IndefiniteLength = 31U, + + MajorTypeShift = SmallValueBitLength, + MajorTypeMask = (int) (~0U << MajorTypeShift), + + BreakByte = (unsigned)Break | (SimpleTypesType << MajorTypeShift) +}; + +static inline void copy_current_position(CborValue *dst, const CborValue *src) +{ + /* This "if" is here for pedantry only: the two branches should perform + * the same memory operation. */ + if (src->parser->flags & CborParserFlag_ExternalSource) + dst->source.token = src->source.token; + else + dst->source.ptr = src->source.ptr; +} + +static inline bool can_read_bytes(const CborValue *it, size_t n) +{ + if (CBOR_PARSER_READER_CONTROL >= 0) { + if (it->parser->flags & CborParserFlag_ExternalSource || CBOR_PARSER_READER_CONTROL != 0) { +#ifdef CBOR_PARSER_CAN_READ_BYTES_FUNCTION + return CBOR_PARSER_CAN_READ_BYTES_FUNCTION(it->source.token, n); +#else + return it->parser->source.ops->can_read_bytes(it->source.token, n); +#endif + } + } + + /* Convert the pointer subtraction to size_t since end >= ptr + * (this prevents issues with (ptrdiff_t)n becoming negative). + */ + return (size_t)(it->parser->source.end - it->source.ptr) >= n; +} + +static inline void advance_bytes(CborValue *it, size_t n) +{ + if (CBOR_PARSER_READER_CONTROL >= 0) { + if (it->parser->flags & CborParserFlag_ExternalSource || CBOR_PARSER_READER_CONTROL != 0) { +#ifdef CBOR_PARSER_ADVANCE_BYTES_FUNCTION + CBOR_PARSER_ADVANCE_BYTES_FUNCTION(it->source.token, n); +#else + it->parser->source.ops->advance_bytes(it->source.token, n); +#endif + return; + } + } + + it->source.ptr += n; +} + +static inline CborError transfer_string(CborValue *it, const void **ptr, size_t offset, size_t len) +{ + if (CBOR_PARSER_READER_CONTROL >= 0) { + if (it->parser->flags & CborParserFlag_ExternalSource || CBOR_PARSER_READER_CONTROL != 0) { +#ifdef CBOR_PARSER_TRANSFER_STRING_FUNCTION + return CBOR_PARSER_TRANSFER_STRING_FUNCTION(it->source.token, ptr, offset, len); +#else + return it->parser->source.ops->transfer_string(it->source.token, ptr, offset, len); +#endif + } + } + + it->source.ptr += offset; + if (can_read_bytes(it, len)) { + *CONST_CAST(const void **, ptr) = it->source.ptr; + it->source.ptr += len; + return CborNoError; + } + return CborErrorUnexpectedEOF; +} + +static inline void *read_bytes_unchecked(const CborValue *it, void *dst, size_t offset, size_t n) +{ + if (CBOR_PARSER_READER_CONTROL >= 0) { + if (it->parser->flags & CborParserFlag_ExternalSource || CBOR_PARSER_READER_CONTROL != 0) { +#ifdef CBOR_PARSER_READ_BYTES_FUNCTION + return CBOR_PARSER_READ_BYTES_FUNCTION(it->source.token, dst, offset, n); +#else + return it->parser->source.ops->read_bytes(it->source.token, dst, offset, n); +#endif + } + } + + return memcpy(dst, it->source.ptr + offset, n); +} + +#ifdef __GNUC__ +__attribute__((warn_unused_result)) +#endif +static inline void *read_bytes(const CborValue *it, void *dst, size_t offset, size_t n) +{ + if (can_read_bytes(it, offset + n)) + return read_bytes_unchecked(it, dst, offset, n); + return NULL; +} + +static inline uint16_t read_uint8(const CborValue *it, size_t offset) +{ + uint8_t result; + read_bytes_unchecked(it, &result, offset, sizeof(result)); + return result; +} + +static inline uint16_t read_uint16(const CborValue *it, size_t offset) +{ + uint16_t result; + read_bytes_unchecked(it, &result, offset, sizeof(result)); + return cbor_ntohs(result); +} + +static inline uint32_t read_uint32(const CborValue *it, size_t offset) +{ + uint32_t result; + read_bytes_unchecked(it, &result, offset, sizeof(result)); + return cbor_ntohl(result); +} + +static inline uint64_t read_uint64(const CborValue *it, size_t offset) +{ + uint64_t result; + read_bytes_unchecked(it, &result, offset, sizeof(result)); + return cbor_ntohll(result); +} + +static inline CborError extract_number_checked(const CborValue *it, uint64_t *value, size_t *bytesUsed) +{ + uint8_t descriptor; + size_t bytesNeeded = 0; + + /* We've already verified that there's at least one byte to be read */ + read_bytes_unchecked(it, &descriptor, 0, 1); + descriptor &= SmallValueMask; + if (descriptor < Value8Bit) { + *value = descriptor; + } else if (unlikely(descriptor > Value64Bit)) { + return CborErrorIllegalNumber; + } else { + bytesNeeded = (size_t)(1 << (descriptor - Value8Bit)); + if (!can_read_bytes(it, 1 + bytesNeeded)) + return CborErrorUnexpectedEOF; + if (descriptor <= Value16Bit) { + if (descriptor == Value16Bit) + *value = read_uint16(it, 1); + else + *value = read_uint8(it, 1); + } else { + if (descriptor == Value32Bit) + *value = read_uint32(it, 1); + else + *value = read_uint64(it, 1); + } + } + + if (bytesUsed) + *bytesUsed = bytesNeeded; + return CborNoError; +} + +#endif /* CBORINTERNAL_P_H */ diff --git a/lib/cbor/tinycbor/src/cborjson.h b/lib/cbor/tinycbor/src/cborjson.h new file mode 100644 index 00000000..8ff27b92 --- /dev/null +++ b/lib/cbor/tinycbor/src/cborjson.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#ifndef CBORJSON_H +#define CBORJSON_H + +#include "cbor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Conversion to JSON */ +enum CborToJsonFlags +{ + CborConvertAddMetadata = 1, + CborConvertTagsToObjects = 2, + CborConvertIgnoreTags = 0, + + CborConvertObeyByteStringTags = 0, + CborConvertByteStringsToBase64Url = 4, + + CborConvertRequireMapStringKeys = 0, + CborConvertStringifyMapKeys = 8, + + CborConvertDefaultFlags = 0 +}; + +CBOR_API CborError cbor_value_to_json_advance(FILE *out, CborValue *value, int flags); +CBOR_INLINE_API CborError cbor_value_to_json(FILE *out, const CborValue *value, int flags) +{ + CborValue copy = *value; + return cbor_value_to_json_advance(out, ©, flags); +} + +#ifdef __cplusplus +} +#endif + +#endif /* CBORJSON_H */ + diff --git a/lib/cbor/tinycbor/src/cborparser.c b/lib/cbor/tinycbor/src/cborparser.c new file mode 100644 index 00000000..74d91a30 --- /dev/null +++ b/lib/cbor/tinycbor/src/cborparser.c @@ -0,0 +1,1529 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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 + +#include "cbor.h" +#include "cborinternal_p.h" +#include "compilersupport_p.h" + +#include <string.h> + +/** + * \defgroup CborParsing Parsing CBOR streams + * \brief Group of functions used to parse CBOR streams. + * + * TinyCBOR provides functions for pull-based stream parsing of a CBOR-encoded + * payload. The main data type for the parsing is a CborValue, which behaves + * like an iterator and can be used to extract the encoded data. It is first + * initialized with a call to cbor_parser_init() and is usually used to extract + * exactly one item, most often an array or map. + * + * Nested CborValue objects can be parsed using cbor_value_enter_container(). + * Each call to cbor_value_enter_container() must be matched by a call to + * cbor_value_leave_container(), with the exact same parameters. + * + * The example below initializes a CborParser object, begins the parsing with a + * CborValue and decodes a single integer: + * + * \code + * int extract_int(const uint8_t *buffer, size_t len) + * { + * CborParser parser; + * CborValue value; + * int result; + * cbor_parser_init(buffer, len, 0, &parser, &value); + * cbor_value_get_int(&value, &result); + * return result; + * } + * \endcode + * + * The code above does no error checking, which means it assumes the data comes + * from a source trusted to send one properly-encoded integer. The following + * example does the exact same operation, but includes error checking and + * returns 0 on parsing failure: + * + * \code + * int extract_int(const uint8_t *buffer, size_t len) + * { + * CborParser parser; + * CborValue value; + * int result; + * if (cbor_parser_init(buffer, len, 0, &parser, &value) != CborNoError) + * return 0; + * if (!cbor_value_is_integer(&value) || + * cbor_value_get_int(&value, &result) != CborNoError) + * return 0; + * return result; + * } + * \endcode + * + * Note, in the example above, that one can't distinguish a parsing failure + * from an encoded value of zero. Reporting a parsing error is left as an + * exercise to the reader. + * + * The code above does not execute a range-check either: it is possible that + * the value decoded from the CBOR stream encodes a number larger than what can + * be represented in a variable of type \c{int}. If detecting that case is + * important, the code should call cbor_value_get_int_checked() instead. + * + * <h3 class="groupheader">Memory and parsing constraints</h3> + * + * TinyCBOR is designed to run with little memory and with minimal overhead. + * Except where otherwise noted, the parser functions always run on constant + * time (O(1)), do not recurse and never allocate memory (thus, stack usage is + * bounded and is O(1)). + * + * <h3 class="groupheader">Error handling and preconditions</h3> + * + * All functions operating on a CborValue return a CborError condition, with + * CborNoError standing for the normal situation in which no parsing error + * occurred. All functions may return parsing errors in case the stream cannot + * be decoded properly, be it due to corrupted data or due to reaching the end + * of the input buffer. + * + * Error conditions must not be ignored. All decoder functions have undefined + * behavior if called after an error has been reported, and may crash. + * + * Some functions are also documented to have preconditions, like + * cbor_value_get_int() requiring that the input be an integral value. + * Violation of preconditions also results in undefined behavior and the + * program may crash. + */ + +/** + * \addtogroup CborParsing + * @{ + */ + +/** + * \struct CborValue + * + * This type contains one value parsed from the CBOR stream. Each CborValue + * behaves as an iterator in a StAX-style parser. + * + * \if privatedocs + * Implementation details: the CborValue contains these fields: + * \list + * \li ptr: pointer to the actual data + * \li flags: flags from the decoder + * \li extra: partially decoded integer value (0, 1 or 2 bytes) + * \li remaining: remaining items in this collection after this item or UINT32_MAX if length is unknown + * \endlist + * \endif + */ + +static uint64_t extract_number_and_advance(CborValue *it) +{ + /* This function is only called after we've verified that the number + * here is valid, so we can just use _cbor_value_extract_int64_helper. */ + uint8_t descriptor; + uint64_t v = _cbor_value_extract_int64_helper(it); + + read_bytes_unchecked(it, &descriptor, 0, 1); + descriptor &= SmallValueMask; + + size_t bytesNeeded = descriptor < Value8Bit ? 0 : (1 << (descriptor - Value8Bit)); + advance_bytes(it, bytesNeeded + 1); + + return v; +} + +static bool is_fixed_type(uint8_t type) +{ + return type != CborTextStringType && type != CborByteStringType && type != CborArrayType && + type != CborMapType; +} + +static CborError preparse_value(CborValue *it) +{ + enum { + /* flags to keep */ + FlagsToKeep = CborIteratorFlag_ContainerIsMap | CborIteratorFlag_NextIsMapKey + }; + uint8_t descriptor; + + /* are we at the end? */ + it->type = CborInvalidType; + it->flags &= FlagsToKeep; + if (!read_bytes(it, &descriptor, 0, 1)) + return CborErrorUnexpectedEOF; + + uint8_t type = descriptor & MajorTypeMask; + it->type = type; + it->extra = (descriptor &= SmallValueMask); + + if (descriptor > Value64Bit) { + if (unlikely(descriptor != IndefiniteLength)) + return type == CborSimpleType ? CborErrorUnknownType : CborErrorIllegalNumber; + if (likely(!is_fixed_type(type))) { + /* special case */ + it->flags |= CborIteratorFlag_UnknownLength; + it->type = type; + return CborNoError; + } + return type == CborSimpleType ? CborErrorUnexpectedBreak : CborErrorIllegalNumber; + } + + size_t bytesNeeded = descriptor < Value8Bit ? 0 : (1 << (descriptor - Value8Bit)); + + if (bytesNeeded) { + if (!can_read_bytes(it, bytesNeeded + 1)) + return CborErrorUnexpectedEOF; + + it->extra = 0; + + /* read up to 16 bits into it->extra */ + if (bytesNeeded == 1) { + uint8_t extra; + read_bytes_unchecked(it, &extra, 1, bytesNeeded); + it->extra = extra; + } else if (bytesNeeded == 2) { + read_bytes_unchecked(it, &it->extra, 1, bytesNeeded); + it->extra = cbor_ntohs(it->extra); + } else { + cbor_static_assert(CborIteratorFlag_IntegerValueTooLarge == (Value32Bit & 3)); + cbor_static_assert((CborIteratorFlag_IntegerValueIs64Bit | + CborIteratorFlag_IntegerValueTooLarge) == (Value64Bit & 3)); + it->flags |= (descriptor & 3); + } + } + + uint8_t majortype = type >> MajorTypeShift; + if (majortype == NegativeIntegerType) { + it->flags |= CborIteratorFlag_NegativeInteger; + it->type = CborIntegerType; + } else if (majortype == SimpleTypesType) { + switch (descriptor) { + case FalseValue: + it->extra = false; + it->type = CborBooleanType; + break; + + case SinglePrecisionFloat: + case DoublePrecisionFloat: + it->flags |= CborIteratorFlag_IntegerValueTooLarge; + /* fall through */ + case TrueValue: + case NullValue: + case UndefinedValue: + case HalfPrecisionFloat: + read_bytes_unchecked(it, &it->type, 0, 1); + break; + + case SimpleTypeInNextByte: +#ifndef CBOR_PARSER_NO_STRICT_CHECKS + if (unlikely(it->extra < 32)) { + it->type = CborInvalidType; + return CborErrorIllegalSimpleType; + } +#endif + break; + + case 28: + case 29: + case 30: + case Break: + cbor_assert(false); /* these conditions can't be reached */ + return CborErrorUnexpectedBreak; + } + } + + return CborNoError; +} + +static CborError preparse_next_value_nodecrement(CborValue *it) +{ + uint8_t byte; + if (it->remaining == UINT32_MAX && read_bytes(it, &byte, 0, 1) && byte == (uint8_t)BreakByte) { + /* end of map or array */ + if ((it->flags & CborIteratorFlag_ContainerIsMap && it->flags & CborIteratorFlag_NextIsMapKey) + || it->type == CborTagType) { + /* but we weren't expecting it! */ + return CborErrorUnexpectedBreak; + } + it->type = CborInvalidType; + it->remaining = 0; + it->flags |= CborIteratorFlag_UnknownLength; /* leave_container must consume the Break */ + return CborNoError; + } + + return preparse_value(it); +} + +static CborError preparse_next_value(CborValue *it) +{ + /* tags don't count towards item totals or whether we've successfully + * read a map's key or value */ + bool itemCounts = it->type != CborTagType; + + if (it->remaining != UINT32_MAX) { + if (itemCounts && --it->remaining == 0) { + it->type = CborInvalidType; + it->flags &= ~CborIteratorFlag_UnknownLength; /* no Break to consume */ + return CborNoError; + } + } + if (itemCounts) { + /* toggle the flag indicating whether this was a map key */ + it->flags ^= CborIteratorFlag_NextIsMapKey; + } + return preparse_next_value_nodecrement(it); +} + +static CborError advance_internal(CborValue *it) +{ + uint64_t length = extract_number_and_advance(it); + + if (it->type == CborByteStringType || it->type == CborTextStringType) { + cbor_assert(length == (size_t)length); + cbor_assert((it->flags & CborIteratorFlag_UnknownLength) == 0); + advance_bytes(it, length); + } + + return preparse_next_value(it); +} + +/** \internal + * + * Decodes the CBOR integer value when it is larger than the 16 bits available + * in value->extra. This function requires that value->flags have the + * CborIteratorFlag_IntegerValueTooLarge flag set. + * + * This function is also used to extract single- and double-precision floating + * point values (SinglePrecisionFloat == Value32Bit and DoublePrecisionFloat == + * Value64Bit). + */ +uint64_t _cbor_value_decode_int64_internal(const CborValue *value) +{ + cbor_assert(value->flags & CborIteratorFlag_IntegerValueTooLarge || + value->type == CborFloatType || value->type == CborDoubleType); + if (value->flags & CborIteratorFlag_IntegerValueIs64Bit) + return read_uint64(value, 1); + + return read_uint32(value, 1); +} + +/** + * Initializes the CBOR parser for parsing \a size bytes beginning at \a + * buffer. Parsing will use flags set in \a flags. The iterator to the first + * element is returned in \a it. + * + * The \a parser structure needs to remain valid throughout the decoding + * process. It is not thread-safe to share one CborParser among multiple + * threads iterating at the same time, but the object can be copied so multiple + * threads can iterate. + */ +CborError cbor_parser_init(const uint8_t *buffer, size_t size, uint32_t flags, CborParser *parser, CborValue *it) +{ + memset(parser, 0, sizeof(*parser)); + parser->source.end = buffer + size; + parser->flags = (enum CborParserGlobalFlags)flags; + it->parser = parser; + it->source.ptr = buffer; + it->remaining = 1; /* there's one type altogether, usually an array or map */ + it->flags = 0; + return preparse_value(it); +} + +CborError cbor_parser_init_reader(const struct CborParserOperations *ops, CborParser *parser, CborValue *it, void *token) +{ + memset(parser, 0, sizeof(*parser)); + parser->source.ops = ops; + parser->flags = CborParserFlag_ExternalSource; + it->parser = parser; + it->source.token = token; + it->remaining = 1; + return preparse_value(it); +} + +/** + * \fn bool cbor_value_at_end(const CborValue *it) + * + * Returns true if \a it has reached the end of the iteration, usually when + * advancing after the last item in an array or map. + * + * In the case of the outermost CborValue object, this function returns true + * after decoding a single element. A pointer to the first byte of the + * remaining data (if any) can be obtained with cbor_value_get_next_byte(). + * + * \sa cbor_value_advance(), cbor_value_is_valid(), cbor_value_get_next_byte() + */ + +/** + * \fn const uint8_t *cbor_value_get_next_byte(const CborValue *it) + * + * Returns a pointer to the next byte that would be decoded if this CborValue + * object were advanced. + * + * This function is useful if cbor_value_at_end() returns true for the + * outermost CborValue: the pointer returned is the first byte of the data + * remaining in the buffer, if any. Code can decide whether to begin decoding a + * new CBOR data stream from this point, or parse some other data appended to + * the same buffer. + * + * This function may be used even after a parsing error. If that occurred, + * then this function returns a pointer to where the parsing error occurred. + * Note that the error recovery is not precise and the pointer may not indicate + * the exact byte containing bad data. + * + * This function makes sense only when using a linear buffer (that is, when the + * parser is initialize by cbor_parser_init()). If using an external source, + * this function may return garbage; instead, consult the external source itself + * to find out more details about the presence of more data. + * + * \sa cbor_value_at_end() + */ + +CborError cbor_value_reparse(CborValue *it) +{ + if (it->flags & CborIteratorFlag_IteratingStringChunks) + return CborNoError; + return preparse_next_value_nodecrement(it); +} + +/** + * \fn bool cbor_value_is_valid(const CborValue *it) + * + * Returns true if the iterator \a it contains a valid value. Invalid iterators + * happen when iteration reaches the end of a container (see \ref + * cbor_value_at_end()) or when a search function resulted in no matches. + * + * \sa cbor_value_advance(), cbor_value_at_end(), cbor_value_get_type() + */ + +/** + * Performs a basic validation of the CBOR stream pointed by \a it and returns + * the error it found. If no error was found, it returns CborNoError and the + * application can iterate over the items with certainty that no other errors + * will appear during parsing. + * + * A basic validation checks for: + * \list + * \li absence of undefined additional information bytes; + * \li well-formedness of all numbers, lengths, and simple values; + * \li string contents match reported sizes; + * \li arrays and maps contain the number of elements they are reported to have; + * \endlist + * + * For further checks, see cbor_value_validate(). + * + * This function has the same timing and memory requirements as + * cbor_value_advance(). + * + * \sa cbor_value_validate(), cbor_value_advance() + */ +CborError cbor_value_validate_basic(const CborValue *it) +{ + CborValue value = *it; + return cbor_value_advance(&value); +} + +/** + * Advances the CBOR value \a it by one fixed-size position. Fixed-size types + * are: integers, tags, simple types (including boolean, null and undefined + * values) and floating point types. + * + * If the type is not of fixed size, this function has undefined behavior. Code + * must be sure that the current type is one of the fixed-size types before + * calling this function. This function is provided because it can guarantee + * that it runs in constant time (O(1)). + * + * If the caller is not able to determine whether the type is fixed or not, code + * can use the cbor_value_advance() function instead. + * + * \sa cbor_value_at_end(), cbor_value_advance(), cbor_value_enter_container(), cbor_value_leave_container() + */ +CborError cbor_value_advance_fixed(CborValue *it) +{ + cbor_assert(it->type != CborInvalidType); + cbor_assert(is_fixed_type(it->type)); + if (!it->remaining) + return CborErrorAdvancePastEOF; + return advance_internal(it); +} + +static CborError advance_recursive(CborValue *it, int nestingLevel) +{ + CborError err; + CborValue recursed; + + if (is_fixed_type(it->type)) + return advance_internal(it); + + if (!cbor_value_is_container(it)) { + size_t len = SIZE_MAX; + return _cbor_value_copy_string(it, NULL, &len, it); + } + + /* map or array */ + if (nestingLevel == 0) + return CborErrorNestingTooDeep; + + err = cbor_value_enter_container(it, &recursed); + if (err) + return err; + while (!cbor_value_at_end(&recursed)) { + err = advance_recursive(&recursed, nestingLevel - 1); + if (err) + return err; + } + return cbor_value_leave_container(it, &recursed); +} + + +/** + * Advances the CBOR value \a it by one element, skipping over containers. + * Unlike cbor_value_advance_fixed(), this function can be called on a CBOR + * value of any type. However, if the type is a container (map or array) or a + * string with a chunked payload, this function will not run in constant time + * and will recurse into itself (it will run on O(n) time for the number of + * elements or chunks and will use O(n) memory for the number of nested + * containers). + * + * The number of recursions can be limited at compile time to avoid stack + * exhaustion in constrained systems. + * + * \sa cbor_value_at_end(), cbor_value_advance_fixed(), cbor_value_enter_container(), cbor_value_leave_container() + */ +CborError cbor_value_advance(CborValue *it) +{ + cbor_assert(it->type != CborInvalidType); + if (!it->remaining) + return CborErrorAdvancePastEOF; + return advance_recursive(it, CBOR_PARSER_MAX_RECURSIONS); +} + +/** + * \fn bool cbor_value_is_tag(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR tag. + * + * \sa cbor_value_get_tag(), cbor_value_skip_tag() + */ + +/** + * \fn CborError cbor_value_get_tag(const CborValue *value, CborTag *result) + * + * Retrieves the CBOR tag value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to a CBOR tag value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_tag is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_tag() + */ + +/** + * Advances the CBOR value \a it until it no longer points to a tag. If \a it is + * already not pointing to a tag, then this function returns it unchanged. + * + * This function does not run in constant time: it will run on O(n) for n being + * the number of tags. It does use constant memory (O(1) memory requirements). + * + * \sa cbor_value_advance_fixed(), cbor_value_advance() + */ +CborError cbor_value_skip_tag(CborValue *it) +{ + while (cbor_value_is_tag(it)) { + CborError err = cbor_value_advance_fixed(it); + if (err) + return err; + } + return CborNoError; +} + +/** + * \fn bool cbor_value_is_container(const CborValue *it) + * + * Returns true if the \a it value is a container and requires recursion in + * order to decode (maps and arrays), false otherwise. + */ + +/** + * Creates a CborValue iterator pointing to the first element of the container + * represented by \a it and saves it in \a recursed. The \a it container object + * needs to be kept and passed again to cbor_value_leave_container() in order + * to continue iterating past this container. + * + * The \a it CborValue iterator must point to a container. + * + * \sa cbor_value_is_container(), cbor_value_leave_container(), cbor_value_advance() + */ +CborError cbor_value_enter_container(const CborValue *it, CborValue *recursed) +{ + cbor_static_assert(CborIteratorFlag_ContainerIsMap == (CborMapType & ~CborArrayType)); + cbor_assert(cbor_value_is_container(it)); + *recursed = *it; + + if (it->flags & CborIteratorFlag_UnknownLength) { + recursed->remaining = UINT32_MAX; + advance_bytes(recursed, 1); + } else { + uint64_t len = extract_number_and_advance(recursed); + + recursed->remaining = (uint32_t)len; + if (recursed->remaining != len || len == UINT32_MAX) { + /* back track the pointer to indicate where the error occurred */ + copy_current_position(recursed, it); + return CborErrorDataTooLarge; + } + if (recursed->type == CborMapType) { + /* maps have keys and values, so we need to multiply by 2 */ + if (recursed->remaining > UINT32_MAX / 2) { + /* back track the pointer to indicate where the error occurred */ + copy_current_position(recursed, it); + return CborErrorDataTooLarge; + } + recursed->remaining *= 2; + } + if (len == 0) { + /* the case of the empty container */ + recursed->type = CborInvalidType; + return CborNoError; + } + } + recursed->flags = (recursed->type & CborIteratorFlag_ContainerIsMap); + return preparse_next_value_nodecrement(recursed); +} + +/** + * Updates \a it to point to the next element after the container. The \a + * recursed object needs to point to the element obtained either by advancing + * the last element of the container (via cbor_value_advance(), + * cbor_value_advance_fixed(), a nested cbor_value_leave_container(), or the \c + * next pointer from cbor_value_copy_string() or cbor_value_dup_string()). + * + * The \a it and \a recursed parameters must be the exact same as passed to + * cbor_value_enter_container(). + * + * \sa cbor_value_enter_container(), cbor_value_at_end() + */ +CborError cbor_value_leave_container(CborValue *it, const CborValue *recursed) +{ + cbor_assert(cbor_value_is_container(it)); + cbor_assert(recursed->type == CborInvalidType); + + copy_current_position(it, recursed); + if (recursed->flags & CborIteratorFlag_UnknownLength) + advance_bytes(it, 1); + return preparse_next_value(it); +} + + +/** + * \fn CborType cbor_value_get_type(const CborValue *value) + * + * Returns the type of the CBOR value that the iterator \a value points to. If + * \a value does not point to a valid value, this function returns \ref + * CborInvalidType. + * + * TinyCBOR also provides functions to test directly if a given CborValue object + * is of a given type, like cbor_value_is_text_string() and cbor_value_is_null(). + * + * \sa cbor_value_is_valid() + */ + +/** + * \fn bool cbor_value_is_null(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR null type. + * + * \sa cbor_value_is_valid(), cbor_value_is_undefined() + */ + +/** + * \fn bool cbor_value_is_undefined(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR undefined type. + * + * \sa cbor_value_is_valid(), cbor_value_is_null() + */ + +/** + * \fn bool cbor_value_is_boolean(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR boolean + * type (true or false). + * + * \sa cbor_value_is_valid(), cbor_value_get_boolean() + */ + +/** + * \fn CborError cbor_value_get_boolean(const CborValue *value, bool *result) + * + * Retrieves the boolean value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to a boolean value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_boolean is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_boolean() + */ + +/** + * \fn bool cbor_value_is_simple_type(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR Simple Type + * type (other than true, false, null and undefined). + * + * \sa cbor_value_is_valid(), cbor_value_get_simple_type() + */ + +/** + * \fn CborError cbor_value_get_simple_type(const CborValue *value, uint8_t *result) + * + * Retrieves the CBOR Simple Type value that \a value points to and stores it + * in \a result. If the iterator \a value does not point to a simple_type + * value, the behavior is undefined, so checking with \ref cbor_value_get_type + * or with \ref cbor_value_is_simple_type is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_simple_type() + */ + +/** + * \fn bool cbor_value_is_integer(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR integer + * type. + * + * \sa cbor_value_is_valid(), cbor_value_get_int, cbor_value_get_int64, cbor_value_get_uint64, cbor_value_get_raw_integer + */ + +/** + * \fn bool cbor_value_is_unsigned_integer(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR unsigned + * integer type (positive values or zero). + * + * \sa cbor_value_is_valid(), cbor_value_get_uint64() + */ + +/** + * \fn bool cbor_value_is_negative_integer(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR negative + * integer type. + * + * \sa cbor_value_is_valid(), cbor_value_get_int, cbor_value_get_int64, cbor_value_get_raw_integer + */ + +/** + * \fn CborError cbor_value_get_int(const CborValue *value, int *result) + * + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * Note that this function does not do range-checking: integral values that do + * not fit in a variable of type \c{int} are silently truncated to fit. Use + * cbor_value_get_int_checked() if that is not acceptable. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer() + */ + +/** + * \fn CborError cbor_value_get_int64(const CborValue *value, int64_t *result) + * + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * Note that this function does not do range-checking: integral values that do + * not fit in a variable of type \c{int64_t} are silently truncated to fit. Use + * cbor_value_get_int64_checked() that is not acceptable. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer() + */ + +/** + * \fn CborError cbor_value_get_uint64(const CborValue *value, uint64_t *result) + * + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an unsigned integer + * value, the behavior is undefined, so checking with \ref cbor_value_get_type + * or with \ref cbor_value_is_unsigned_integer is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_unsigned_integer() + */ + +/** + * \fn CborError cbor_value_get_raw_integer(const CborValue *value, uint64_t *result) + * + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * This function is provided because CBOR negative integers can assume values + * that cannot be represented with normal 64-bit integer variables. + * + * If the integer is unsigned (that is, if cbor_value_is_unsigned_integer() + * returns true), then \a result will contain the actual value. If the integer + * is negative, then \a result will contain the absolute value of that integer, + * minus one. That is, \c {actual = -result - 1}. On architectures using two's + * complement for representation of negative integers, it is equivalent to say + * that \a result will contain the bitwise negation of the actual value. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer() + */ + +/** + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * Unlike \ref cbor_value_get_int64(), this function performs a check to see if the + * stored integer fits in \a result without data loss. If the number is outside + * the valid range for the data type, this function returns the recoverable + * error CborErrorDataTooLarge. In that case, use either + * cbor_value_get_uint64() (if the number is positive) or + * cbor_value_get_raw_integer(). + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer(), cbor_value_get_int64() + */ +CborError cbor_value_get_int64_checked(const CborValue *value, int64_t *result) +{ + uint64_t v; + cbor_assert(cbor_value_is_integer(value)); + v = _cbor_value_extract_int64_helper(value); + + /* Check before converting, as the standard says (C11 6.3.1.3 paragraph 3): + * "[if] the new type is signed and the value cannot be represented in it; either the + * result is implementation-defined or an implementation-defined signal is raised." + * + * The range for int64_t is -2^63 to 2^63-1 (int64_t is required to be + * two's complement, C11 7.20.1.1 paragraph 3), which in CBOR is + * represented the same way, differing only on the "sign bit" (the major + * type). + */ + + if (unlikely(v > (uint64_t)INT64_MAX)) + return CborErrorDataTooLarge; + + *result = v; + if (value->flags & CborIteratorFlag_NegativeInteger) + *result = -*result - 1; + return CborNoError; +} + +/** + * Retrieves the CBOR integer value that \a value points to and stores it in \a + * result. If the iterator \a value does not point to an integer value, the + * behavior is undefined, so checking with \ref cbor_value_get_type or with + * \ref cbor_value_is_integer is recommended. + * + * Unlike \ref cbor_value_get_int(), this function performs a check to see if the + * stored integer fits in \a result without data loss. If the number is outside + * the valid range for the data type, this function returns the recoverable + * error CborErrorDataTooLarge. In that case, use one of the other integer + * functions to obtain the value. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_integer(), cbor_value_get_int64(), + * cbor_value_get_uint64(), cbor_value_get_int64_checked(), cbor_value_get_raw_integer() + */ +CborError cbor_value_get_int_checked(const CborValue *value, int *result) +{ + uint64_t v; + cbor_assert(cbor_value_is_integer(value)); + v = _cbor_value_extract_int64_helper(value); + + /* Check before converting, as the standard says (C11 6.3.1.3 paragraph 3): + * "[if] the new type is signed and the value cannot be represented in it; either the + * result is implementation-defined or an implementation-defined signal is raised." + * + * But we can convert from signed to unsigned without fault (paragraph 2). + * + * The range for int is implementation-defined and int is not guaranteed to use + * two's complement representation (although int32_t is). + */ + + if (value->flags & CborIteratorFlag_NegativeInteger) { + if (unlikely(v > (unsigned) -(INT_MIN + 1))) + return CborErrorDataTooLarge; + + *result = (int)v; + *result = -*result - 1; + } else { + if (unlikely(v > (uint64_t)INT_MAX)) + return CborErrorDataTooLarge; + + *result = (int)v; + } + return CborNoError; + +} + +/** + * \fn bool cbor_value_is_length_known(const CborValue *value) + * + * Returns true if the length of this type is known without calculation. That + * is, if the length of this CBOR string, map or array is encoded in the data + * stream, this function returns true. If the length is not encoded, it returns + * false. + * + * If the length is known, code can call cbor_value_get_string_length(), + * cbor_value_get_array_length() or cbor_value_get_map_length() to obtain the + * length. If the length is not known but is necessary, code can use the + * cbor_value_calculate_string_length() function (no equivalent function is + * provided for maps and arrays). + */ + +/** + * \fn bool cbor_value_is_text_string(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR text + * string. CBOR text strings are UTF-8 encoded and usually contain + * human-readable text. + * + * \sa cbor_value_is_valid(), cbor_value_get_string_length(), cbor_value_calculate_string_length(), + * cbor_value_copy_text_string(), cbor_value_dup_text_string() + */ + +/** + * \fn bool cbor_value_is_byte_string(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR byte + * string. CBOR byte strings are binary data with no specified encoding or + * format. + * + * \sa cbor_value_is_valid(), cbor_value_get_string_length(), cbor_value_calculate_string_length(), + * cbor_value_copy_byte_string(), cbor_value_dup_byte_string() + */ + +/** + * \fn CborError cbor_value_get_string_length(const CborValue *value, size_t *length) + * + * Extracts the length of the byte or text string that \a value points to and + * stores it in \a result. If the iterator \a value does not point to a text + * string or a byte string, the behaviour is undefined, so checking with \ref + * cbor_value_get_type, with \ref cbor_value_is_text_string or \ref + * cbor_value_is_byte_string is recommended. + * + * If the length of this string is not encoded in the CBOR data stream, this + * function will return the recoverable error CborErrorUnknownLength. You may + * also check whether that is the case by using cbor_value_is_length_known(). + * + * If the length of the string is required but the length was not encoded, use + * cbor_value_calculate_string_length(), but note that that function does not + * run in constant time. + * + * \note On 32-bit platforms, this function will return error condition of \ref + * CborErrorDataTooLarge if the stream indicates a length that is too big to + * fit in 32-bit. + * + * \sa cbor_value_is_valid(), cbor_value_is_length_known(), cbor_value_calculate_string_length() + */ + +/** + * Calculates the length of the byte or text string that \a value points to and + * stores it in \a len. If the iterator \a value does not point to a text + * string or a byte string, the behaviour is undefined, so checking with \ref + * cbor_value_get_type, with \ref cbor_value_is_text_string or \ref + * cbor_value_is_byte_string is recommended. + * + * This function is different from cbor_value_get_string_length() in that it + * calculates the length even for strings sent in chunks. For that reason, this + * function may not run in constant time (it will run in O(n) time on the + * number of chunks). It does use constant memory (O(1)). + * + * \note On 32-bit platforms, this function will return error condition of \ref + * CborErrorDataTooLarge if the stream indicates a length that is too big to + * fit in 32-bit. + * + * \sa cbor_value_get_string_length(), cbor_value_copy_text_string(), cbor_value_copy_byte_string(), cbor_value_is_length_known() + */ +CborError cbor_value_calculate_string_length(const CborValue *value, size_t *len) +{ + *len = SIZE_MAX; + return _cbor_value_copy_string(value, NULL, len, NULL); +} + +CborError _cbor_value_begin_string_iteration(CborValue *it) +{ + it->flags |= CborIteratorFlag_IteratingStringChunks | + CborIteratorFlag_BeforeFirstStringChunk; + if (!cbor_value_is_length_known(it)) { + /* chunked string: we're before the first chunk; + * advance to the first chunk */ + advance_bytes(it, 1); + } + + return CborNoError; +} + +CborError _cbor_value_finish_string_iteration(CborValue *it) +{ + if (!cbor_value_is_length_known(it)) + advance_bytes(it, 1); /* skip the Break */ + + return preparse_next_value(it); +} + +static CborError get_string_chunk_size(const CborValue *it, size_t *offset, size_t *len) +{ + uint8_t descriptor; + size_t bytesNeeded = 1; + + if (cbor_value_is_length_known(it) && (it->flags & CborIteratorFlag_BeforeFirstStringChunk) == 0) + return CborErrorNoMoreStringChunks; + + /* are we at the end? */ + if (!read_bytes(it, &descriptor, 0, 1)) + return CborErrorUnexpectedEOF; + + if (descriptor == BreakByte) + return CborErrorNoMoreStringChunks; + if ((descriptor & MajorTypeMask) != it->type) + return CborErrorIllegalType; + + /* find the string length */ + descriptor &= SmallValueMask; + if (descriptor < Value8Bit) { + *len = descriptor; + } else if (unlikely(descriptor > Value64Bit)) { + return CborErrorIllegalNumber; + } else { + uint64_t val; + bytesNeeded = (size_t)(1 << (descriptor - Value8Bit)); + if (!can_read_bytes(it, 1 + bytesNeeded)) + return CborErrorUnexpectedEOF; + + if (descriptor <= Value16Bit) { + if (descriptor == Value16Bit) + val = read_uint16(it, 1); + else + val = read_uint8(it, 1); + } else { + if (descriptor == Value32Bit) + val = read_uint32(it, 1); + else + val = read_uint64(it, 1); + } + + *len = val; + if (*len != val) + return CborErrorDataTooLarge; + + ++bytesNeeded; + } + + *offset = bytesNeeded; + return CborNoError; +} + +CborError _cbor_value_get_string_chunk_size(const CborValue *value, size_t *len) +{ + size_t offset; + return get_string_chunk_size(value, &offset, len); +} + +static CborError get_string_chunk(CborValue *it, const void **bufferptr, size_t *len) +{ + size_t offset; + CborError err = get_string_chunk_size(it, &offset, len); + if (err) + return err; + + /* we're good, transfer the string now */ + err = transfer_string(it, bufferptr, offset, *len); + if (err) + return err; + + /* we've iterated at least once */ + it->flags &= ~CborIteratorFlag_BeforeFirstStringChunk; + return CborNoError; +} + +/** + * \fn CborError cbor_value_get_text_string_chunk(const CborValue *value, const char **bufferptr, size_t *len, CborValue *next) + * + * Extracts one text string chunk pointed to by \a value and stores a pointer + * to the data in \a buffer and the size in \a len, which must not be null. If + * no more chunks are available, then \a bufferptr will be set to null. This + * function may be used to iterate over any string without causing its contents + * to be copied to a separate buffer, like the convenience function + * cbor_value_copy_text_string() does. + * + * It is designed to be used in code like: + * + * \code + * if (cbor_value_is_text_string(value)) { + * char *ptr; + * size_t len; + * while (1) { + * err = cbor_value_get_text_string_chunk(value, &ptr, &len, &value)); + * if (err) return err; + * if (ptr == NULL) return CborNoError; + * consume(ptr, len); + * } + * } + * \endcode + * + * If the iterator \a value does not point to a text string, the behaviour is + * undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_text_string is recommended. + * + * The \a next pointer, if not null, will be updated to point to the next item + * after this string. During iteration, the pointer must only be passed back + * again to this function; passing it to any other function in this library + * results in undefined behavior. If there are no more chunks to be read from + * \a value, then \a next will be set to the next item after this string; if \a + * value points to the last item, then \a next will be invalid. + * + * \note This function does not perform UTF-8 validation on the incoming text + * string. + * + * \sa cbor_value_dup_text_string(), cbor_value_copy_text_string(), cbor_value_caculate_string_length(), cbor_value_get_byte_string_chunk() + */ + +/** + * \fn CborError cbor_value_get_byte_string_chunk(const CborValue *value, const char **bufferptr, size_t *len, CborValue *next) + * + * Extracts one byte string chunk pointed to by \a value and stores a pointer + * to the data in \a buffer and the size in \a len, which must not be null. If + * no more chunks are available, then \a bufferptr will be set to null. This + * function may be used to iterate over any string without causing its contents + * to be copied to a separate buffer, like the convenience function + * cbor_value_copy_byte_string() does. + * + * It is designed to be used in code like: + * + * \code + * if (cbor_value_is_byte_string(value)) { + * char *ptr; + * size_t len; + * while (1) { + * err = cbor_value_get_byte_string_chunk(value, &ptr, &len, &value)); + * if (err) return err; + * if (ptr == NULL) return CborNoError; + * consume(ptr, len); + * } + * } + * \endcode + * + * If the iterator \a value does not point to a byte string, the behaviour is + * undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_byte_string is recommended. + * + * The \a next pointer, if not null, will be updated to point to the next item + * after this string. During iteration, the pointer must only be passed back + * again to this function; passing it to any other function in this library + * results in undefined behavior. If there are no more chunks to be read from + * \a value, then \a next will be set to the next item after this string; if \a + * value points to the last item, then \a next will be invalid. + * + * \sa cbor_value_dup_byte_string(), cbor_value_copy_byte_string(), cbor_value_caculate_string_length(), cbor_value_get_text_string_chunk() + */ + +CborError _cbor_value_get_string_chunk(const CborValue *value, const void **bufferptr, + size_t *len, CborValue *next) +{ + CborValue tmp; + if (!next) + next = &tmp; + *next = *value; + return get_string_chunk(next, bufferptr, len); +} + +/* We return uintptr_t so that we can pass memcpy directly as the iteration + * function. The choice is to optimize for memcpy, which is used in the base + * parser API (cbor_value_copy_string), while memcmp is used in convenience API + * only. */ +typedef uintptr_t (*IterateFunction)(char *, const uint8_t *, size_t); + +static uintptr_t iterate_noop(char *dest, const uint8_t *src, size_t len) +{ + (void)dest; + (void)src; + (void)len; + return true; +} + +static uintptr_t iterate_memcmp(char *s1, const uint8_t *s2, size_t len) +{ + return memcmp(s1, (const char *)s2, len) == 0; +} + +static uintptr_t iterate_memcpy(char *dest, const uint8_t *src, size_t len) +{ + return (uintptr_t)memcpy(dest, src, len); +} + +static CborError iterate_string_chunks(const CborValue *value, char *buffer, size_t *buflen, + bool *result, CborValue *next, IterateFunction func) +{ + CborError err; + CborValue tmp; + size_t total = 0; + const void *ptr; + + cbor_assert(cbor_value_is_byte_string(value) || cbor_value_is_text_string(value)); + if (!next) + next = &tmp; + *next = *value; + *result = true; + + err = _cbor_value_begin_string_iteration(next); + if (err) + return err; + + while (1) { + size_t newTotal; + size_t chunkLen; + err = get_string_chunk(next, &ptr, &chunkLen); + if (err == CborErrorNoMoreStringChunks) + break; + if (err) + return err; + + if (unlikely(add_check_overflow(total, chunkLen, &newTotal))) + return CborErrorDataTooLarge; + + if (*result && *buflen >= newTotal) + *result = !!func(buffer + total, (const uint8_t *)ptr, chunkLen); + else + *result = false; + + total = newTotal; + } + + /* is there enough room for the ending NUL byte? */ + if (*result && *buflen > total) { + uint8_t nul[] = { 0 }; + *result = !!func(buffer + total, nul, 1); + } + *buflen = total; + return _cbor_value_finish_string_iteration(next); +} + +/** + * \fn CborError cbor_value_copy_text_string(const CborValue *value, char *buffer, size_t *buflen, CborValue *next) + * + * Copies the string pointed to by \a value into the buffer provided at \a buffer + * of \a buflen bytes. If \a buffer is a NULL pointer, this function will not + * copy anything and will only update the \a next value. + * + * If the iterator \a value does not point to a text string, the behaviour is + * undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_text_string is recommended. + * + * If the provided buffer length was too small, this function returns an error + * condition of \ref CborErrorOutOfMemory. If you need to calculate the length + * of the string in order to preallocate a buffer, use + * cbor_value_calculate_string_length(). + * + * On success, this function sets the number of bytes copied to \c{*buflen}. If + * the buffer is large enough, this function will insert a null byte after the + * last copied byte, to facilitate manipulation of text strings. That byte is + * not included in the returned value of \c{*buflen}. If there was no space for + * the terminating null, no error is returned, so callers must check the value + * of *buflen after the call, before relying on the '\0'; if it has not been + * changed by the call, there is no '\0'-termination on the buffer's contents. + * + * 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. + * + * 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)). + * + * \note This function does not perform UTF-8 validation on the incoming text + * string. + * + * \sa cbor_value_get_text_string_chunk() cbor_value_dup_text_string(), cbor_value_copy_byte_string(), cbor_value_get_string_length(), cbor_value_calculate_string_length() + */ + +/** + * \fn CborError cbor_value_copy_byte_string(const CborValue *value, uint8_t *buffer, size_t *buflen, CborValue *next) + * + * Copies the string pointed by \a value into the buffer provided at \a buffer + * of \a buflen bytes. If \a buffer is a NULL pointer, this function will not + * copy anything and will only update the \a next value. + * + * If the iterator \a value does not point to a byte string, the behaviour is + * undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_byte_string is recommended. + * + * If the provided buffer length was too small, this function returns an error + * condition of \ref CborErrorOutOfMemory. If you need to calculate the length + * of the string in order to preallocate a buffer, use + * cbor_value_calculate_string_length(). + * + * On success, this function sets the number of bytes copied to \c{*buflen}. If + * the buffer is large enough, this function will insert a null byte after the + * last copied byte, to facilitate manipulation of null-terminated strings. + * That byte is not included in the returned value of \c{*buflen}. + * + * 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. + * + * 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)). + * + * \sa cbor_value_get_byte_string_chunk(), cbor_value_dup_text_string(), cbor_value_copy_text_string(), cbor_value_get_string_length(), cbor_value_calculate_string_length() + */ + +CborError _cbor_value_copy_string(const CborValue *value, void *buffer, + size_t *buflen, CborValue *next) +{ + bool copied_all; + CborError err = iterate_string_chunks(value, (char*)buffer, buflen, &copied_all, next, + buffer ? iterate_memcpy : iterate_noop); + return err ? err : + copied_all ? CborNoError : CborErrorOutOfMemory; +} + +/** + * Compares the entry \a value with the string \a string and stores the result + * in \a result. If the value is different from \a string \a result will + * contain \c false. + * + * The entry at \a value may be a tagged string. If \a value is not a string or + * a tagged string, the comparison result will be false. + * + * CBOR requires text strings to be encoded in UTF-8, but this function does + * not validate either the strings in the stream or the string \a string to be + * matched. Moreover, comparison is done on strict codepoint comparison, + * without any Unicode normalization. + * + * 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)). + * + * \sa cbor_value_skip_tag(), cbor_value_copy_text_string() + */ +CborError cbor_value_text_string_equals(const CborValue *value, const char *string, bool *result) +{ + size_t len; + CborValue copy = *value; + CborError err = cbor_value_skip_tag(©); + if (err) + return err; + if (!cbor_value_is_text_string(©)) { + *result = false; + return CborNoError; + } + + len = strlen(string); + return iterate_string_chunks(©, CONST_CAST(char *, string), &len, result, NULL, iterate_memcmp); +} + +/** + * \fn bool cbor_value_is_array(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR array. + * + * \sa cbor_value_is_valid(), cbor_value_is_map() + */ + +/** + * \fn CborError cbor_value_get_array_length(const CborValue *value, size_t *length) + * + * Extracts the length of the CBOR array that \a value points to and stores it + * in \a result. If the iterator \a value does not point to a CBOR array, the + * behaviour is undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_array is recommended. + * + * If the length of this array is not encoded in the CBOR data stream, this + * function will return the recoverable error CborErrorUnknownLength. You may + * also check whether that is the case by using cbor_value_is_length_known(). + * + * \note On 32-bit platforms, this function will return error condition of \ref + * CborErrorDataTooLarge if the stream indicates a length that is too big to + * fit in 32-bit. + * + * \sa cbor_value_is_valid(), cbor_value_is_length_known() + */ + +/** + * \fn bool cbor_value_is_map(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR map. + * + * \sa cbor_value_is_valid(), cbor_value_is_array() + */ + +/** + * \fn CborError cbor_value_get_map_length(const CborValue *value, size_t *length) + * + * Extracts the length of the CBOR map that \a value points to and stores it in + * \a result. If the iterator \a value does not point to a CBOR map, the + * behaviour is undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_map is recommended. + * + * If the length of this map is not encoded in the CBOR data stream, this + * function will return the recoverable error CborErrorUnknownLength. You may + * also check whether that is the case by using cbor_value_is_length_known(). + * + * \note On 32-bit platforms, this function will return error condition of \ref + * CborErrorDataTooLarge if the stream indicates a length that is too big to + * fit in 32-bit. + * + * \sa cbor_value_is_valid(), cbor_value_is_length_known() + */ + +/** + * Attempts to find the value in map \a map that corresponds to the text string + * entry \a string. If the iterator \a value does not point to a CBOR map, the + * behaviour is undefined, so checking with \ref cbor_value_get_type or \ref + * cbor_value_is_map is recommended. + * + * If the item is found, it is stored in \a result. If no item is found + * matching the key, then \a result will contain an element of type \ref + * CborInvalidType. Matching is performed using + * cbor_value_text_string_equals(), so tagged strings will also match. + * + * This function has a time complexity of O(n) where n is the number of + * elements in the map to be searched. In addition, this function is has O(n) + * memory requirement based on the number of nested containers (maps or arrays) + * found as elements of this map. + * + * \sa cbor_value_is_valid(), cbor_value_text_string_equals(), cbor_value_advance() + */ +CborError cbor_value_map_find_value(const CborValue *map, const char *string, CborValue *element) +{ + CborError err; + size_t len = strlen(string); + cbor_assert(cbor_value_is_map(map)); + err = cbor_value_enter_container(map, element); + if (err) + goto error; + + while (!cbor_value_at_end(element)) { + /* find the non-tag so we can compare */ + err = cbor_value_skip_tag(element); + if (err) + goto error; + if (cbor_value_is_text_string(element)) { + bool equals; + size_t dummyLen = len; + err = iterate_string_chunks(element, CONST_CAST(char *, string), &dummyLen, + &equals, element, iterate_memcmp); + if (err) + goto error; + if (equals) + return preparse_value(element); + } else { + /* skip this key */ + err = cbor_value_advance(element); + if (err) + goto error; + } + + /* skip this value */ + err = cbor_value_skip_tag(element); + if (err) + goto error; + err = cbor_value_advance(element); + if (err) + goto error; + } + + /* not found */ + element->type = CborInvalidType; + return CborNoError; + +error: + element->type = CborInvalidType; + return err; +} + +/** + * \fn bool cbor_value_is_float(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR + * single-precision floating point (32-bit). + * + * \sa cbor_value_is_valid(), cbor_value_is_double(), cbor_value_is_half_float() + */ + +/** + * \fn CborError cbor_value_get_float(const CborValue *value, float *result) + * + * Retrieves the CBOR single-precision floating point (32-bit) value that \a + * value points to and stores it in \a result. If the iterator \a value does + * not point to a single-precision floating point value, the behavior is + * undefined, so checking with \ref cbor_value_get_type or with \ref + * cbor_value_is_float is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_float(), cbor_value_get_double() + */ + +/** + * \fn bool cbor_value_is_double(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR + * double-precision floating point (64-bit). + * + * \sa cbor_value_is_valid(), cbor_value_is_float(), cbor_value_is_half_float() + */ + +/** + * \fn CborError cbor_value_get_double(const CborValue *value, float *result) + * + * Retrieves the CBOR double-precision floating point (64-bit) value that \a + * value points to and stores it in \a result. If the iterator \a value does + * not point to a double-precision floating point value, the behavior is + * undefined, so checking with \ref cbor_value_get_type or with \ref + * cbor_value_is_double is recommended. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_double(), cbor_value_get_float() + */ + +/** + * \fn bool cbor_value_is_half_float(const CborValue *value) + * + * Returns true if the iterator \a value is valid and points to a CBOR + * single-precision floating point (16-bit). + * + * \sa cbor_value_is_valid(), cbor_value_is_double(), cbor_value_is_float() + */ + +/** + * \fn CborError cbor_value_get_half_float(const CborValue *value, void *result) + * + * Retrieves the CBOR half-precision floating point (16-bit) value that \a + * value points to and stores it in \a result. If the iterator \a value does + * not point to a half-precision floating point value, the behavior is + * undefined, so checking with \ref cbor_value_get_type or with \ref + * cbor_value_is_half_float is recommended. + * + * Note: since the C language does not have a standard type for half-precision + * floating point, this function takes a \c{void *} as a parameter for the + * storage area, which must be at least 16 bits wide. + * + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_half_float(), cbor_value_get_half_float_as_float(), cbor_value_get_float() + */ + +/** @} */ diff --git a/lib/cbor/tinycbor/src/cborparser_dup_string.c b/lib/cbor/tinycbor/src/cborparser_dup_string.c new file mode 100644 index 00000000..061c5ac7 --- /dev/null +++ b/lib/cbor/tinycbor/src/cborparser_dup_string.c @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 + +#include "cbor.h" +#include "compilersupport_p.h" +#include <stdlib.h> + +/** + * \fn CborError cbor_value_dup_text_string(const CborValue *value, char **buffer, size_t *buflen, CborValue *next) + * + * Allocates memory for the string pointed by \a value and copies it into this + * buffer. The pointer to the buffer is stored in \a buffer and the number of + * bytes copied is stored in \a buflen (those variables must not be NULL). + * + * If the iterator \a value does not point to a text string, the behaviour is + * 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 + * invalid. + * + * 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. + * + * \note This function does not perform UTF-8 validation on the incoming text + * string. + * + * \sa cbor_value_get_text_string_chunk(), cbor_value_copy_text_string(), cbor_value_dup_byte_string() + */ + +/** + * \fn CborError cbor_value_dup_byte_string(const CborValue *value, uint8_t **buffer, size_t *buflen, CborValue *next) + * + * Allocates memory for the string pointed by \a value and copies it into this + * buffer. The pointer to the buffer is stored in \a buffer and the number of + * bytes copied is stored in \a buflen (those variables must not be NULL). + * + * If the iterator \a value does not point to a byte string, the behaviour is + * 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 + * invalid. + * + * 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. + * + * \sa cbor_value_get_text_string_chunk(), cbor_value_copy_byte_string(), cbor_value_dup_text_string() + */ +CborError _cbor_value_dup_string(const CborValue *value, void **buffer, size_t *buflen, CborValue *next) +{ + CborError err; + cbor_assert(buffer); + cbor_assert(buflen); + *buflen = SIZE_MAX; + err = _cbor_value_copy_string(value, NULL, buflen, NULL); + if (err) + return err; + + ++*buflen; + *buffer = malloc(*buflen); + if (!*buffer) { + /* out of memory */ + return CborErrorOutOfMemory; + } + err = _cbor_value_copy_string(value, *buffer, buflen, next); + if (err) { + free(*buffer); + return err; + } + return CborNoError; +} diff --git a/lib/cbor/tinycbor/src/cborparser_float.c b/lib/cbor/tinycbor/src/cborparser_float.c new file mode 100644 index 00000000..426cd7d4 --- /dev/null +++ b/lib/cbor/tinycbor/src/cborparser_float.c @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2019 S.Phirsov +** +** 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. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" + +#include "cborinternal_p.h" + +#ifndef CBOR_NO_HALF_FLOAT_TYPE +/** + * Retrieves the CBOR half-precision floating point (16-bit) value that \a + * value points to, converts it to the float and store it in \a result. + * If the iterator \a value does not point to a half-precision floating + * point value, the behavior is undefined, so checking with \ref + * cbor_value_get_type or with \ref cbor_value_is_half_float is recommended. + * \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_half_float(), cbor_value_get_half_float(), cbor_value_get_float() + */ +CborError cbor_value_get_half_float_as_float(const CborValue *value, float *result) +{ + uint16_t v; + CborError err = cbor_value_get_half_float(value, &v); + cbor_assert(err == CborNoError); + + *result = (float)decode_half((unsigned short)v); + + return CborNoError; +} +#endif diff --git a/lib/cbor/tinycbor/src/cborpretty.c b/lib/cbor/tinycbor/src/cborpretty.c new file mode 100644 index 00000000..49d3cae7 --- /dev/null +++ b/lib/cbor/tinycbor/src/cborpretty.c @@ -0,0 +1,579 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include "cborinternal_p.h" +#include "compilersupport_p.h" +#include "utf8_p.h" + +#include <inttypes.h> +#include <string.h> + +/** + * \defgroup CborPretty Converting CBOR to text + * \brief Group of functions used to convert CBOR to text form. + * + * This group contains two functions that can be used to convert a \ref + * CborValue object to a text representation. This module attempts to follow + * the recommendations from RFC 7049 section 6 "Diagnostic Notation", though it + * has a few differences. They are noted below. + * + * TinyCBOR does not provide a way to convert from the text representation back + * to encoded form. To produce a text form meant to be parsed, CborToJson is + * recommended instead. + * + * Either of the functions in this section will attempt to convert exactly one + * CborValue object to text. Those functions may return any error documented + * for the functions for CborParsing. In addition, if the C standard library + * stream functions return with error, the text conversion will return with + * error CborErrorIO. + * + * These functions also perform UTF-8 validation in CBOR text strings. If they + * encounter a sequence of bytes that is not permitted in UTF-8, they will return + * CborErrorInvalidUtf8TextString. That includes encoding of surrogate points + * in UTF-8. + * + * \warning The output type produced by these functions is not guaranteed to + * remain stable. A future update of TinyCBOR may produce different output for + * the same input and parsers may be unable to handle it. + * + * \sa CborParsing, CborToJson, cbor_parser_init() + */ + +/** + * \addtogroup CborPretty + * @{ + * <h2 class="groupheader">Text format</h2> + * + * As described in RFC 7049 section 6 "Diagnostic Notation", the format is + * largely borrowed from JSON, but modified to suit CBOR's different data + * types. TinyCBOR makes further modifications to distinguish different, but + * similar values. + * + * CBOR values are currently encoded as follows: + * \par Integrals (unsigned and negative) + * Base-10 (decimal) text representation of the value + * \par Byte strings: + * <tt>"h'"</tt> followed by the Base16 (hex) representation of the binary data, followed by an ending quote (') + * \par Text strings: + * C-style escaped string in quotes, with C11/C++11 escaping of Unicode codepoints above U+007F. + * \par Tags: + * Tag value, with the tagged value in parentheses. No special encoding of the tagged value is performed. + * \par Simple types: + * <tt>"simple(nn)"</tt> where \c nn is the simple value + * \par Null: + * \c null + * \par Undefined: + * \c undefined + * \par Booleans: + * \c true or \c false + * \par Floating point: + * If NaN or infinite, the actual words \c NaN or \c infinite. + * Otherwise, the decimal representation with as many digits as necessary to ensure no loss of information. + * By default, float values are suffixed by "f" and half-float values suffixed by "f16" (doubles have no suffix). + * If the CborPrettyNumericEncodingIndicators flag is active, the values instead are encoded following the + * Section 6 recommended encoding indicators: float values are suffixed with "_2" and half-float with "_1". + * A decimal point is always present. + * \par Arrays: + * Comma-separated list of elements, enclosed in square brackets ("[" and "]"). + * \par Maps: + * Comma-separated list of key-value pairs, with the key and value separated + * by a colon (":"), enclosed in curly braces ("{" and "}"). + * + * The CborPrettyFlags enumerator contains flags to control some aspects of the + * encoding: + * \par String fragmentation + * When the CborPrettyShowStringFragments option is active, text and byte + * strings that are transmitted in fragments are shown instead inside + * parentheses ("(" and ")") with no preceding number and each fragment is + * displayed individually. If a tag precedes the string, then the output + * will contain a double set of parentheses. If the option is not active, + * the fragments are merged together and the display will not show any + * difference from a string transmitted with determinate length. + * \par Encoding indicators + * Numbers and lengths in CBOR can be encoded in multiple representations. + * If the CborPrettyIndicateOverlongNumbers option is active, numbers + * and lengths that are transmitted in a longer encoding than necessary + * will be indicated, by appending an underscore ("_") to either the + * number or the opening bracket or brace, followed by a number + * indicating the CBOR additional information: 0 for 1 byte, 1 for 2 + * bytes, 2 for 4 bytes and 3 for 8 bytes. + * If the CborPrettyIndicateIndeterminateLength option is active, maps, + * arrays and strings encoded with indeterminate length will be marked by + * an underscore after the opening bracket or brace or the string (if not + * showing fragments), without a number after it. + */ + +/** + * \enum CborPrettyFlags + * The CborPrettyFlags enum contains flags that control the conversion of CBOR to text format. + * + * \value CborPrettyNumericEncodingIndicators Use numeric encoding indicators instead of textual for float and half-float. + * \value CborPrettyTextualEncodingIndicators Use textual encoding indicators for float ("f") and half-float ("f16"). + * \value CborPrettyIndicateIndeterminateLength (default) Indicate when a map or array has indeterminate length. + * \value CborPrettyIndicateOverlongNumbers Indicate when a number or length was encoded with more bytes than needed. + * \value CborPrettyShowStringFragments If the byte or text string is transmitted in chunks, show each individually. + * \value CborPrettyMergeStringFragment Merge all chunked byte or text strings and display them in a single entry. + * \value CborPrettyDefaultFlags Default conversion flags. + */ + +#ifndef CBOR_NO_FLOATING_POINT +static inline bool convertToUint64(double v, uint64_t *absolute) +{ + double supremum; + v = fabs(v); + + /* C11 standard section 6.3.1.4 "Real floating and integer" says: + * + * 1 When a finite value of real floating type is converted to an integer + * type other than _Bool, the fractional part is discarded (i.e., the + * value is truncated toward zero). If the value of the integral part + * cannot be represented by the integer type, the behavior is undefined. + * + * So we must perform a range check that v <= UINT64_MAX, but we can't use + * UINT64_MAX + 1.0 because the standard continues: + * + * 2 When a value of integer type is converted to a real floating type, if + * the value being converted can be represented exactly in the new type, + * it is unchanged. If the value being converted is in the range of + * values that can be represented but cannot be represented exactly, the + * result is either the nearest higher or nearest lower representable + * value, chosen in an implementation-defined manner. + */ + supremum = -2.0 * INT64_MIN; /* -2 * (- 2^63) == 2^64 */ + if (v >= supremum) + return false; + + /* Now we can convert, these two conversions cannot be UB */ + *absolute = v; + return *absolute == v; +} +#endif + +static void printRecursionLimit(CborStreamFunction stream, void *out) +{ + stream(out, "<nesting too deep, recursion stopped>"); +} + +static CborError hexDump(CborStreamFunction stream, void *out, const void *ptr, size_t n) +{ + const uint8_t *buffer = (const uint8_t *)ptr; + CborError err = CborNoError; + while (n-- && !err) + err = stream(out, "%02" PRIx8, *buffer++); + + return err; +} + +/* This function decodes buffer as UTF-8 and prints as escaped UTF-16. + * On UTF-8 decoding error, it returns CborErrorInvalidUtf8TextString */ +static CborError utf8EscapedDump(CborStreamFunction stream, void *out, const void *ptr, size_t n) +{ + const uint8_t *buffer = (const uint8_t *)ptr; + const uint8_t * const end = buffer + n; + CborError err = CborNoError; + + while (buffer < end && !err) { + uint32_t uc = get_utf8(&buffer, end); + if (uc == ~0U) + return CborErrorInvalidUtf8TextString; + + if (uc < 0x80) { + /* single-byte UTF-8 */ + unsigned char escaped = (unsigned char)uc; + if (uc < 0x7f && uc >= 0x20 && uc != '\\' && uc != '"') { + err = stream(out, "%c", (char)uc); + continue; + } + + /* print as an escape sequence */ + switch (uc) { + case '"': + case '\\': + break; + case '\b': + escaped = 'b'; + break; + case '\f': + escaped = 'f'; + break; + case '\n': + escaped = 'n'; + break; + case '\r': + escaped = 'r'; + break; + case '\t': + escaped = 't'; + break; + default: + goto print_utf16; + } + err = stream(out, "\\%c", escaped); + continue; + } + + /* now print the sequence */ + if (uc > 0xffffU) { + /* needs surrogate pairs */ + err = stream(out, "\\u%04" PRIX32 "\\u%04" PRIX32, + (uc >> 10) + 0xd7c0, /* high surrogate */ + (uc % 0x0400) + 0xdc00); + } else { +print_utf16: + /* no surrogate pair needed */ + err = stream(out, "\\u%04" PRIX32, uc); + } + } + return err; +} + +static const char *resolve_indicator(const CborValue *it, int flags) +{ + static const char indicators[8][3] = { + "_0", "_1", "_2", "_3", + "", "", "", /* these are not possible */ + "_" + }; + const char *no_indicator = indicators[5]; /* empty string */ + uint8_t additional_information; + uint8_t expected_information; + uint64_t value; + CborError err; + + if (!read_bytes(it, &additional_information, 0, 1)) + return NULL; /* CborErrorUnexpectedEOF */ + + additional_information &= SmallValueMask; + if (additional_information < Value8Bit) + return no_indicator; + + /* determine whether to show anything */ + if ((flags & CborPrettyIndicateIndeterminateLength) && + additional_information == IndefiniteLength) + return indicators[IndefiniteLength - Value8Bit]; + if ((flags & CborPrettyIndicateOverlongNumbers) == 0) + return no_indicator; + + err = extract_number_checked(it, &value, NULL); + if (err) + return NULL; /* CborErrorUnexpectedEOF */ + + expected_information = Value8Bit - 1; + if (value >= Value8Bit) + ++expected_information; + if (value > 0xffU) + ++expected_information; + if (value > 0xffffU) + ++expected_information; + if (value > 0xffffffffU) + ++expected_information; + return expected_information == additional_information ? + no_indicator : + indicators[additional_information - Value8Bit]; +} + +static const char *get_indicator(const CborValue *it, int flags) +{ + return resolve_indicator(it, flags); +} + +static CborError value_to_pretty(CborStreamFunction stream, void *out, CborValue *it, int flags, int recursionsLeft); +static CborError container_to_pretty(CborStreamFunction stream, void *out, CborValue *it, CborType containerType, + int flags, int recursionsLeft) +{ + const char *comma = ""; + CborError err = CborNoError; + + if (!recursionsLeft) { + printRecursionLimit(stream, out); + return err; /* do allow the dumping to continue */ + } + + while (!cbor_value_at_end(it) && !err) { + err = stream(out, "%s", comma); + comma = ", "; + + if (!err) + err = value_to_pretty(stream, out, it, flags, recursionsLeft); + + if (containerType == CborArrayType) + continue; + + /* map: that was the key, so get the value */ + if (!err) + err = stream(out, ": "); + if (!err) + err = value_to_pretty(stream, out, it, flags, recursionsLeft); + } + return err; +} + +static CborError value_to_pretty(CborStreamFunction stream, void *out, CborValue *it, int flags, int recursionsLeft) +{ + CborError err = CborNoError; + CborType type = cbor_value_get_type(it); + switch (type) { + case CborArrayType: + case CborMapType: { + /* recursive type */ + CborValue recursed; + const char *indicator = get_indicator(it, flags); + const char *space = *indicator ? " " : indicator; + + err = stream(out, "%c%s%s", type == CborArrayType ? '[' : '{', indicator, space); + if (err) + return err; + + err = cbor_value_enter_container(it, &recursed); + if (err) { + copy_current_position(it, &recursed); + return err; /* parse error */ + } + err = container_to_pretty(stream, out, &recursed, type, flags, recursionsLeft - 1); + if (err) { + copy_current_position(it, &recursed); + return err; /* parse error */ + } + err = cbor_value_leave_container(it, &recursed); + if (err) + return err; /* parse error */ + + return stream(out, type == CborArrayType ? "]" : "}"); + } + + case CborIntegerType: { + uint64_t val; + cbor_value_get_raw_integer(it, &val); /* can't fail */ + + if (cbor_value_is_unsigned_integer(it)) { + err = stream(out, "%" PRIu64, val); + } else { + /* CBOR stores the negative number X as -1 - X + * (that is, -1 is stored as 0, -2 as 1 and so forth) */ + if (++val) { /* unsigned overflow may happen */ + err = stream(out, "-%" PRIu64, val); + } else { + /* overflown + * 0xffff`ffff`ffff`ffff + 1 = + * 0x1`0000`0000`0000`0000 = 18446744073709551616 (2^64) */ + err = stream(out, "-18446744073709551616"); + } + } + if (!err) + err = stream(out, "%s", get_indicator(it, flags)); + break; + } + + case CborByteStringType: + case CborTextStringType: { + size_t n = 0; + const void *ptr; + bool showingFragments = (flags & CborPrettyShowStringFragments) && !cbor_value_is_length_known(it); + const char *separator = ""; + char close = '\''; + char open[3] = "h'"; + const char *indicator = NULL; + + if (type == CborTextStringType) { + close = open[0] = '"'; + open[1] = '\0'; + } + + if (showingFragments) + err = stream(out, "(_ "); + else + err = stream(out, "%s", open); + + if (!err) + err = cbor_value_begin_string_iteration(it); + while (!err) { + if (showingFragments || indicator == NULL) { + /* any iteration, except the second for a non-chunked string */ + indicator = resolve_indicator(it, flags); + } + + err = _cbor_value_get_string_chunk(it, &ptr, &n, it); + if (err == CborErrorNoMoreStringChunks) { + err = cbor_value_finish_string_iteration(it); + break; + } + + if (!err && showingFragments) + err = stream(out, "%s%s", separator, open); + if (!err) + err = (type == CborByteStringType ? + hexDump(stream, out, ptr, n) : + utf8EscapedDump(stream, out, ptr, n)); + if (!err && showingFragments) { + err = stream(out, "%c%s", close, indicator); + separator = ", "; + } + } + + if (!err) { + if (showingFragments) + err = stream(out, ")"); + else + err = stream(out, "%c%s", close, indicator); + } + return err; + } + + case CborTagType: { + CborTag tag; + cbor_value_get_tag(it, &tag); /* can't fail */ + err = stream(out, "%" PRIu64 "%s(", tag, get_indicator(it, flags)); + if (!err) + err = cbor_value_advance_fixed(it); + if (!err && recursionsLeft) + err = value_to_pretty(stream, out, it, flags, recursionsLeft - 1); + else if (!err) + printRecursionLimit(stream, out); + if (!err) + err = stream(out, ")"); + return err; + } + + case CborSimpleType: { + /* simple types can't fail and can't have overlong encoding */ + uint8_t simple_type; + cbor_value_get_simple_type(it, &simple_type); + err = stream(out, "simple(%" PRIu8 ")", simple_type); + break; + } + + case CborNullType: + err = stream(out, "null"); + break; + + case CborUndefinedType: + err = stream(out, "undefined"); + break; + + case CborBooleanType: { + bool val; + cbor_value_get_boolean(it, &val); /* can't fail */ + err = stream(out, val ? "true" : "false"); + break; + } + +#ifndef CBOR_NO_FLOATING_POINT + case CborDoubleType: { + const char *suffix; + double val; + int r; + uint64_t ival; + + if (false) { + float f; + case CborFloatType: + cbor_value_get_float(it, &f); + val = f; + suffix = flags & CborPrettyNumericEncodingIndicators ? "_2" : "f"; + } else if (false) { + uint16_t f16; + case CborHalfFloatType: +#ifndef CBOR_NO_HALF_FLOAT_TYPE + cbor_value_get_half_float(it, &f16); + val = decode_half(f16); + suffix = flags & CborPrettyNumericEncodingIndicators ? "_1" : "f16"; +#else + (void)f16; + err = CborErrorUnsupportedType; + break; +#endif + } else { + cbor_value_get_double(it, &val); + suffix = ""; + } + + if ((flags & CborPrettyNumericEncodingIndicators) == 0) { + r = fpclassify(val); + if (r == FP_NAN || r == FP_INFINITE) + suffix = ""; + } + + if (convertToUint64(val, &ival)) { + /* this double value fits in a 64-bit integer, so show it as such + * (followed by a floating point suffix, to disambiguate) */ + err = stream(out, "%s%" PRIu64 ".%s", val < 0 ? "-" : "", ival, suffix); + } else { + /* this number is definitely not a 64-bit integer */ + err = stream(out, "%." DBL_DECIMAL_DIG_STR "g%s", val, suffix); + } + break; + } +#else + case CborDoubleType: + case CborFloatType: + case CborHalfFloatType: + err = CborErrorUnsupportedType; + break; +#endif /* !CBOR_NO_FLOATING_POINT */ + + case CborInvalidType: + err = stream(out, "invalid"); + if (err) + return err; + return CborErrorUnknownType; + } + + if (!err) + err = cbor_value_advance_fixed(it); + return err; +} + +/** + * Converts the current CBOR type pointed by \a value to its textual + * representation and writes it to the stream by calling the \a streamFunction. + * If an error occurs, this function returns an error code similar to + * \ref CborParsing. + * + * The textual representation can be controlled by the \a flags parameter (see + * \ref CborPrettyFlags for more information). + * + * If no error ocurred, this function advances \a value to the next element. + * Often, concatenating the text representation of multiple elements can be + * done by appending a comma to the output stream in between calls to this + * function. + * + * The \a streamFunction function will be called with the \a token value as the + * first parameter and a printf-style format string as the second, with a variable + * number of further parameters. + * + * \sa cbor_value_to_pretty(), cbor_value_to_json_advance() + */ +CborError cbor_value_to_pretty_stream(CborStreamFunction streamFunction, void *token, CborValue *value, int flags) +{ + return value_to_pretty(streamFunction, token, value, flags, CBOR_PARSER_MAX_RECURSIONS); +} + +/** @} */ diff --git a/lib/cbor/tinycbor/src/cborpretty_stdio.c b/lib/cbor/tinycbor/src/cborpretty_stdio.c new file mode 100644 index 00000000..20131850 --- /dev/null +++ b/lib/cbor/tinycbor/src/cborpretty_stdio.c @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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. +** +****************************************************************************/ + +#include "cbor.h" +#include <stdarg.h> +#include <stdio.h> + +static CborError cbor_fprintf(void *out, const char *fmt, ...) +{ + int n; + + va_list list; + va_start(list, fmt); + n = vfprintf((FILE *)out, fmt, list); + va_end(list); + + return n < 0 ? CborErrorIO : CborNoError; +} + +/** + * \fn CborError cbor_value_to_pretty(FILE *out, const CborValue *value) + * + * Converts the current CBOR type pointed to by \a value to its textual + * representation and writes it to the \a out stream. If an error occurs, this + * function returns an error code similar to CborParsing. + * + * \sa cbor_value_to_pretty_advance(), cbor_value_to_json_advance() + */ + +/** + * Converts the current CBOR type pointed to by \a value to its textual + * representation and writes it to the \a out stream. If an error occurs, this + * function returns an error code similar to CborParsing. + * + * If no error ocurred, this function advances \a value to the next element. + * Often, concatenating the text representation of multiple elements can be + * done by appending a comma to the output stream in between calls to this + * function. + * + * \sa cbor_value_to_pretty(), cbor_value_to_pretty_stream(), cbor_value_to_json_advance() + */ +CborError cbor_value_to_pretty_advance(FILE *out, CborValue *value) +{ + return cbor_value_to_pretty_stream(cbor_fprintf, out, value, CborPrettyDefaultFlags); +} + +/** + * Converts the current CBOR type pointed to by \a value to its textual + * representation and writes it to the \a out stream. If an error occurs, this + * function returns an error code similar to CborParsing. + * + * The textual representation can be controlled by the \a flags parameter (see + * CborPrettyFlags for more information). + * + * If no error ocurred, this function advances \a value to the next element. + * Often, concatenating the text representation of multiple elements can be + * done by appending a comma to the output stream in between calls to this + * function. + * + * \sa cbor_value_to_pretty_stream(), cbor_value_to_pretty(), cbor_value_to_json_advance() + */ +CborError cbor_value_to_pretty_advance_flags(FILE *out, CborValue *value, int flags) +{ + return cbor_value_to_pretty_stream(cbor_fprintf, out, value, flags); +} + diff --git a/lib/cbor/tinycbor/src/cbortojson.c b/lib/cbor/tinycbor/src/cbortojson.c new file mode 100644 index 00000000..856d9268 --- /dev/null +++ b/lib/cbor/tinycbor/src/cbortojson.c @@ -0,0 +1,709 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#define _GNU_SOURCE 1 +#define _POSIX_C_SOURCE 200809L +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include "cborjson.h" +#include "cborinternal_p.h" +#include "compilersupport_p.h" +#include "cborinternal_p.h" + +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/** + * \defgroup CborToJson Converting CBOR to JSON + * \brief Group of functions used to convert CBOR to JSON. + * + * This group contains two functions that can be used to convert a \ref + * CborValue object to an equivalent JSON representation. This module attempts + * to follow the recommendations from RFC 7049 section 4.1 "Converting from + * CBOR to JSON", though it has a few differences. They are noted below. + * + * These functions produce a "minified" JSON output, with no spacing, + * indentation or line breaks. If those are necessary, they need to be applied + * in a post-processing phase. + * + * Note that JSON cannot support all CBOR types with fidelity, so the + * conversion is usually lossy. For that reason, TinyCBOR supports adding a set + * of metadata JSON values that can be used by a JSON-to-CBOR converter to + * restore the original data types. + * + * The TinyCBOR library does not provide a way to convert from JSON + * representation back to encoded form. However, it provides a tool called + * \c json2cbor which can be used for that purpose. That tool supports the + * metadata format that these functions may produce. + * + * Either of the functions in this section will attempt to convert exactly one + * CborValue object to JSON. Those functions may return any error documented + * for the functions for CborParsing. In addition, if the C standard library + * stream functions return with error, the text conversion will return with + * error CborErrorIO. + * + * These functions also perform UTF-8 validation in CBOR text strings. If they + * encounter a sequence of bytes that is not permitted in UTF-8, they will return + * CborErrorInvalidUtf8TextString. That includes encoding of surrogate points + * in UTF-8. + * + * \warning The metadata produced by these functions is not guaranteed to + * remain stable. A future update of TinyCBOR may produce different output for + * the same input and parsers may be unable to handle it. + * + * \sa CborParsing, CborPretty, cbor_parser_init() + */ + +/** + * \addtogroup CborToJson + * @{ + * <h2 class="groupheader">Conversion limitations</h2> + * + * When converting from CBOR to JSON, there may be information loss. This + * section lists the possible scenarios. + * + * \par Number precision: + * ALL JSON numbers, due to its JavaScript heritage, are IEEE 754 + * double-precision floating point. This means JSON is not capable of + * representing all integers numbers outside the range [-(2<sup>53</sup>)+1, + * 2<sup>53</sup>-1] and is not capable of representing NaN or infinite. If the + * CBOR data contains a number outside the valid range, the conversion will + * lose precision. If the input was NaN or infinite, the result of the + * conversion will be the JSON null value. In addition, the distinction between + * half-, single- and double-precision is lost. + * + * \par + * If enabled, the original value and original type are stored in the metadata. + * + * \par Non-native types: + * CBOR's type system is richer than JSON's, which means some data values + * cannot be represented when converted to JSON. The conversion silently turns + * them into strings: CBOR simple types become "simple(nn)" where \c nn is the + * simple type's value, with the exception of CBOR undefined, which becomes + * "undefined", while CBOR byte strings are converted to an Base16, Base64, or + * Base64url encoding + * + * \par + * If enabled, the original type is stored in the metadata. + * + * \par Presence of tags: + * JSON has no support for tagged values, so by default tags are dropped when + * converting to JSON. However, if the CborConvertObeyByteStringTags option is + * active (default), then certain known tags are honored and are used to format + * the conversion of the tagged byte string to JSON. + * + * \par + * If the CborConvertTagsToObjects option is active, then the tag and the + * tagged value are converted to a JSON object. Otherwise, if enabled, the + * last (innermost) tag is stored in the metadata. + * + * \par Non-string keys in maps: + * JSON requires all Object keys to be strings, while CBOR does not. By + * default, if a non-string key is found, the conversion fails with error + * CborErrorJsonObjectKeyNotString. If the CborConvertStringifyMapKeys option + * is active, then the conversion attempts to create a string representation + * using CborPretty. Note that the \c json2cbor tool is not able to parse this + * back to the original form. + * + * \par Duplicate keys in maps: + * Neither JSON nor CBOR allow duplicated keys, but current TinyCBOR does not + * validate that this is the case. If there are duplicated keys in the input, + * they will be repeated in the output, which many JSON tools may flag as + * invalid. In addition to that, if the CborConvertStringifyMapKeys option is + * active, it is possible that a non-string key in a CBOR map will be converted + * to a string form that is identical to another key. + * + * \par + * When metadata support is active, the conversion will add extra key-value + * pairs to the JSON output so it can store the metadata. It is possible that + * the keys for the metadata clash with existing keys in the JSON map. + */ + +extern FILE *open_memstream(char **bufptr, size_t *sizeptr); + +enum ConversionStatusFlags { + TypeWasNotNative = 0x100, /* anything but strings, boolean, null, arrays and maps */ + TypeWasTagged = 0x200, + NumberPrecisionWasLost = 0x400, + NumberWasNaN = 0x800, + NumberWasInfinite = 0x1000, + NumberWasNegative = 0x2000, /* only used with NumberWasInifite or NumberWasTooBig */ + + FinalTypeMask = 0xff +}; + +typedef struct ConversionStatus { + CborTag lastTag; + uint64_t originalNumber; + int flags; +} ConversionStatus; + +static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType type, ConversionStatus *status); + +static CborError dump_bytestring_base16(char **result, CborValue *it) +{ + static const char characters[] = "0123456789abcdef"; + size_t i; + size_t n = 0; + uint8_t *buffer; + CborError err = cbor_value_calculate_string_length(it, &n); + if (err) + return err; + + /* a Base16 (hex) output is twice as big as our buffer */ + buffer = (uint8_t *)malloc(n * 2 + 1); + if (buffer == NULL) + /* out of memory */ + return CborErrorOutOfMemory; + + *result = (char *)buffer; + + /* let cbor_value_copy_byte_string know we have an extra byte for the terminating NUL */ + ++n; + err = cbor_value_copy_byte_string(it, buffer + n - 1, &n, it); + cbor_assert(err == CborNoError); + + 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]; + } + return CborNoError; +} + +static CborError generic_dump_base64(char **result, CborValue *it, const char alphabet[65]) +{ + size_t n = 0, i; + uint8_t *buffer, *out, *in; + CborError err = cbor_value_calculate_string_length(it, &n); + if (err) + 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 *)malloc(len + 1); + if (buffer == NULL) + /* out of memory */ + return CborErrorOutOfMemory; + + out = buffer; + *result = (char *)buffer; + + /* we read our byte string at the tail end of the buffer + * so we can do an in-place conversion while iterating forwards */ + in = buffer + len - n; + + /* let cbor_value_copy_byte_string know we have an extra byte for the terminating NUL */ + ++n; + err = cbor_value_copy_byte_string(it, in, &n, it); + cbor_assert(err == CborNoError); + + uint_least32_t val = 0; + for (i = 0; n - i >= 3; i += 3) { + /* read 3 bytes x 8 bits = 24 bits */ + if (false) { +#ifdef __GNUC__ + } else if (i) { + __builtin_memcpy(&val, in + i - 1, sizeof(val)); + val = cbor_ntohl(val); +#endif + } else { + val = (in[i] << 16) | (in[i + 1] << 8) | in[i + 2]; + } + + /* write 4 chars x 6 bits = 24 bits */ + *out++ = alphabet[(val >> 18) & 0x3f]; + *out++ = alphabet[(val >> 12) & 0x3f]; + *out++ = alphabet[(val >> 6) & 0x3f]; + *out++ = alphabet[val & 0x3f]; + } + + /* maybe 1 or 2 bytes left */ + if (n - i) { + /* we can read in[i + 1] even if it's past the end of the string because + * we know (by construction) that it's a NUL byte */ +#ifdef __GNUC__ + uint16_t val16; + __builtin_memcpy(&val16, in + i, sizeof(val16)); + val = cbor_ntohs(val16); +#else + val = (in[i] << 8) | in[i + 1]; +#endif + val <<= 8; + + /* the 65th character in the alphabet is our filler: either '=' or '\0' */ + out[4] = '\0'; + out[3] = alphabet[64]; + if (n - i == 2) { + /* write the third char in 3 chars x 6 bits = 18 bits */ + out[2] = alphabet[(val >> 6) & 0x3f]; + } else { + out[2] = alphabet[64]; /* filler */ + } + out[1] = alphabet[(val >> 12) & 0x3f]; + out[0] = alphabet[(val >> 18) & 0x3f]; + } else { + out[0] = '\0'; + } + + return CborNoError; +} + +static CborError dump_bytestring_base64(char **result, CborValue *it) +{ + static const char alphabet[] = "ABCDEFGH" "IJKLMNOP" "QRSTUVWX" "YZabcdef" + "ghijklmn" "opqrstuv" "wxyz0123" "456789+/" "="; + return generic_dump_base64(result, it, alphabet); +} + +static CborError dump_bytestring_base64url(char **result, CborValue *it) +{ + static const char alphabet[] = "ABCDEFGH" "IJKLMNOP" "QRSTUVWX" "YZabcdef" + "ghijklmn" "opqrstuv" "wxyz0123" "456789-_"; + return generic_dump_base64(result, it, alphabet); +} + +static CborError add_value_metadata(FILE *out, CborType type, const ConversionStatus *status) +{ + int flags = status->flags; + if (flags & TypeWasTagged) { + /* extract the tagged type, which may be JSON native */ + type = flags & FinalTypeMask; + flags &= ~(FinalTypeMask | TypeWasTagged); + + if (fprintf(out, "\"tag\":\"%" PRIu64 "\"%s", status->lastTag, + flags & ~TypeWasTagged ? "," : "") < 0) + return CborErrorIO; + } + + if (!flags) + return CborNoError; + + /* print at least the type */ + if (fprintf(out, "\"t\":%d", type) < 0) + return CborErrorIO; + + if (flags & NumberWasNaN) + if (fprintf(out, ",\"v\":\"nan\"") < 0) + return CborErrorIO; + if (flags & NumberWasInfinite) + if (fprintf(out, ",\"v\":\"%sinf\"", flags & NumberWasNegative ? "-" : "") < 0) + return CborErrorIO; + if (flags & NumberPrecisionWasLost) + if (fprintf(out, ",\"v\":\"%c%" PRIx64 "\"", flags & NumberWasNegative ? '-' : '+', + status->originalNumber) < 0) + return CborErrorIO; + if (type == CborSimpleType) + if (fprintf(out, ",\"v\":%d", (int)status->originalNumber) < 0) + return CborErrorIO; + return CborNoError; +} + +static CborError find_tagged_type(CborValue *it, CborTag *tag, CborType *type) +{ + CborError err = CborNoError; + *type = cbor_value_get_type(it); + while (*type == CborTagType) { + cbor_value_get_tag(it, tag); /* can't fail */ + err = cbor_value_advance_fixed(it); + if (err) + return err; + + *type = cbor_value_get_type(it); + } + return err; +} + +static CborError tagged_value_to_json(FILE *out, CborValue *it, int flags, ConversionStatus *status) +{ + CborTag tag; + CborError err; + + if (flags & CborConvertTagsToObjects) { + cbor_value_get_tag(it, &tag); /* can't fail */ + err = cbor_value_advance_fixed(it); + if (err) + return err; + + if (fprintf(out, "{\"tag%" PRIu64 "\":", tag) < 0) + return CborErrorIO; + + CborType type = cbor_value_get_type(it); + err = value_to_json(out, it, flags, type, status); + if (err) + return err; + if (flags & CborConvertAddMetadata && status->flags) { + if (fprintf(out, ",\"tag%" PRIu64 "$cbor\":{", tag) < 0 || + add_value_metadata(out, type, status) != CborNoError || + fputc('}', out) < 0) + return CborErrorIO; + } + if (fputc('}', out) < 0) + return CborErrorIO; + status->flags = TypeWasNotNative | CborTagType; + return CborNoError; + } + + CborType type; + err = find_tagged_type(it, &status->lastTag, &type); + if (err) + return err; + tag = status->lastTag; + + /* special handling of byte strings? */ + if (type == CborByteStringType && (flags & CborConvertByteStringsToBase64Url) == 0 && + (tag == CborNegativeBignumTag || tag == CborExpectedBase16Tag || tag == CborExpectedBase64Tag)) { + char *str; + const char *pre = ""; + + if (tag == CborNegativeBignumTag) { + pre = "~"; + err = dump_bytestring_base64url(&str, it); + } else if (tag == CborExpectedBase64Tag) { + err = dump_bytestring_base64(&str, it); + } else { /* tag == CborExpectedBase16Tag */ + err = dump_bytestring_base16(&str, it); + } + if (err) + return err; + err = fprintf(out, "\"%s%s\"", pre, str) < 0 ? CborErrorIO : CborNoError; + free(str); + status->flags = TypeWasNotNative | TypeWasTagged | CborByteStringType; + return err; + } + + /* no special handling */ + err = value_to_json(out, it, flags, type, status); + status->flags |= TypeWasTagged | type; + return err; +} + +static CborError stringify_map_key(char **key, CborValue *it, int flags, CborType type) +{ + (void)flags; /* unused */ + (void)type; /* unused */ +#ifdef WITHOUT_OPEN_MEMSTREAM + (void)key; /* unused */ + (void)it; /* unused */ + return CborErrorJsonNotImplemented; +#else + size_t size; + + FILE *memstream = open_memstream(key, &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)) + return CborErrorInternalError; + return err; +#endif +} + +static CborError array_to_json(FILE *out, CborValue *it, int flags, ConversionStatus *status) +{ + const char *comma = ""; + while (!cbor_value_at_end(it)) { + if (fprintf(out, "%s", comma) < 0) + return CborErrorIO; + comma = ","; + + CborError err = value_to_json(out, it, flags, cbor_value_get_type(it), status); + if (err) + return err; + } + return CborNoError; +} + +static CborError map_to_json(FILE *out, CborValue *it, int flags, ConversionStatus *status) +{ + const char *comma = ""; + CborError err; + while (!cbor_value_at_end(it)) { + char *key; + 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); + } else if (flags & CborConvertStringifyMapKeys) { + err = stringify_map_key(&key, it, flags, keyType); + } else { + return CborErrorJsonObjectKeyNotString; + } + if (err) + return err; + + /* first, print the key */ + if (fprintf(out, "\"%s\":", key) < 0) { + free(key); + return CborErrorIO; + } + + /* then, print the value */ + CborType valueType = cbor_value_get_type(it); + err = value_to_json(out, it, flags, valueType, status); + + /* finally, print any metadata we may have */ + if (flags & CborConvertAddMetadata) { + if (!err && keyType != CborTextStringType) { + if (fprintf(out, ",\"%s$keycbordump\":true", key) < 0) + err = CborErrorIO; + } + if (!err && status->flags) { + if (fprintf(out, ",\"%s$cbor\":{", key) < 0 || + add_value_metadata(out, valueType, status) != CborNoError || + fputc('}', out) < 0) + err = CborErrorIO; + } + } + + free(key); + if (err) + return err; + } + return CborNoError; +} + +static CborError value_to_json(FILE *out, CborValue *it, int flags, CborType type, ConversionStatus *status) +{ + CborError err; + status->flags = 0; + + switch (type) { + case CborArrayType: + case CborMapType: { + /* recursive type */ + CborValue recursed; + err = cbor_value_enter_container(it, &recursed); + if (err) { + copy_current_position(it, &recursed); + return err; /* parse error */ + } + if (fputc(type == CborArrayType ? '[' : '{', out) < 0) + return CborErrorIO; + + err = (type == CborArrayType) ? + array_to_json(out, &recursed, flags, status) : + map_to_json(out, &recursed, flags, status); + if (err) { + copy_current_position(it, &recursed); + return err; /* parse error */ + } + + if (fputc(type == CborArrayType ? ']' : '}', out) < 0) + return CborErrorIO; + err = cbor_value_leave_container(it, &recursed); + if (err) + return err; /* parse error */ + + status->flags = 0; /* reset, there are never conversion errors for us */ + 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 CborByteStringType: + case CborTextStringType: { + char *str; + if (type == CborByteStringType) { + err = dump_bytestring_base64url(&str, it); + status->flags = TypeWasNotNative; + } else { + size_t n = 0; + err = cbor_value_dup_text_string(it, &str, &n, it); + } + if (err) + return err; + err = (fprintf(out, "\"%s\"", str) < 0) ? CborErrorIO : CborNoError; + free(str); + return err; + } + + case CborTagType: + return tagged_value_to_json(out, it, flags, status); + + case CborSimpleType: { + uint8_t simple_type; + cbor_value_get_simple_type(it, &simple_type); /* can't fail */ + status->flags = TypeWasNotNative; + status->originalNumber = simple_type; + if (fprintf(out, "\"simple(%" PRIu8 ")\"", simple_type) < 0) + return CborErrorIO; + 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; + if (false) { + float f; + case CborFloatType: + status->flags = TypeWasNotNative; + cbor_value_get_float(it, &f); + val = f; + } else if (false) { + uint16_t f16; + case CborHalfFloatType: +# ifndef CBOR_NO_HALF_FLOAT_TYPE + status->flags = TypeWasNotNative; + cbor_value_get_half_float(it, &f16); + val = decode_half(f16); +# else + (void)f16; + err = CborErrorUnsupportedType; + break; +# endif + } else { + cbor_value_get_double(it, &val); + } + + int r = fpclassify(val); + if (r == FP_NAN || r == FP_INFINITE) { + if (fprintf(out, "null") < 0) + return CborErrorIO; + status->flags |= r == FP_NAN ? NumberWasNaN : + NumberWasInfinite | (val < 0 ? NumberWasNegative : 0); + } else { + uint64_t ival = (uint64_t)fabs(val); + if ((double)ival == fabs(val)) { + /* 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 */ + } else { + /* this number is definitely not a 64-bit integer */ + r = fprintf(out, "%." DBL_DECIMAL_DIG_STR "g", val); + } + if (r < 0) + return CborErrorIO; + } + break; + } +#else + case CborDoubleType: + case CborFloatType: + case CborHalfFloatType: + err = CborErrorUnsupportedType; + break; +#endif /* !CBOR_NO_FLOATING_POINT */ + + case CborInvalidType: + return CborErrorUnknownType; + } + + return cbor_value_advance_fixed(it); +} + +/** + * \enum CborToJsonFlags + * The CborToJsonFlags enum contains flags that control the conversion of CBOR to JSON. + * + * \value CborConvertAddMetadata Adds metadata to facilitate restoration of the original CBOR data. + * \value CborConvertTagsToObjects Converts CBOR tags to JSON objects + * \value CborConvertIgnoreTags (default) Ignore CBOR tags, except for byte strings + * \value CborConvertObeyByteStringTags (default) Honor formatting of CBOR byte strings if so tagged + * \value CborConvertByteStringsToBase64Url Force the conversion of all CBOR byte strings to Base64url encoding, despite any tags + * \value CborConvertRequireMapStringKeys (default) Require CBOR map keys to be strings, failing the conversion if they are not + * \value CborConvertStringifyMapKeys Convert non-string keys in CBOR maps to a string form + * \value CborConvertDefaultFlags Default conversion flags. + */ + +/** + * \fn CborError cbor_value_to_json(FILE *out, const CborValue *value, int flags) + * + * Converts the current CBOR type pointed to by \a value to JSON and writes that + * to the \a out stream. If an error occurs, this function returns an error + * code similar to CborParsing. The \a flags parameter indicates one or more of + * the flags from CborToJsonFlags that control the conversion. + * + * \sa cbor_value_to_json_advance(), cbor_value_to_pretty() + */ + +/** + * Converts the current CBOR type pointed to by \a value to JSON and writes that + * to the \a out stream. If an error occurs, this function returns an error + * code similar to CborParsing. The \a flags parameter indicates one or more of + * the flags from CborToJsonFlags that control the conversion. + * + * If no error ocurred, this function advances \a value to the next element. + * + * \sa cbor_value_to_json(), cbor_value_to_pretty_advance() + */ +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); +} + +/** @} */ diff --git a/lib/cbor/tinycbor/src/cborvalidation.c b/lib/cbor/tinycbor/src/cborvalidation.c new file mode 100644 index 00000000..4c11fb13 --- /dev/null +++ b/lib/cbor/tinycbor/src/cborvalidation.c @@ -0,0 +1,657 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS 1 +#endif + +#include "cbor.h" +#include "cborinternal_p.h" +#include "compilersupport_p.h" +#include "utf8_p.h" + +#include <string.h> + +#ifndef CBOR_NO_FLOATING_POINT +# include <float.h> +# include <math.h> +#endif + + +#ifndef CBOR_PARSER_MAX_RECURSIONS +# define CBOR_PARSER_MAX_RECURSIONS 1024 +#endif + +/** + * \addtogroup CborParsing + * @{ + */ + +/** + * \enum CborValidationFlags + * The CborValidationFlags enum contains flags that control the validation of a + * CBOR stream. + * + * \value CborValidateBasic Validates only the syntactic correctedness of the stream. + * \value CborValidateCanonical Validates that the stream is in canonical format, according to + * RFC 7049 section 3.9. + * \value CborValidateStrictMode Performs strict validation, according to RFC 7049 section 3.10. + * \value CborValidateStrictest Attempt to perform the strictest validation we know of. + * + * \value CborValidateShortestIntegrals (Canonical) Validate that integral numbers and lengths are + * enconded in their shortest form possible. + * \value CborValidateShortestFloatingPoint (Canonical) Validate that floating-point numbers are encoded + * in their shortest form possible. + * \value CborValidateShortestNumbers (Canonical) Validate both integral and floating-point numbers + * are in their shortest form possible. + * \value CborValidateNoIndeterminateLength (Canonical) Validate that no string, array or map uses + * indeterminate length encoding. + * \value CborValidateMapIsSorted (Canonical & Strict mode) Validate that map keys appear in + * sorted order. + * \value CborValidateMapKeysAreUnique (Strict mode) Validate that map keys are unique. + * \value CborValidateTagUse (Strict mode) Validate that known tags are used with the + * correct types. This does not validate that the content of + * those types is syntactically correct. For example, this + * option validates that tag 1 (DateTimeString) is used with + * a Text String, but it does not validate that the string is + * a valid date/time representation. + * \value CborValidateUtf8 (Strict mode) Validate that text strings are appropriately + * encoded in UTF-8. + * \value CborValidateMapKeysAreString Validate that all map keys are text strings. + * \value CborValidateNoUndefined Validate that no elements of type "undefined" are present. + * \value CborValidateNoTags Validate that no tags are used. + * \value CborValidateFiniteFloatingPoint Validate that all floating point numbers are finite (no NaN or + * infinities are allowed). + * \value CborValidateCompleteData Validate that the stream is complete and there is no more data + * in the buffer. + * \value CborValidateNoUnknownSimpleTypesSA Validate that all Standards Action simple types are registered + * with IANA. + * \value CborValidateNoUnknownSimpleTypes Validate that all simple types used are registered with IANA. + * \value CborValidateNoUnknownTagsSA Validate that all Standard Actions tags are registered with IANA. + * \value CborValidateNoUnknownTagsSR Validate that all Standard Actions and Specification Required tags + * are registered with IANA (see below for limitations). + * \value CborValidateNoUnkonwnTags Validate that all tags are registered with IANA + * (see below for limitations). + * + * \par Simple type registry + * The CBOR specification requires that registration for use of the first 19 + * simple types must be done by way of Standards Action. The rest of the simple + * types only require a specification. The official list can be obtained from + * https://www.iana.org/assignments/cbor-simple-values/cbor-simple-values.xhtml. + * + * \par + * There are no registered simple types recognized by this release of TinyCBOR + * (beyond those defined by RFC 7049). + * + * \par Tag registry + * The CBOR specification requires that registration for use of the first 23 + * tags must be done by way of Standards Action. The next up to tag 255 only + * require a specification. Finally, all other tags can be registered on a + * first-come-first-serve basis. The official list can be ontained from + * https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml. + * + * \par + * Given the variability of this list, TinyCBOR cannot recognize all tags + * registered with IANA. Instead, the implementation only recognizes tags + * that are backed by an RFC. + * + * \par + * These are the tags known to the current TinyCBOR release: +<table> + <tr> + <th>Tag</th> + <th>Data Item</th> + <th>Semantics</th> + </tr> + <tr> + <td>0</td> + <td>UTF-8 text string</td> + <td>Standard date/time string</td> + </tr> + <tr> + <td>1</td> + <td>integer</td> + <td>Epoch-based date/time</td> + </tr> + <tr> + <td>2</td> + <td>byte string</td> + <td>Positive bignum</td> + </tr> + <tr> + <td>3</td> + <td>byte string</td> + <td>Negative bignum</td> + </tr> + <tr> + <td>4</td> + <td>array</td> + <td>Decimal fraction</td> + </tr> + <tr> + <td>5</td> + <td>array</td> + <td>Bigfloat</td> + </tr> + <tr> + <td>16</td> + <td>array</td> + <td>COSE Single Recipient Encrypted Data Object (RFC 8152)</td> + </tr> + <tr> + <td>17</td> + <td>array</td> + <td>COSE Mac w/o Recipients Object (RFC 8152)</td> + </tr> + <tr> + <td>18</td> + <td>array</td> + <td>COSE Single Signer Data Object (RFC 8162)</td> + </tr> + <tr> + <td>21</td> + <td>byte string, array, map</td> + <td>Expected conversion to base64url encoding</td> + </tr> + <tr> + <td>22</td> + <td>byte string, array, map</td> + <td>Expected conversion to base64 encoding</td> + </tr> + <tr> + <td>23</td> + <td>byte string, array, map</td> + <td>Expected conversion to base16 encoding</td> + </tr> + <tr> + <td>24</td> + <td>byte string</td> + <td>Encoded CBOR data item</td> + </tr> + <tr> + <td>32</td> + <td>UTF-8 text string</td> + <td>URI</td> + </tr> + <tr> + <td>33</td> + <td>UTF-8 text string</td> + <td>base64url</td> + </tr> + <tr> + <td>34</td> + <td>UTF-8 text string</td> + <td>base64</td> + </tr> + <tr> + <td>35</td> + <td>UTF-8 text string</td> + <td>Regular expression</td> + </tr> + <tr> + <td>36</td> + <td>UTF-8 text string</td> + <td>MIME message</td> + </tr> + <tr> + <td>96</td> + <td>array</td> + <td>COSE Encrypted Data Object (RFC 8152)</td> + </tr> + <tr> + <td>97</td> + <td>array</td> + <td>COSE MACed Data Object (RFC 8152)</td> + </tr> + <tr> + <td>98</td> + <td>array</td> + <td>COSE Signed Data Object (RFC 8152)</td> + </tr> + <tr> + <td>55799</td> + <td>any</td> + <td>Self-describe CBOR</td> + </tr> +</table> + */ + +struct KnownTagData { uint32_t tag; uint32_t types; }; +static const struct KnownTagData knownTagData[] = { + { 0, (uint32_t)CborTextStringType }, + { 1, (uint32_t)(CborIntegerType+1) }, + { 2, (uint32_t)CborByteStringType }, + { 3, (uint32_t)CborByteStringType }, + { 4, (uint32_t)CborArrayType }, + { 5, (uint32_t)CborArrayType }, + { 16, (uint32_t)CborArrayType }, + { 17, (uint32_t)CborArrayType }, + { 18, (uint32_t)CborArrayType }, + { 21, (uint32_t)CborByteStringType | ((uint32_t)CborArrayType << 8) | ((uint32_t)CborMapType << 16) }, + { 22, (uint32_t)CborByteStringType | ((uint32_t)CborArrayType << 8) | ((uint32_t)CborMapType << 16) }, + { 23, (uint32_t)CborByteStringType | ((uint32_t)CborArrayType << 8) | ((uint32_t)CborMapType << 16) }, + { 24, (uint32_t)CborByteStringType }, + { 32, (uint32_t)CborTextStringType }, + { 33, (uint32_t)CborTextStringType }, + { 34, (uint32_t)CborTextStringType }, + { 35, (uint32_t)CborTextStringType }, + { 36, (uint32_t)CborTextStringType }, + { 96, (uint32_t)CborArrayType }, + { 97, (uint32_t)CborArrayType }, + { 98, (uint32_t)CborArrayType }, + { 55799, 0U } +}; + +static CborError validate_value(CborValue *it, uint32_t flags, int recursionLeft); + +static inline CborError validate_utf8_string(const void *ptr, size_t n) +{ + const uint8_t *buffer = (const uint8_t *)ptr; + const uint8_t * const end = buffer + n; + while (buffer < end) { + uint32_t uc = get_utf8(&buffer, end); + if (uc == ~0U) + return CborErrorInvalidUtf8TextString; + } + return CborNoError; +} + +static inline CborError validate_simple_type(uint8_t simple_type, uint32_t flags) +{ + /* At current time, all known simple types are those from RFC 7049, + * which are parsed by the parser into different CBOR types. + * That means that if we've got here, the type is unknown */ + if (simple_type < 32) + return (flags & CborValidateNoUnknownSimpleTypesSA) ? CborErrorUnknownSimpleType : CborNoError; + return (flags & CborValidateNoUnknownSimpleTypes) == CborValidateNoUnknownSimpleTypes ? + CborErrorUnknownSimpleType : CborNoError; +} + +static inline CborError validate_number(const CborValue *it, CborType type, uint32_t flags) +{ + CborError err = CborNoError; + size_t bytesUsed, bytesNeeded; + uint64_t value; + + if ((flags & CborValidateShortestIntegrals) == 0) + return err; + if (type >= CborHalfFloatType && type <= CborDoubleType) + return err; /* checked elsewhere */ + + err = extract_number_checked(it, &value, &bytesUsed); + if (err) + return err; + + bytesNeeded = 0; + if (value >= Value8Bit) + ++bytesNeeded; + if (value > 0xffU) + ++bytesNeeded; + if (value > 0xffffU) + bytesNeeded += 2; + if (value > 0xffffffffU) + bytesNeeded += 4; + if (bytesNeeded < bytesUsed) + return CborErrorOverlongEncoding; + return CborNoError; +} + +static inline CborError validate_tag(CborValue *it, CborTag tag, uint32_t flags, int recursionLeft) +{ + CborType type = cbor_value_get_type(it); + const size_t knownTagCount = sizeof(knownTagData) / sizeof(knownTagData[0]); + const struct KnownTagData *tagData = knownTagData; + const struct KnownTagData * const knownTagDataEnd = knownTagData + knownTagCount; + + if (!recursionLeft) + return CborErrorNestingTooDeep; + if (flags & CborValidateNoTags) + return CborErrorExcludedType; + + /* find the tag data, if any */ + for ( ; tagData != knownTagDataEnd; ++tagData) { + if (tagData->tag < tag) + continue; + if (tagData->tag > tag) + tagData = NULL; + break; + } + if (tagData == knownTagDataEnd) + tagData = NULL; + + if (flags & CborValidateNoUnknownTags && !tagData) { + /* tag not found */ + if (flags & CborValidateNoUnknownTagsSA && tag < 24) + return CborErrorUnknownTag; + if ((flags & CborValidateNoUnknownTagsSR) == CborValidateNoUnknownTagsSR && tag < 256) + return CborErrorUnknownTag; + if ((flags & CborValidateNoUnknownTags) == CborValidateNoUnknownTags) + return CborErrorUnknownTag; + } + + if (flags & CborValidateTagUse && tagData && tagData->types) { + uint32_t allowedTypes = tagData->types; + + /* correct Integer so it's not zero */ + if (type == CborIntegerType) + type = (CborType)(type + 1); + + while (allowedTypes) { + if ((uint8_t)(allowedTypes & 0xff) == type) + break; + allowedTypes >>= 8; + } + if (!allowedTypes) + return CborErrorInappropriateTagForType; + } + + return validate_value(it, flags, recursionLeft); +} + +#ifndef CBOR_NO_FLOATING_POINT +static inline CborError validate_floating_point(CborValue *it, CborType type, uint32_t flags) +{ + CborError err; + int r; + double val; + float valf; + uint16_t valf16 = 0x7c01; /* dummy value, an infinity */ + + if (type != CborDoubleType) { + if (type == CborFloatType) { + err = cbor_value_get_float(it, &valf); + val = valf; + } else { +# ifdef CBOR_NO_HALF_FLOAT_TYPE + (void)valf16; + return CborErrorUnsupportedType; +# else + err = cbor_value_get_half_float(it, &valf16); + val = decode_half(valf16); +# endif + } + } else { + err = cbor_value_get_double(it, &val); + } + cbor_assert(err == CborNoError); /* can't fail */ + + r = fpclassify(val); + if (r == FP_NAN || r == FP_INFINITE) { + if (flags & CborValidateFiniteFloatingPoint) + return CborErrorExcludedValue; + if (flags & CborValidateShortestFloatingPoint) { + if (type == CborDoubleType) + return CborErrorOverlongEncoding; +# ifndef CBOR_NO_HALF_FLOAT_TYPE + if (type == CborFloatType) + return CborErrorOverlongEncoding; + if (r == FP_NAN && valf16 != 0x7e00) + return CborErrorImproperValue; + if (r == FP_INFINITE && valf16 != 0x7c00 && valf16 != 0xfc00) + return CborErrorImproperValue; +# endif + } + } + + if (flags & CborValidateShortestFloatingPoint && type > CborHalfFloatType) { + if (type == CborDoubleType) { + valf = (float)val; + if ((double)valf == val) + return CborErrorOverlongEncoding; + } +# ifndef CBOR_NO_HALF_FLOAT_TYPE + if (type == CborFloatType) { + valf16 = encode_half(valf); + if (valf == decode_half(valf16)) + return CborErrorOverlongEncoding; + } +# endif + } + + return CborNoError; +} +#endif + +static CborError validate_container(CborValue *it, int containerType, uint32_t flags, int recursionLeft) +{ + CborError err; + const uint8_t *previous = NULL; + const uint8_t *previous_end = NULL; + + if (!recursionLeft) + return CborErrorNestingTooDeep; + + while (!cbor_value_at_end(it)) { + const uint8_t *current = cbor_value_get_next_byte(it); + + if (containerType == CborMapType) { + if (flags & CborValidateMapKeysAreString) { + CborType type = cbor_value_get_type(it); + if (type == CborTagType) { + /* skip the tags */ + CborValue copy = *it; + err = cbor_value_skip_tag(©); + if (err) + return err; + type = cbor_value_get_type(©); + } + if (type != CborTextStringType) + return CborErrorMapKeyNotString; + } + } + + err = validate_value(it, flags, recursionLeft); + if (err) + return err; + + if (containerType != CborMapType) + continue; + + if (flags & CborValidateMapIsSorted) { + if (it->parser->flags & CborParserFlag_ExternalSource) + return CborErrorUnimplementedValidation; + if (previous) { + size_t bytelen1 = (size_t)(previous_end - previous); + size_t bytelen2 = (size_t)(cbor_value_get_next_byte(it) - current); + int r = memcmp(previous, current, bytelen1 <= bytelen2 ? bytelen1 : bytelen2); + + if (r == 0 && bytelen1 != bytelen2) + r = bytelen1 < bytelen2 ? -1 : +1; + if (r > 0) + return CborErrorMapNotSorted; + if (r == 0 && (flags & CborValidateMapKeysAreUnique) == CborValidateMapKeysAreUnique) + return CborErrorMapKeysNotUnique; + } + + previous = current; + previous_end = cbor_value_get_next_byte(it); + } + + /* map: that was the key, so get the value */ + err = validate_value(it, flags, recursionLeft); + if (err) + return err; + } + return CborNoError; +} + +static CborError validate_value(CborValue *it, uint32_t flags, int recursionLeft) +{ + CborError err; + CborType type = cbor_value_get_type(it); + + if (cbor_value_is_length_known(it)) { + err = validate_number(it, type, flags); + if (err) + return err; + } else { + if (flags & CborValidateNoIndeterminateLength) + return CborErrorUnknownLength; + } + + switch (type) { + case CborArrayType: + case CborMapType: { + /* recursive type */ + CborValue recursed; + err = cbor_value_enter_container(it, &recursed); + if (!err) + err = validate_container(&recursed, type, flags, recursionLeft - 1); + if (err) { + copy_current_position(it, &recursed); + return err; + } + err = cbor_value_leave_container(it, &recursed); + if (err) + return err; + return CborNoError; + } + + case CborIntegerType: { + uint64_t val; + err = cbor_value_get_raw_integer(it, &val); + cbor_assert(err == CborNoError); /* can't fail */ + + break; + } + + case CborByteStringType: + case CborTextStringType: { + size_t n = 0; + const void *ptr; + + err = cbor_value_begin_string_iteration(it); + if (err) + return err; + + while (1) { + CborValue next; + err = _cbor_value_get_string_chunk(it, &ptr, &n, &next); + if (!err) { + err = validate_number(it, type, flags); + if (err) + return err; + } + + *it = next; + if (err == CborErrorNoMoreStringChunks) + return cbor_value_finish_string_iteration(it); + if (err) + return err; + + if (type == CborTextStringType && flags & CborValidateUtf8) { + err = validate_utf8_string(ptr, n); + if (err) + return err; + } + } + + return CborNoError; + } + + case CborTagType: { + CborTag tag; + err = cbor_value_get_tag(it, &tag); + cbor_assert(err == CborNoError); /* can't fail */ + + err = cbor_value_advance_fixed(it); + if (err) + return err; + err = validate_tag(it, tag, flags, recursionLeft - 1); + if (err) + return err; + + return CborNoError; + } + + case CborSimpleType: { + uint8_t simple_type; + err = cbor_value_get_simple_type(it, &simple_type); + cbor_assert(err == CborNoError); /* can't fail */ + err = validate_simple_type(simple_type, flags); + if (err) + return err; + break; + } + + case CborNullType: + case CborBooleanType: + break; + + case CborUndefinedType: + if (flags & CborValidateNoUndefined) + return CborErrorExcludedType; + break; + + case CborHalfFloatType: + case CborFloatType: + case CborDoubleType: { +#ifdef CBOR_NO_FLOATING_POINT + return CborErrorUnsupportedType; +#else + err = validate_floating_point(it, type, flags); + if (err) + return err; + break; +#endif /* !CBOR_NO_FLOATING_POINT */ + } + + case CborInvalidType: + return CborErrorUnknownType; + } + + err = cbor_value_advance_fixed(it); + return err; +} + +/** + * Performs a full validation, controlled by the \a flags options, of the CBOR + * stream pointed by \a it and returns the error it found. If no error was + * found, it returns CborNoError and the application can iterate over the items + * with certainty that no errors will appear during parsing. + * + * If \a flags is CborValidateBasic, the result should be the same as + * cbor_value_validate_basic(). + * + * This function has the same timing and memory requirements as + * cbor_value_advance() and cbor_value_validate_basic(). + * + * \sa CborValidationFlags, cbor_value_validate_basic(), cbor_value_advance() + */ +CborError cbor_value_validate(const CborValue *it, uint32_t flags) +{ + CborValue value = *it; + CborError err = validate_value(&value, flags, CBOR_PARSER_MAX_RECURSIONS); + if (err) + return err; + if (flags & CborValidateCompleteData && can_read_bytes(it, 1)) + return CborErrorGarbageAtEnd; + return CborNoError; +} + +/** + * @} + */ diff --git a/lib/cbor/tinycbor/src/compilersupport_p.h b/lib/cbor/tinycbor/src/compilersupport_p.h new file mode 100644 index 00000000..08798016 --- /dev/null +++ b/lib/cbor/tinycbor/src/compilersupport_p.h @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 COMPILERSUPPORT_H +#define COMPILERSUPPORT_H + +#include "cbor.h" + +#ifndef _BSD_SOURCE +# define _BSD_SOURCE +#endif +#ifndef _DEFAULT_SOURCE +# define _DEFAULT_SOURCE +#endif +#ifndef assert +# include <assert.h> +#endif +#include <stddef.h> +#include <stdint.h> +#include <string.h> + +#ifndef __cplusplus +# include <stdbool.h> +#endif + +#if __STDC_VERSION__ >= 201112L || (defined(__cplusplus) && __cplusplus >= 201103L) || (defined(__cpp_static_assert) && __cpp_static_assert >= 200410) +# define cbor_static_assert(x) static_assert(x, #x) +#elif !defined(__cplusplus) && defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 406) && (__STDC_VERSION__ > 199901L) +# define cbor_static_assert(x) _Static_assert(x, #x) +#else +# define cbor_static_assert(x) ((void)sizeof(char[2*!!(x) - 1])) +#endif +#if __STDC_VERSION__ >= 199901L || defined(__cplusplus) +/* inline is a keyword */ +#else +/* use the definition from cbor.h */ +# define inline CBOR_INLINE +#endif + +#ifdef NDEBUG +# define cbor_assert(cond) do { if (!(cond)) unreachable(); } while (0) +#else +# define cbor_assert(cond) assert(cond) +#endif + +#ifndef STRINGIFY +#define STRINGIFY(x) STRINGIFY2(x) +#endif +#define STRINGIFY2(x) #x + +#if !defined(UINT32_MAX) || !defined(INT64_MAX) +/* C89? We can define UINT32_MAX portably, but not INT64_MAX */ +# error "Your system has stdint.h but that doesn't define UINT32_MAX or INT64_MAX" +#endif + +#ifndef DBL_DECIMAL_DIG +/* DBL_DECIMAL_DIG is C11 */ +# define DBL_DECIMAL_DIG 17 +#endif +#define DBL_DECIMAL_DIG_STR STRINGIFY(DBL_DECIMAL_DIG) + +#if defined(__GNUC__) && defined(__i386__) && !defined(__iamcu__) +# define CBOR_INTERNAL_API_CC __attribute__((regparm(3))) +#elif defined(_MSC_VER) && defined(_M_IX86) +# define CBOR_INTERNAL_API_CC __fastcall +#else +# define CBOR_INTERNAL_API_CC +#endif + +#ifndef __has_builtin +# define __has_builtin(x) 0 +#endif + +#if (defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 403)) || \ + (__has_builtin(__builtin_bswap64) && __has_builtin(__builtin_bswap32)) +# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define cbor_ntohll __builtin_bswap64 +# define cbor_htonll __builtin_bswap64 +# define cbor_ntohl __builtin_bswap32 +# define cbor_htonl __builtin_bswap32 +# ifdef __INTEL_COMPILER +# define cbor_ntohs _bswap16 +# define cbor_htons _bswap16 +# elif (__GNUC__ * 100 + __GNUC_MINOR__ >= 608) || __has_builtin(__builtin_bswap16) +# define cbor_ntohs __builtin_bswap16 +# define cbor_htons __builtin_bswap16 +# else +# define cbor_ntohs(x) (((uint16_t)(x) >> 8) | ((uint16_t)(x) << 8)) +# define cbor_htons cbor_ntohs +# endif +# else +# define cbor_ntohll +# define cbor_htonll +# define cbor_ntohl +# define cbor_htonl +# define cbor_ntohs +# define cbor_htons +# endif +#elif defined(__sun) +# include <sys/byteorder.h> +#elif defined(_MSC_VER) +/* MSVC, which implies Windows, which implies little-endian and sizeof(long) == 4 */ +# include <stdlib.h> +# define cbor_ntohll _byteswap_uint64 +# define cbor_htonll _byteswap_uint64 +# define cbor_ntohl _byteswap_ulong +# define cbor_htonl _byteswap_ulong +# define cbor_ntohs _byteswap_ushort +# define cbor_htons _byteswap_ushort +#endif +#ifndef cbor_ntohs +# include <arpa/inet.h> +# define cbor_ntohs ntohs +# define cbor_htons htons +#endif +#ifndef cbor_ntohl +# include <arpa/inet.h> +# define cbor_ntohl ntohl +# define cbor_htonl htonl +#endif +#ifndef cbor_ntohll +# define cbor_ntohll ntohll +# define cbor_htonll htonll +/* ntohll isn't usually defined */ +# ifndef ntohll +# if (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) || \ + (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && __BYTE_ORDER == __BIG_ENDIAN) || \ + (defined(BYTE_ORDER) && defined(BIG_ENDIAN) && BYTE_ORDER == BIG_ENDIAN) || \ + (defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN)) || (defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)) || \ + defined(__ARMEB__) || defined(__MIPSEB__) || defined(__s390__) || defined(__sparc__) +# define ntohll +# define htonll +# elif (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || \ + (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && __BYTE_ORDER == __LITTLE_ENDIAN) || \ + (defined(BYTE_ORDER) && defined(LITTLE_ENDIAN) && BYTE_ORDER == LITTLE_ENDIAN) || \ + defined(_LITTLE_ENDIAN) || defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || defined(__MIPSEL__) || \ + defined(__i386) || defined(__i386__) || defined(__x86_64) || defined(__x86_64__) || defined(__amd64) +# define ntohll(x) ((ntohl((uint32_t)(x)) * UINT64_C(0x100000000)) + (ntohl((x) >> 32))) +# define htonll ntohll +# else +# error "Unable to determine byte order!" +# endif +# endif +#endif + + +#ifdef __cplusplus +# define CONST_CAST(t, v) const_cast<t>(v) +#else +/* C-style const_cast without triggering a warning with -Wcast-qual */ +# define CONST_CAST(t, v) (t)(uintptr_t)(v) +#endif + +#ifdef __GNUC__ +#ifndef likely +# 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 + +static inline bool add_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_add_overflow(v1, v2, r); +#else + /* unsigned additions are well-defined */ + *r = v1 + v2; + return v1 > v1 + v2; +#endif +} + +#endif /* COMPILERSUPPORT_H */ + diff --git a/lib/cbor/tinycbor/src/open_memstream.c b/lib/cbor/tinycbor/src/open_memstream.c new file mode 100644 index 00000000..33653784 --- /dev/null +++ b/lib/cbor/tinycbor/src/open_memstream.c @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#define _GNU_SOURCE 1 + +#include <sys/types.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#if defined(__unix__) || defined(__APPLE__) +# include <unistd.h> +#endif +#ifdef __APPLE__ +typedef int RetType; +typedef int LenType; +#elif __linux__ +typedef ssize_t RetType; +typedef size_t LenType; +#else +# error "Cannot implement open_memstream!" +#endif + +#include "compilersupport_p.h" + +struct Buffer +{ + char **ptr; + size_t *len; + size_t alloc; +}; + +static RetType write_to_buffer(void *cookie, const char *data, LenType len) +{ + struct Buffer *b = (struct Buffer *)cookie; + char *ptr = *b->ptr; + size_t newsize; + + errno = EFBIG; + if (unlikely(add_check_overflow(*b->len, len, &newsize))) + return -1; + + if (newsize >= b->alloc) { // NB! one extra byte is needed to avoid buffer overflow at close_buffer + // make room + size_t newalloc = newsize + newsize / 2 + 1; // give 50% more room + ptr = realloc(ptr, newalloc); + if (ptr == NULL) + return -1; + b->alloc = newalloc; + *b->ptr = ptr; + } + + memcpy(ptr + *b->len, data, len); + *b->len = newsize; + return len; +} + +static int close_buffer(void *cookie) +{ + struct Buffer *b = (struct Buffer *)cookie; + if (*b->ptr) + (*b->ptr)[*b->len] = '\0'; + free(b); + return 0; +} + +FILE *open_memstream(char **bufptr, size_t *lenptr) +{ + struct Buffer *b = (struct Buffer *)malloc(sizeof(struct Buffer)); + if (b == NULL) + return NULL; + b->alloc = 0; + b->len = lenptr; + b->ptr = bufptr; + *bufptr = NULL; + *lenptr = 0; + +#ifdef __APPLE__ + return funopen(b, NULL, write_to_buffer, NULL, close_buffer); +#elif __linux__ + static const cookie_io_functions_t vtable = { + NULL, + write_to_buffer, + NULL, + close_buffer + }; + return fopencookie(b, "w", vtable); +#endif +} + diff --git a/lib/cbor/tinycbor/src/parsetags.pl b/lib/cbor/tinycbor/src/parsetags.pl new file mode 100755 index 00000000..fbb18299 --- /dev/null +++ b/lib/cbor/tinycbor/src/parsetags.pl @@ -0,0 +1,116 @@ +#!/usr/bin/perl -l +## Copyright (C) 2017 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. +## +use strict; +my $fname = shift @ARGV + or die("Usage: parsetags.pl tags.txt"); +open TAGS, "<", $fname + or die("Cannot open $fname: $!"); + +my %typedescriptions = ( + "Integer" => "integer", + "ByteString" => "byte string", + "TextString" => "UTF-8 text string", + "Array" => "array", + "Map" => "map", + "Tag" => "tag", # shouldn't happen + "Simple" => "any simple type", + "Boolean" => "boolean", + "Null" => "null", + "Undefined" => "undefined", + "HalfFloat" => "IEEE 754 half-precision floating point", + "Float" => "IEEE 754 single-precision floating point", + "Double" => "IEEE 754 double-precision floating point" +); + +my %tags; +while (<TAGS>) { + s/\s*#.*$//; + next if /^$/; + chomp; + + die("Could not parse line \"$_\"") + unless /^(\d+);(\w+);([\w,]*);(.*)$/; + $tags{$1}{id} = $2; + $tags{$1}{semantic} = $4; + my @types = split(',', $3); + $tags{$1}{types} = \@types; +} +close TAGS or die; + +my @tagnumbers = sort { $a <=> $b } keys %tags; + +print "==== HTML listing ===="; +print "<table>\n <tr>\n <th>Tag</th>\n <th>Data Item</th>\n <th>Semantics</th>\n </tr>"; +for my $n (@tagnumbers) { + print " <tr>"; + print " <td>$n</td>"; + + my @types = @{$tags{$n}{types}}; + @types = map { $typedescriptions{$_}; } @types; + unshift @types, "any" + if (scalar @types == 0); + printf " <td>%s</td>\n", join(', ', @types); + printf " <td>%s</td>\n", $tags{$n}{semantic}; + print " </td>"; +} +print "</table>"; + +print "\n==== enum listing for cbor.h ====\n"; +printf "typedef enum CborKnownTags {"; +my $comma = ""; +for my $n (@tagnumbers) { + printf "%s\n Cbor%sTag%s = %d", $comma, + $tags{$n}{id}, + ' ' x (23 - length($tags{$n}{id})), + $n; + $comma = ","; +} +print "\n} CborKnownTags;"; +print "\n/* #define the constants so we can check with #ifdef */"; +for my $n (@tagnumbers) { + printf "#define Cbor%sTag Cbor%sTag\n", $tags{$n}{id}, $tags{$n}{id}; +} + +print "\n==== search table ====\n"; +print "struct KnownTagData { uint32_t tag; uint32_t types; };"; +printf "static const struct KnownTagData knownTagData[] = {"; +$comma = ""; +for my $n (@tagnumbers) { + my @types = @{$tags{$n}{types}}; + + my $typemask; + my $shift = 0; + for my $type (@types) { + die("Too many match types for tag $n") if $shift == 32; + my $actualtype = "Cbor${type}Type"; + $actualtype = "($actualtype+1)" if $type eq "Integer"; + $typemask .= " | " if $typemask ne ""; + $typemask .= "((uint32_t)$actualtype << $shift)" if $shift; + $typemask .= "(uint32_t)$actualtype" unless $shift; + $shift += 8; + } + $typemask = "0U" if $typemask eq ""; + + printf "%s\n { %d, %s }", $comma, $n, $typemask; + $comma = ","; +} +print "\n};"; diff --git a/lib/cbor/tinycbor/src/src.pri b/lib/cbor/tinycbor/src/src.pri new file mode 100644 index 00000000..07495326 --- /dev/null +++ b/lib/cbor/tinycbor/src/src.pri @@ -0,0 +1,26 @@ +SOURCES += \ + $$PWD/cborencoder.c \ + $$PWD/cborencoder_close_container_checked.c \ + $$PWD/cborencoder_float.c \ + $$PWD/cborerrorstrings.c \ + $$PWD/cborparser.c \ + $$PWD/cborparser_dup_string.c \ + $$PWD/cborparser_float.c \ + $$PWD/cborpretty.c \ + $$PWD/cborpretty_stdio.c \ + $$PWD/cbortojson.c \ + $$PWD/cborvalidation.c \ + +HEADERS += \ + $$PWD/cbor.h \ + $$PWD/cborinternal_p.h \ + $$PWD/cborjson.h \ + $$PWD/compilersupport_p.h \ + $$PWD/tinycbor-version.h \ + $$PWD/utf8_p.h \ + + +QMAKE_CFLAGS *= $$QMAKE_CFLAGS_SPLIT_SECTIONS +QMAKE_LFLAGS *= $$QMAKE_LFLAGS_GCSECTIONS +INCLUDEPATH += $$PWD +CONFIG(release, debug|release): DEFINES += NDEBUG diff --git a/lib/cbor/tinycbor/src/tags.txt b/lib/cbor/tinycbor/src/tags.txt new file mode 100644 index 00000000..ef78cfb5 --- /dev/null +++ b/lib/cbor/tinycbor/src/tags.txt @@ -0,0 +1,23 @@ +# Tag number; Tag ID; Applicable types (comma-separated); Semantics +0;DateTimeString;TextString;Standard date/time string +1;UnixTime_t;Integer;Epoch-based date/time +2;PositiveBignum;ByteString;Positive bignum +3;NegativeBignum;ByteString;Negative bignum +4;Decimal;Array;Decimal fraction +5;Bigfloat;Array;Bigfloat +16;COSE_Encrypt0;Array;COSE Single Recipient Encrypted Data Object (RFC 8152) +17;COSE_Mac0;Array;COSE Mac w/o Recipients Object (RFC 8152) +18;COSE_Sign1;Array;COSE Single Signer Data Object (RFC 8162) +21;ExpectedBase64url;ByteString,Array,Map;Expected conversion to base64url encoding +22;ExpectedBase64;ByteString,Array,Map;Expected conversion to base64 encoding +23;ExpectedBase16;ByteString,Array,Map;Expected conversion to base16 encoding +24;EncodedCbor;ByteString;Encoded CBOR data item +32;Url;TextString;URI +33;Base64url;TextString;base64url +34;Base64;TextString;base64 +35;RegularExpression;TextString;Regular expression +36;MimeMessage;TextString;MIME message +96;COSE_Encrypt;Array;COSE Encrypted Data Object (RFC 8152) +97;COSE_Mac;Array;COSE MACed Data Object (RFC 8152) +98;COSE_Sign;Array;COSE Signed Data Object (RFC 8152) +55799;Signature;;Self-describe CBOR diff --git a/lib/cbor/tinycbor/src/tinycbor-version.h b/lib/cbor/tinycbor/src/tinycbor-version.h new file mode 100644 index 00000000..c26560cc --- /dev/null +++ b/lib/cbor/tinycbor/src/tinycbor-version.h @@ -0,0 +1,3 @@ +#define TINYCBOR_VERSION_MAJOR 0 +#define TINYCBOR_VERSION_MINOR 6 +#define TINYCBOR_VERSION_PATCH 0 diff --git a/lib/cbor/tinycbor/src/tinycbor.pro b/lib/cbor/tinycbor/src/tinycbor.pro new file mode 100644 index 00000000..2ba508a6 --- /dev/null +++ b/lib/cbor/tinycbor/src/tinycbor.pro @@ -0,0 +1,10 @@ +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/lib/cbor/tinycbor/src/utf8_p.h b/lib/cbor/tinycbor/src/utf8_p.h new file mode 100644 index 00000000..ca438350 --- /dev/null +++ b/lib/cbor/tinycbor/src/utf8_p.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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 CBOR_UTF8_H +#define CBOR_UTF8_H + +#include "compilersupport_p.h" + +#include <stdint.h> + +static inline uint32_t get_utf8(const uint8_t **buffer, const uint8_t *end) +{ + int charsNeeded; + uint32_t uc, min_uc; + uint8_t b; + ptrdiff_t n = end - *buffer; + if (n == 0) + return ~0U; + + uc = *(*buffer)++; + if (uc < 0x80) { + /* single-byte UTF-8 */ + return uc; + } + + /* multi-byte UTF-8, decode it */ + if (unlikely(uc <= 0xC1)) + return ~0U; + if (uc < 0xE0) { + /* two-byte UTF-8 */ + charsNeeded = 2; + min_uc = 0x80; + uc &= 0x1f; + } else if (uc < 0xF0) { + /* three-byte UTF-8 */ + charsNeeded = 3; + min_uc = 0x800; + uc &= 0x0f; + } else if (uc < 0xF5) { + /* four-byte UTF-8 */ + charsNeeded = 4; + min_uc = 0x10000; + uc &= 0x07; + } else { + return ~0U; + } + + if (n < charsNeeded) + return ~0U; + + /* first continuation character */ + b = *(*buffer)++; + if ((b & 0xc0) != 0x80) + return ~0U; + uc <<= 6; + uc |= b & 0x3f; + + if (charsNeeded > 2) { + /* second continuation character */ + b = *(*buffer)++; + if ((b & 0xc0) != 0x80) + return ~0U; + uc <<= 6; + uc |= b & 0x3f; + + if (charsNeeded > 3) { + /* third continuation character */ + b = *(*buffer)++; + if ((b & 0xc0) != 0x80) + return ~0U; + uc <<= 6; + uc |= b & 0x3f; + } + } + + /* overlong sequence? surrogate pair? out or range? */ + if (uc < min_uc || uc - 0xd800U < 2048U || uc > 0x10ffff) + return ~0U; + + return uc; +} + +#endif /* CBOR_UTF8_H */ diff --git a/lib/cbor/tinycbor/tests/.gitignore b/lib/cbor/tinycbor/tests/.gitignore new file mode 100644 index 00000000..e65577d2 --- /dev/null +++ b/lib/cbor/tinycbor/tests/.gitignore @@ -0,0 +1,15 @@ +Makefile +debug +moc_predefs.h +release +target_wrapper.* + +# The executables +cpp/cpp +cpp/cpp.exe +encoder/encoder +encoder/encoder.exe +parser/parser +parser/parser.exe +tojson/tojson +tojson/tojson.exe diff --git a/lib/cbor/tinycbor/tests/c90/c90.pro b/lib/cbor/tinycbor/tests/c90/c90.pro new file mode 100644 index 00000000..59166b4e --- /dev/null +++ b/lib/cbor/tinycbor/tests/c90/c90.pro @@ -0,0 +1,7 @@ +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/lib/cbor/tinycbor/tests/c90/tst_c90.c b/lib/cbor/tinycbor/tests/c90/tst_c90.c new file mode 100644 index 00000000..edb49abd --- /dev/null +++ b/lib/cbor/tinycbor/tests/c90/tst_c90.c @@ -0,0 +1,30 @@ +/**************************************************************************** +** +** Copyright (C) 2018 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. +** +****************************************************************************/ + +#include "cbor.h" + +int main() +{ + return 0; +} diff --git a/lib/cbor/tinycbor/tests/cpp/cpp.pro b/lib/cbor/tinycbor/tests/cpp/cpp.pro new file mode 100644 index 00000000..5e9e6089 --- /dev/null +++ b/lib/cbor/tinycbor/tests/cpp/cpp.pro @@ -0,0 +1,5 @@ +CONFIG += testcase parallel_test c++11 +QT = core testlib + +SOURCES = tst_cpp.cpp +INCLUDEPATH += ../../src diff --git a/lib/cbor/tinycbor/tests/cpp/tst_cpp.cpp b/lib/cbor/tinycbor/tests/cpp/tst_cpp.cpp new file mode 100644 index 00000000..7f0eefc0 --- /dev/null +++ b/lib/cbor/tinycbor/tests/cpp/tst_cpp.cpp @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2017 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. +** +****************************************************************************/ + +#include "../../src/cborencoder.c" +#include "../../src/cborencoder_float.c" +#include "../../src/cborerrorstrings.c" +#include "../../src/cborparser.c" +#include "../../src/cborparser_dup_string.c" +#include "../../src/cborparser_float.c" +#include "../../src/cborvalidation.c" + +#include <QtTest> + +// This is a compilation-only test. +// All it does is verify that the four source files above +// compile as C++ without errors. +class tst_Cpp : public QObject +{ + Q_OBJECT +}; + +QTEST_MAIN(tst_Cpp) +#include "tst_cpp.moc" diff --git a/lib/cbor/tinycbor/tests/encoder/data.cpp b/lib/cbor/tinycbor/tests/encoder/data.cpp new file mode 100644 index 00000000..6dca49d4 --- /dev/null +++ b/lib/cbor/tinycbor/tests/encoder/data.cpp @@ -0,0 +1,346 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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. +** +****************************************************************************/ + +#include <QtTest> + +static float myNaNf() +{ + uint32_t v = 0x7fc00000; + float f; + memcpy(&f, &v, sizeof(f)); + Q_ASSERT(qIsNaN(f)); + return f; +} + +static float myInff() +{ + uint32_t v = 0x7f800000; + float f; + memcpy(&f, &v, sizeof(f)); + Q_ASSERT(qIsInf(f)); + return f; +} + +static float myNInff() +{ + uint32_t v = 0xff800000; + float f; + memcpy(&f, &v, sizeof(f)); + Q_ASSERT(qIsInf(f)); + return f; +} + +static double myNaN() +{ + uint64_t v = UINT64_C(0x7ff8000000000000); + double f; + memcpy(&f, &v, sizeof(f)); + Q_ASSERT(qIsNaN(f)); + return f; +} + +static double myInf() +{ + uint64_t v = UINT64_C(0x7ff0000000000000); + double f; + memcpy(&f, &v, sizeof(f)); + Q_ASSERT(qIsInf(f)); + return f; +} + +static double myNInf() +{ + uint64_t v = UINT64_C(0xfff0000000000000); + double f; + memcpy(&f, &v, sizeof(f)); + Q_ASSERT(qIsInf(f)); + return f; +} + +template <size_t N> QByteArray raw(const char (&data)[N]) +{ + return QByteArray::fromRawData(data, N - 1); +} + +struct NegativeInteger { quint64 abs; }; +Q_DECLARE_METATYPE(NegativeInteger) + +struct SimpleType { uint8_t type; }; +Q_DECLARE_METATYPE(SimpleType) + +struct Float16Standin { uint16_t val; }; +Q_DECLARE_METATYPE(Float16Standin) + +struct Tag { CborTag tag; QVariant tagged; }; +Q_DECLARE_METATYPE(Tag) + +template <typename... Args> +QVariant make_list(const Args &... args) +{ + return QVariantList{args...}; +} + +typedef QVector<QPair<QVariant, QVariant>> Map; +Q_DECLARE_METATYPE(Map) +QVariant make_map(const std::initializer_list<QPair<QVariant, QVariant>> &list) +{ + return QVariant::fromValue(Map(list)); +} + +struct IndeterminateLengthArray : QVariantList { using QVariantList::QVariantList; }; +struct IndeterminateLengthMap : Map { using Map::Map; }; +Q_DECLARE_METATYPE(IndeterminateLengthArray) +Q_DECLARE_METATYPE(IndeterminateLengthMap) + +QVariant make_ilarray(const std::initializer_list<QVariant> &list) +{ + return QVariant::fromValue(IndeterminateLengthArray(list)); +} + +QVariant make_ilmap(const std::initializer_list<QPair<QVariant, QVariant>> &list) +{ + return QVariant::fromValue(IndeterminateLengthMap(list)); +} + +void addHalfFloat() +{ + QTest::addColumn<QByteArray>("output"); + QTest::addColumn<unsigned>("rawInput"); + QTest::addColumn<double>("floatInput"); + + QTest::newRow("+0") << raw("\x00\x00") << 0U << 0.0; + QTest::newRow("-0") << raw("\x80\x00") << 0x8000U << 0.0; + + QTest::newRow("min.denorm") << raw("\x00\x01") << 1U << ldexp(1.0, -14) * ldexp(1.0, -10); + QTest::newRow("-min.denorm") << raw("\x80\x01") << 0x8001U << ldexp(-1.0, -14) * ldexp(1.0, -10); + + QTest::newRow("max.denorm") << raw("\x03\xff") << 0x03ffU << ldexp(1.0, -14) * (1.0 - ldexp(1.0, -10)); + QTest::newRow("-max.denorm") << raw("\x83\xff") << 0x83ffU << ldexp(-1.0, -14) * (1.0 - ldexp(1.0, -10)); + + QTest::newRow("min.norm") << raw("\x04\x00") << 0x0400U << ldexp(1.0, -14); + QTest::newRow("-min.norm") << raw("\x84\x00") << 0x8400U << ldexp(-1.0, -14); + + QTest::newRow("1.0") << raw("\x3c\x00") << 0x3c00U << 1.0; + QTest::newRow("-1.0") << raw("\xbc\x00") << 0xbc00U << -1.0; + + QTest::newRow("1.5") << raw("\x3e\x00") << 0x3e00U << 1.5; + QTest::newRow("-1.5") << raw("\xbe\x00") << 0xbe00U << -1.5; + + QTest::newRow("max") << raw("\x7b\xff") << 0x7bffU << ldexp(1.0, 15) * (2.0 - ldexp(1.0, -10)); + QTest::newRow("-max") << raw("\xfb\xff") << 0xfbffU << ldexp(-1.0, 15) * (2.0 - ldexp(1.0, -10)); + + QTest::newRow("inf") << raw("\x7c\x00") << 0x7c00U << myInf(); + QTest::newRow("-inf") << raw("\xfc\x00") << 0xfc00U << myNInf(); + + QTest::newRow("nan1") << raw("\x7c\x01") << 0x7c01U << myNaN(); + QTest::newRow("nan2") << raw("\xfc\x01") << 0xfc01U << myNaN(); + QTest::newRow("nan3") << raw("\x7e\x00") << 0x7e00U << myNaN(); + QTest::newRow("nan4") << raw("\xfe\x00") << 0xfe00U << myNaN(); +} + +void addColumns() +{ + QTest::addColumn<QByteArray>("output"); + QTest::addColumn<QVariant>("input"); +} + +void addFixedData() +{ + // unsigned integers + QTest::newRow("0U") << raw("\x00") << QVariant(0U); + QTest::newRow("1U") << raw("\x01") << QVariant(1U); + QTest::newRow("10U") << raw("\x0a") << QVariant(10U); + QTest::newRow("23U") << raw("\x17") << QVariant(23U); + QTest::newRow("24U") << raw("\x18\x18") << QVariant(24U); + QTest::newRow("255U") << raw("\x18\xff") << QVariant(255U); + QTest::newRow("256U") << raw("\x19\x01\x00") << QVariant(256U); + QTest::newRow("65535U") << raw("\x19\xff\xff") << QVariant(65535U); + QTest::newRow("65536U") << raw("\x1a\0\1\x00\x00") << QVariant(65536U); + QTest::newRow("4294967295U") << raw("\x1a\xff\xff\xff\xff") << QVariant(4294967295U); + QTest::newRow("4294967296U") << raw("\x1b\0\0\0\1\0\0\0\0") << QVariant(Q_UINT64_C(4294967296)); + QTest::newRow("UINT64_MAX") << raw("\x1b" "\xff\xff\xff\xff" "\xff\xff\xff\xff") + << QVariant(std::numeric_limits<quint64>::max()); + + // signed integers containing non-negative numbers + QTest::newRow("0") << raw("\x00") << QVariant(0); + QTest::newRow("1") << raw("\x01") << QVariant(1); + QTest::newRow("10") << raw("\x0a") << QVariant(10); + QTest::newRow("23") << raw("\x17") << QVariant(23); + QTest::newRow("24") << raw("\x18\x18") << QVariant(24); + QTest::newRow("255") << raw("\x18\xff") << QVariant(255); + QTest::newRow("256") << raw("\x19\x01\x00") << QVariant(256); + QTest::newRow("65535") << raw("\x19\xff\xff") << QVariant(65535); + QTest::newRow("65536") << raw("\x1a\0\1\x00\x00") << QVariant(65536); + QTest::newRow("4294967295") << raw("\x1a\xff\xff\xff\xff") << QVariant(Q_INT64_C(4294967295)); + QTest::newRow("4294967296") << raw("\x1b\0\0\0\1\0\0\0\0") << QVariant(Q_INT64_C(4294967296)); + + // signed integers containing negative numbers + QTest::newRow("-1") << raw("\x20") << QVariant(-1); + QTest::newRow("-2") << raw("\x21") << QVariant(-2); + QTest::newRow("-24") << raw("\x37") << QVariant(-24); + QTest::newRow("-25") << raw("\x38\x18") << QVariant(-25); + QTest::newRow("-UINT8_MAX") << raw("\x38\xff") << QVariant(-256); + QTest::newRow("-UINT8_MAX-1") << raw("\x39\x01\x00") << QVariant(-257); + QTest::newRow("-UINT16_MAX") << raw("\x39\xff\xff") << QVariant(-65536); + QTest::newRow("-UINT16_MAX-1") << raw("\x3a\0\1\x00\x00") << QVariant(-65537); + QTest::newRow("-UINT32_MAX") << raw("\x3a\xff\xff\xff\xff") << QVariant(Q_INT64_C(-4294967296)); + QTest::newRow("-UINT32_MAX-1") << raw("\x3b\0\0\0\1\0\0\0\0") << QVariant(Q_INT64_C(-4294967297)); + + // negative integers + auto neg = [](quint64 v) { return QVariant::fromValue<NegativeInteger>({v}); }; + QTest::newRow("negative1") << raw("\x20") << neg(1); + QTest::newRow("negative2") << raw("\x21") << neg(2); + QTest::newRow("negative24") << raw("\x37") << neg(24); + QTest::newRow("negative25") << raw("\x38\x18") << neg(25); + QTest::newRow("negativeUINT8_MAX") << raw("\x38\xff") << neg(256); + QTest::newRow("negativeUINT8_MAX-1") << raw("\x39\x01\x00") << neg(257); + QTest::newRow("negativeUINT16_MAX") << raw("\x39\xff\xff") << neg(65536); + QTest::newRow("negativeUINT16_MAX-1") << raw("\x3a\0\1\x00\x00") << neg(65537); + QTest::newRow("negativeUINT32_MAX") << raw("\x3a\xff\xff\xff\xff") << neg(Q_UINT64_C(4294967296)); + QTest::newRow("negativeUINT32_MAX-1") << raw("\x3b\0\0\0\1\0\0\0\0") << neg(Q_UINT64_C(4294967297)); + QTest::newRow("negativeUINT64_MAX") << raw("\x3b" "\xff\xff\xff\xff" "\xff\xff\xff\xfe") + << neg(std::numeric_limits<quint64>::max()); + QTest::newRow("negativeUINT64_MAX+1") << raw("\x3b" "\xff\xff\xff\xff" "\xff\xff\xff\xff") << neg(0); + + QTest::newRow("simple0") << raw("\xe0") << QVariant::fromValue(SimpleType{0}); + QTest::newRow("simple19") << raw("\xf3") << QVariant::fromValue(SimpleType{19}); + QTest::newRow("false") << raw("\xf4") << QVariant(false); + QTest::newRow("true") << raw("\xf5") << QVariant(true); + QTest::newRow("null") << raw("\xf6") << QVariant::fromValue<void *>(nullptr); + QTest::newRow("undefined") << raw("\xf7") << QVariant(); + QTest::newRow("simple32") << raw("\xf8\x20") << QVariant::fromValue(SimpleType{32}); + QTest::newRow("simple255") << raw("\xf8\xff") << QVariant::fromValue(SimpleType{255}); + + // floating point +#if QT_VERSION < QT_VERSION_CHECK(5, 9, 0) + QTest::newRow("0.f16") << raw("\xf9\0\0") << QVariant::fromValue(Float16Standin{0x0000}); +#else + QTest::newRow("0.f16") << raw("\xf9\0\0") << QVariant::fromValue(qfloat16(0)); + QTest::newRow("-1.f16") << raw("\xf9\xbc\0") << QVariant::fromValue(qfloat16(-1)); + QTest::newRow("1.5f16") << raw("\xf9\x3e\0") << QVariant::fromValue(qfloat16(1.5)); + QTest::newRow("nan_f16") << raw("\xf9\x7e\0") << QVariant::fromValue<qfloat16>(myNaNf()); + QTest::newRow("-inf_f16") << raw("\xf9\xfc\0") << QVariant::fromValue<qfloat16>(myNInff()); + QTest::newRow("+inf_f16") << raw("\xf9\x7c\0") << QVariant::fromValue<qfloat16>(myInff()); +#endif + + QTest::newRow("0.f") << raw("\xfa\0\0\0\0") << QVariant::fromValue(0.f); + QTest::newRow("0.") << raw("\xfb\0\0\0\0\0\0\0\0") << QVariant(0.); + QTest::newRow("-1.f") << raw("\xfa\xbf\x80\0\0") << QVariant::fromValue(-1.f); + QTest::newRow("-1.") << raw("\xfb\xbf\xf0\0\0\0\0\0\0") << QVariant(-1.); + QTest::newRow("16777215.f") << raw("\xfa\x4b\x7f\xff\xff") << QVariant::fromValue(16777215.f); + QTest::newRow("16777215.") << raw("\xfb\x41\x6f\xff\xff\xe0\0\0\0") << QVariant::fromValue(16777215.); + QTest::newRow("-16777215.f") << raw("\xfa\xcb\x7f\xff\xff") << QVariant(-16777215.f); + QTest::newRow("-16777215.") << raw("\xfb\xc1\x6f\xff\xff\xe0\0\0\0") << QVariant::fromValue(-16777215.); + + QTest::newRow("nan_f") << raw("\xfa\x7f\xc0\0\0") << QVariant::fromValue<float>(myNaNf()); + QTest::newRow("nan") << raw("\xfb\x7f\xf8\0\0\0\0\0\0") << QVariant(myNaN()); + QTest::newRow("-inf_f") << raw("\xfa\xff\x80\0\0") << QVariant::fromValue<float>(myNInff()); + QTest::newRow("-inf") << raw("\xfb\xff\xf0\0\0\0\0\0\0") << QVariant(myNInf()); + QTest::newRow("+inf_f") << raw("\xfa\x7f\x80\0\0") << QVariant::fromValue<float>(myInff()); + QTest::newRow("+inf") << raw("\xfb\x7f\xf0\0\0\0\0\0\0") << QVariant(myInf()); +} + +void addStringsData() +{ + // byte strings + QTest::newRow("emptybytestring") << raw("\x40") << QVariant(QByteArray("")); + QTest::newRow("bytestring1") << raw("\x41 ") << QVariant(QByteArray(" ")); + QTest::newRow("bytestring1-nul") << raw("\x41\0") << QVariant(QByteArray("", 1)); + QTest::newRow("bytestring5") << raw("\x45Hello") << QVariant(QByteArray("Hello")); + QTest::newRow("bytestring24") << raw("\x58\x18""123456789012345678901234") + << QVariant(QByteArray("123456789012345678901234")); + QTest::newRow("bytestring256") << raw("\x59\1\0") + QByteArray(256, '3') + << QVariant(QByteArray(256, '3')); + + // text strings + QTest::newRow("emptytextstring") << raw("\x60") << QVariant(""); + QTest::newRow("textstring1") << raw("\x61 ") << QVariant(" "); + QTest::newRow("textstring1-nul") << raw("\x61\0") << QVariant(QString::fromLatin1("", 1)); + QTest::newRow("textstring5") << raw("\x65Hello") << QVariant("Hello"); + QTest::newRow("textstring24") << raw("\x78\x18""123456789012345678901234") + << QVariant("123456789012345678901234"); + QTest::newRow("textstring256") << raw("\x79\1\0") + QByteArray(256, '3') + << QVariant(QString(256, '3')); +} + +void addArraysAndMaps() +{ + QTest::newRow("emptyarray") << raw("\x80") << make_list(); + QTest::newRow("emptymap") << raw("\xa0") << make_map({}); + + QTest::newRow("array-0") << raw("\x81\0") << make_list(0); + QTest::newRow("array-{0-0}") << raw("\x82\0\0") << make_list(0, 0); + QTest::newRow("array-Hello") << raw("\x81\x65Hello") << make_list("Hello"); + QTest::newRow("array-array-0") << raw("\x81\x81\0") << make_list(make_list(0)); + QTest::newRow("array-array-{0-0}") << raw("\x81\x82\0\0") << make_list(make_list(0, 0)); + QTest::newRow("array-array-0-0") << raw("\x82\x81\0\0") << make_list(make_list(0),0); + QTest::newRow("array-array-Hello") << raw("\x81\x81\x65Hello") << make_list(make_list("Hello")); + + QTest::newRow("map-0:0") << raw("\xa1\0\0") << make_map({{0,0}}); + QTest::newRow("map-0:0-1:1") << raw("\xa2\0\0\1\1") << make_map({{0,0}, {1,1}}); + QTest::newRow("map-0:{map-0:0-1:1}") << raw("\xa1\0\xa2\0\0\1\1") << make_map({{0, make_map({{0,0}, {1,1}})}}); + + QTest::newRow("array-map1") << raw("\x81\xa1\0\0") << make_list(make_map({{0,0}})); + QTest::newRow("array-map2") << raw("\x82\xa1\0\0\xa1\1\1") << make_list(make_map({{0,0}}), make_map({{1,1}})); + + QTest::newRow("map-array1") << raw("\xa1\x62oc\x81\0") << make_map({{"oc", make_list(0)}}); + QTest::newRow("map-array2") << raw("\xa1\x62oc\x84\0\1\2\3") << make_map({{"oc", make_list(0, 1, 2, 3)}}); + QTest::newRow("map-array3") << raw("\xa2\x62oc\x82\0\1\2\3") << make_map({{"oc", make_list(0, 1)}, {2, 3}}); + + // indeterminate length + QTest::newRow("_emptyarray") << raw("\x9f\xff") << QVariant::fromValue(IndeterminateLengthArray{}); + QTest::newRow("_emptymap") << raw("\xbf\xff") << make_ilmap({}); + + QTest::newRow("_array-0") << raw("\x9f\0\xff") << make_ilarray({0}); + QTest::newRow("_array-{0-0}") << raw("\x9f\0\0\xff") << make_ilarray({0, 0}); + QTest::newRow("_array-Hello") << raw("\x9f\x65Hello\xff") << make_ilarray({"Hello"}); + QTest::newRow("_array-array-0") << raw("\x9f\x81\0\xff") << make_ilarray({make_list(0)}); + QTest::newRow("_array-_array-0") << raw("\x9f\x9f\0\xff\xff") << make_ilarray({make_ilarray({0})}); + QTest::newRow("_array-_array-{0-0}") << raw("\x9f\x9f\0\0\xff\xff") << make_ilarray({make_ilarray({0, 0})}); + QTest::newRow("_array-_array-0-0") << raw("\x9f\x9f\0\xff\0\xff") << make_ilarray({make_ilarray({0}),0}); + QTest::newRow("_array-_array-Hello") << raw("\x9f\x9f\x65Hello\xff\xff") << make_ilarray({make_ilarray({"Hello"})}); + + QTest::newRow("_map-0:0") << raw("\xbf\0\0\xff") << make_ilmap({{0,0}}); + QTest::newRow("_map-0:0-1:1") << raw("\xbf\0\0\1\1\xff") << make_ilmap({{0,0}, {1,1}}); + QTest::newRow("_map-0:{map-0:0-1:1}") << raw("\xbf\0\xa2\0\0\1\1\xff") << make_ilmap({{0, make_map({{0,0}, {1,1}})}}); + QTest::newRow("_map-0:{_map-0:0-1:1}") << raw("\xbf\0\xbf\0\0\1\1\xff\xff") << make_ilmap({{0, make_ilmap({{0,0}, {1,1}})}}); + + QTest::newRow("_array-map1") << raw("\x9f\xa1\0\0\xff") << make_ilarray({make_map({{0,0}})}); + QTest::newRow("_array-_map1") << raw("\x9f\xbf\0\0\xff\xff") << make_ilarray({make_ilmap({{0,0}})}); + QTest::newRow("_array-map2") << raw("\x9f\xa1\0\0\xa1\1\1\xff") << make_ilarray({make_map({{0,0}}), make_map({{1,1}})}); + QTest::newRow("_array-_map2") << raw("\x9f\xbf\0\0\xff\xbf\1\1\xff\xff") << make_ilarray({make_ilmap({{0,0}}), make_ilmap({{1,1}})}); + + QTest::newRow("_map-array1") << raw("\xbf\x62oc\x81\0\xff") << make_ilmap({{"oc", make_list(0)}}); + QTest::newRow("_map-_array1") << raw("\xbf\x62oc\x9f\0\xff\xff") << make_ilmap({{"oc", make_ilarray({0})}}); + QTest::newRow("_map-array2") << raw("\xbf\x62oc\x84\0\1\2\3\xff") << make_ilmap({{"oc", make_list(0, 1, 2, 3)}}); + QTest::newRow("_map-_array2") << raw("\xbf\x62oc\x9f\0\1\2\3\xff\xff") << make_ilmap({{"oc", make_ilarray({0, 1, 2, 3})}}); + QTest::newRow("_map-array3") << raw("\xbf\x62oc\x82\0\1\2\3\xff") << make_ilmap({{"oc", make_list(0, 1)}, {2, 3}}); + QTest::newRow("_map-_array3") << raw("\xbf\x62oc\x9f\0\1\xff\2\3\xff") << make_ilmap({{"oc", make_ilarray({0, 1})}, {2, 3}}); + + // tagged + QTest::newRow("array-1(0)") << raw("\x81\xc1\0") << make_list(QVariant::fromValue(Tag{1, 0})); + QTest::newRow("array-1(map)") << raw("\x81\xc1\xa0") << make_list(QVariant::fromValue(Tag{1, make_map({})})); + QTest::newRow("map-1(2):3(4)") << raw("\xa1\xc1\2\xc3\4") << make_map({{QVariant::fromValue(Tag{1, 2}), QVariant::fromValue(Tag{3, 4})}}); +} + diff --git a/lib/cbor/tinycbor/tests/encoder/encoder.pro b/lib/cbor/tinycbor/tests/encoder/encoder.pro new file mode 100644 index 00000000..62d9b7e4 --- /dev/null +++ b/lib/cbor/tinycbor/tests/encoder/encoder.pro @@ -0,0 +1,9 @@ +SOURCES += tst_encoder.cpp + +CONFIG += testcase parallel_test c++11 +QT = core testlib + +INCLUDEPATH += ../../src +msvc: POST_TARGETDEPS = ../../lib/tinycbor.lib +else: POST_TARGETDEPS += ../../lib/libtinycbor.a +LIBS += $$POST_TARGETDEPS diff --git a/lib/cbor/tinycbor/tests/encoder/tst_encoder.cpp b/lib/cbor/tinycbor/tests/encoder/tst_encoder.cpp new file mode 100644 index 00000000..31c29152 --- /dev/null +++ b/lib/cbor/tinycbor/tests/encoder/tst_encoder.cpp @@ -0,0 +1,609 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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. +** +****************************************************************************/ + +#include <QtTest> +#include "cbor.h" + +#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) +#include <qfloat16.h> +#endif + +Q_DECLARE_METATYPE(CborError) +namespace QTest { +template<> char *toString<CborError>(const CborError &err) +{ + return qstrdup(cbor_error_string(err)); +} +} + +class tst_Encoder : public QObject +{ + Q_OBJECT +private slots: + void floatAsHalfFloat_data(); + void floatAsHalfFloat(); + void halfFloat_data(); + void halfFloat(); + void floatAsHalfFloatCloseToZero_data(); + void floatAsHalfFloatCloseToZero(); + void floatAsHalfFloatNaN(); + void fixed_data(); + void fixed(); + void strings_data(); + void strings() { fixed(); } + void arraysAndMaps_data(); + void arraysAndMaps() { fixed(); } + void tags_data(); + void tags(); + void arrays_data() { tags_data(); } + void arrays(); + void maps_data() { tags_data(); } + void maps(); + + void writerApi_data() { tags_data(); } + void writerApi(); + void writerApiFail_data() { tags_data(); } + void writerApiFail(); + void shortBuffer_data() { tags_data(); } + void shortBuffer(); + void tooShortArrays_data() { tags_data(); } + void tooShortArrays(); + void tooShortMaps_data() { tags_data(); } + void tooShortMaps(); + void tooBigArrays_data() { tags_data(); } + void tooBigArrays(); + void tooBigMaps_data() { tags_data(); } + void tooBigMaps(); + void illegalSimpleType_data(); + void illegalSimpleType(); +}; + +#include "tst_encoder.moc" +#include "data.cpp" + +static inline bool isOomError(CborError err) +{ + return err == CborErrorOutOfMemory; +} + +CborError encodeVariant(CborEncoder *encoder, const QVariant &v) +{ + int type = v.userType(); + switch (type) { + case QVariant::Int: + case QVariant::LongLong: + return cbor_encode_int(encoder, v.toLongLong()); + + case QVariant::UInt: + case QVariant::ULongLong: + return cbor_encode_uint(encoder, v.toULongLong()); + + case QVariant::Bool: + return cbor_encode_boolean(encoder, v.toBool()); + + case QVariant::Invalid: + return cbor_encode_undefined(encoder); + + case QMetaType::VoidStar: + return cbor_encode_null(encoder); + + case QVariant::Double: + return cbor_encode_double(encoder, v.toDouble()); + + case QMetaType::Float: + return cbor_encode_float(encoder, v.toFloat()); + + case QVariant::String: { + QByteArray string = v.toString().toUtf8(); + return cbor_encode_text_string(encoder, string.constData(), string.length()); + } + + case QVariant::ByteArray: { + QByteArray string = v.toByteArray(); + return cbor_encode_byte_string(encoder, reinterpret_cast<const quint8 *>(string.constData()), string.length()); + } + + default: + if (type == qMetaTypeId<NegativeInteger>()) + return cbor_encode_negative_int(encoder, v.value<NegativeInteger>().abs); + if (type == qMetaTypeId<SimpleType>()) + return cbor_encode_simple_value(encoder, v.value<SimpleType>().type); +#if QT_VERSION < QT_VERSION_CHECK(5, 9, 0) + if (type == qMetaTypeId<Float16Standin>()) + return cbor_encode_half_float(encoder, v.constData()); +#else + if (type == qMetaTypeId<qfloat16>()) + return cbor_encode_half_float(encoder, v.constData()); +#endif + if (type == qMetaTypeId<Tag>()) { + CborError err = cbor_encode_tag(encoder, v.value<Tag>().tag); + if (err && !isOomError(err)) + return err; + return static_cast<CborError>(err | encodeVariant(encoder, v.value<Tag>().tagged)); + } + if (type == QVariant::List || type == qMetaTypeId<IndeterminateLengthArray>()) { + CborEncoder sub; + QVariantList list = v.toList(); + size_t len = list.length(); + if (type == qMetaTypeId<IndeterminateLengthArray>()) { + len = CborIndefiniteLength; + list = v.value<IndeterminateLengthArray>(); + } + CborError err = cbor_encoder_create_array(encoder, &sub, len); + if (err && !isOomError(err)) + return err; + foreach (const QVariant &v2, list) { + err = static_cast<CborError>(err | encodeVariant(&sub, v2)); + if (err && !isOomError(err)) + return err; + } + return cbor_encoder_close_container_checked(encoder, &sub); + } + if (type == qMetaTypeId<Map>() || type == qMetaTypeId<IndeterminateLengthMap>()) { + CborEncoder sub; + Map map = v.value<Map>(); + size_t len = map.length(); + if (type == qMetaTypeId<IndeterminateLengthMap>()) { + len = CborIndefiniteLength; + map = v.value<IndeterminateLengthMap>(); + } + CborError err = cbor_encoder_create_map(encoder, &sub, len); + if (err && !isOomError(err)) + return err; + for (auto pair : map) { + err = static_cast<CborError>(err | encodeVariant(&sub, pair.first)); + if (err && !isOomError(err)) + return err; + err = static_cast<CborError>(err | encodeVariant(&sub, pair.second)); + if (err && !isOomError(err)) + return err; + } + return cbor_encoder_close_container_checked(encoder, &sub); + } + } + return CborErrorUnknownType; +} + +template <typename Input, typename FnUnderTest> +void encodeOne(Input input, FnUnderTest fn_under_test, QByteArray &buffer, CborError &error) +{ + uint8_t *bufptr = reinterpret_cast<quint8 *>(buffer.data()); + CborEncoder encoder; + cbor_encoder_init(&encoder, bufptr, buffer.length(), 0); + + error = fn_under_test(&encoder, input); + + if (error == CborNoError) { + QCOMPARE(encoder.remaining, size_t(1)); + QCOMPARE(cbor_encoder_get_extra_bytes_needed(&encoder), size_t(0)); + + buffer.resize(int(cbor_encoder_get_buffer_size(&encoder, bufptr))); + } +} + +template <typename Input, typename FnUnderTest> +void compare(Input input, FnUnderTest fn_under_test, const QByteArray &output) +{ + QByteArray buffer(output.length(), Qt::Uninitialized); + CborError error; + + encodeOne(input, fn_under_test, buffer, error); + if (QTest::currentTestFailed()) + return; + + QCOMPARE(error, CborNoError); + QCOMPARE(buffer, output); +} + +void compare(const QVariant &input, const QByteArray &output) +{ + compare(input, encodeVariant, output); +} + +void tst_Encoder::floatAsHalfFloat_data() +{ + addHalfFloat(); +} + +void tst_Encoder::floatAsHalfFloat() +{ + QFETCH(unsigned, rawInput); + QFETCH(double, floatInput); + QFETCH(QByteArray, output); + + if (rawInput == 0U || rawInput == 0x8000U) + QSKIP("zero values are out of scope of this test case", QTest::SkipSingle); + + if (qIsNaN(floatInput)) + QSKIP("NaN values are out of scope of this test case", QTest::SkipSingle); + + output.prepend('\xf9'); + + compare((float)floatInput, cbor_encode_float_as_half_float, output); +} + +void tst_Encoder::halfFloat_data() +{ + addHalfFloat(); +} + +void tst_Encoder::halfFloat() +{ + QFETCH(unsigned, rawInput); + QFETCH(QByteArray, output); + + uint16_t v = (uint16_t)rawInput; + output.prepend('\xf9'); + + compare(&v, cbor_encode_half_float, output); +} + +void tst_Encoder::floatAsHalfFloatCloseToZero_data() +{ + QTest::addColumn<double>("floatInput"); + + QTest::newRow("+0") << 0.0; + QTest::newRow("-0") << -0.0; + + QTest::newRow("below min.denorm") << ldexp(1.0, -14) * ldexp(1.0, -11); + QTest::newRow("above -min.denorm") << ldexp(-1.0, -14) * ldexp(1.0, -11); +} + +void tst_Encoder::floatAsHalfFloatCloseToZero() +{ + QFETCH(double, floatInput); + + QByteArray buffer(4, Qt::Uninitialized); + CborError error; + + encodeOne((float)floatInput, cbor_encode_float_as_half_float, buffer, error); + + QCOMPARE(error, CborNoError); + + QVERIFY2( + buffer == raw("\xf9\x00\x00") || buffer == raw("\xf9\x80\x00"), + "Got value " + QByteArray::number(floatInput) + " encoded to: " + buffer); +} + +void tst_Encoder::floatAsHalfFloatNaN() +{ + QByteArray buffer(4, Qt::Uninitialized); + CborError error; + + encodeOne(myNaNf(), cbor_encode_float_as_half_float, buffer, error); + + QCOMPARE(error, CborNoError); + QCOMPARE(buffer.size(), 3); + + uint8_t ini_byte = (uint8_t)buffer[0], + exp = (uint8_t)buffer[1] & 0x7cU, + manth = (uint8_t)buffer[1] & 0x03U, + mantl = (uint8_t)buffer[2]; + + QCOMPARE((unsigned)ini_byte, 0xf9U); + QCOMPARE((unsigned)exp, 0x7cU); + QVERIFY((manth | mantl) != 0); +} + +void tst_Encoder::fixed_data() +{ + addColumns(); + addFixedData(); +} + +void tst_Encoder::fixed() +{ + QFETCH(QVariant, input); + QFETCH(QByteArray, output); + compare(input, output); +} + +void tst_Encoder::strings_data() +{ + addColumns(); + addStringsData(); +} + +void tst_Encoder::arraysAndMaps_data() +{ + addColumns(); + addArraysAndMaps(); +} + +void tst_Encoder::tags_data() +{ + addColumns(); + addFixedData(); + addStringsData(); + addArraysAndMaps(); +} + +void tst_Encoder::tags() +{ + QFETCH(QVariant, input); + QFETCH(QByteArray, output); + + compare(QVariant::fromValue(Tag{1, input}), "\xc1" + output); + if (QTest::currentTestFailed()) return; + + compare(QVariant::fromValue(Tag{24, input}), "\xd8\x18" + output); + if (QTest::currentTestFailed()) return; + + compare(QVariant::fromValue(Tag{255, input}), "\xd8\xff" + output); + if (QTest::currentTestFailed()) return; + + compare(QVariant::fromValue(Tag{256, input}), raw("\xd9\1\0") + output); + if (QTest::currentTestFailed()) return; + + compare(QVariant::fromValue(Tag{CborSignatureTag, input}), raw("\xd9\xd9\xf7") + output); + if (QTest::currentTestFailed()) return; + + compare(QVariant::fromValue(Tag{65535, input}), raw("\xd9\xff\xff") + output); + if (QTest::currentTestFailed()) return; + + compare(QVariant::fromValue(Tag{65536, input}), raw("\xda\0\1\0\0") + output); + if (QTest::currentTestFailed()) return; + + compare(QVariant::fromValue(Tag{UINT32_MAX, input}), raw("\xda\xff\xff\xff\xff") + output); + if (QTest::currentTestFailed()) return; + + compare(QVariant::fromValue(Tag{UINT32_MAX + Q_UINT64_C(1), input}), raw("\xdb\0\0\0\1\0\0\0\0") + output); + if (QTest::currentTestFailed()) return; + + compare(QVariant::fromValue(Tag{UINT64_MAX, input}), raw("\xdb\xff\xff\xff\xff\xff\xff\xff\xff") + output); + if (QTest::currentTestFailed()) return; + + // nested tags + compare(QVariant::fromValue(Tag{1, QVariant::fromValue(Tag{1, input})}), "\xc1\xc1" + output); +} + +void tst_Encoder::arrays() +{ + QFETCH(QVariant, input); + QFETCH(QByteArray, output); + + compare(make_list(input), "\x81" + output); + if (QTest::currentTestFailed()) return; + + compare(make_list(input, input), "\x82" + output + output); + if (QTest::currentTestFailed()) return; + + { + QVariantList list{input}; + QByteArray longoutput = output; + + // make a list with 32 elements (1 << 5) + for (int i = 0; i < 5; ++i) { + list += list; + longoutput += longoutput; + } + compare(list, "\x98\x20" + longoutput); + if (QTest::currentTestFailed()) return; + + // now 256 elements (32 << 3) + for (int i = 0; i < 3; ++i) { + list += list; + longoutput += longoutput; + } + compare(list, raw("\x99\1\0") + longoutput); + if (QTest::currentTestFailed()) return; + } + + // nested lists + compare(make_list(make_list(input)), "\x81\x81" + output); + if (QTest::currentTestFailed()) return; + + compare(make_list(make_list(input, input)), "\x81\x82" + output + output); + if (QTest::currentTestFailed()) return; + + compare(make_list(make_list(input), input), "\x82\x81" + output + output); + if (QTest::currentTestFailed()) return; + + compare(make_list(make_list(input), make_list(input)), "\x82\x81" + output + "\x81" + output); +} + +void tst_Encoder::maps() +{ + QFETCH(QVariant, input); + QFETCH(QByteArray, output); + + compare(make_map({{1, input}}), "\xa1\1" + output); + if (QTest::currentTestFailed()) return; + + compare(make_map({{1, input}, {input, 24}}), "\xa2\1" + output + output + "\x18\x18"); + if (QTest::currentTestFailed()) return; + + compare(make_map({{input, input}}), "\xa1" + output + output); + if (QTest::currentTestFailed()) return; + + { + Map map{{1, input}}; + QByteArray longoutput = "\1" + output; + + // make a map with 32 elements (1 << 5) + for (int i = 0; i < 5; ++i) { + map += map; + longoutput += longoutput; + } + compare(QVariant::fromValue(map), "\xb8\x20" + longoutput); + if (QTest::currentTestFailed()) return; + + // now 256 elements (32 << 3) + for (int i = 0; i < 3; ++i) { + map += map; + longoutput += longoutput; + } + compare(QVariant::fromValue(map), raw("\xb9\1\0") + longoutput); + if (QTest::currentTestFailed()) return; + } + + // nested maps + compare(make_map({{1, make_map({{2, input}})}}), "\xa1\1\xa1\2" + output); + if (QTest::currentTestFailed()) return; + + compare(make_map({{1, make_map({{2, input}, {input, false}})}}), "\xa1\1\xa2\2" + output + output + "\xf4"); + if (QTest::currentTestFailed()) return; + + compare(make_map({{1, make_map({{2, input}})}, {input, false}}), "\xa2\1\xa1\2" + output + output + "\xf4"); + if (QTest::currentTestFailed()) return; +} + +void tst_Encoder::writerApi() +{ + QFETCH(QVariant, input); + QFETCH(QByteArray, output); + + // instead of writing to a QByteArray like all other tests, write to a QBuffer + QBuffer buffer; + buffer.open(QIODevice::ReadWrite); + auto callback = [](void *token, const void *data, size_t len, CborEncoderAppendType) { + auto buffer = static_cast<QBuffer *>(token); + buffer->write(static_cast<const char *>(data), len); + return CborNoError; + }; + + CborEncoder encoder; + cbor_encoder_init_writer(&encoder, callback, &buffer); + QCOMPARE(encodeVariant(&encoder, input), CborNoError); + + buffer.reset(); + QCOMPARE(buffer.readAll(), output); +} + +void tst_Encoder::writerApiFail() +{ + QFETCH(QVariant, input); + QFETCH(QByteArray, output); + + // same as above, but we'll produce an error during writing and we expect + // it to be returned + int callCount = 0; + auto callback = [](void *token, const void *, size_t, CborEncoderAppendType) { + ++*static_cast<int *>(token); + return CborErrorIO; + }; + + CborEncoder encoder; + cbor_encoder_init_writer(&encoder, callback, &callCount); + QCOMPARE(encodeVariant(&encoder, input), CborErrorIO); + QCOMPARE(callCount, 1); +} + +void tst_Encoder::shortBuffer() +{ + QFETCH(QVariant, input); + QFETCH(QByteArray, output); + QByteArray buffer(output.length(), Qt::Uninitialized); + + for (int len = 0; len < output.length(); ++len) { + CborEncoder encoder; + cbor_encoder_init(&encoder, reinterpret_cast<quint8 *>(buffer.data()), len, 0); + QCOMPARE(encodeVariant(&encoder, input), CborErrorOutOfMemory); + QVERIFY(cbor_encoder_get_extra_bytes_needed(&encoder) != 0); + QCOMPARE(len + cbor_encoder_get_extra_bytes_needed(&encoder), size_t(output.length())); + } +} + +void tst_Encoder::tooShortArrays() +{ + QFETCH(QVariant, input); + QFETCH(QByteArray, output); + QByteArray buffer(output.length() + 1, Qt::Uninitialized); + + CborEncoder encoder, container; + cbor_encoder_init(&encoder, reinterpret_cast<quint8 *>(buffer.data()), buffer.length(), 0); + QCOMPARE(cbor_encoder_create_array(&encoder, &container, 2), CborNoError); + QCOMPARE(encodeVariant(&container, input), CborNoError); + QCOMPARE(container.remaining, size_t(2)); + QCOMPARE(cbor_encoder_close_container_checked(&encoder, &container), CborErrorTooFewItems); +} + +void tst_Encoder::tooShortMaps() +{ + QFETCH(QVariant, input); + QFETCH(QByteArray, output); + QByteArray buffer(output.length() + 1, Qt::Uninitialized); + + CborEncoder encoder, container; + cbor_encoder_init(&encoder, reinterpret_cast<quint8 *>(buffer.data()), buffer.length(), 0); + QCOMPARE(cbor_encoder_create_map(&encoder, &container, 2), CborNoError); + QCOMPARE(encodeVariant(&container, input), CborNoError); + QCOMPARE(container.remaining, size_t(4)); + QCOMPARE(cbor_encoder_close_container_checked(&encoder, &container), CborErrorTooFewItems); +} + +void tst_Encoder::tooBigArrays() +{ + QFETCH(QVariant, input); + QFETCH(QByteArray, output); + QByteArray buffer(output.length() * 2 + 1, Qt::Uninitialized); + + CborEncoder encoder, container; + cbor_encoder_init(&encoder, reinterpret_cast<quint8 *>(buffer.data()), buffer.length(), 0); + QCOMPARE(cbor_encoder_create_array(&encoder, &container, 1), CborNoError); + QCOMPARE(encodeVariant(&container, input), CborNoError); + QCOMPARE(encodeVariant(&container, input), CborNoError); + QCOMPARE(container.remaining, size_t(0)); + QCOMPARE(cbor_encoder_close_container_checked(&encoder, &container), CborErrorTooManyItems); +} + +void tst_Encoder::tooBigMaps() +{ + QFETCH(QVariant, input); + QFETCH(QByteArray, output); + QByteArray buffer(output.length() * 3 + 1, Qt::Uninitialized); + + CborEncoder encoder, container; + cbor_encoder_init(&encoder, reinterpret_cast<quint8 *>(buffer.data()), buffer.length(), 0); + QCOMPARE(cbor_encoder_create_map(&encoder, &container, 1), CborNoError); + QCOMPARE(encodeVariant(&container, input), CborNoError); + QCOMPARE(encodeVariant(&container, input), CborNoError); + QCOMPARE(encodeVariant(&container, input), CborNoError); + QCOMPARE(container.remaining, size_t(0)); + QCOMPARE(cbor_encoder_close_container_checked(&encoder, &container), CborErrorTooManyItems); +} + +void tst_Encoder::illegalSimpleType_data() +{ + QTest::addColumn<int>("type"); + QTest::newRow("half-float") << 25; + QTest::newRow("float") << 26; + QTest::newRow("double") << 27; + QTest::newRow("28") << 28; + QTest::newRow("29") << 29; + QTest::newRow("30") << 30; + QTest::newRow("31") << 31; +} + +void tst_Encoder::illegalSimpleType() +{ + QFETCH(int, type); + + quint8 buf[2]; + CborEncoder encoder; + cbor_encoder_init(&encoder, buf, sizeof(buf), 0); + QCOMPARE(cbor_encode_simple_value(&encoder, type), CborErrorIllegalSimpleType); +} + +QTEST_MAIN(tst_Encoder) diff --git a/lib/cbor/tinycbor/tests/parser/data.cpp b/lib/cbor/tinycbor/tests/parser/data.cpp new file mode 100644 index 00000000..f701a5a5 --- /dev/null +++ b/lib/cbor/tinycbor/tests/parser/data.cpp @@ -0,0 +1,607 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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. +** +****************************************************************************/ + +#include <QtTest> +#include <limits> +#include <cbor.h> + +Q_DECLARE_METATYPE(CborError) + +template <size_t N> QByteArray raw(const char (&data)[N]) +{ + return QByteArray::fromRawData(data, N - 1); +} + +void addIntegers() +{ + QTest::addColumn<QByteArray>("data"); + QTest::addColumn<quint64>("expectedRaw"); + QTest::addColumn<qint64>("expectedValue"); + QTest::addColumn<bool>("isNegative"); + QTest::addColumn<bool>("inInt64Range"); + + // unsigned integers + QTest::newRow("0") << raw("\x00") << Q_UINT64_C(0) << Q_INT64_C(0) << false << true; + QTest::newRow("1") << raw("\x01") << Q_UINT64_C(1) << Q_INT64_C(1) << false << true; + QTest::newRow("10") << raw("\x0a") << Q_UINT64_C(10) << Q_INT64_C(10) << false << true; + QTest::newRow("23") << raw("\x17") << Q_UINT64_C(23) << Q_INT64_C(23) << false << true; + QTest::newRow("24") << raw("\x18\x18") << Q_UINT64_C(24) << Q_INT64_C(24) << false << true; + QTest::newRow("UINT8_MAX") << raw("\x18\xff") << Q_UINT64_C(255) << Q_INT64_C(255) << false << true; + QTest::newRow("UINT8_MAX+1") << raw("\x19\x01\x00") << Q_UINT64_C(256) << Q_INT64_C(256) << false << true; + QTest::newRow("UINT16_MAX") << raw("\x19\xff\xff") << Q_UINT64_C(65535) << Q_INT64_C(65535) << false << true; + QTest::newRow("UINT16_MAX+1") << raw("\x1a\0\1\x00\x00") << Q_UINT64_C(65536) << Q_INT64_C(65536) << false << true; + QTest::newRow("UINT32_MAX") << raw("\x1a\xff\xff\xff\xff") << Q_UINT64_C(4294967295) << Q_INT64_C(4294967295) << false << true; + QTest::newRow("UINT32_MAX+1") << raw("\x1b\0\0\0\1\0\0\0\0") << Q_UINT64_C(4294967296) << Q_INT64_C(4294967296) << false << true; + QTest::newRow("INT64_MAX") << raw("\x1b" "\x7f\xff\xff\xff" "\xff\xff\xff\xff") + << quint64(std::numeric_limits<qint64>::max()) + << std::numeric_limits<qint64>::max() << false << true; + QTest::newRow("UINT64_MAX") << raw("\x1b" "\xff\xff\xff\xff" "\xff\xff\xff\xff") + << std::numeric_limits<quint64>::max() << qint64(-123456) << false << false; + + // negative integers + QTest::newRow("-1") << raw("\x20") << Q_UINT64_C(0) << Q_INT64_C(-1) << true << true; + QTest::newRow("-2") << raw("\x21") << Q_UINT64_C(1) << Q_INT64_C(-2) << true << true; + QTest::newRow("-24") << raw("\x37") << Q_UINT64_C(23) << Q_INT64_C(-24) << true << true; + QTest::newRow("-25") << raw("\x38\x18") << Q_UINT64_C(24) << Q_INT64_C(-25) << true << true; + QTest::newRow("-UINT8_MAX") << raw("\x38\xff") << Q_UINT64_C(255) << Q_INT64_C(-256) << true << true; + QTest::newRow("-UINT8_MAX-1") << raw("\x39\x01\x00") << Q_UINT64_C(256) << Q_INT64_C(-257) << true << true; + QTest::newRow("-UINT16_MAX") << raw("\x39\xff\xff") << Q_UINT64_C(65535) << Q_INT64_C(-65536) << true << true; + QTest::newRow("-UINT16_MAX-1") << raw("\x3a\0\1\x00\x00") << Q_UINT64_C(65536) << Q_INT64_C(-65537) << true << true; + QTest::newRow("-UINT32_MAX") << raw("\x3a\xff\xff\xff\xff") << Q_UINT64_C(4294967295) << Q_INT64_C(-4294967296) << true << true; + QTest::newRow("-UINT32_MAX-1") << raw("\x3b\0\0\0\1\0\0\0\0") << Q_UINT64_C(4294967296) << Q_INT64_C(-4294967297) << true << true; + QTest::newRow("INT64_MIN+1") << raw("\x3b\x7f\xff\xff\xff""\xff\xff\xff\xfe") + << quint64(std::numeric_limits<qint64>::max() - 1) + << (std::numeric_limits<qint64>::min() + 1) + << true << true; + QTest::newRow("INT64_MIN") << raw("\x3b\x7f\xff\xff\xff""\xff\xff\xff\xff") + << quint64(std::numeric_limits<qint64>::max()) + << std::numeric_limits<qint64>::min() + << true << true; + QTest::newRow("INT64_MIN-1") << raw("\x3b\x80\0\0\0""\0\0\0\0") << Q_UINT64_C(9223372036854775808) << qint64(-123456) << true << false; + QTest::newRow("-UINT64_MAX") << raw("\x3b" "\xff\xff\xff\xff" "\xff\xff\xff\xfe") + << (std::numeric_limits<quint64>::max() - 1) << qint64(-123456) << true << false; + QTest::newRow("-UINT64_MAX+1") << raw("\x3b" "\xff\xff\xff\xff" "\xff\xff\xff\xff") + << std::numeric_limits<quint64>::max() << qint64(-123456) << true << false; +} + +void addColumns() +{ + QTest::addColumn<QByteArray>("data"); + QTest::addColumn<QString>("expected"); + QTest::addColumn<int>("n"); // some aux integer, not added in all columns +} + +void addFixedData() +{ + // unsigned integers + QTest::newRow("0") << raw("\x00") << "0"; + QTest::newRow("1") << raw("\x01") << "1"; + QTest::newRow("10") << raw("\x0a") << "10"; + QTest::newRow("23") << raw("\x17") << "23"; + QTest::newRow("24") << raw("\x18\x18") << "24"; + QTest::newRow("UINT8_MAX") << raw("\x18\xff") << "255"; + QTest::newRow("UINT8_MAX+1") << raw("\x19\x01\x00") << "256"; + QTest::newRow("UINT16_MAX") << raw("\x19\xff\xff") << "65535"; + QTest::newRow("UINT16_MAX+1") << raw("\x1a\0\1\x00\x00") << "65536"; + QTest::newRow("UINT32_MAX") << raw("\x1a\xff\xff\xff\xff") << "4294967295"; + QTest::newRow("UINT32_MAX+1") << raw("\x1b\0\0\0\1\0\0\0\0") << "4294967296"; + QTest::newRow("UINT64_MAX") << raw("\x1b" "\xff\xff\xff\xff" "\xff\xff\xff\xff") + << QString::number(std::numeric_limits<uint64_t>::max()); + + // negative integers + QTest::newRow("-1") << raw("\x20") << "-1"; + QTest::newRow("-2") << raw("\x21") << "-2"; + QTest::newRow("-24") << raw("\x37") << "-24"; + QTest::newRow("-25") << raw("\x38\x18") << "-25"; + QTest::newRow("-UINT8_MAX") << raw("\x38\xff") << "-256"; + QTest::newRow("-UINT8_MAX-1") << raw("\x39\x01\x00") << "-257"; + QTest::newRow("-UINT16_MAX") << raw("\x39\xff\xff") << "-65536"; + QTest::newRow("-UINT16_MAX-1") << raw("\x3a\0\1\x00\x00") << "-65537"; + QTest::newRow("-UINT32_MAX") << raw("\x3a\xff\xff\xff\xff") << "-4294967296"; + QTest::newRow("-UINT32_MAX-1") << raw("\x3b\0\0\0\1\0\0\0\0") << "-4294967297"; + QTest::newRow("INT64_MIN+1") << raw("\x3b\x7f\xff\xff\xff""\xff\xff\xff\xfe") + << QString::number(std::numeric_limits<int64_t>::min() + 1); + QTest::newRow("INT64_MIN") << raw("\x3b\x7f\xff\xff\xff""\xff\xff\xff\xff") + << QString::number(std::numeric_limits<int64_t>::min()); + QTest::newRow("INT64_MIN-1") << raw("\x3b\x80\0\0\0""\0\0\0\0") << "-9223372036854775809"; + QTest::newRow("-UINT64_MAX") << raw("\x3b" "\xff\xff\xff\xff" "\xff\xff\xff\xfe") + << '-' + QString::number(std::numeric_limits<uint64_t>::max()); + QTest::newRow("-UINT64_MAX+1") << raw("\x3b" "\xff\xff\xff\xff" "\xff\xff\xff\xff") + << "-18446744073709551616"; + + // overlongs + QTest::newRow("0*1") << raw("\x18\x00") << "0_0"; + QTest::newRow("0*2") << raw("\x19\x00\x00") << "0_1"; + QTest::newRow("0*4") << raw("\x1a\0\0\0\0") << "0_2"; + QTest::newRow("0*8") << raw("\x1b\0\0\0\0\0\0\0\0") << "0_3"; + QTest::newRow("-1*1") << raw("\x38\x00") << "-1_0"; + QTest::newRow("-1*2") << raw("\x39\x00\x00") << "-1_1"; + QTest::newRow("-1*4") << raw("\x3a\0\0\0\0") << "-1_2"; + QTest::newRow("-1*8") << raw("\x3b\0\0\0\0\0\0\0\0") << "-1_3"; + + QTest::newRow("simple0") << raw("\xe0") << "simple(0)"; + QTest::newRow("simple19") << raw("\xf3") << "simple(19)"; + QTest::newRow("false") << raw("\xf4") << "false"; + QTest::newRow("true") << raw("\xf5") << "true"; + QTest::newRow("null") << raw("\xf6") << "null"; + QTest::newRow("undefined") << raw("\xf7") << "undefined"; + QTest::newRow("simple32") << raw("\xf8\x20") << "simple(32)"; + QTest::newRow("simple255") << raw("\xf8\xff") << "simple(255)"; + + // floating point + + QTest::newRow("0.f16") << raw("\xf9\0\0") << "0.f16"; + QTest::newRow("0.f") << raw("\xfa\0\0\0\0") << "0.f"; + QTest::newRow("0.") << raw("\xfb\0\0\0\0\0\0\0\0") << "0."; + QTest::newRow("-1.f16") << raw("\xf9\xbc\x00") << "-1.f16"; + QTest::newRow("-1.f") << raw("\xfa\xbf\x80\0\0") << "-1.f"; + QTest::newRow("-1.") << raw("\xfb\xbf\xf0\0\0\0\0\0\0") << "-1."; + QTest::newRow("65504.f16") << raw("\xf9\x7b\xff") << "65504.f16"; + QTest::newRow("16777215.f") << raw("\xfa\x4b\x7f\xff\xff") << "16777215.f"; + QTest::newRow("16777215.") << raw("\xfb\x41\x6f\xff\xff\xe0\0\0\0") << "16777215."; + QTest::newRow("-16777215.f") << raw("\xfa\xcb\x7f\xff\xff") << "-16777215.f"; + QTest::newRow("-16777215.") << raw("\xfb\xc1\x6f\xff\xff\xe0\0\0\0") << "-16777215."; + + QTest::newRow("0.5f16") << raw("\xf9\x38\0") << "0.5f16"; + QTest::newRow("0.5f") << raw("\xfa\x3f\0\0\0") << "0.5f"; + QTest::newRow("0.5") << raw("\xfb\x3f\xe0\0\0\0\0\0\0") << "0.5"; + QTest::newRow("2.f16^11-1") << raw("\xf9\x67\xff") << "2047.f16"; + QTest::newRow("2.f^24-1") << raw("\xfa\x4b\x7f\xff\xff") << "16777215.f"; + QTest::newRow("2.^53-1") << raw("\xfb\x43\x3f\xff\xff""\xff\xff\xff\xff") << "9007199254740991."; + QTest::newRow("2.f^64-epsilon") << raw("\xfa\x5f\x7f\xff\xff") << "18446742974197923840.f"; + QTest::newRow("2.^64-epsilon") << raw("\xfb\x43\xef\xff\xff""\xff\xff\xff\xff") << "18446744073709549568."; + QTest::newRow("2.f^64") << raw("\xfa\x5f\x80\0\0") << "1.8446744073709552e+19f"; + QTest::newRow("2.^64") << raw("\xfb\x43\xf0\0\0\0\0\0\0") << "1.8446744073709552e+19"; + + QTest::newRow("nan_f16") << raw("\xf9\x7e\x00") << "nan"; + QTest::newRow("nan_f") << raw("\xfa\x7f\xc0\0\0") << "nan"; + QTest::newRow("nan") << raw("\xfb\x7f\xf8\0\0\0\0\0\0") << "nan"; + QTest::newRow("-inf_f16") << raw("\xf9\xfc\x00") << "-inf"; + QTest::newRow("-inf_f") << raw("\xfa\xff\x80\0\0") << "-inf"; + QTest::newRow("-inf") << raw("\xfb\xff\xf0\0\0\0\0\0\0") << "-inf"; + QTest::newRow("+inf_f16") << raw("\xf9\x7c\x00") << "inf"; + QTest::newRow("+inf_f") << raw("\xfa\x7f\x80\0\0") << "inf"; + QTest::newRow("+inf") << raw("\xfb\x7f\xf0\0\0\0\0\0\0") << "inf"; + +} + +void addNonChunkedStringsData() +{ + // byte strings + QTest::newRow("emptybytestring") << raw("\x40") << "h''"; + QTest::newRow("bytestring1") << raw("\x41 ") << "h'20'"; + QTest::newRow("bytestring1-nul") << raw("\x41\0") << "h'00'"; + QTest::newRow("bytestring5") << raw("\x45Hello") << "h'48656c6c6f'"; + QTest::newRow("bytestring24") << raw("\x58\x18""123456789012345678901234") + << "h'313233343536373839303132333435363738393031323334'"; + QTest::newRow("bytestring256") << raw("\x59\1\0") + QByteArray(256, '3') + << "h'" + QString(256 * 2, '3') + '\''; + + // text strings + QTest::newRow("emptytextstring") << raw("\x60") << "\"\""; + QTest::newRow("textstring1") << raw("\x61 ") << "\" \""; + QTest::newRow("textstring1-nul") << raw("\x61\0") << "\"\\u0000\""; + QTest::newRow("textstring5") << raw("\x65Hello") << "\"Hello\""; + QTest::newRow("textstring24") << raw("\x78\x18""123456789012345678901234") + << "\"123456789012345678901234\""; + QTest::newRow("textstring256") << raw("\x79\1\0") + QByteArray(256, '3') + << '"' + QString(256, '3') + '"'; + + // some strings with UTF-8 content + // we had a bug in the pretty dumper - see issue #54 + QTest::newRow("textstringutf8-2char") << raw("\x62\xc2\xa0") << "\"\\u00A0\""; + QTest::newRow("textstringutf8-2char2") << raw("\x64\xc2\xa0\xc2\xa9") << "\"\\u00A0\\u00A9\""; + QTest::newRow("textstringutf8-3char") << raw("\x63\xe2\x88\x80") << "\"\\u2200\""; + QTest::newRow("textstringutf8-4char") << raw("\x64\xf0\x90\x88\x83") << "\"\\uD800\\uDE03\""; + + // strings with overlong length + QTest::newRow("emptybytestring*1") << raw("\x58\x00") << "h''_0"; + QTest::newRow("emptytextstring*1") << raw("\x78\x00") << "\"\"_0"; + QTest::newRow("emptybytestring*2") << raw("\x59\x00\x00") << "h''_1"; + QTest::newRow("emptytextstring*2") << raw("\x79\x00\x00") << "\"\"_1"; + QTest::newRow("emptybytestring*4") << raw("\x5a\0\0\0\0") << "h''_2"; + QTest::newRow("emptytextstring*4") << raw("\x7a\0\0\0\0") << "\"\"_2"; + QTest::newRow("emptybytestring*8") << raw("\x5b\0\0\0\0\0\0\0\0") << "h''_3"; + QTest::newRow("emptytextstring*8") << raw("\x7b\0\0\0\0\0\0\0\0") << "\"\"_3"; + QTest::newRow("bytestring5*1") << raw("\x58\x05Hello") << "h'48656c6c6f'_0"; + QTest::newRow("textstring5*1") << raw("\x78\x05Hello") << "\"Hello\"_0"; + QTest::newRow("bytestring5*2") << raw("\x59\0\5Hello") << "h'48656c6c6f'_1"; + QTest::newRow("textstring5*2") << raw("\x79\0\x05Hello") << "\"Hello\"_1"; + QTest::newRow("bytestring5*4") << raw("\x5a\0\0\0\5Hello") << "h'48656c6c6f'_2"; + QTest::newRow("textstring5*4") << raw("\x7a\0\0\0\x05Hello") << "\"Hello\"_2"; + QTest::newRow("bytestring5*8") << raw("\x5b\0\0\0\0\0\0\0\5Hello") << "h'48656c6c6f'_3"; + QTest::newRow("textstring5*8") << raw("\x7b\0\0\0\0\0\0\0\x05Hello") << "\"Hello\"_3"; + +} + +void addStringsData() +{ + addNonChunkedStringsData(); + + // strings with undefined length + QTest::newRow("_emptybytestring") << raw("\x5f\xff") << "(_ )"; + QTest::newRow("_emptytextstring") << raw("\x7f\xff") << "(_ )"; + QTest::newRow("_emptybytestring2") << raw("\x5f\x40\xff") << "(_ h'')"; + QTest::newRow("_emptytextstring2") << raw("\x7f\x60\xff") << "(_ \"\")"; + QTest::newRow("_emptybytestring2*1") << raw("\x5f\x58\x00\xff") << "(_ h''_0)"; + QTest::newRow("_emptytextstring2*1") << raw("\x7f\x78\x00\xff") << "(_ \"\"_0)"; + QTest::newRow("_emptybytestring3") << raw("\x5f\x40\x40\xff") << "(_ h'', h'')"; + QTest::newRow("_emptytextstring3") << raw("\x7f\x60\x60\xff") << "(_ \"\", \"\")"; + QTest::newRow("_emptybytestring3*2") << raw("\x5f\x59\x00\x00\x40\xff") << "(_ h''_1, h'')"; + QTest::newRow("_emptytextstring3*2") << raw("\x7f\x79\x00\x00\x60\xff") << "(_ \"\"_1, \"\")"; + QTest::newRow("_bytestring5x2") << raw("\x5f\x43Hel\x42lo\xff") << "(_ h'48656c', h'6c6f')"; + QTest::newRow("_textstring5x2") << raw("\x7f\x63Hel\x62lo\xff") << "(_ \"Hel\", \"lo\")"; + QTest::newRow("_bytestring5x2*8*4") << raw("\x5f\x5b\0\0\0\0\0\0\0\3Hel\x5a\0\0\0\2lo\xff") << "(_ h'48656c'_3, h'6c6f'_2)"; + QTest::newRow("_textstring5x2*8*4") << raw("\x7f\x7b\0\0\0\0\0\0\0\3Hel\x7a\0\0\0\2lo\xff") << "(_ \"Hel\"_3, \"lo\"_2)"; + QTest::newRow("_bytestring5x5") << raw("\x5f\x41H\x41""e\x41l\x41l\x41o\xff") << "(_ h'48', h'65', h'6c', h'6c', h'6f')"; + QTest::newRow("_textstring5x5") << raw("\x7f\x61H\x61""e\x61l\x61l\x61o\xff") << "(_ \"H\", \"e\", \"l\", \"l\", \"o\")"; + QTest::newRow("_bytestring5x6") << raw("\x5f\x41H\x41""e\x40\x41l\x41l\x41o\xff") << "(_ h'48', h'65', h'', h'6c', h'6c', h'6f')"; + QTest::newRow("_textstring5x6") << raw("\x7f\x61H\x61""e\x61l\x60\x61l\x61o\xff") << "(_ \"H\", \"e\", \"l\", \"\", \"l\", \"o\")"; +} + +void addTagsData() +{ + // since parseOne() works recursively for tags, we can't test lone tags + QTest::newRow("tag0") << raw("\xc0\x00") << "0(0)"; + QTest::newRow("tag1") << raw("\xc1\x00") << "1(0)"; + QTest::newRow("tag24") << raw("\xd8\x18\x00") << "24(0)"; + QTest::newRow("tag255") << raw("\xd8\xff\x00") << "255(0)"; + QTest::newRow("tag256") << raw("\xd9\1\0\x00") << "256(0)"; + QTest::newRow("tag65535") << raw("\xd9\xff\xff\x00") << "65535(0)"; + QTest::newRow("tag65536") << raw("\xda\0\1\0\0\x00") << "65536(0)"; + QTest::newRow("tagUINT32_MAX-1") << raw("\xda\xff\xff\xff\xff\x00") << "4294967295(0)"; + QTest::newRow("tagUINT32_MAX") << raw("\xdb\0\0\0\1\0\0\0\0\x00") << "4294967296(0)"; + QTest::newRow("tagUINT64_MAX") << raw("\xdb" "\xff\xff\xff\xff" "\xff\xff\xff\xff" "\x00") + << QString::number(std::numeric_limits<uint64_t>::max()) + "(0)"; + + // overlong tags + QTest::newRow("tag0*1") << raw("\xd8\0\x00") << "0_0(0)"; + QTest::newRow("tag0*2") << raw("\xd9\0\0\x00") << "0_1(0)"; + QTest::newRow("tag0*4") << raw("\xda\0\0\0\0\x00") << "0_2(0)"; + QTest::newRow("tag0*8") << raw("\xdb\0\0\0\0\0\0\0\0\x00") << "0_3(0)"; + + // tag other things + QTest::newRow("unixtime") << raw("\xc1\x1a\x55\x4b\xbf\xd3") << "1(1431027667)"; + QTest::newRow("rfc3339date") << raw("\xc0\x78\x19" "2015-05-07 12:41:07-07:00") + << "0(\"2015-05-07 12:41:07-07:00\")"; + QTest::newRow("tag6+false") << raw("\xc6\xf4") << "6(false)"; + QTest::newRow("tag25+true") << raw("\xd8\x19\xf5") << "25(true)"; + QTest::newRow("tag256+null") << raw("\xd9\1\0\xf6") << "256(null)"; + QTest::newRow("tag65536+simple32") << raw("\xda\0\1\0\0\xf8\x20") << "65536(simple(32))"; + QTest::newRow("float+unixtime") << raw("\xc1\xfa\x4e\xaa\x97\x80") << "1(1431027712.f)"; + QTest::newRow("double+unixtime") << raw("\xc1\xfb" "\x41\xd5\x52\xef" "\xf4\xc7\xce\xfe") + << "1(1431027667.1220088)"; +} + +void addEmptyContainersData() +{ + QTest::newRow("emptyarray") << raw("\x80") << "[]" << 0; + QTest::newRow("emptymap") << raw("\xa0") << "{}" << 0; + QTest::newRow("_emptyarray") << raw("\x9f\xff") << "[_ ]" << -1; + QTest::newRow("_emptymap") << raw("\xbf\xff") << "{_ }" << -1; +} + +void addMapMixedData() +{ + QTest::newRow("map-0-24") << raw("\xa1\0\x18\x18") << "{0: 24}" << 1; + QTest::newRow("map-0*1-24") << raw("\xa1\x18\0\x18\x18") << "{0_0: 24}" << 1; + QTest::newRow("map-0*1-24*2") << raw("\xa1\x18\0\x19\0\x18") << "{0_0: 24_1}" << 1; + QTest::newRow("map-0*4-24*2") << raw("\xa1\x1a\0\0\0\0\x19\0\x18") << "{0_2: 24_1}" << 1; + QTest::newRow("map-24-0") << raw("\xa1\x18\x18\0") << "{24: 0}" << 1; + QTest::newRow("map-24-0*1") << raw("\xa1\x18\x18\x18\0") << "{24: 0_0}" << 1; + QTest::newRow("map-255-65535") << raw("\xa1\x18\xff\x19\xff\xff") << "{255: 65535}" << 1; + + QTest::newRow("_map-0-24") << raw("\xbf\0\x18\x18\xff") << "{_ 0: 24}" << 1; + QTest::newRow("_map-0*1-24") << raw("\xbf\x18\0\x18\x18\xff") << "{_ 0_0: 24}" << 1; + QTest::newRow("_map-0*1-24*2") << raw("\xbf\x18\0\x19\0\x18\xff") << "{_ 0_0: 24_1}" << 1; + QTest::newRow("_map-0*4-24*2") << raw("\xbf\x1a\0\0\0\0\x19\0\x18\xff") << "{_ 0_2: 24_1}" << 1; + QTest::newRow("_map-24-0") << raw("\xbf\x18\x18\0\xff") << "{_ 24: 0}" << 1; + QTest::newRow("_map-24-0*1") << raw("\xbf\x18\x18\x18\0\xff") << "{_ 24: 0_0}" << 1; + QTest::newRow("_map-255-65535") << raw("\xbf\x18\xff\x19\xff\xff\xff") << "{_ 255: 65535}" << 1; +} + +void addChunkedStringData() +{ + QTest::addColumn<QByteArray>("data"); + QTest::addColumn<QString>("concatenated"); + QTest::addColumn<QStringList>("chunks"); + + // non-chunked: + QTest::newRow("emptybytestring") << raw("\x40") << "h''" << QStringList{"h''"}; + QTest::newRow("bytestring1") << raw("\x41 ") << "h'20'" << QStringList{"h'20'"}; + QTest::newRow("emptytextstring") << raw("\x60") << "\"\"" << QStringList{"\"\""}; + QTest::newRow("textstring1") << raw("\x61 ") << "\" \"" << QStringList{"\" \""}; + + // empty chunked: + QTest::newRow("_emptybytestring") << raw("\x5f\xff") << "h''" << QStringList{}; + QTest::newRow("_emptytextstring") << raw("\x7f\xff") << "\"\"" << QStringList{}; + QTest::newRow("_emptybytestring2") << raw("\x5f\x40\xff") << "h''" << QStringList{"h''"}; + QTest::newRow("_emptytextstring2") << raw("\x7f\x60\xff") << "\"\"" << QStringList{"\"\""}; + QTest::newRow("_emptybytestring3") << raw("\x5f\x40\x40\xff") << "h''" << QStringList{"h''", "h''"}; + QTest::newRow("_emptytextstring3") << raw("\x7f\x60\x60\xff") << "\"\"" << QStringList{"\"\"", "\"\""}; + + // regular chunks + QTest::newRow("_bytestring1") << raw("\x5f\x41 \xff") << "h'20'" << QStringList{"h'20'"}; + QTest::newRow("_bytestring2") << raw("\x5f\x41 \x41z\xff") << "h'207a'" << QStringList{"h'20'", "h'7a'"}; + QTest::newRow("_bytestring3") << raw("\x5f\x41 \x58\x18""123456789012345678901234\x41z\xff") + << "h'203132333435363738393031323334353637383930313233347a'" + << QStringList{"h'20'", "h'313233343536373839303132333435363738393031323334'", "h'7a'"}; + + QTest::newRow("_textstring1") << raw("\x7f\x61 \xff") << "\" \"" << QStringList{"\" \""}; + QTest::newRow("_textstring2") << raw("\x7f\x61 \x61z\xff") << "\" z\"" << QStringList{"\" \"", "\"z\""}; + QTest::newRow("_textstring3") << raw("\x7f\x61 \x78\x18""123456789012345678901234\x61z\xff") + << "\" 123456789012345678901234z\"" + << QStringList{"\" \"", "\"123456789012345678901234\"", "\"z\""}; +} + +void addValidationColumns() +{ + QTest::addColumn<QByteArray>("data"); + QTest::addColumn<int>("flags"); // future + QTest::addColumn<CborError>("expectedError"); +} + +void addValidationData(size_t minInvalid = ~size_t(0)) +{ + // illegal numbers are future extension points + QTest::newRow("illegal-number-in-unsigned-1") << raw("\x81\x1c") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-unsigned-2") << raw("\x81\x1d") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-unsigned-3") << raw("\x81\x1e") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-unsigned-4") << raw("\x81\x1f") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-negative-1") << raw("\x81\x3c") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-negative-2") << raw("\x81\x3d") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-negative-3") << raw("\x81\x3e") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-negative-4") << raw("\x81\x3f") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-bytearray-length-1") << raw("\x81\x5c") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-bytearray-length-2") << raw("\x81\x5d") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-bytearray-length-3") << raw("\x81\x5e") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-string-length-1") << raw("\x81\x7c") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-string-length-2") << raw("\x81\x7d") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-string-length-3") << raw("\x81\x7e") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-array-length-1") << raw("\x81\x9c") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-array-length-2") << raw("\x81\x9d") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-array-length-3") << raw("\x81\x9e") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-map-length-1") << raw("\x81\xbc") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-map-length-2") << raw("\x81\xbd") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-map-length-3") << raw("\x81\xbe") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-tag-1") << raw("\x81\xdc") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-tag-2") << raw("\x81\xdd") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-tag-3") << raw("\x81\xde") << 0 << CborErrorIllegalNumber; + QTest::newRow("illegal-number-in-tag-4") << raw("\x81\xdf") << 0 << CborErrorIllegalNumber; + + QTest::newRow("unsigned-too-short-1-0") << raw("\x81\x18") << 0 << CborErrorUnexpectedEOF; // requires 1 byte, 0 given + QTest::newRow("unsigned-too-short-2-0") << raw("\x81\x19") << 0 << CborErrorUnexpectedEOF; // requires 2 bytes, 0 given + QTest::newRow("unsigned-too-short-2-1") << raw("\x81\x19\x01") << 0 << CborErrorUnexpectedEOF; // etc + QTest::newRow("unsigned-too-short-4-0") << raw("\x81\x1a") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("unsigned-too-short-4-3") << raw("\x81\x1a\x01\x02\x03") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("unsigned-too-short-8-0") << raw("\x81\x1b") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("unsigned-too-short-8-7") << raw("\x81\x1b\1\2\3\4\5\6\7") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("negative-length-too-short-1-0") << raw("\x81\x38") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("negative-length-too-short-2-0") << raw("\x81\x39") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("negative-length-too-short-2-1") << raw("\x81\x39\x01") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("negative-length-too-short-4-0") << raw("\x81\x3a") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("negative-length-too-short-4-3") << raw("\x81\x3a\x01\x02\x03") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("negative-length-too-short-8-0") << raw("\x81\x3b") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("negative-length-too-short-8-7") << raw("\x81\x3b\1\2\3\4\5\6\7") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-length-too-short-1-0") << raw("\x81\x58") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-length-too-short-2-0") << raw("\x81\x59") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-length-too-short-2-1") << raw("\x81\x59\x01") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-length-too-short-4-0") << raw("\x81\x5a") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-length-too-short-4-3") << raw("\x81\x5a\x01\x02\x03") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-length-too-short-8-0") << raw("\x81\x5b") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-length-too-short-8-7") << raw("\x81\x5b\1\2\3\4\5\6\7") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-length-too-short-1-0") << raw("\x81\x78") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-length-too-short-2-0") << raw("\x81\x79") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-length-too-short-2-1") << raw("\x81\x79\x01") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-length-too-short-4-0") << raw("\x81\x7a") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-length-too-short-4-3") << raw("\x81\x7a\x01\x02\x03") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-length-too-short-8-0") << raw("\x81\x7b") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-length-too-short-8-7") << raw("\x81\x7b\1\2\3\4\5\6\7") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-length-too-short-1-0") << raw("\x81\x5f\x58") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-length-too-short-2-0") << raw("\x81\x5f\x59") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-length-too-short-2-1") << raw("\x81\x5f\x59\x01") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-length-too-short-4-0") << raw("\x81\x5f\x5a") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-length-too-short-4-3") << raw("\x81\x5f\x5a\x01\x02\x03") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-length-too-short-8-0") << raw("\x81\x5f\x5b") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-length-too-short-8-7") << raw("\x81\x5f\x5b\1\2\3\4\5\6\7") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-length-too-short-1-0") << raw("\x81\x7f\x78") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-length-too-short-2-0") << raw("\x81\x7f\x79") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-length-too-short-2-1") << raw("\x81\x7f\x79\x01") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-length-too-short-4-0") << raw("\x81\x7f\x7a") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-length-too-short-4-3") << raw("\x81\x7f\x7a\x01\x02\x03") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-length-too-short-8-0") << raw("\x81\x7f\x7b") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-length-too-short-8-7") << raw("\x81\x7f\x7b\1\2\3\4\5\6\7") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-2-length-too-short-1-0") << raw("\x81\x5f\x40\x58") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-2-length-too-short-2-0") << raw("\x81\x5f\x40\x59") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-2-length-too-short-2-1") << raw("\x81\x5f\x40\x59\x01") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-2-length-too-short-4-0") << raw("\x81\x5f\x40\x5a") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-2-length-too-short-4-3") << raw("\x81\x5f\x40\x5a\x01\x02\x03") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-2-length-too-short-8-0") << raw("\x81\x5f\x40\x5b") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-2-length-too-short-8-7") << raw("\x81\x5f\x40\x5b\1\2\3\4\5\6\7") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-2-length-too-short-1-0") << raw("\x81\x7f\x60\x78") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-2-length-too-short-2-0") << raw("\x81\x7f\x60\x79") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-2-length-too-short-2-1") << raw("\x81\x7f\x60\x79\x01") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-2-length-too-short-4-0") << raw("\x81\x7f\x60\x7a") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-2-length-too-short-4-3") << raw("\x81\x7f\x60\x7a\x01\x02\x03") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-2-length-too-short-8-0") << raw("\x81\x7f\x60\x7b") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-2-length-too-short-8-7") << raw("\x81\x7f\x60\x7b\1\2\3\4\5\6\7") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("array-length-too-short-1-0") << raw("\x81\x98") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("array-length-too-short-2-0") << raw("\x81\x99") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("array-length-too-short-2-1") << raw("\x81\x99\x01") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("array-length-too-short-4-0") << raw("\x81\x9a") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("array-length-too-short-4-3") << raw("\x81\x9a\x01\x02\x03") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("array-length-too-short-8-0") << raw("\x81\x9b") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("array-length-too-short-8-7") << raw("\x81\x9b\1\2\3\4\5\6\7") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("map-length-too-short-1-0") << raw("\x81\xb8") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("map-length-too-short-2-0") << raw("\x81\xb9") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("map-length-too-short-2-1") << raw("\x81\xb9\x01") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("map-length-too-short-4-0") << raw("\x81\xba") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("map-length-too-short-4-3") << raw("\x81\xba\x01\x02\x03") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("map-length-too-short-8-0") << raw("\x81\xbb") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("map-length-too-short-8-7") << raw("\x81\xbb\1\2\3\4\5\6\7") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("tag-too-short-1-0") << raw("\x81\xd8") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("tag-too-short-2-0") << raw("\x81\xd9") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("tag-too-short-2-1") << raw("\x81\xd9\x01") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("tag-too-short-4-0") << raw("\x81\xda") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("tag-too-short-4-3") << raw("\x81\xda\x01\x02\x03") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("tag-too-short-8-0") << raw("\x81\xdb") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("tag-too-short-8-7") << raw("\x81\xdb\1\2\3\4\5\6\7") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("fp16-too-short1") << raw("\x81\xf9") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("fp16-too-short2") << raw("\x81\xf9\x00") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("float-too-short1") << raw("\x81\xfa") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("float-too-short2") << raw("\x81\xfa\0\0\0") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("double-too-short1") << raw("\x81\xfb") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("double-too-short2") << raw("\x81\xfb\0\0\0\0\0\0\0") << 0 << CborErrorUnexpectedEOF; + + QTest::newRow("bytearray-too-short1") << raw("\x81\x42z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-too-short2") << raw("\x81\x58\x02z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-too-short3") << raw("\x81\x5a\0\0\0\2z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-too-short4") << raw("\x81\x5b\0\0\0\0\0\0\0\2z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-too-short1") << raw("\x81\x62z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-too-short2") << raw("\x81\x78\x02z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-too-short3") << raw("\x81\x7a\0\0\0\2z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-too-short4") << raw("\x81\x7b\0\0\0\0\0\0\0\2z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-too-short1") << raw("\x81\x5f\x42z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-too-short2") << raw("\x81\x5f\x58\x02z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-too-short3") << raw("\x81\x5f\x5a\0\0\0\2z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-too-short4") << raw("\x81\x5f\x5b\0\0\0\0\0\0\0\2z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-too-short1") << raw("\x81\x7f\x62z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-too-short2") << raw("\x81\x7f\x78\x02z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-too-short3") << raw("\x81\x7f\x7a\0\0\0\2z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-too-short4") << raw("\x81\x7f\x7b\0\0\0\0\0\0\0\2z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-too-short1x2") << raw("\x81\x5f\x40\x42z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-too-short2x2") << raw("\x81\x5f\x40\x58\x02z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-too-short3x2") << raw("\x81\x5f\x40\x5a\0\0\0\2z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-chunked-too-short4x2") << raw("\x81\x5f\x40\x5b\0\0\0\0\0\0\0\2z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-too-short1x2") << raw("\x81\x7f\x60\x62z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-too-short2x2") << raw("\x81\x7f\x60\x78\x02z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-too-short3x2") << raw("\x81\x7f\x60\x7a\0\0\0\2z") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-chunked-too-short4x2") << raw("\x81\x7f\x60\x7b\0\0\0\0\0\0\0\2z") << 0 << CborErrorUnexpectedEOF; + + QTest::newRow("bytearray-no-break1") << raw("\x81\x5f") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("bytearray-no-break2") << raw("\x81\x5f\x40") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-no-break1") << raw("\x81\x7f") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("string-no-break2") << raw("\x81\x7f\x60") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("array-no-break1") << raw("\x81\x9f") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("array-no-break2") << raw("\x81\x9f\0") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("map-no-break1") << raw("\x81\xbf") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("map-no-break2") << raw("\x81\xbf\0\0") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("map-break-after-key") << raw("\x81\xbf\0\xff") << 0 << CborErrorUnexpectedBreak; + QTest::newRow("map-break-after-second-key") << raw("\x81\xbf\x64xyzw\x04\x00\xff") << 0 << CborErrorUnexpectedBreak; + QTest::newRow("map-break-after-value-tag") << raw("\x81\xbf\0\xc0\xff") << 0 << CborErrorUnexpectedBreak; + QTest::newRow("map-break-after-value-tag2") << raw("\x81\xbf\0\xd8\x20\xff") << 0 << CborErrorUnexpectedBreak; + + // check for pointer additions wrapping over the limit of the address space + auto wraparoundError = [minInvalid](uint64_t encodedSize) { + if (encodedSize > minInvalid) + return CborErrorDataTooLarge; + return CborErrorUnexpectedEOF; + }; + constexpr uint64_t FourGB = UINT32_MAX + UINT64_C(1); + // on 32-bit systems, this is a -1 + QTest::newRow("bytearray-wraparound1") << raw("\x81\x5a\xff\xff\xff\xff") << 0 << wraparoundError(UINT32_MAX); + QTest::newRow("string-wraparound1") << raw("\x81\x7a\xff\xff\xff\xff") << 0 << wraparoundError(UINT32_MAX); + // on 32-bit systems, a 4GB addition could be dropped + QTest::newRow("bytearray-wraparound2") << raw("\x81\x5b\0\0\0\1\0\0\0\0") << 0 << wraparoundError(FourGB); + QTest::newRow("string-wraparound2") << raw("\x81\x7b\0\0\0\1\0\0\0\0") << 0 << wraparoundError(FourGB); + // on 64-bit systems, this could be a -1 + QTest::newRow("bytearray-wraparound3") << raw("\x81\x5b\xff\xff\xff\xff\xff\xff\xff\xff") << 0 + << wraparoundError(UINT64_MAX); + QTest::newRow("string-wraparound3") << raw("\x81\x7b\xff\xff\xff\xff\xff\xff\xff\xff") << 0 + << wraparoundError(UINT64_MAX); + + // ditto on chunks + QTest::newRow("bytearray-chunk-wraparound1") << raw("\x81\x5f\x5a\xff\xff\xff\xff") << 0 << wraparoundError(UINT32_MAX); + QTest::newRow("string-chunk-wraparound1") << raw("\x81\x7f\x7a\xff\xff\xff\xff") << 0 << wraparoundError(UINT32_MAX); + // on 32-bit systems, a 4GB addition could be dropped + QTest::newRow("bytearray-chunk-wraparound2") << raw("\x81\x5f\x5b\0\0\0\1\0\0\0\0") << 0 << wraparoundError(FourGB); + QTest::newRow("string-chunk-wraparound2") << raw("\x81\x7f\x7b\0\0\0\1\0\0\0\0") << 0 << wraparoundError(FourGB); + // on 64-bit systems, this could be a -1 + QTest::newRow("bytearray-chunk-wraparound3") << raw("\x81\x5f\x5b\xff\xff\xff\xff\xff\xff\xff\xff") << 0 + << wraparoundError(UINT64_MAX); + QTest::newRow("string-chunk-wraparound3") << raw("\x81\x7f\x7b\xff\xff\xff\xff\xff\xff\xff\xff") << 0 + << wraparoundError(UINT64_MAX); + + QTest::newRow("eof-after-array") << raw("\x81") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("eof-after-array2") << raw("\x81\x78\x20") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("eof-after-array-element") << raw("\x81\x82\x01") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("eof-after-object") << raw("\x81\xa1") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("eof-after-object2") << raw("\x81\xb8\x20") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("eof-after-object-key") << raw("\x81\xa1\x01") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("eof-after-object-value") << raw("\x81\xa2\x01\x01") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("eof-after-tag") << raw("\x81\xc0") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("eof-after-tag2") << raw("\x81\xd8\x20") << 0 << CborErrorUnexpectedEOF; + + // major type 7 has future types + QTest::newRow("future-type-28") << raw("\x81\xfc") << 0 << CborErrorUnknownType; + QTest::newRow("future-type-29") << raw("\x81\xfd") << 0 << CborErrorUnknownType; + QTest::newRow("future-type-30") << raw("\x81\xfe") << 0 << CborErrorUnknownType; + QTest::newRow("unexpected-break") << raw("\x81\xff") << 0 << CborErrorUnexpectedBreak; + QTest::newRow("illegal-simple-0") << raw("\x81\xf8\0") << 0 << CborErrorIllegalSimpleType; + QTest::newRow("illegal-simple-31") << raw("\x81\xf8\x1f") << 0 << CborErrorIllegalSimpleType; + + // not only too big (UINT_MAX or UINT_MAX+1 in size), but also incomplete + if (sizeof(size_t) < sizeof(uint64_t)) { + QTest::newRow("bytearray-too-big1") << raw("\x81\x5b\0\0\0\1\0\0\0\0") << 0 << CborErrorDataTooLarge; + QTest::newRow("string-too-big1") << raw("\x81\x7b\0\0\0\1\0\0\0\0") << 0 << CborErrorDataTooLarge; + } + QTest::newRow("array-too-big1") << raw("\x81\x9a\xff\xff\xff\xff\0\0\0\0") << 0 << CborErrorDataTooLarge; + QTest::newRow("array-too-big2") << raw("\x81\x9b\0\0\0\1\0\0\0\0") << 0 << CborErrorDataTooLarge; + QTest::newRow("object-too-big1") << raw("\x81\xba\xff\xff\xff\xff\0\0\0\0") << 0 << CborErrorDataTooLarge; + QTest::newRow("object-too-big2") << raw("\x81\xbb\0\0\0\1\0\0\0\0") << 0 << CborErrorDataTooLarge; + + QTest::newRow("no-break-for-array0") << raw("\x81\x9f") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("no-break-for-array1") << raw("\x81\x9f\x01") << 0 << CborErrorUnexpectedEOF; + + QTest::newRow("no-break-string0") << raw("\x81\x7f") << 0 << CborErrorUnexpectedEOF; + QTest::newRow("no-break-string1") << raw("\x81\x7f\x61Z") << 0 << CborErrorUnexpectedEOF; + + QTest::newRow("nested-indefinite-length-bytearrays") << raw("\x81\x5f\x5f\xff\xff") << 0 << CborErrorIllegalNumber; + QTest::newRow("nested-indefinite-length-strings") << raw("\x81\x7f\x7f\xff\xff") << 0 << CborErrorIllegalNumber; + + QTest::newRow("string-chunk-unsigned") << raw("\x81\x7f\0\xff") << 0 << CborErrorIllegalType; + QTest::newRow("string-chunk-negative") << raw("\x81\x7f\x20\xff") << 0 << CborErrorIllegalType; + QTest::newRow("string-chunk-bytearray") << raw("\x81\x7f\x40\xff") << 0 << CborErrorIllegalType; + QTest::newRow("string-chunk-array") << raw("\x81\x7f\x80\xff") << 0 << CborErrorIllegalType; + QTest::newRow("string-chunk-map") << raw("\x81\x7f\xa0\xff") << 0 << CborErrorIllegalType; + QTest::newRow("string-chunk-tag") << raw("\x81\x7f\xc0\xff") << 0 << CborErrorIllegalType; + QTest::newRow("string-chunk-tagged-string") << raw("\x81\x7f\xc0\x60\xff") << 0 << CborErrorIllegalType; + QTest::newRow("string-chunk-simple0") << raw("\x81\x7f\xe0\xff") << 0 << CborErrorIllegalType; + QTest::newRow("string-chunk-false") << raw("\x81\x7f\xf4\xff") << 0 << CborErrorIllegalType; + QTest::newRow("string-chunk-true") << raw("\x81\x7f\xf5\xff") << 0 << CborErrorIllegalType; + QTest::newRow("string-chunk-null") << raw("\x81\x7f\xf6\xff") << 0 << CborErrorIllegalType; + QTest::newRow("string-chunk-undefined") << raw("\x81\x7f\xf7\xff") << 0 << CborErrorIllegalType; + + QTest::newRow("bytearray-chunk-string") << raw("\x81\x5f\x60\xff") << 0 << CborErrorIllegalType; + QTest::newRow("bytearray-chunk-tagged-bytearray") << raw("\x81\x7f\xc0\x40\xff") << 0 << CborErrorIllegalType; + + // RFC 7049 Section 2.2.2 "Indefinite-Length Byte Strings and Text Strings" says + // Text strings with indefinite lengths act the same as byte strings + // with indefinite lengths, except that all their chunks MUST be + // definite-length text strings. Note that this implies that the bytes + // of a single UTF-8 character cannot be spread between chunks: a new + // chunk can only be started at a character boundary. + // This test technically tests the dumper, not the parser. + QTest::newRow("string-utf8-chunk-split") << raw("\x81\x7f\x61\xc2\x61\xa0\xff") << 0 << CborErrorInvalidUtf8TextString; +} diff --git a/lib/cbor/tinycbor/tests/parser/parser.pro b/lib/cbor/tinycbor/tests/parser/parser.pro new file mode 100644 index 00000000..a61291a9 --- /dev/null +++ b/lib/cbor/tinycbor/tests/parser/parser.pro @@ -0,0 +1,10 @@ +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/lib/cbor/tinycbor/tests/parser/tst_parser.cpp b/lib/cbor/tinycbor/tests/parser/tst_parser.cpp new file mode 100644 index 00000000..91a65a00 --- /dev/null +++ b/lib/cbor/tinycbor/tests/parser/tst_parser.cpp @@ -0,0 +1,1888 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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. +** +****************************************************************************/ + +#define _XOPEN_SOURCE 700 +#define _DARWIN_C_SOURCE 1 /* need MAP_ANON */ +#include <QtTest> +#include "cbor.h" +#include <stdio.h> +#include <stdarg.h> + +#if defined(Q_OS_UNIX) +# include <sys/mman.h> +# include <unistd.h> +#elif defined(Q_OS_WIN) +# define WIN32_LEAN_AND_MEAN 1 +# define NOMINMAX 1 +# include <windows.h> +#endif + + +namespace QTest { +template<> char *toString<CborError>(const CborError &err) +{ + return qstrdup(cbor_error_string(err)); +} +} + +class tst_Parser : public QObject +{ + Q_OBJECT +private slots: + void initParserEmpty(); + + // parsing API + void integers_data(); + void integers(); + void halfFloat_data(); + void halfFloat(); + void fixed_data(); + void fixed(); + void strings_data(); + void strings() { fixed(); } + void tags_data(); + void tags() { fixed(); } + void tagTags_data() { tags_data(); } + void tagTags(); + void emptyContainers_data(); + void emptyContainers(); + void arrays_data(); + void arrays(); + void undefLengthArrays_data() { arrays_data(); } + void undefLengthArrays(); + void nestedArrays_data() { arrays_data(); } + void nestedArrays(); + void maps_data(); + void maps(); + void undefLengthMaps_data() { maps_data(); } + void undefLengthMaps(); + void nestedMaps_data() { maps_data(); } + void nestedMaps(); + void mapMixed_data(); + void mapMixed() { arrays(); } + void mapsAndArrays_data() { arrays_data(); } + void mapsAndArrays(); + + void readerApi_data() { arrays_data(); } + void readerApi(); + void reparse_data(); + void reparse(); + + // chunked string API + void chunkedString_data(); + void chunkedString(); + void chunkedStringInUndefArray_data() { chunkedString_data(); } + void chunkedStringInUndefArray(); + + // convenience API + void stringLength_data(); + void stringLength(); + void stringCompare_data(); + void stringCompare(); + void mapFind_data(); + void mapFind(); + + // validation & errors + void checkedIntegers_data(); + void checkedIntegers(); + void validation_data(); + void validation(); + void strictValidation_data(); + void strictValidation(); + void incompleteData_data(); + void incompleteData(); + void endPointer_data(); + void endPointer(); + void recursionLimit_data(); + void recursionLimit(); +}; + +struct ParserWrapper +{ + void *realdata = nullptr; + uint8_t *data; + size_t len; + CborParser parser; + CborValue first; + + ~ParserWrapper() { freeMemory(); } + + CborError init(const QByteArray &ba, uint32_t flags = 0) + { + return init(ba.constData(), ba.size(), flags); + } + CborError init(const char *ptr, int n, uint32_t flags = 0) + { + freeMemory(); + data = allocateMemory(n); + memcpy(data, ptr, len); + return cbor_parser_init(data, len, flags, &parser, &first); + } + uint8_t *begin() { return data; } + uint8_t *end() { return data + len; } + + uint8_t *allocateMemory(size_t); + void freeMemory(); + + static const size_t PageSize = 4096; + static inline size_t mmapAllocation(size_t n) + { + // round up and add one page + return (n + 2*PageSize) & ~(PageSize - 1); + } + static bool shouldUseMmap(); +}; + +bool ParserWrapper::shouldUseMmap() +{ + static int v = qEnvironmentVariableIntValue("PARSER_NO_MMAP"); + return !v; +} + +uint8_t *ParserWrapper::allocateMemory(size_t n) +{ + len = n; + if (shouldUseMmap()) { + size_t alloc = mmapAllocation(n); +#if defined(Q_OS_UNIX) + realdata = mmap(nullptr, alloc, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + Q_ASSERT_X(realdata != MAP_FAILED, "allocateMemory", "mmap failed!"); + + // mark last page inaccessible + uint8_t *ptr = static_cast<uint8_t *>(realdata); + ptr += alloc - PageSize; + mprotect(ptr, PageSize, PROT_NONE); + + ptr -= n; + return ptr; +#elif defined(Q_OS_WIN) + DWORD flAllocationType = MEM_COMMIT | MEM_RESERVE; + DWORD flProtect = PAGE_READWRITE; + realdata = VirtualAlloc(nullptr, alloc, flAllocationType, flProtect); + Q_ASSERT_X(realdata, "allocateMemory", "VirtualAlloc failed!"); + + // mark last page inaccessible + uint8_t *ptr = static_cast<uint8_t *>(realdata); + ptr += alloc - PageSize; + VirtualProtect(ptr, PageSize, PAGE_NOACCESS, nullptr); + + ptr -= n; + return ptr; +#endif + } + realdata = malloc(n); + return static_cast<uint8_t *>(realdata); +} + +void ParserWrapper::freeMemory() +{ + if (shouldUseMmap()) { + if (realdata) { +#if defined(Q_OS_UNIX) + size_t alloc = mmapAllocation(len); + munmap(realdata, alloc); +#elif defined(Q_OS_WIN) + VirtualFree(realdata, 0, MEM_RELEASE); +#endif + } + return; + } + + free(realdata); +} + +static CborError qstring_printf(void *out, const char *fmt, ...) +{ + auto str = static_cast<QString *>(out); + va_list va; + va_start(va, fmt); + *str += QString::vasprintf(fmt, va); + va_end(va); + return CborNoError; +}; + +CborError parseOne(CborValue *it, QString *parsed) +{ + int flags = CborPrettyShowStringFragments | CborPrettyIndicateIndeterminateLength | + CborPrettyIndicateOverlongNumbers; + + parsed->clear(); + return cbor_value_to_pretty_stream(qstring_printf, parsed, it, flags); +} + +CborError parseOneChunk(CborValue *it, QString *parsed) +{ + CborError err; + CborType ourType = cbor_value_get_type(it); + if (ourType == CborByteStringType) { + const uint8_t *bytes; + size_t len; + err = cbor_value_get_byte_string_chunk(it, &bytes, &len, it); + if (err) + return err; + + if (bytes) + *parsed = QString::fromLatin1("h'" + + QByteArray::fromRawData(reinterpret_cast<const char *>(bytes), len).toHex() + + '\''); + } else if (ourType == CborTextStringType) { + const char *text; + size_t len; + err = cbor_value_get_text_string_chunk(it, &text, &len, it); + if (err) + return err; + + if (text) + *parsed = '"' + QString::fromUtf8(text, len) + '"'; + } else { + Q_ASSERT(false); + } + return err; +} + +void tst_Parser::initParserEmpty() +{ + CborParser parser; + CborValue first; + CborError err = cbor_parser_init((const quint8 *)"", 0, 0, &parser, &first); + QCOMPARE(err, CborErrorUnexpectedEOF); +} + +bool compareFailed = true; +void compareOne_real(const QByteArray &data, const QString &expected, int line, int n = -1) +{ + compareFailed = true; + ParserWrapper w; + CborError err = w.init(data); + QVERIFY2(!err, QByteArray::number(line) + ": Got error \"" + cbor_error_string(err) + "\""); + + if (cbor_value_get_type(&w.first) == CborArrayType) { + size_t len; + if (n >= 0) { + QVERIFY(cbor_value_is_length_known(&w.first)); + QCOMPARE(cbor_value_get_array_length(&w.first, &len), CborNoError); + QCOMPARE(len, size_t(len)); + } else { + QVERIFY(!cbor_value_is_length_known(&w.first)); + QCOMPARE(cbor_value_get_array_length(&w.first, &len), CborErrorUnknownLength); + } + } else if (cbor_value_get_type(&w.first) == CborMapType) { + size_t len; + if (n >= 0) { + QVERIFY(cbor_value_is_length_known(&w.first)); + QCOMPARE(cbor_value_get_map_length(&w.first, &len), CborNoError); + QCOMPARE(len, size_t(len)); + } else { + QVERIFY(!cbor_value_is_length_known(&w.first)); + QCOMPARE(cbor_value_get_map_length(&w.first, &len), CborErrorUnknownLength); + } + } else if (cbor_value_is_text_string(&w.first) || cbor_value_is_byte_string(&w.first)) { + size_t len; + QCOMPARE(cbor_value_calculate_string_length(&w.first, &len), CborNoError); + if (cbor_value_is_length_known(&w.first)) { + size_t len2; + QCOMPARE(cbor_value_get_string_length(&w.first, &len2), CborNoError); + QCOMPARE(len2, len); + } else { + QCOMPARE(cbor_value_get_string_length(&w.first, &len), CborErrorUnknownLength); + } + } + + CborError err2 = cbor_value_validate_basic(&w.first); + + QString decoded; + err = parseOne(&w.first, &decoded); + QVERIFY2(!err, QByteArray::number(line) + ": Got error \"" + cbor_error_string(err) + + "\"; decoded stream:\n" + decoded.toLatin1()); + QCOMPARE(decoded, expected); + + // check that the errors are the same + QCOMPARE(err2, err); + + // check that we consumed everything + QCOMPARE((void*)cbor_value_get_next_byte(&w.first), (void*)w.end()); + + compareFailed = false; +} +#define compareOne(data, expected) compareOne_real(data, expected, __LINE__) +#define compareOneSize(n, data, expected) compareOne_real(data, expected, __LINE__, n) + +#include "data.cpp" + +void tst_Parser::integers_data() +{ + addIntegers(); +} + +void tst_Parser::integers() +{ + QFETCH(QByteArray, data); + QFETCH(bool, isNegative); + QFETCH(quint64, expectedRaw); + QFETCH(qint64, expectedValue); + QFETCH(bool, inInt64Range); + + ParserWrapper w; + CborError err = w.init(data); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + QVERIFY(cbor_value_is_integer(&w.first)); + + uint64_t raw; + cbor_value_get_raw_integer(&w.first, &raw); + QCOMPARE(quint64(raw), expectedRaw); + + if (isNegative) { + QVERIFY(cbor_value_is_negative_integer(&w.first)); + QVERIFY(!cbor_value_is_unsigned_integer(&w.first)); + } else { + QVERIFY(!cbor_value_is_negative_integer(&w.first)); + QVERIFY(cbor_value_is_unsigned_integer(&w.first)); + } + + int64_t value; + if (inInt64Range) { + cbor_value_get_int64(&w.first, &value); + QCOMPARE(qint64(value), expectedValue); + } + + err = cbor_value_get_int64_checked(&w.first, &value); + QCOMPARE(err, inInt64Range ? CborNoError : CborErrorDataTooLarge); + + int ivalue; + bool inIntRange = inInt64Range && (expectedValue == int(expectedValue)); + err = cbor_value_get_int_checked(&w.first, &ivalue); + QCOMPARE(err, inIntRange ? CborNoError : CborErrorDataTooLarge); +} + +static void addHalfFloat() +{ + QTest::addColumn<QByteArray>("data"); + QTest::addColumn<unsigned>("expectedRaw"); + QTest::addColumn<double>("expectedValue"); + + QTest::newRow("+0") << raw("\x00\x00") << 0U << 0.0; + QTest::newRow("-0") << raw("\x80\x00") << 0x8000U << 0.0; + + QTest::newRow("min.denorm") << raw("\x00\x01") << 1U << ldexp(1.0, -14) * ldexp(1.0, -10); + QTest::newRow("-min.denorm") << raw("\x80\x01") << 0x8001U << ldexp(-1.0, -14) * ldexp(1.0, -10); + + QTest::newRow("max.denorm") << raw("\x03\xff") << 0x03ffU << ldexp(1.0, -14) * (1.0 - ldexp(1.0, -10)); + QTest::newRow("-max.denorm") << raw("\x83\xff") << 0x83ffU << ldexp(-1.0, -14) * (1.0 - ldexp(1.0, -10)); + + QTest::newRow("min.norm") << raw("\x04\x00") << 0x0400U << ldexp(1.0, -14); + QTest::newRow("-min.norm") << raw("\x84\x00") << 0x8400U << ldexp(-1.0, -14); + + QTest::newRow("1.0") << raw("\x3c\x00") << 0x3c00U << 1.0; + QTest::newRow("-1.0") << raw("\xbc\x00") << 0xbc00U << -1.0; + + QTest::newRow("1.5") << raw("\x3e\x00") << 0x3e00U << 1.5; + QTest::newRow("-1.5") << raw("\xbe\x00") << 0xbe00U << -1.5; + + QTest::newRow("max") << raw("\x7b\xff") << 0x7bffU << ldexp(1.0, 15) * (2.0 - ldexp(1.0, -10)); + QTest::newRow("-max") << raw("\xfb\xff") << 0xfbffU << ldexp(-1.0, 15) * (2.0 - ldexp(1.0, -10)); + + QTest::newRow("inf") << raw("\x7c\x00") << 0x7c00U << double(INFINITY); + QTest::newRow("-inf") << raw("\xfc\x00") << 0xfc00U << double(-INFINITY); + + QTest::newRow("nan") << raw("\x7c\x01") << 0x7c01U << double(NAN); + QTest::newRow("nan2") << raw("\xfc\x01") << 0xfc01U << double(NAN); + QTest::newRow("nan3") << raw("\x7e\x00") << 0x7e00U << double(NAN); + QTest::newRow("nan4") << raw("\xfe\x00") << 0xfe00U << double(NAN); +} + +void tst_Parser::halfFloat_data() +{ + addHalfFloat(); +} + +void tst_Parser::halfFloat() +{ + QFETCH(QByteArray, data); + QFETCH(unsigned, expectedRaw); + QFETCH(double, expectedValue); + + CborParser parser; + CborValue first; + + data.prepend('\xf9'); + + 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) + "\""); + QVERIFY(cbor_value_is_half_float(&first)); + + uint16_t raw; + cbor_value_get_half_float(&first, &raw); + QCOMPARE(raw, uint16_t(expectedRaw)); + + float value; + cbor_value_get_half_float_as_float(&first, &value); + + const double epsilon = ldexp(1.0, -25); + + if (qIsNaN(expectedValue)) { + QVERIFY(qIsNaN(value)); + } else if (qIsInf(expectedValue)) { + QVERIFY(value == (float)expectedValue); + } else { + QVERIFY(qAbs(value - (float)expectedValue) < epsilon); + } +} + +void tst_Parser::fixed_data() +{ + addColumns(); + addFixedData(); +} + +void tst_Parser::fixed() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + + compareOne(data, expected); +} + +void tst_Parser::strings_data() +{ + addColumns(); + addStringsData(); +} + +void tst_Parser::tags_data() +{ + addColumns(); + addTagsData(); +} + +void tst_Parser::tagTags() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + + compareOne("\xd9\xd9\xf7" + data, "55799(" + expected + ')'); + if (!compareFailed) + compareOne("\xd9\xd9\xf7" "\xd9\xd9\xf7" + data, "55799(55799(" + expected + "))"); +} + +void tst_Parser::emptyContainers_data() +{ + addColumns(); + addEmptyContainersData(); +} + +void tst_Parser::emptyContainers() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + QFETCH(int, n); + + compareOneSize(n, data, expected); +} + +void tst_Parser::arrays_data() +{ + addColumns(); + addFixedData(); + addStringsData(); + addTagsData(); +} + +void tst_Parser::arrays() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + + compareOneSize(1, "\x81" + data, '[' + expected + ']'); + if (compareFailed) return; + + compareOneSize(2, "\x82" + data + data, '[' + expected + ", " + expected + ']'); + if (compareFailed) return; + + // overlong length + compareOneSize(1, "\x98\1" + data, "[_0 " + expected + ']'); + if (compareFailed) return; + compareOneSize(1, raw("\x99\0\1") + data, "[_1 " + expected + ']'); + if (compareFailed) return; + compareOneSize(1, raw("\x9a\0\0\0\1") + data, "[_2 " + expected + ']'); + if (compareFailed) return; + compareOneSize(1, raw("\x9b\0\0\0\0\0\0\0\1") + data, "[_3 " + expected + ']'); + if (compareFailed) return; + + // medium-sized array: 32 elements (1 << 5) + expected += ", "; + for (int i = 0; i < 5; ++i) { + data += data; + expected += expected; + } + expected.chop(2); // remove the last ", " + compareOneSize(32, "\x98\x20" + data, '[' + expected + ']'); + if (compareFailed) return; + + // large array: 256 elements (32 << 3) + expected += ", "; + for (int i = 0; i < 3; ++i) { + data += data; + expected += expected; + } + expected.chop(2); // remove the last ", " + compareOneSize(256, raw("\x99\1\0") + data, '[' + expected + ']'); + if (compareFailed) return; +} + +void tst_Parser::undefLengthArrays() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + + compareOne("\x9f" + data + "\xff", "[_ " + expected + ']'); + if (compareFailed) return; + + compareOne("\x9f" + data + data + "\xff", "[_ " + expected + ", " + expected + ']'); +} + +void tst_Parser::nestedArrays() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + + compareOneSize(1, "\x81\x81" + data, "[[" + expected + "]]"); + if (compareFailed) return; + + compareOneSize(1, "\x81\x81\x81" + data, "[[[" + expected + "]]]"); + if (compareFailed) return; + + compareOneSize(1, "\x81\x82" + data + data, "[[" + expected + ", " + expected + "]]"); + if (compareFailed) return; + + compareOneSize(2, "\x82\x81" + data + data, "[[" + expected + "], " + expected + "]"); + if (compareFailed) return; + + compareOneSize(2, "\x82\x81" + data + '\x81' + data, "[[" + expected + "], [" + expected + "]]"); + if (compareFailed) return; + + // undefined length + compareOneSize(-1, "\x9f\x9f" + data + data + "\xff\xff", "[_ [_ " + expected + ", " + expected + "]]"); + if (compareFailed) return; + + compareOneSize(-1, "\x9f\x9f" + data + "\xff\x9f" + data + "\xff\xff", "[_ [_ " + expected + "], [_ " + expected + "]]"); + if (compareFailed) return; + + compareOneSize(-1, "\x9f\x9f" + data + data + "\xff\x9f" + data + "\xff\xff", + "[_ [_ " + expected + ", " + expected + "], [_ " + expected + "]]"); + if (compareFailed) return; + + // mix them + compareOneSize(1, "\x81\x9f" + data + "\xff", "[[_ " + expected + "]]"); + if (compareFailed) return; + + compareOneSize(-1, "\x9f\x81" + data + "\xff", "[_ [" + expected + "]]"); +} + +void tst_Parser::maps_data() +{ + arrays_data(); +} + +void tst_Parser::maps() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + + // integer key + compareOneSize(1, "\xa1\1" + data, "{1: " + expected + '}'); + if (compareFailed) return; + + // string key + compareOneSize(1, "\xa1\x65" "Hello" + data, "{\"Hello\": " + expected + '}'); + if (compareFailed) return; + + // map to self + compareOneSize(1, "\xa1" + data + data, '{' + expected + ": " + expected + '}'); + if (compareFailed) return; + + // two integer keys + compareOneSize(2, "\xa2\1" + data + "\2" + data, "{1: " + expected + ", 2: " + expected + '}'); + if (compareFailed) return; + + // OneSize integer and OneSize string key + compareOneSize(2, "\xa2\1" + data + "\x65" "Hello" + data, "{1: " + expected + ", \"Hello\": " + expected + '}'); + if (compareFailed) return; +} + +void tst_Parser::undefLengthMaps() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + + // integer key + compareOne("\xbf\1" + data + '\xff', "{_ 1: " + expected + '}'); + if (compareFailed) return; + + compareOne("\xbf\1" + data + '\2' + data + '\xff', "{_ 1: " + expected + ", 2: " + expected + '}'); + if (compareFailed) return; + + compareOne("\xbf\1" + data + "\x65Hello" + data + '\xff', "{_ 1: " + expected + ", \"Hello\": " + expected + '}'); + if (compareFailed) return; + + compareOne("\xbf\x65Hello" + data + '\1' + data + '\xff', "{_ \"Hello\": " + expected + ", 1: " + expected + '}'); +} + +void tst_Parser::nestedMaps() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + + // nested maps as values + compareOneSize(1, "\xa1\1\xa1\2" + data, "{1: {2: " + expected + "}}"); + if (compareFailed) return; + + compareOneSize(1, "\xa1\x65Hello\xa1\2" + data, "{\"Hello\": {2: " + expected + "}}"); + if (compareFailed) return; + + compareOneSize(1, "\xa1\1\xa2\2" + data + '\x20' + data, "{1: {2: " + expected + ", -1: " + expected + "}}"); + if (compareFailed) return; + + compareOneSize(2, "\xa2\1\xa1\2" + data + "\2\xa1\x20" + data, "{1: {2: " + expected + "}, 2: {-1: " + expected + "}}"); + if (compareFailed) return; + + // nested maps as keys + compareOneSize(1, "\xa1\xa1\xf4" + data + "\xf5", "{{false: " + expected + "}: true}"); + if (compareFailed) return; + + compareOneSize(1, "\xa1\xa1" + data + data + "\xa1" + data + data, + "{{" + expected + ": " + expected + "}: {" + expected + ": " + expected + "}}"); + if (compareFailed) return; + + // undefined length + compareOneSize(-1, "\xbf\1\xbf\2" + data + "\xff\xff", "{_ 1: {_ 2: " + expected + "}}"); + if (compareFailed) return; + + compareOneSize(-1, "\xbf\1\xbf\2" + data + '\x20' + data + "\xff\xff", "{_ 1: {_ 2: " + expected + ", -1: " + expected + "}}"); + if (compareFailed) return; + + compareOneSize(-1, "\xbf\1\xbf\2" + data + "\xff\2\xbf\x20" + data + "\xff\xff", + "{_ 1: {_ 2: " + expected + "}, 2: {_ -1: " + expected + "}}"); + if (compareFailed) return; + + compareOneSize(-1, "\xbf\xbf" + data + data + "\xff\xbf" + data + data + "\xff\xff", + "{_ {_ " + expected + ": " + expected + "}: {_ " + expected + ": " + expected + "}}"); + if (compareFailed) return; + + // mix them + compareOneSize(1, "\xa1\1\xbf\2" + data + "\xff", "{1: {_ 2: " + expected + "}}"); + if (compareFailed) return; + + compareOneSize(-1, "\xbf\1\xa1\2" + data + "\xff", "{_ 1: {2: " + expected + "}}"); + if (compareFailed) return; +} + +void tst_Parser::mapMixed_data() +{ + addColumns(); + addMapMixedData(); +} + +void tst_Parser::mapsAndArrays() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + + // arrays of maps + compareOneSize(1, "\x81\xa1\1" + data, "[{1: " + expected + "}]"); + if (compareFailed) return; + + compareOneSize(2, "\x82\xa1\1" + data + "\xa1\2" + data, "[{1: " + expected + "}, {2: " + expected + "}]"); + if (compareFailed) return; + + compareOneSize(1, "\x81\xa2\1" + data + "\2" + data, "[{1: " + expected + ", 2: " + expected + "}]"); + if (compareFailed) return; + + compareOneSize(-1, "\x9f\xa1\1" + data + "\xff", "[_ {1: " + expected + "}]"); + if (compareFailed) return; + + compareOneSize(1, "\x81\xbf\1" + data + "\xff", "[{_ 1: " + expected + "}]"); + if (compareFailed) return; + + compareOneSize(-1, "\x9f\xbf\1" + data + "\xff\xff", "[_ {_ 1: " + expected + "}]"); + if (compareFailed) return; + + // maps of arrays + compareOneSize(1, "\xa1\1\x81" + data, "{1: [" + expected + "]}"); + if (compareFailed) return; + + compareOneSize(1, "\xa1\1\x82" + data + data, "{1: [" + expected + ", " + expected + "]}"); + if (compareFailed) return; + + compareOneSize(2, "\xa2\1\x81" + data + "\x65Hello\x81" + data, "{1: [" + expected + "], \"Hello\": [" + expected + "]}"); + if (compareFailed) return; + + compareOneSize(1, "\xa1\1\x9f" + data + "\xff", "{1: [_ " + expected + "]}"); + if (compareFailed) return; + + compareOneSize(1, "\xa1\1\x9f" + data + data + "\xff", "{1: [_ " + expected + ", " + expected + "]}"); + if (compareFailed) return; + + compareOneSize(-1, "\xbf\1\x81" + data + "\xff", "{_ 1: [" + expected + "]}"); + if (compareFailed) return; + + compareOneSize(-1, "\xbf\1\x9f" + data + "\xff\xff", "{_ 1: [_ " + expected + "]}"); + if (compareFailed) return; + + compareOneSize(-1, "\xbf\1\x9f" + data + data + "\xff\xff", "{_ 1: [_ " + expected + ", " + expected + "]}"); + if (compareFailed) return; + + // mixed with indeterminate length strings + compareOneSize(-1, "\xbf\1\x9f" + data + "\xff\x65Hello\xbf" + data + "\x7f\xff\xff\xff", + "{_ 1: [_ " + expected + "], \"Hello\": {_ " + expected + ": (_ )}}"); +} + +struct Input { + QByteArray data; + int consumed; +}; + +static const CborParserOperations byteArrayOps = { + /* can_read_bytes = */ [](void *token, size_t len) { + auto input = static_cast<Input *>(token); + return input->data.size() - input->consumed >= int(len); + }, + /* read_bytes = */ [](void *token, void *dst, size_t offset, size_t len) { + auto input = static_cast<Input *>(token); + return memcpy(dst, input->data.constData() + input->consumed + offset, len); + }, + /* advance_bytes = */ [](void *token, size_t len) { + auto input = static_cast<Input *>(token); + input->consumed += int(len); + }, + /* transfer_string = */ [](void *token, const void **userptr, size_t offset, size_t len) { + // ### + auto input = static_cast<Input *>(token); + if (input->data.size() - input->consumed < int(len + offset)) + return CborErrorUnexpectedEOF; + input->consumed += int(offset); + *userptr = input->data.constData() + input->consumed; + input->consumed += int(len); + return CborNoError; + } +}; + +void tst_Parser::readerApi() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + + Input input = { data, 0 }; + + CborParser parser; + CborValue first; + CborError err = cbor_parser_init_reader(&byteArrayOps, &parser, &first, &input); + QCOMPARE(err, CborNoError); + + QString decoded; + err = parseOne(&first, &decoded); + QCOMPARE(err, CborNoError); + QCOMPARE(decoded, expected); + + // check we consumed everything + QCOMPARE(input.consumed, data.size()); +} + +void tst_Parser::reparse_data() +{ + // only one-item rows + addColumns(); + addFixedData(); +} + +void tst_Parser::reparse() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + + Input input = { QByteArray(), 0 }; + CborParser parser; + CborValue first; + CborError err = cbor_parser_init_reader(&byteArrayOps, &parser, &first, &input); + QCOMPARE(err, CborErrorUnexpectedEOF); + + for (int i = 0; i < data.size(); ++i) { + input.data = data.left(i); + err = cbor_value_reparse(&first); + if (err != CborErrorUnexpectedEOF) + qDebug() << "At" << i; + QCOMPARE(err, CborErrorUnexpectedEOF); + QCOMPARE(input.consumed, 0); + } + + // now it should work + input.data = data; + err = cbor_value_reparse(&first); + QCOMPARE(err, CborNoError); + + QString decoded; + err = parseOne(&first, &decoded); + QCOMPARE(err, CborNoError); + QCOMPARE(decoded, expected); + + // check we consumed everything + QCOMPARE(input.consumed, data.size()); +} + +void tst_Parser::chunkedString_data() +{ + addChunkedStringData(); +} + +static void chunkedStringTest(const QByteArray &data, const QString &concatenated, + QStringList &chunks, CborType ourType) +{ + ParserWrapper w; + CborError err = w.init(data); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + + CborValue value; + QVERIFY(cbor_value_is_array(&w.first)); + err = cbor_value_enter_container(&w.first, &value); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + QVERIFY(cbor_value_is_byte_string(&value) || cbor_value_is_text_string(&value)); + + CborValue copy = value; + + err = cbor_value_begin_string_iteration(&value); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + forever { + QString decoded; + err = parseOneChunk(&value, &decoded); + if (err == CborErrorNoMoreStringChunks) + break; // last chunk + + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + + QVERIFY2(!chunks.isEmpty(), "Too many chunks"); + QString expected = chunks.takeFirst(); + QCOMPARE(decoded, expected); + } + + err = cbor_value_finish_string_iteration(&value); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + QVERIFY2(chunks.isEmpty(), "Too few chunks"); + + // compare to the concatenated data + { + size_t n; + err = cbor_value_calculate_string_length(©, &n); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + + size_t nn = n; + QByteArray buffer(n + 1, Qt::Uninitialized); + QByteArray buffer2(n + 1, Qt::Uninitialized); + buffer[int(n)] = 0xff; + buffer2[int(n)] = 0xff; + QString formatted; + if (cbor_value_is_byte_string(©)) { + err = cbor_value_copy_byte_string(©, (uint8_t *)buffer.data(), &nn, nullptr); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + QCOMPARE(nn, n); + + formatted = QString::fromLatin1("h'" + QByteArray::fromRawData(buffer.data(), n).toHex() + '\''); + + // repeat by allowing the null termination + nn = n + 1; + err = cbor_value_copy_byte_string(©, (uint8_t *)buffer2.data(), &nn, nullptr); + } else { + err = cbor_value_copy_text_string(©, buffer.data(), &n, nullptr); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + QCOMPARE(nn, n); + + formatted = '"' + QString::fromUtf8(buffer.data(), n) + '"'; + + // repeat by allowing the null termination + nn = n + 1; + err = cbor_value_copy_text_string(©, buffer2.data(), &nn, nullptr); + } + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + QCOMPARE(formatted, concatenated); + + // verify terminators + QCOMPARE(buffer.at(n), char(0xff)); + QCOMPARE(buffer2.at(n), '\0'); + QCOMPARE(nn, n); + + buffer.truncate(n); + buffer2.truncate(n); + QCOMPARE(buffer2, buffer); + } + + // confirm that the extra string we appended is still here + QVERIFY(!cbor_value_at_end(&value)); + QCOMPARE(cbor_value_get_type(&value), ourType); + size_t len; + err = cbor_value_get_string_length(&value, &len); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + QCOMPARE(len, size_t(0)); + + err = cbor_value_advance(&value); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + + // confirm EOF + QVERIFY(cbor_value_at_end(&value)); + + err = cbor_value_leave_container(&w.first, &value); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + QCOMPARE((void*)cbor_value_get_next_byte(&w.first), (void*)w.end()); +} + +void tst_Parser::chunkedString() +{ + QFETCH(QByteArray, data); + QFETCH(QString, concatenated); + QFETCH(QStringList, chunks); + + // Make this an array of two entries, with the second an empty byte or text string + CborType ourType = CborType(data.at(0) & 0xe0); + data.prepend(char(0x82)); + data.append(ourType); + + chunkedStringTest(data, concatenated, chunks, ourType); +} + +void tst_Parser::chunkedStringInUndefArray() +{ + QFETCH(QByteArray, data); + QFETCH(QString, concatenated); + QFETCH(QStringList, chunks); + + // Make this an array of undefined length entries, with the second entry an empty byte or text string + CborType ourType = CborType(data.at(0) & 0xe0); + data.prepend(char(0x9f)); + data.append(ourType); + data.append(char(0xff)); + + chunkedStringTest(data, concatenated, chunks, ourType); +} + +void tst_Parser::stringLength_data() +{ + QTest::addColumn<QByteArray>("data"); + QTest::addColumn<int>("expected"); + + QTest::newRow("emptybytestring") << raw("\x40") << 0; + QTest::newRow("bytestring1") << raw("\x41 ") << 1; + QTest::newRow("bytestring1-nul") << raw("\x41\0") << 1; + QTest::newRow("bytestring5") << raw("\x45Hello") << 5; + QTest::newRow("bytestring24") << raw("\x58\x18""123456789012345678901234") << 24; + QTest::newRow("bytestring256") << raw("\x59\1\0") + QByteArray(256, '3') << 256; + + // text strings + QTest::newRow("emptytextstring") << raw("\x60") << 0; + QTest::newRow("textstring1") << raw("\x61 ") << 1; + QTest::newRow("textstring1-nul") << raw("\x61\0") << 1; + QTest::newRow("textstring5") << raw("\x65Hello") << 5; + QTest::newRow("textstring24") << raw("\x78\x18""123456789012345678901234") << 24; + QTest::newRow("textstring256") << raw("\x79\1\0") + QByteArray(256, '3') << 256; + + // strings with overlong length + QTest::newRow("emptybytestring*1") << raw("\x58\x00") << 0; + QTest::newRow("emptytextstring*1") << raw("\x78\x00") << 0; + QTest::newRow("emptybytestring*2") << raw("\x59\x00\x00") << 0; + QTest::newRow("emptytextstring*2") << raw("\x79\x00\x00") << 0; + QTest::newRow("emptybytestring*4") << raw("\x5a\0\0\0\0") << 0; + QTest::newRow("emptytextstring*4") << raw("\x7a\0\0\0\0") << 0; + QTest::newRow("emptybytestring*8") << raw("\x5b\0\0\0\0\0\0\0\0") << 0; + QTest::newRow("emptytextstring*8") << raw("\x7b\0\0\0\0\0\0\0\0") << 0; + QTest::newRow("bytestring5*1") << raw("\x58\x05Hello") << 5; + QTest::newRow("textstring5*1") << raw("\x78\x05Hello") << 5; + QTest::newRow("bytestring5*2") << raw("\x59\0\5Hello") << 5; + QTest::newRow("textstring5*2") << raw("\x79\0\x05Hello") << 5; + QTest::newRow("bytestring5*4") << raw("\x5a\0\0\0\5Hello") << 5; + QTest::newRow("textstring5*4") << raw("\x7a\0\0\0\x05Hello") << 5; + QTest::newRow("bytestring5*8") << raw("\x5b\0\0\0\0\0\0\0\5Hello") << 5; + QTest::newRow("textstring5*8") << raw("\x7b\0\0\0\0\0\0\0\x05Hello") << 5; + + // strings with undefined length + QTest::newRow("_emptybytestring") << raw("\x5f\xff") << 0; + QTest::newRow("_emptytextstring") << raw("\x7f\xff") << 0; + QTest::newRow("_emptybytestring2") << raw("\x5f\x40\xff") << 0; + QTest::newRow("_emptytextstring2") << raw("\x7f\x60\xff") << 0; + QTest::newRow("_emptybytestring3") << raw("\x5f\x40\x40\xff") << 0; + QTest::newRow("_emptytextstring3") << raw("\x7f\x60\x60\xff") << 0; + QTest::newRow("_bytestring5*2") << raw("\x5f\x43Hel\x42lo\xff") << 5; + QTest::newRow("_textstring5*2") << raw("\x7f\x63Hel\x62lo\xff") << 5; + QTest::newRow("_bytestring5*5") << raw("\x5f\x41H\x41""e\x41l\x41l\x41o\xff") << 5; + QTest::newRow("_textstring5*5") << raw("\x7f\x61H\x61""e\x61l\x61l\x61o\xff") << 5; + QTest::newRow("_bytestring5*6") << raw("\x5f\x41H\x41""e\x40\x41l\x41l\x41o\xff") << 5; + QTest::newRow("_textstring5*6") << raw("\x7f\x61H\x61""e\x61l\x60\x61l\x61o\xff") << 5; +} + +void tst_Parser::stringLength() +{ + QFETCH(QByteArray, data); + QFETCH(int, expected); + + ParserWrapper w; + CborError err = w.init(data); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + + size_t result; + err = cbor_value_calculate_string_length(&w.first, &result); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + QCOMPARE(result, size_t(expected)); + + if (cbor_value_is_length_known(&w.first)) { + QCOMPARE(cbor_value_get_string_length(&w.first, &result), CborNoError); + QCOMPARE(result, size_t(expected)); + } + +} + +void tst_Parser::stringCompare_data() +{ + QTest::addColumn<QByteArray>("data"); + QTest::addColumn<QString>("string"); + QTest::addColumn<bool>("expected"); + + // compare empty to empty + QTest::newRow("empty-empty") << raw("\x60") << QString() << true; + QTest::newRow("_empty-empty") << raw("\x7f\xff") << QString() << true; + QTest::newRow("_empty*1-empty") << raw("\x7f\x60\xff") << QString() << true; + QTest::newRow("_empty*2-empty") << raw("\x7f\x60\x60\xff") << QString() << true; + + // compare empty to non-empty + QTest::newRow("empty-nonempty") << raw("\x60") << "Hello" << false; + QTest::newRow("_empty-nonempty") << raw("\x7f\xff") << "Hello" << false; + QTest::newRow("_empty*1-nonempty") << raw("\x7f\x60\xff") << "Hello" << false; + QTest::newRow("_empty*2-nonempty") << raw("\x7f\x60\x60\xff") << "Hello" << false; + + // compare same strings + QTest::newRow("same-short-short") << raw("\x65Hello") << "Hello" << true; + QTest::newRow("same-_short*1-short") << raw("\x7f\x65Hello\xff") << "Hello" << true; + QTest::newRow("same-_short*2-short") << raw("\x7f\x63Hel\x62lo\xff") << "Hello" << true; + QTest::newRow("same-_short*5-short") << raw("\x7f\x61H\x61""e\x61l\x61l\x61o\xff") << "Hello" << true; + QTest::newRow("same-_short*8-short") << raw("\x7f\x61H\x60\x61""e\x60\x61l\x61l\x60\x61o\xff") << "Hello" << true; + QTest::newRow("same-long-long") << raw("\x78\x2aGood morning, good afternoon and goodnight") + << "Good morning, good afternoon and goodnight" << true; + QTest::newRow("same-_long*1-long") << raw("\x7f\x78\x2aGood morning, good afternoon and goodnight\xff") + << "Good morning, good afternoon and goodnight" << true; + QTest::newRow("same-_long*2-long") << raw("\x7f\x78\x1cGood morning, good afternoon\x6e and goodnight\xff") + << "Good morning, good afternoon and goodnight" << true; + + // compare different strings (same length) + QTest::newRow("diff-same-length-short-short") << raw("\x65Hello") << "World" << false; + QTest::newRow("diff-same-length-_short*1-short") << raw("\x7f\x65Hello\xff") << "World" << false; + QTest::newRow("diff-same-length-_short*2-short") << raw("\x7f\x63Hel\x62lo\xff") << "World" << false; + QTest::newRow("diff-same-length-_short*5-short") << raw("\x7f\x61H\x61""e\x61l\x61l\x61o\xff") << "World" << false; + QTest::newRow("diff-same-length-_short*8-short") << raw("\x7f\x61H\x60\x61""e\x60\x61l\x61l\x60\x61o\xff") << "World" << false; + QTest::newRow("diff-same-length-long-long") << raw("\x78\x2aGood morning, good afternoon and goodnight") + << "Good morning, good afternoon and goodnight, world" << false; + QTest::newRow("diff-same-length-_long*1-long") << raw("\x7f\x78\x2aGood morning, good afternoon and goodnight\xff") + << "Good morning, good afternoon and goodnight, world" << false; + QTest::newRow("diff-same-length-_long*2-long") << raw("\x7f\x78\x1cGood morning, good afternoon\x6e and goodnight\xff") + << "Good morning, good afternoon and goodnight, world" << false; + + // compare different strings (different length) + QTest::newRow("diff-diff-length-short-short") << raw("\x65Hello") << "Hello World" << false; + QTest::newRow("diff-diff-length-_short*1-short") << raw("\x7f\x65Hello\xff") << "Hello World" << false; + QTest::newRow("diff-diff-length-_short*2-short") << raw("\x7f\x63Hel\x62lo\xff") << "Hello World" << false; + QTest::newRow("diff-diff-length-_short*5-short") << raw("\x7f\x61H\x61""e\x61l\x61l\x61o\xff") << "Hello World" << false; + QTest::newRow("diff-diff-length-_short*8-short") << raw("\x7f\x61H\x60\x61""e\x60\x61l\x61l\x60\x61o\xff") << "Hello World" << false; + QTest::newRow("diff-diff-length-long-long") << raw("\x78\x2aGood morning, good afternoon and goodnight") + << "Good morning, good afternoon and goodnight World" << false; + QTest::newRow("diff-diff-length-_long*1-long") << raw("\x7f\x78\x2aGood morning, good afternoon and goodnight\xff") + << "Good morning, good afternoon and goodnight World" << false; + QTest::newRow("diff-diff-length-_long*2-long") << raw("\x7f\x78\x1cGood morning, good afternoon\x6e and goodnight\xff") + << "Good morning, good afternoon and goodnight World" << false; + + // compare against non-strings + QTest::newRow("unsigned") << raw("\0") << "0" << false; + QTest::newRow("negative") << raw("\x20") << "-1" << false; + QTest::newRow("emptybytestring") << raw("\x40") << "" << false; + QTest::newRow("_emptybytestring") << raw("\x5f\xff") << "" << false; + QTest::newRow("shortbytestring") << raw("\x45Hello") << "Hello" << false; + QTest::newRow("longbytestring") << raw("\x58\x2aGood morning, good afternoon and goodnight") + << "Good morning, good afternoon and goodnight" << false; + QTest::newRow("emptyarray") << raw("\x80") << "" << false; + QTest::newRow("emptymap") << raw("\xa0") << "" << false; + QTest::newRow("array") << raw("\x81\x65Hello") << "Hello" << false; + QTest::newRow("map") << raw("\xa1\x65Hello\x65World") << "Hello World" << false; + QTest::newRow("false") << raw("\xf4") << "false" << false; + QTest::newRow("true") << raw("\xf5") << "true" << false; + QTest::newRow("null") << raw("\xf6") << "null" << false; +} + +void compareOneString(const QByteArray &data, const QString &string, bool expected, int line) +{ + compareFailed = true; + + ParserWrapper w; + CborError err = w.init(data); + QVERIFY2(!err, QByteArray::number(line) + ": Got error \"" + cbor_error_string(err) + "\""); + + bool result; + QByteArray bastring = string.toUtf8(); + err = cbor_value_text_string_equals(&w.first, bastring.constData(), &result); + QVERIFY2(!err, QByteArray::number(line) + ": Got error \"" + cbor_error_string(err) + "\""); + QCOMPARE(result, expected); + + if (expected) { + size_t len; + cbor_value_skip_tag(&w.first); + if (cbor_value_is_length_known(&w.first)) { + QCOMPARE(cbor_value_get_string_length(&w.first, &len), CborNoError); + QCOMPARE(int(len), bastring.size()); + } + QCOMPARE(cbor_value_calculate_string_length(&w.first, &len), CborNoError); + QCOMPARE(int(len), bastring.size()); + } + + compareFailed = false; +} +#define compareOneString(data, string, expected) compareOneString(data, string, expected, __LINE__) + +void tst_Parser::stringCompare() +{ + QFETCH(QByteArray, data); + QFETCH(QString, string); + QFETCH(bool, expected); + + compareOneString(data, string, expected); + if (compareFailed) return; + + // tag it + compareOneString("\xc1" + data, string, expected); + if (compareFailed) return; + + compareOneString("\xc1\xc2" + data, string, expected); +} + +void tst_Parser::mapFind_data() +{ + // Rules: + // we are searching for string "needle" + // if present, the value should be the string "haystack" (with tag 42) + + QTest::addColumn<QByteArray>("data"); + QTest::addColumn<bool>("expected"); + + QTest::newRow("emptymap") << raw("\xa0") << false; + QTest::newRow("_emptymap") << raw("\xbf\xff") << false; + + // maps not containing our items + QTest::newRow("absent-unsigned-unsigned") << raw("\xa1\0\0") << false; + QTest::newRow("absent-taggedunsigned-unsigned") << raw("\xa1\xc0\0\0") << false; + QTest::newRow("absent-unsigned-taggedunsigned") << raw("\xa1\0\xc0\0") << false; + QTest::newRow("absent-taggedunsigned-taggedunsigned") << raw("\xa1\xc0\0\xc0\0") << false; + QTest::newRow("absent-string-unsigned") << raw("\xa1\x68haystack\0") << false; + QTest::newRow("absent-taggedstring-unsigned") << raw("\xa1\xc0\x68haystack\0") << false; + QTest::newRow("absent-string-taggedunsigned") << raw("\xa1\x68haystack\xc0\0") << false; + QTest::newRow("absent-taggedstring-taggedunsigned") << raw("\xa1\xc0\x68haystack\xc0\0") << false; + QTest::newRow("absent-string-string") << raw("\xa1\x68haystack\x66needle") << false; + QTest::newRow("absent-string-taggedstring") << raw("\xa1\x68haystack\xc0\x66needle") << false; + QTest::newRow("absent-taggedstring-string") << raw("\xa1\xc0\x68haystack\x66needle") << false; + QTest::newRow("absent-string-taggedstring") << raw("\xa1\xc0\x68haystack\xc0\x66needle") << false; + + QTest::newRow("absent-string-emptyarray") << raw("\xa1\x68haystack\x80") << false; + QTest::newRow("absent-string-_emptyarray") << raw("\xa1\x68haystack\x9f\xff") << false; + QTest::newRow("absent-string-array1") << raw("\xa1\x68haystack\x81\0") << false; + QTest::newRow("absent-string-array2") << raw("\xa1\x68haystack\x85\0\1\2\3\4") << false; + QTest::newRow("absent-string-array3") << raw("\xa1\x68haystack\x85\x63one\x63two\x65three\x64""four\x64""five") << false; + + QTest::newRow("absent-string-emptymap") << raw("\xa1\x68haystack\xa0") << false; + QTest::newRow("absent-string-_emptymap") << raw("\xa1\x68haystack\xbf\xff") << false; + QTest::newRow("absent-string-map") << raw("\xa1\x68haystack\xa1\x68haystack\x66needle") << false; + QTest::newRow("absent-string-map2") << raw("\xa1\x68haystack\xa1\x68haystack\x66needle\61z\62yx") << false; + + // maps containing our items + QTest::newRow("alone") << raw("\xa1\x66needle\xd8\x2a\x68haystack") << true; + QTest::newRow("tagged") << raw("\xa1\xc1\x66needle\xd8\x2a\x68haystack") << true; + QTest::newRow("doubletagged") << raw("\xa1\xc1\xc2\x66needle\xd8\x2a\x68haystack") << true; + QTest::newRow("chunked") << raw("\xa1\x7f\x66needle\xff\xd8\x2a\x68haystack") << true; + QTest::newRow("chunked*2") << raw("\xa1\x7f\x60\x66needle\xff\xd8\x2a\x68haystack") << true; + QTest::newRow("chunked*2bis") << raw("\xa1\x7f\x66needle\x60\xff\xd8\x2a\x68haystack") << true; + QTest::newRow("chunked*3") << raw("\xa1\x7f\x62ne\x62""ed\x62le\xff\xd8\x2a\x68haystack") << true; + QTest::newRow("chunked*8") << raw("\xa1\x7f\x61n\x61""e\x60\x61""e\x61""d\x60\x62le\x60\xff\xd8\x2a\x68haystack") << true; + + QTest::newRow("1before") << raw("\xa2\x68haystack\x66needle\x66needle\xd8\x2a\x68haystack") << true; + QTest::newRow("tagged-1before") << raw("\xa2\xc1\x68haystack\x66needle\xc1\x66needle\xd8\x2a\x68haystack") << true; + QTest::newRow("doubletagged-1before2") << raw("\xa2\xc1\xc2\x68haystack\x66needle\xc1\xc2\x66needle\xd8\x2a\x68haystack") << true; + + QTest::newRow("arraybefore") << raw("\xa2\x61z\x80\x66needle\xd8\x2a\x68haystack") << true; + QTest::newRow("nestedarraybefore") << raw("\xa2\x61z\x81\x81\0\x66needle\xd8\x2a\x68haystack") << true; + QTest::newRow("arrayarraybefore") << raw("\xa2\x82\1\2\x80\x66needle\xd8\x2a\x68haystack") << true; + + QTest::newRow("mapbefore") << raw("\xa2\x61z\xa0\x66needle\xd8\x2a\x68haystack") << true; + QTest::newRow("nestedmapbefore") << raw("\xa2\x61z\xa1\0\x81\0\x66needle\xd8\x2a\x68haystack") << true; + QTest::newRow("mapmapbefore") << raw("\xa2\xa1\1\2\xa0\x66needle\xd8\x2a\x68haystack") << true; +} + +void tst_Parser::mapFind() +{ + QFETCH(QByteArray, data); + QFETCH(bool, expected); + + ParserWrapper w; + CborError err = w.init(data); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + + CborValue element; + err = cbor_value_map_find_value(&w.first, "needle", &element); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + + if (expected) { + QCOMPARE(int(element.type), int(CborTagType)); + + CborTag tag; + err = cbor_value_get_tag(&element, &tag); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + QCOMPARE(int(tag), 42); + + bool equals; + err = cbor_value_text_string_equals(&element, "haystack", &equals); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + QVERIFY(equals); + } else { + QCOMPARE(int(element.type), int(CborInvalidType)); + } +} + +void tst_Parser::checkedIntegers_data() +{ + QTest::addColumn<QByteArray>("data"); + QTest::addColumn<QVariant>("result"); // QVariant so we can note numbers out of int64_t range + + QTest::newRow("0") << raw("\x00") << QVariant(Q_INT64_C(0)); + QTest::newRow("1") << raw("\x01") << QVariant(Q_INT64_C(1)); + QTest::newRow("10") << raw("\x0a") << QVariant(Q_INT64_C(10)); + QTest::newRow("23") << raw("\x17") << QVariant(Q_INT64_C(23)); + QTest::newRow("24") << raw("\x18\x18") << QVariant(Q_INT64_C(24)); + QTest::newRow("UINT8_MAX") << raw("\x18\xff") << QVariant(Q_INT64_C(255)); + QTest::newRow("UINT8_MAX+1") << raw("\x19\x01\x00") << QVariant(Q_INT64_C(256)); + QTest::newRow("UINT16_MAX") << raw("\x19\xff\xff") << QVariant(Q_INT64_C(65535)); + QTest::newRow("UINT16_MAX+1") << raw("\x1a\0\1\x00\x00") << QVariant(Q_INT64_C(65536)); + QTest::newRow("INT32_MAX") << raw("\x1a\x7f\xff\xff\xff") << QVariant(Q_INT64_C(2147483647)); + QTest::newRow("INT32_MAX+1") << raw("\x1a\x80\x00\x00\x00") << QVariant(Q_INT64_C(2147483648)); + QTest::newRow("UINT32_MAX") << raw("\x1a\xff\xff\xff\xff") << QVariant(Q_INT64_C(4294967295)); + QTest::newRow("UINT32_MAX+1") << raw("\x1b\0\0\0\1\0\0\0\0") << QVariant(Q_INT64_C(4294967296)); + QTest::newRow("UINT64_MAX") << raw("\x1b" "\xff\xff\xff\xff" "\xff\xff\xff\xff") + << QVariant(); // out of range + + // negative integers + QTest::newRow("-1") << raw("\x20") << QVariant(Q_INT64_C(-1)); + QTest::newRow("-2") << raw("\x21") << QVariant(Q_INT64_C(-2)); + QTest::newRow("-24") << raw("\x37") << QVariant(Q_INT64_C(-24)); + QTest::newRow("-25") << raw("\x38\x18") << QVariant(Q_INT64_C(-25)); + QTest::newRow("-UINT8_MAX") << raw("\x38\xff") << QVariant(Q_INT64_C(-256)); + QTest::newRow("-UINT8_MAX-1") << raw("\x39\x01\x00") << QVariant(Q_INT64_C(-257)); + QTest::newRow("-UINT16_MAX") << raw("\x39\xff\xff") << QVariant(Q_INT64_C(-65536)); + QTest::newRow("-UINT16_MAX-1") << raw("\x3a\0\1\x00\x00") << QVariant(Q_INT64_C(-65537)); + QTest::newRow("INT32_MIN") << raw("\x3a\x7f\xff\xff\xff") << QVariant(Q_INT64_C(-2147483648)); + QTest::newRow("INT32_MIN-1") << raw("\x3a\x80\x00\x00\x00") << QVariant(Q_INT64_C(-2147483649)); + QTest::newRow("-UINT32_MAX") << raw("\x3a\xff\xff\xff\xff") << QVariant(Q_INT64_C(-4294967296)); + QTest::newRow("-UINT32_MAX-1") << raw("\x3b\0\0\0\1\0\0\0\0") << QVariant(Q_INT64_C(-4294967297)); + QTest::newRow("INT64_MIN+1") << raw("\x3b\x7f\xff\xff\xff""\xff\xff\xff\xfe") + << QVariant(std::numeric_limits<qint64>::min() + 1); + QTest::newRow("INT64_MIN") << raw("\x3b\x7f\xff\xff\xff""\xff\xff\xff\xff") + << QVariant(std::numeric_limits<qint64>::min()); + QTest::newRow("INT64_MIN-1") << raw("\x3b\x80\0\0\0""\0\0\0\0") << QVariant(); // out of range + QTest::newRow("-UINT64_MAX") << raw("\x3b" "\xff\xff\xff\xff" "\xff\xff\xff\xfe") + << QVariant(); // out of range + QTest::newRow("-UINT64_MAX+1") << raw("\x3b" "\xff\xff\xff\xff" "\xff\xff\xff\xff") + << QVariant(); // out of range +} + +void tst_Parser::checkedIntegers() +{ + QFETCH(QByteArray, data); + QFETCH(QVariant, result); + int64_t expected = result.toLongLong(); + + ParserWrapper w; + CborError err = w.init(data); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + + int64_t v; + err = cbor_value_get_int64_checked(&w.first, &v); + if (result.isNull()) { + QCOMPARE(err, CborErrorDataTooLarge); + } else { + QCOMPARE(v, expected); + } + + int v2; + err = cbor_value_get_int_checked(&w.first, &v2); + if (result.isNull() || expected < std::numeric_limits<int>::min() || expected > std::numeric_limits<int>::max()) { + QCOMPARE(err, CborErrorDataTooLarge); + } else { + QCOMPARE(int64_t(v2), expected); + } +} + +void tst_Parser::validation_data() +{ + addValidationColumns(); + addValidationData(); +} + +void tst_Parser::validation() +{ + QFETCH(QByteArray, data); + QFETCH(int, flags); + QFETCH(CborError, expectedError); + + QString decoded; + ParserWrapper w; + CborError err = w.init(data, uint32_t(flags)); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + + CborError err2 = cbor_value_validate_basic(&w.first); + CborError err3 = cbor_value_validate(&w.first, CborValidateBasic); + err = parseOne(&w.first, &decoded); + QCOMPARE(err, expectedError); + if (!QByteArray(QTest::currentDataTag()).contains("utf8")) { + QCOMPARE(err2, expectedError); + QCOMPARE(err3, expectedError); + } + + // see if we've got a map + if (QByteArray(QTest::currentDataTag()).startsWith("map")) { + w.init(data, uint32_t(flags)); // reinit + QVERIFY(cbor_value_is_array(&w.first)); + + CborValue map; + CborError err = cbor_value_enter_container(&w.first, &map); + if (err == CborNoError) { + QVERIFY(cbor_value_is_map(&map)); + CborValue element; + err = cbor_value_map_find_value(&map, "foobar", &element); + if (err == CborNoError) + QVERIFY(!cbor_value_is_valid(&element)); + } + + QCOMPARE(err, expectedError); + } +} + +void tst_Parser::strictValidation_data() +{ + addValidationColumns(); + + // Canonical validation - fixed types + QTest::newRow("unsigned-0") << raw("\x00") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("unsigned-24") << raw("\x18\x18") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("unsigned-256") << raw("\x19\1\0") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("unsigned-65536") << raw("\x1a\0\1\0\0") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("unsigned-4294967296") << raw("\x1b\0\0\0\1\0\0\0\0") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("overlong-unsigned-0*1") << raw("\x18\x00") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-unsigned-0*2") << raw("\x19\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-unsigned-0*4") << raw("\x1a\0\0\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-unsigned-0*8") << raw("\x1b\0\0\0\0\0\0\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-unsigned-24*2") << raw("\x19\0\x18") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-unsigned-24*4") << raw("\x1a\0\0\0\x18") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-unsigned-24*8") << raw("\x1b\0\0\0\0\0\0\0\x18") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-unsigned-256*4") << raw("\x1a\0\0\1\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-unsigned-256*8") << raw("\x1b\0\0\0\0\0\0\1\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-unsigned-65536*8") << raw("\x1b\0\0\0\0\0\1\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("negative-1") << raw("\x20") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("negative-25") << raw("\x38\x38") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("negative-257") << raw("\x39\1\0") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("negative-65537") << raw("\x3a\0\1\0\0") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("negative-4294967297") << raw("\x3b\0\0\0\1\0\0\0\0") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("overlong-negative-1*1") << raw("\x38\x00") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-negative-1*2") << raw("\x39\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-negative-1*4") << raw("\x3a\0\0\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-negative-1*8") << raw("\x3b\0\0\0\0\0\0\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-negative-25*2") << raw("\x39\0\x18") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-negative-25*4") << raw("\x3a\0\0\0\x18") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-negative-25*8") << raw("\x3b\0\0\0\0\0\0\0\x18") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-negative-257*4") << raw("\x3a\0\0\1\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-negative-257*8") << raw("\x3b\0\0\0\0\0\0\1\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-negative-65537*8") << raw("\x3b\0\0\0\0\0\1\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("simple-0") << raw("\xe0") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("false") << raw("\xf4") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("true") << raw("\xf5") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("null") << raw("\xf6") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("undefined") << raw("\xf7") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("simple-32") << raw("\xf8\x20") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("fp-nan") << raw("\xf9\x7e\00") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("fp--inf") << raw("\xf9\xfc\00") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("fp-+inf") << raw("\xf9\x7c\00") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("overlong-fp-nan_f") << raw("\xfa\x7f\xc0\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-fp--inf_f") << raw("\xfa\xff\x80\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-fp-+inf_f") << raw("\xfa\x7f\x80\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-fp-nan") << raw("\xfb\x7f\xf8\0\0\0\0\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-fp--inf") << raw("\xfb\xff\xf0\0\0\0\0\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-fp-+inf") << raw("\xfb\x7f\xf0\0\0\0\0\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + + // canonical - lengths + QByteArray data24(24, 0x20); // also decodes as -1 + QByteArray data256(256, 0x40); // also decodes as h'' + QByteArray data65536(65536, 0x60);// also decodes as "" + QTest::newRow("bytearray-0") << raw("\x40") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("bytearray-24") << (raw("\x58\x18") + data24) << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("bytearray-256") << (raw("\x59\1\0") + data256) << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("bytearray-65536") << (raw("\x5a\0\1\0\0") + data65536) << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("_bytearray-0") << raw("\x5f\xff") << int(CborValidateCanonicalFormat) << CborErrorUnknownLength; + QTest::newRow("overlong-bytearray-0*1") << raw("\x58\x00") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-bytearray-0*2") << raw("\x59\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-bytearray-0*4") << raw("\x5a\0\0\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-bytearray-0*8") << raw("\x5b\0\0\0\0\0\0\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-bytearray-24*2") << (raw("\x59\0\x18") + data24) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-bytearray-24*4") << (raw("\x5a\0\0\0\x18") + data24) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-bytearray-24*8") << (raw("\x5b\0\0\0\0\0\0\0\x18") + data24) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-bytearray-256*4") << (raw("\x5a\0\0\1\0") + data256) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-bytearray-256*8") << (raw("\x5b\0\0\0\0\0\0\1\0") + data256) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-bytearray-65536*8") << (raw("\x5b\0\0\0\0\0\1\0\0") + data65536) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("string-0") << raw("\x60") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("string-24") << (raw("\x78\x18") + data24) << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("string-256") << (raw("\x79\1\0") + data256) << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("string-65536") << (raw("\x7a\0\1\0\0") + data65536) << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("_string-0") << raw("\x7f\xff") << int(CborValidateCanonicalFormat) << CborErrorUnknownLength; + QTest::newRow("overlong-string-0*1") << raw("\x78\x00") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-string-0*2") << raw("\x79\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-string-0*4") << raw("\x7a\0\0\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-string-0*8") << raw("\x7b\0\0\0\0\0\0\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-string-24*2") << (raw("\x79\0\x18") + data24) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-string-24*4") << (raw("\x7a\0\0\0\x18") + data24) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-string-24*8") << (raw("\x7b\0\0\0\0\0\0\0\x18") + data24) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-string-256*4") << (raw("\x7a\0\0\1\0") + data256) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-string-256*8") << (raw("\x7b\0\0\0\0\0\0\1\0") + data256) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-string-65536*8") << (raw("\x7b\0\0\0\0\0\1\0\0") + data65536) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("array-0") << raw("\x80") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("array-24") << (raw("\x98\x18") + data24) << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("array-256") << (raw("\x99\1\0") + data256) << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("array-65536") << (raw("\x9a\0\1\0\0") + data65536) << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("_array-0") << raw("\x9f\xff") << int(CborValidateCanonicalFormat) << CborErrorUnknownLength; + QTest::newRow("overlong-array-0*1") << raw("\x98\x00") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-array-0*2") << raw("\x99\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-array-0*4") << raw("\x9a\0\0\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-array-0*8") << raw("\x9b\0\0\0\0\0\0\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-array-24*2") << (raw("\x99\0\x18") + data24) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-array-24*4") << (raw("\x9a\0\0\0\x18") + data24) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-array-24*8") << (raw("\x9b\0\0\0\0\0\0\0\x18") + data24) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-array-256*4") << (raw("\x9a\0\0\1\0") + data256) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-array-256*8") << (raw("\x9b\0\0\0\0\0\0\1\0") + data256) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-array-65536*8") << (raw("\x9b\0\0\0\0\0\1\0\0") + data65536) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + + // we need unique, sorted, string keys for map + // we'll make all key-value pairs a total of 4 bytes + char mapentry[] = { 0x62, 0, 0, 0x20 }; + QByteArray mapdata24(24 * sizeof(mapentry), Qt::Uninitialized); + QByteArray mapdata256(256 * sizeof(mapentry), Qt::Uninitialized); + char *mapdata24ptr = mapdata24.data(); + char *mapdata256ptr = mapdata256.data(); + for (int i = 0; i < 256; ++i) { + mapentry[1] = 'A' + (i >> 4); + mapentry[2] = 'a' + (i & 0xf); + memcpy(mapdata256ptr + i * sizeof(mapentry), mapentry, sizeof(mapentry)); + if (i < 24) + memcpy(mapdata24ptr + i * sizeof(mapentry), mapentry, sizeof(mapentry)); + } + QTest::newRow("map-0") << raw("\xa0") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("map-24") << (raw("\xb8\x18") + mapdata24) << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("map-256") << (raw("\xb9\1\0") + mapdata256) << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("_map-0") << raw("\xbf\xff") << int(CborValidateCanonicalFormat) << CborErrorUnknownLength; + QTest::newRow("overlong-map-0*1") << raw("\xb8\x00") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-map-0*2") << raw("\xb9\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-map-0*4") << raw("\xba\0\0\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-map-0*8") << raw("\xbb\0\0\0\0\0\0\0\0") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-map-24*2") << (raw("\xb9\0\x18") + mapdata24) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-map-24*4") << (raw("\xba\0\0\0\x18") + mapdata24) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-map-24*8") << (raw("\xbb\0\0\0\0\0\0\0\x18") + mapdata24) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-map-256*4") << (raw("\xba\0\0\1\0") + mapdata256) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-map-256*8") << (raw("\xbb\0\0\0\0\0\0\1\0") + mapdata256) << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("unsorted-length-map-UU") << raw("\xa2\1\1\0\0") << int(CborValidateCanonicalFormat) << CborErrorMapNotSorted; + QTest::newRow("unsorted-length-map-UUU") << raw("\xa3\1\1\1\1\0\0") << int(CborValidateCanonicalFormat) << CborErrorMapNotSorted; + QTest::newRow("unsorted-length-map-SS") << raw("\xa2\x61z\1\x60\0") << int(CborValidateCanonicalFormat) << CborErrorMapNotSorted; + QTest::newRow("unsorted-length-map-SSS") << raw("\xa3\x61z\1\x61z\2\x60\0") << int(CborValidateCanonicalFormat) << CborErrorMapNotSorted; + QTest::newRow("unsorted-length-map-SB") << raw("\xa2\x61z\1\x40\0") << int(CborValidateCanonicalFormat) << CborErrorMapNotSorted; + QTest::newRow("unsorted-length-map-AS") << raw("\xa2\x83\0\x20\x45Hello\1\x60\0") << int(CborValidateCanonicalFormat) << CborErrorMapNotSorted; + QTest::newRow("unsorted-content-map-SS") << raw("\xa2\x61z\1\x61y\0") << int(CborValidateCanonicalFormat) << CborErrorMapNotSorted; + QTest::newRow("unsorted-content-map-AS") << raw("\xa2\x81\x21\1\x61\x21\0") << int(CborValidateCanonicalFormat) << CborErrorMapNotSorted; + + QTest::newRow("tag-0") << raw("\xc0\x60") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("tag-24") << raw("\xd8\x18\x40") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("tag-65536") << raw("\xda\0\1\0\0\x60") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("tag-4294967296") << raw("\xdb\0\0\0\1\0\0\0\0\x60") << int(CborValidateCanonicalFormat) << CborNoError; + QTest::newRow("overlong-tag-0*1") << raw("\xd8\x00\x60") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-tag-0*2") << raw("\xd9\0\0\x60") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-tag-0*4") << raw("\xda\0\0\0\0\x60") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-tag-0*8") << raw("\xdb\0\0\0\0\0\0\0\0\x60") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-tag-24*2") << raw("\xd9\0\x18\x60") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-tag-24*4") << raw("\xda\0\0\0\x18\x60") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-tag-24*8") << raw("\xdb\0\0\0\0\0\0\0\x18\x60") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-tag-256*4") << raw("\xda\0\0\1\0\x60") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-tag-256*8") << raw("\xdb\0\0\0\0\0\0\1\0\x60") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + QTest::newRow("overlong-tag-65536*8") << raw("\xdb\0\0\0\0\0\1\0\0\x60") << int(CborValidateCanonicalFormat) << CborErrorOverlongEncoding; + + // non-canonical: string length in chunked transfer + QTest::newRow("overlong-_bytearray-0*1") << raw("\x5f\x58\x00\xff") << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_bytearray-0*2") << raw("\x5f\x59\0\0\xff") << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_bytearray-0*4") << raw("\x5f\x5a\0\0\0\0\xff") << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_bytearray-0*8") << raw("\x5f\x5b\0\0\0\0\0\0\0\0\xff") << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_bytearray-24*2") << (raw("\x5f\x59\0\x18") + data24 + '\xff') << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_bytearray-24*4") << (raw("\x5f\x5a\0\0\0\x18") + data24 + '\xff') << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_bytearray-24*8") << (raw("\x5f\x5b\0\0\0\0\0\0\0\x18") + data24 + '\xff') << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_bytearray-256*4") << (raw("\x5f\x5a\0\0\1\0") + data256 + '\xff') << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_bytearray-256*8") << (raw("\x5f\x5b\0\0\0\0\0\0\1\0") + data256 + '\xff') << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_bytearray-65536*8") << (raw("\x5f\x5b\0\0\0\0\0\1\0\0") + data65536 + '\xff') << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_bytearrayx2-0*1") << raw("\x5f\x40\x58\x00\xff") << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_bytearrayx2-0*2") << raw("\x5f\x40\x59\0\0\xff") << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_bytearrayx2-0*4") << raw("\x5f\x40\x5a\0\0\0\0\xff") << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_bytearrayx2-0*8") << raw("\x5f\x40\x5b\0\0\0\0\0\0\0\0\xff") << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_string-0*1") << raw("\x7f\x78\x00\xff") << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_string-0*2") << raw("\x7f\x79\0\0\xff") << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_string-0*4") << raw("\x7f\x7a\0\0\0\0\xff") << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_string-0*8") << raw("\x7f\x7b\0\0\0\0\0\0\0\0\xff") << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_string-24*2") << (raw("\x7f\x79\0\x18") + data24 + '\xff') << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_string-24*4") << (raw("\x7f\x7a\0\0\0\x18") + data24 + '\xff') << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_string-24*8") << (raw("\x7f\x7b\0\0\0\0\0\0\0\x18") + data24 + '\xff') << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_string-256*4") << (raw("\x7f\x7a\0\0\1\0") + data256 + '\xff') << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_string-256*8") << (raw("\x7f\x7b\0\0\0\0\0\0\1\0") + data256 + '\xff') << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_string-65536*8") << (raw("\x7f\x7b\0\0\0\0\0\1\0\0") + data65536 + '\xff') << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_stringx2-0*1") << raw("\x7f\x60\x78\x00\xff") << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_stringx2-0*2") << raw("\x7f\x60\x79\0\0\xff") << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_stringx2-0*4") << raw("\x7f\x60\x7a\0\0\0\0\xff") << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + QTest::newRow("overlong-_stringx2-0*8") << raw("\x7f\x60\x7b\0\0\0\0\0\0\0\0\xff") << int(CborValidateShortestNumbers) << CborErrorOverlongEncoding; + + // strict mode + // UTF-8 sequences with invalid continuation bytes + QTest::newRow("invalid-utf8-bad-continuation-1char") << raw("\x61\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-bad-continuation-2chars-1") << raw("\x62\xc2\xc0") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-bad-continuation-2chars-2") << raw("\x62\xc3\xdf") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-bad-continuation-2chars-3") << raw("\x62\xc7\xf0") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-bad-continuation-3chars-1") << raw("\x63\xe0\xa0\xc0") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-bad-continuation-3chars-2") << raw("\x63\xe0\xc0\xa0") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-bad-continuation-4chars-1") << raw("\x64\xf0\x90\x80\xc0") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-bad-continuation-4chars-2") << raw("\x64\xf0\x90\xc0\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-bad-continuation-4chars-3") << raw("\x64\xf0\xc0\x80\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + // Too short UTF-8 sequences (in an array so there's a byte after that would make it valid UTF-8 if it were part of the string) + QTest::newRow("invalid-utf8-too-short-2chars") << raw("\x82\x61\xc2\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-too-short-3chars-1") << raw("\x82\x61\xe0\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-too-short-3chars-2") << raw("\x82\x62\xe0\xa0\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-too-short-4chars-1") << raw("\x82\x61\xf0\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-too-short-4chars-2") << raw("\x82\x62\xf0\x90\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-too-short-4chars-3") << raw("\x82\x63\xf0\x90\x80\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + // UTF-16 surrogages encoded in UTF-8 + QTest::newRow("invalid-utf8-hi-surrogate") << raw("\x63\xed\xa0\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-lo-surrogate") << raw("\x63\xed\xb0\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-surrogate-pair") << raw("\x66\xed\xa0\x80\xed\xb0\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + // Non-Unicode UTF-8 sequences + QTest::newRow("invalid-utf8-non-unicode-1") << raw("\x64\xf4\x90\x80\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-non-unicode-2") << raw("\x65\xf8\x88\x80\x80\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-non-unicode-3") << raw("\x66\xfc\x84\x80\x80\x80\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-non-unicode-4") << raw("\x66\xfd\xbf\xbf\xbf\xbf\xbf") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + // invalid bytes in UTF-8 + QTest::newRow("invalid-utf8-fe") << raw("\x61\xfe") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-ff") << raw("\x61\xff") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + // Overlong sequences + QTest::newRow("invalid-utf8-overlong-1-2") << raw("\x62\xc1\x81") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-overlong-1-3") << raw("\x63\xe0\x81\x81") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-overlong-1-4") << raw("\x64\xf0\x80\x81\x81") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-overlong-1-5") << raw("\x65\xf8\x80\x80\x81\x81") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-overlong-1-6") << raw("\x66\xfc\x80\x80\x80\x81\x81") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-overlong-2-3") << raw("\x63\xe0\x82\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-overlong-2-4") << raw("\x64\xf0\x80\x82\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-overlong-2-5") << raw("\x65\xf8\x80\x80\x82\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-overlong-2-6") << raw("\x66\xfc\x80\x80\x80\x82\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-overlong-3-4") << raw("\x64\xf0\x80\xa0\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-overlong-3-5") << raw("\x65\xf8\x80\x80\xa0\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-overlong-3-6") << raw("\x66\xfc\x80\x80\x80\xa0\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-overlong-4-5") << raw("\x65\xf8\x80\x84\x80\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + QTest::newRow("invalid-utf8-overlong-4-6") << raw("\x66\xfc\x80\x80\x84\x80\x80") << int(CborValidateStrictMode) << CborErrorInvalidUtf8TextString; + + QTest::newRow("nonunique-content-map-UU") << raw("\xa2\0\1\0\2") << int(CborValidateStrictMode) << CborErrorMapKeysNotUnique; + QTest::newRow("nonunique-content-map-SS") << raw("\xa2\x61z\1\x61z\2") << int(CborValidateStrictMode) << CborErrorMapKeysNotUnique; + QTest::newRow("nonunique-content-map-AA") << raw("\xa2\x81\x65Hello\1\x81\x65Hello\2") << int(CborValidateStrictMode) << CborErrorMapKeysNotUnique; + + QTest::newRow("tag-0-unsigned") << raw("\xc0\x00") << int(CborValidateStrictMode) << CborErrorInappropriateTagForType; + QTest::newRow("tag-0-bytearray") << raw("\xc0\x40") << int(CborValidateStrictMode) << CborErrorInappropriateTagForType; + QTest::newRow("tag-0-string") << raw("\xc0\x60") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-0-tag-0-string") << raw("\xc0\xc0\x60") << int(CborValidateStrictMode) << CborErrorInappropriateTagForType; + QTest::newRow("tag-1-unsigned") << raw("\xc1\x00") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-1-negative") << raw("\xc1\x20") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-1-bytearray") << raw("\xc1\x40") << int(CborValidateStrictMode) << CborErrorInappropriateTagForType; + QTest::newRow("tag-2-bytearray") << raw("\xc2\x40") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-2-string") << raw("\xc2\x60") << int(CborValidateStrictMode) << CborErrorInappropriateTagForType; + QTest::newRow("tag-3-bytearray") << raw("\xc3\x40") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-3-string") << raw("\xc3\x60") << int(CborValidateStrictMode) << CborErrorInappropriateTagForType; + QTest::newRow("tag-4-string") << raw("\xc4\x60") << int(CborValidateStrictMode) << CborErrorInappropriateTagForType; + QTest::newRow("tag-4-array") << raw("\xc4\x82\0\1") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-5-string") << raw("\xc5\x60") << int(CborValidateStrictMode) << CborErrorInappropriateTagForType; + QTest::newRow("tag-5-array") << raw("\xc5\x82\0\1") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-21-bytearray") << raw("\xd5\x40") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-21-string") << raw("\xd5\x60") << int(CborValidateStrictMode) << CborErrorInappropriateTagForType; + QTest::newRow("tag-21-array") << raw("\xd5\x80") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-21-map") << raw("\xd5\xa0") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-22-bytearray") << raw("\xd6\x40") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-22-string") << raw("\xd6\x60") << int(CborValidateStrictMode) << CborErrorInappropriateTagForType; + QTest::newRow("tag-22-array") << raw("\xd6\x80") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-22-map") << raw("\xd6\xa0") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-23-bytearray") << raw("\xd7\x40") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-23-string") << raw("\xd7\x60") << int(CborValidateStrictMode) << CborErrorInappropriateTagForType; + QTest::newRow("tag-23-array") << raw("\xd7\x80") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-23-map") << raw("\xd7\xa0") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-24-bytearray") << raw("\xd8\x18\x40") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-24-string") << raw("\xd8\x18\x60") << int(CborValidateStrictMode) << CborErrorInappropriateTagForType; + QTest::newRow("tag-32-bytearray") << raw("\xd8\x20\x40") << int(CborValidateStrictMode) << CborErrorInappropriateTagForType; + QTest::newRow("tag-32-string") << raw("\xd8\x20\x60") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-33-bytearray") << raw("\xd8\x21\x40") << int(CborValidateStrictMode) << CborErrorInappropriateTagForType; + QTest::newRow("tag-33-string") << raw("\xd8\x21\x60") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-34-bytearray") << raw("\xd8\x22\x40") << int(CborValidateStrictMode) << CborErrorInappropriateTagForType; + QTest::newRow("tag-34-string") << raw("\xd8\x22\x60") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-35-bytearray") << raw("\xd8\x23\x40") << int(CborValidateStrictMode) << CborErrorInappropriateTagForType; + QTest::newRow("tag-35-string") << raw("\xd8\x23\x60") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-36-bytearray") << raw("\xd8\x24\x40") << int(CborValidateStrictMode) << CborErrorInappropriateTagForType; + QTest::newRow("tag-36-string") << raw("\xd8\x24\x60") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-55799-unsigned") << raw("\xd9\xd9\xf7\x00") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-55799-negative") << raw("\xd9\xd9\xf7\x20") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-55799-bytearray") << raw("\xd9\xd9\xf7\x40") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-55799-string") << raw("\xd9\xd9\xf7\x60") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-55799-array") << raw("\xd9\xd9\xf7\x80") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-55799-map") << raw("\xd9\xd9\xf7\xa0") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-55799-tag-0-unsigned") << raw("\xd9\xd9\xf7\xc0\x00") << int(CborValidateStrictMode) << CborErrorInappropriateTagForType; + QTest::newRow("tag-55799-tag-0-string") << raw("\xd9\xd9\xf7\xc0\x60") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-55799-simple0") << raw("\xd9\xd9\xf7\xe0") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-55799-false") << raw("\xd9\xd9\xf7\xf4") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-55799-true") << raw("\xd9\xd9\xf7\xf5") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-55799-null") << raw("\xd9\xd9\xf7\xf6") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-55799-undefined") << raw("\xd9\xd9\xf7\xf7") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-55799-simple32") << raw("\xd9\xd9\xf7\xf8\x20") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-55799-half") << raw("\xd9\xd9\xf7\xf9\0\0") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-55799-float") << raw("\xd9\xd9\xf7\xfa\0\0\0\0") << int(CborValidateStrictMode) << CborNoError; + QTest::newRow("tag-55799-double") << raw("\xd9\xd9\xf7\xfb\0\0\0\0\0\0\0\0") << int(CborValidateStrictMode) << CborNoError; + + // excluded non-finite + QTest::newRow("excluded-fp-nan") << raw("\xfb\x7f\xf8\0\0\0\0\0\0") << int(CborValidateFiniteFloatingPoint) << CborErrorExcludedValue; + QTest::newRow("excluded-fp-nan_f") << raw("\xfa\x7f\xc0\0\0") << int(CborValidateFiniteFloatingPoint) << CborErrorExcludedValue; + QTest::newRow("excluded-fp--inf_f") << raw("\xfa\xff\x80\0\0") << int(CborValidateFiniteFloatingPoint) << CborErrorExcludedValue; + QTest::newRow("excluded-fp--inf") << raw("\xfb\xff\xf0\0\0\0\0\0\0") << int(CborValidateFiniteFloatingPoint) << CborErrorExcludedValue; + QTest::newRow("excluded-fp-+inf_f") << raw("\xfa\x7f\x80\0\0") << int(CborValidateFiniteFloatingPoint) << CborErrorExcludedValue; + QTest::newRow("excluded-fp-+inf") << raw("\xfb\x7f\xf0\0\0\0\0\0\0") << int(CborValidateFiniteFloatingPoint) << CborErrorExcludedValue; + + // excluded undefined + QTest::newRow("no-undefined") << raw("\xf7") << int(CborValidateNoUndefined) << CborErrorExcludedType; + + // exclude non-finite + QTest::newRow("excluded-fp-nan_f16") << raw("\xf9\x7e\00") << int(CborValidateFiniteFloatingPoint) << CborErrorExcludedValue; + QTest::newRow("excluded-fp--inf_f16") << raw("\xf9\xfc\00") << int(CborValidateFiniteFloatingPoint) << CborErrorExcludedValue; + QTest::newRow("excluded-fp-+inf_f16") << raw("\xf9\x7c\00") << int(CborValidateFiniteFloatingPoint) << CborErrorExcludedValue; + QTest::newRow("excluded-fp-nan_f") << raw("\xfa\x7f\xc0\0\0") << int(CborValidateFiniteFloatingPoint) << CborErrorExcludedValue; + QTest::newRow("excluded-fp--inf_f") << raw("\xfa\xff\x80\0\0") << int(CborValidateFiniteFloatingPoint) << CborErrorExcludedValue; + QTest::newRow("excluded-fp-+inf_f") << raw("\xfa\x7f\x80\0\0") << int(CborValidateFiniteFloatingPoint) << CborErrorExcludedValue; + QTest::newRow("excluded-fp-nan") << raw("\xfb\x7f\xf8\0\0\0\0\0\0") << int(CborValidateFiniteFloatingPoint) << CborErrorExcludedValue; + QTest::newRow("excluded-fp--inf") << raw("\xfb\xff\xf0\0\0\0\0\0\0") << int(CborValidateFiniteFloatingPoint) << CborErrorExcludedValue; + QTest::newRow("excluded-fp-+inf") << raw("\xfb\x7f\xf0\0\0\0\0\0\0") << int(CborValidateFiniteFloatingPoint) << CborErrorExcludedValue; + + // exclude non-string keys in maps + QTest::newRow("excluded-map-unsigned") << raw("\xa1\x00\1") << int(CborValidateMapKeysAreString) << CborErrorMapKeyNotString; + QTest::newRow("excluded-map-negative") << raw("\xa1\x20\1") << int(CborValidateMapKeysAreString) << CborErrorMapKeyNotString; + QTest::newRow("excluded-map-bytearray") << raw("\xa1\x40\1") << int(CborValidateMapKeysAreString) << CborErrorMapKeyNotString; + QTest::newRow("map-string") << raw("\xa1\x60\1") << int(CborValidateMapKeysAreString) << CborNoError; + QTest::newRow("map-tag-0-string") << raw("\xa1\xc0\x60\1") << int(CborValidateMapKeysAreString) << CborNoError; + QTest::newRow("excluded-map-array") << raw("\xa1\x80\1") << int(CborValidateMapKeysAreString) << CborErrorMapKeyNotString; + QTest::newRow("excluded-map-map") << raw("\xa1\xa0\1") << int(CborValidateMapKeysAreString) << CborErrorMapKeyNotString; + QTest::newRow("excluded-map-simple-0") << raw("\xa1\xe0\1") << int(CborValidateMapKeysAreString) << CborErrorMapKeyNotString; + QTest::newRow("excluded-map-false") << raw("\xa1\xf4\1") << int(CborValidateMapKeysAreString) << CborErrorMapKeyNotString; + QTest::newRow("excluded-map-true") << raw("\xa1\xf5\1") << int(CborValidateMapKeysAreString) << CborErrorMapKeyNotString; + QTest::newRow("excluded-map-null") << raw("\xa1\xf6\1") << int(CborValidateMapKeysAreString) << CborErrorMapKeyNotString; + QTest::newRow("excluded-map-undefined") << raw("\xa1\xf7\1") << int(CborValidateMapKeysAreString) << CborErrorMapKeyNotString; + QTest::newRow("excluded-map-half") << raw("\xa1\xf9\0\0\1") << int(CborValidateMapKeysAreString) << CborErrorMapKeyNotString; + QTest::newRow("excluded-map-float") << raw("\xa1\xfa\0\0\0\0\1") << int(CborValidateMapKeysAreString) << CborErrorMapKeyNotString; + QTest::newRow("excluded-map-double") << raw("\xa1\xfb\0\0\0\0\0\0\0\0\1") << int(CborValidateMapKeysAreString) << CborErrorMapKeyNotString; + + // unknown simple types + QTest::newRow("unknown-simple-type-0") << raw("\xe0") << int(CborValidateNoUnknownSimpleTypes) << CborErrorUnknownSimpleType; + QTest::newRow("unknown-simple-type-32") << raw("\xf8\x20") << int(CborValidateNoUnknownSimpleTypes) << CborErrorUnknownSimpleType; + QTest::newRow("allowed-simple-type-32") << raw("\xf8\x20") << int(CborValidateNoUnknownSimpleTypesSA) << CborNoError; + + // unknown tags + QTest::newRow("unknown-tag-6") << raw("\xc6\x60") << int(CborValidateNoUnknownTags) << CborErrorUnknownTag; + QTest::newRow("unknown-tag-31") << raw("\xd8\x1f\x60") << int(CborValidateNoUnknownTags) << CborErrorUnknownTag; + QTest::newRow("unknown-tag-256") << raw("\xd9\1\0\x60") << int(CborValidateNoUnknownTags) << CborErrorUnknownTag; + QTest::newRow("unknown-tag-65536") << raw("\xda\0\1\0\0\x60") << int(CborValidateNoUnknownTags) << CborErrorUnknownTag; + QTest::newRow("unknown-tag-4294967296") << raw("\xdb\0\0\0\1\0\0\0\0\x60") << int(CborValidateNoUnknownTags) << CborErrorUnknownTag; + QTest::newRow("allowed-tag-31") << raw("\xd8\x1f\x60") << int(CborValidateNoUnknownTagsSA) << CborNoError; + QTest::newRow("allowed-tag-256") << raw("\xd9\1\0\x60") << int(CborValidateNoUnknownTagsSR) << CborNoError; + + // excluded tags + QTest::newRow("excluded-tag-0") << raw("\xc0\x60") << int(CborValidateNoTags) << CborErrorExcludedType; + QTest::newRow("excluded-tag-24") << raw("\xd8\x18\x40") << int(CborValidateNoTags) << CborErrorExcludedType; + QTest::newRow("excluded-tag-55799") << raw("\xd9\xd9\xf7\x60") << int(CborValidateNoTags) << CborErrorExcludedType; + + // complete data + QTest::newRow("garbage-data-0") << raw("\0\1") << int(CborValidateCompleteData) << CborErrorGarbageAtEnd; + QTest::newRow("garbage-data-1") << raw("\x20\1") << int(CborValidateCompleteData) << CborErrorGarbageAtEnd; + QTest::newRow("garbage-data-2") << raw("\x40\1") << int(CborValidateCompleteData) << CborErrorGarbageAtEnd; + QTest::newRow("garbage-data-3") << raw("\x60\1") << int(CborValidateCompleteData) << CborErrorGarbageAtEnd; + QTest::newRow("garbage-data-4") << raw("\x80\1") << int(CborValidateCompleteData) << CborErrorGarbageAtEnd; + QTest::newRow("garbage-data-5") << raw("\xa0\1") << int(CborValidateCompleteData) << CborErrorGarbageAtEnd; + QTest::newRow("garbage-data-6") << raw("\xc0\x60\1") << int(CborValidateCompleteData) << CborErrorGarbageAtEnd; + QTest::newRow("garbage-data-7") << raw("\xf4\1") << int(CborValidateCompleteData) << CborErrorGarbageAtEnd; + QTest::newRow("garbage-data-f16") << raw("\xf9\0\0\1") << int(CborValidateCompleteData) << CborErrorGarbageAtEnd; + QTest::newRow("garbage-data-f32") << raw("\xfa\0\0\0\0\1") << int(CborValidateCompleteData) << CborErrorGarbageAtEnd; + QTest::newRow("garbage-data-f64") << raw("\xfb\0\0\0\0\0\0\0\0\1") << int(CborValidateCompleteData) << CborErrorGarbageAtEnd; +} + +void tst_Parser::strictValidation() +{ + QFETCH(QByteArray, data); + QFETCH(int, flags); + QFETCH(CborError, expectedError); + + QString decoded; + ParserWrapper w; + CborError err = w.init(data); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + + err = cbor_value_validate(&w.first, flags); + QCOMPARE(err, expectedError); +} + +void tst_Parser::incompleteData_data() +{ + addColumns(); + addFixedData(); + addStringsData(); + addTagsData(); + addMapMixedData(); +} + +void tst_Parser::incompleteData() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + + for (int len = 0; len < data.length() - 1; ++len) { + ParserWrapper w; + CborError err = w.init(data.constData(), len); + if (!err) { + QString decoded; + err = parseOne(&w.first, &decoded); + } + if (err != CborErrorUnexpectedEOF) + qDebug() << "Length is" << len; + QCOMPARE(err, CborErrorUnexpectedEOF); + } +} + +void tst_Parser::endPointer_data() +{ + QTest::addColumn<QByteArray>("data"); + QTest::addColumn<int>("offset"); + + QTest::newRow("number1") << raw("\x81\x01\x01") << 2; + QTest::newRow("number24") << raw("\x81\x18\x18\x01") << 3; + QTest::newRow("string") << raw("\x81\x61Z\x01") << 3; + QTest::newRow("indefinite-string") << raw("\x81\x7f\x61Z\xff\x01") << 5; + QTest::newRow("array") << raw("\x81\x02\x01") << 2; + QTest::newRow("indefinite-array") << raw("\x81\x9f\x02\xff\x01") << 4; + QTest::newRow("object") << raw("\x81\xa1\x03\x02\x01") << 4; + QTest::newRow("indefinite-object") << raw("\x81\xbf\x03\x02\xff\x01") << 5; +} + +void tst_Parser::endPointer() +{ + QFETCH(QByteArray, data); + QFETCH(int, offset); + + QString decoded; + ParserWrapper w; + CborError err = w.init(data); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + + err = parseOne(&w.first, &decoded); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + QCOMPARE(int(cbor_value_get_next_byte(&w.first) - w.begin()), offset); +} + +void tst_Parser::recursionLimit_data() +{ + static const int recursions = CBOR_PARSER_MAX_RECURSIONS + 2; + QTest::addColumn<QByteArray>("data"); + + QTest::newRow("array") << QByteArray(recursions, '\x81') + '\x20'; + QTest::newRow("_array") << QByteArray(recursions, '\x9f') + '\x20' + QByteArray(recursions, '\xff'); + + QByteArray data; + for (int i = 0; i < recursions; ++i) + data += "\xa1\x65Hello"; + data += '\2'; + QTest::newRow("map-recursive-values") << data; + + data.clear(); + for (int i = 0; i < recursions; ++i) + data += "\xbf\x65World"; + data += '\2'; + for (int i = 0; i < recursions; ++i) + data += "\xff"; + QTest::newRow("_map-recursive-values") << data; + + data = QByteArray(recursions, '\xa1'); + data += '\2'; + for (int i = 0; i < recursions; ++i) + data += "\x7f\x64quux\xff"; + QTest::newRow("map-recursive-keys") << data; + + data = QByteArray(recursions, '\xbf'); + data += '\2'; + for (int i = 0; i < recursions; ++i) + data += "\1\xff"; + QTest::newRow("_map-recursive-keys") << data; + + data.clear(); + for (int i = 0; i < recursions / 2; ++i) + data += "\x81\xa1\1"; + data += '\2'; + QTest::newRow("mixed") << data; +} + +void tst_Parser::recursionLimit() +{ + QFETCH(QByteArray, data); + + ParserWrapper w; + CborError err = w.init(data); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + + // check that it is valid: + CborValue it = w.first; + { + QString dummy; + err = parseOne(&it, &dummy); + QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\""); + } + + it = w.first; + err = cbor_value_advance(&it); + QCOMPARE(err, CborErrorNestingTooDeep); + + it = w.first; + if (cbor_value_is_map(&it)) { + CborValue dummy; + err = cbor_value_map_find_value(&it, "foo", &dummy); + QCOMPARE(err, CborErrorNestingTooDeep); + } +} + +QTEST_MAIN(tst_Parser) +#include "tst_parser.moc" diff --git a/lib/cbor/tinycbor/tests/tests.pro b/lib/cbor/tinycbor/tests/tests.pro new file mode 100644 index 00000000..6036f0f9 --- /dev/null +++ b/lib/cbor/tinycbor/tests/tests.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs +SUBDIRS = parser encoder c90 cpp tojson +msvc: SUBDIRS -= tojson diff --git a/lib/cbor/tinycbor/tests/tojson/tojson.pro b/lib/cbor/tinycbor/tests/tojson/tojson.pro new file mode 100644 index 00000000..b4226520 --- /dev/null +++ b/lib/cbor/tinycbor/tests/tojson/tojson.pro @@ -0,0 +1,8 @@ +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/lib/cbor/tinycbor/tests/tojson/tst_tojson.cpp b/lib/cbor/tinycbor/tests/tojson/tst_tojson.cpp new file mode 100644 index 00000000..89b5d129 --- /dev/null +++ b/lib/cbor/tinycbor/tests/tojson/tst_tojson.cpp @@ -0,0 +1,721 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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. +** +****************************************************************************/ + +#include <QtTest> +#include "cbor.h" +#include "cborjson.h" +#include <locale.h> + +extern "C" FILE *open_memstream(char **bufptr, size_t *sizeptr); + +class tst_ToJson : public QObject +{ + Q_OBJECT +private slots: + void initTestCase(); + + void fixed_data(); + void fixed(); + void textstrings_data(); + void textstrings() { fixed(); } + void nonjson_data(); + void nonjson() { fixed(); } + void bytestrings_data(); + void bytestrings() { fixed(); } + void emptyContainers_data(); + void emptyContainers() { fixed(); } + void arrays_data(); + void arrays(); + void nestedArrays_data() { arrays_data(); } + void nestedArrays(); + void maps_data() { arrays_data(); } + void maps(); + void nestedMaps_data() { maps_data(); } + void nestedMaps(); + void nonStringKeyMaps_data(); + void nonStringKeyMaps(); + + void tagsToObjects_data(); + void tagsToObjects(); + void taggedByteStringsToBase16_data(); + void taggedByteStringsToBase16(); + void taggedByteStringsToBase64_data() { taggedByteStringsToBase16_data(); } + void taggedByteStringsToBase64(); + void taggedByteStringsToBigNum_data() { taggedByteStringsToBase16_data(); } + void taggedByteStringsToBigNum(); + void otherTags_data(); + void otherTags(); + + void metaData_data(); + void metaData(); + void metaDataAndTagsToObjects_data() { tagsToObjects_data(); } + void metaDataAndTagsToObjects(); + void metaDataForKeys_data(); + void metaDataForKeys(); +}; +#include "tst_tojson.moc" + +template <size_t N> QByteArray raw(const char (&data)[N]) +{ + return QByteArray::fromRawData(data, N - 1); +} + +void addColumns() +{ + QTest::addColumn<QByteArray>("data"); + QTest::addColumn<QString>("expected"); +} + +void addFixedData() +{ + // unsigned integers + 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"; + + // 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("false") << raw("\xf4") << "false"; + QTest::newRow("true") << raw("\xf5") << "true"; + QTest::newRow("null") << raw("\xf6") << "null"; + + QTest::newRow("0.f16") << raw("\xf9\0\0") << "0"; + QTest::newRow("0.f") << raw("\xfa\0\0\0\0") << "0"; + QTest::newRow("0.") << raw("\xfb\0\0\0\0\0\0\0\0") << "0"; + QTest::newRow("-1.f16") << raw("\xf9\xbc\x00") << "-1"; + QTest::newRow("-1.f") << raw("\xfa\xbf\x80\0\0") << "-1"; + QTest::newRow("-1.") << raw("\xfb\xbf\xf0\0\0\0\0\0\0") << "-1"; + QTest::newRow("16777215.f") << raw("\xfa\x4b\x7f\xff\xff") << "16777215"; + QTest::newRow("16777215.") << raw("\xfb\x41\x6f\xff\xff\xe0\0\0\0") << "16777215"; + QTest::newRow("-16777215.f") << raw("\xfa\xcb\x7f\xff\xff") << "-16777215"; + QTest::newRow("-16777215.") << raw("\xfb\xc1\x6f\xff\xff\xe0\0\0\0") << "-16777215"; + + QTest::newRow("0.5f16") << raw("\xf9\x38\0") << "0.5"; + QTest::newRow("0.5f") << raw("\xfa\x3f\0\0\0") << "0.5"; + QTest::newRow("0.5") << raw("\xfb\x3f\xe0\0\0\0\0\0\0") << "0.5"; + QTest::newRow("2.f^24-1") << raw("\xfa\x4b\x7f\xff\xff") << "16777215"; + QTest::newRow("2.^53-1") << raw("\xfb\x43\x3f\xff\xff""\xff\xff\xff\xff") << "9007199254740991"; + QTest::newRow("2.f^64-epsilon") << raw("\xfa\x5f\x7f\xff\xff") << "18446742974197923840"; + QTest::newRow("2.^64-epsilon") << raw("\xfb\x43\xef\xff\xff""\xff\xff\xff\xff") << "18446744073709549568"; + QTest::newRow("2.f^64") << raw("\xfa\x5f\x80\0\0") << "1.8446744073709552e+19"; + QTest::newRow("2.^64") << raw("\xfb\x43\xf0\0\0\0\0\0\0") << "1.8446744073709552e+19"; + + // infinities and NaN are not supported in JSON, they convert to null + QTest::newRow("nan_f16") << raw("\xf9\x7e\x00") << "null"; + QTest::newRow("nan_f") << raw("\xfa\x7f\xc0\0\0") << "null"; + QTest::newRow("nan") << raw("\xfb\x7f\xf8\0\0\0\0\0\0") << "null"; + QTest::newRow("-inf_f") << raw("\xfa\xff\x80\0\0") << "null"; + QTest::newRow("-inf_f16") << raw("\xf9\xfc\x00") << "null"; + QTest::newRow("-inf") << raw("\xfb\xff\xf0\0\0\0\0\0\0") << "null"; + QTest::newRow("+inf_f") << raw("\xfa\x7f\x80\0\0") << "null"; + QTest::newRow("+inf_f16") << raw("\xf9\x7c\x00") << "null"; + QTest::newRow("+inf") << raw("\xfb\x7f\xf0\0\0\0\0\0\0") << "null"; +} + +void addTextStringsData() +{ + QTest::newRow("emptytextstring") << raw("\x60") << "\"\""; + QTest::newRow("textstring1") << raw("\x61 ") << "\" \""; + QTest::newRow("textstring5") << raw("\x65Hello") << "\"Hello\""; + QTest::newRow("textstring24") << raw("\x78\x18""123456789012345678901234") + << "\"123456789012345678901234\""; + QTest::newRow("textstring256") << raw("\x79\1\0") + QByteArray(256, '3') + << '"' + QString(256, '3') + '"'; + + // strings with undefined length + QTest::newRow("_emptytextstring") << raw("\x7f\xff") << "\"\""; + QTest::newRow("_emptytextstring2") << raw("\x7f\x60\xff") << "\"\""; + QTest::newRow("_emptytextstring3") << raw("\x7f\x60\x60\xff") << "\"\""; + 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\""; +} + +void addNonJsonData() +{ + QTest::newRow("undefined") << raw("\xf7") << "\"undefined\""; + QTest::newRow("simple0") << raw("\xe0") << "\"simple(0)\""; + QTest::newRow("simple19") << raw("\xf3") << "\"simple(19)\""; + QTest::newRow("simple32") << raw("\xf8\x20") << "\"simple(32)\""; + QTest::newRow("simple255") << raw("\xf8\xff") << "\"simple(255)\""; +} + +void addByteStringsData() +{ + QTest::newRow("emptybytestring") << raw("\x40") << "\"\""; + QTest::newRow("bytestring1") << raw("\x41 ") << "\"IA\""; + QTest::newRow("bytestring1-nul") << raw("\x41\0") << "\"AA\""; + QTest::newRow("bytestring2") << raw("\x42Hi") << "\"SGk\""; + QTest::newRow("bytestring3") << raw("\x43Hey") << "\"SGV5\""; + QTest::newRow("bytestring4") << raw("\x44Hola") << "\"SG9sYQ\""; + QTest::newRow("bytestring5") << raw("\x45Hello") << "\"SGVsbG8\""; + QTest::newRow("bytestring24") << raw("\x58\x18""123456789012345678901234") + << "\"MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0\""; + + // strings with undefined length + QTest::newRow("_emptybytestring") << raw("\x5f\xff") << "\"\""; + QTest::newRow("_emptybytestring2") << raw("\x5f\x40\xff") << "\"\""; + QTest::newRow("_emptybytestring3") << raw("\x5f\x40\x40\xff") << "\"\""; + QTest::newRow("_bytestring5*2") << raw("\x5f\x43Hel\x42lo\xff") << "\"SGVsbG8\""; + QTest::newRow("_bytestring5*5") << raw("\x5f\x41H\x41""e\x41l\x41l\x41o\xff") << "\"SGVsbG8\""; + QTest::newRow("_bytestring5*6") << raw("\x5f\x41H\x41""e\x40\x41l\x41l\x41o\xff") << "\"SGVsbG8\""; +} + +void addEmptyContainersData() +{ + QTest::newRow("emptyarray") << raw("\x80") << "[]"; + QTest::newRow("emptymap") << raw("\xa0") << "{}"; + QTest::newRow("_emptyarray") << raw("\x9f\xff") << "[]"; + QTest::newRow("_emptymap") << raw("\xbf\xff") << "{}"; +} + +CborError parseOne(CborValue *it, QString *parsed, int flags) +{ + char *buffer; + size_t size; + + FILE *f = open_memstream(&buffer, &size); + CborError err = cbor_value_to_json_advance(f, it, flags); + fclose(f); + + *parsed = QString::fromLatin1(buffer); + free(buffer); + return err; +} + +bool compareFailed = true; +void compareOne_real(const QByteArray &data, const QString &expected, int flags, int line) +{ + compareFailed = true; + CborParser parser; + CborValue first; + CborError err = cbor_parser_init(reinterpret_cast<const quint8 *>(data.constData()), data.length(), 0, &parser, &first); + QVERIFY2(!err, QByteArray::number(line) + ": Got error \"" + cbor_error_string(err) + "\""); + + QString decoded; + err = parseOne(&first, &decoded, flags); + QVERIFY2(!err, QByteArray::number(line) + ": Got error \"" + cbor_error_string(err) + + "\"; decoded stream:\n" + decoded.toLatin1()); + QCOMPARE(decoded, expected); + + // check that we consumed everything + QCOMPARE((void*)cbor_value_get_next_byte(&first), (void*)data.constEnd()); + + compareFailed = false; +} +#define compareOne(data, expected, flags) \ + compareOne_real(data, expected, flags, __LINE__); \ + if (compareFailed) return + +void tst_ToJson::initTestCase() +{ + setlocale(LC_ALL, "C"); +} + +void tst_ToJson::fixed_data() +{ + addColumns(); + addFixedData(); +} + +void tst_ToJson::fixed() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + + compareOne(data, expected, 0); +} + +void tst_ToJson::textstrings_data() +{ + addColumns(); + addTextStringsData(); +} + +void tst_ToJson::nonjson_data() +{ + addColumns(); + addNonJsonData(); +} + +void tst_ToJson::bytestrings_data() +{ + addColumns(); + addByteStringsData(); +} + +void tst_ToJson::emptyContainers_data() +{ + addColumns(); + addEmptyContainersData(); +} + +void tst_ToJson::arrays_data() +{ + addColumns(); + addFixedData(); + addTextStringsData(); + addNonJsonData(); + addByteStringsData(); +} + +void tst_ToJson::arrays() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + + compareOne("\x81" + data, '[' + expected + ']', 0); + compareOne("\x82" + data + data, '[' + expected + ',' + expected + ']', 0); +} + +void tst_ToJson::nestedArrays() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + + compareOne("\x81\x81" + data, "[[" + expected + "]]", 0); + compareOne("\x81\x81\x81" + data, "[[[" + expected + "]]]", 0); + compareOne("\x81\x82" + data + data, "[[" + expected + ',' + expected + "]]", 0); + compareOne("\x82\x81" + data + data, "[[" + expected + "]," + expected + "]", 0); + compareOne("\x82\x81" + data + '\x81' + data, "[[" + expected + "],[" + expected + "]]", 0); +} + +void tst_ToJson::maps() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + + compareOne("\xa1\x65" "Hello" + data, "{\"Hello\":" + expected + '}', 0); +} + +void tst_ToJson::nestedMaps() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + + compareOne("\xa1\x65Hello\xa1\x65World" + data, "{\"Hello\":{\"World\":" + expected + "}}", 0); +// compareOne("\xa1\x63""foo\xa1\63""bar" + data + "\63""baz\xa1\x64quux" + data, +// "{\"foo\":{\"bar\":" + expected + "},\"baz\":{\"quux\":" + expected + "}", 0); +} + +void tst_ToJson::nonStringKeyMaps_data() +{ + addColumns(); + + QTest::newRow("0") << raw("\x00") << "0"; + QTest::newRow("1") << raw("\x01") << "1"; + QTest::newRow("UINT32_MAX") << raw("\x1a\xff\xff\xff\xff") << "4294967295"; + QTest::newRow("UINT32_MAX+1") << raw("\x1b\0\0\0\1\0\0\0\0") << "4294967296"; + QTest::newRow("UINT64_MAX") << raw("\x1b" "\xff\xff\xff\xff" "\xff\xff\xff\xff") + << QString::number(std::numeric_limits<uint64_t>::max()); + + QTest::newRow("-1") << raw("\x20") << "-1"; + QTest::newRow("-UINT32_MAX") << raw("\x3a\xff\xff\xff\xff") << "-4294967296"; + QTest::newRow("-UINT32_MAX-1") << raw("\x3b\0\0\0\1\0\0\0\0") << "-4294967297"; + QTest::newRow("-UINT64_MAX") << raw("\x3b" "\xff\xff\xff\xff" "\xff\xff\xff\xfe") + << '-' + QString::number(std::numeric_limits<uint64_t>::max()); + QTest::newRow("-UINT64_MAX-1") << raw("\x3b" "\xff\xff\xff\xff" "\xff\xff\xff\xff") + << "-18446744073709551616"; + + QTest::newRow("simple0") << raw("\xe0") << "simple(0)"; + QTest::newRow("simple19") << raw("\xf3") << "simple(19)"; + QTest::newRow("false") << raw("\xf4") << "false"; + QTest::newRow("true") << raw("\xf5") << "true"; + QTest::newRow("null") << raw("\xf6") << "null"; + QTest::newRow("undefined") << raw("\xf7") << "undefined"; + QTest::newRow("simple32") << raw("\xf8\x20") << "simple(32)"; + QTest::newRow("simple255") << raw("\xf8\xff") << "simple(255)"; + + QTest::newRow("0.f16") << raw("\xf9\0\0") << "0.f16"; + QTest::newRow("0.f") << raw("\xfa\0\0\0\0") << "0.f"; + QTest::newRow("0.") << raw("\xfb\0\0\0\0\0\0\0\0") << "0."; + QTest::newRow("-1.f16") << raw("\xf9\xbc\x00") << "-1.f16"; + QTest::newRow("-1.f") << raw("\xfa\xbf\x80\0\0") << "-1.f"; + QTest::newRow("-1.") << raw("\xfb\xbf\xf0\0\0\0\0\0\0") << "-1."; + QTest::newRow("65504.f16") << raw("\xf9\x7b\xff") << "65504.f16"; + QTest::newRow("16777215.f") << raw("\xfa\x4b\x7f\xff\xff") << "16777215.f"; + QTest::newRow("16777215.") << raw("\xfb\x41\x6f\xff\xff\xe0\0\0\0") << "16777215."; + QTest::newRow("-16777215.f") << raw("\xfa\xcb\x7f\xff\xff") << "-16777215.f"; + QTest::newRow("-16777215.") << raw("\xfb\xc1\x6f\xff\xff\xe0\0\0\0") << "-16777215."; + + QTest::newRow("0.5f16") << raw("\xf9\x38\0") << "0.5f16"; + QTest::newRow("0.5f") << raw("\xfa\x3f\0\0\0") << "0.5f"; + QTest::newRow("0.5") << raw("\xfb\x3f\xe0\0\0\0\0\0\0") << "0.5"; + QTest::newRow("2.f16^11-1") << raw("\xf9\x67\xff") << "2047.f16"; + QTest::newRow("2.f^24-1") << raw("\xfa\x4b\x7f\xff\xff") << "16777215.f"; + QTest::newRow("2.^53-1") << raw("\xfb\x43\x3f\xff\xff""\xff\xff\xff\xff") << "9007199254740991."; + QTest::newRow("2.f^64-epsilon") << raw("\xfa\x5f\x7f\xff\xff") << "18446742974197923840.f"; + QTest::newRow("2.^64-epsilon") << raw("\xfb\x43\xef\xff\xff""\xff\xff\xff\xff") << "18446744073709549568."; + QTest::newRow("2.f^64") << raw("\xfa\x5f\x80\0\0") << "1.8446744073709552e+19f"; + QTest::newRow("2.^64") << raw("\xfb\x43\xf0\0\0\0\0\0\0") << "1.8446744073709552e+19"; + + QTest::newRow("nan_f16") << raw("\xf9\x7e\x00") << "nan"; + QTest::newRow("nan_f") << raw("\xfa\x7f\xc0\0\0") << "nan"; + QTest::newRow("nan") << raw("\xfb\x7f\xf8\0\0\0\0\0\0") << "nan"; + QTest::newRow("-inf_f16") << raw("\xf9\xfc\x00") << "-inf"; + QTest::newRow("-inf_f") << raw("\xfa\xff\x80\0\0") << "-inf"; + QTest::newRow("-inf") << raw("\xfb\xff\xf0\0\0\0\0\0\0") << "-inf"; + QTest::newRow("+inf_f16") << raw("\xf9\x7c\x00") << "inf"; + QTest::newRow("+inf_f") << raw("\xfa\x7f\x80\0\0") << "inf"; + QTest::newRow("+inf") << raw("\xfb\x7f\xf0\0\0\0\0\0\0") << "inf"; + + QTest::newRow("emptybytestring") << raw("\x40") << "h''"; + QTest::newRow("bytestring1") << raw("\x41 ") << "h'20'"; + QTest::newRow("bytestring1-nul") << raw("\x41\0") << "h'00'"; + QTest::newRow("bytestring5") << raw("\x45Hello") << "h'48656c6c6f'"; + QTest::newRow("bytestring24") << raw("\x58\x18""123456789012345678901234") + << "h'313233343536373839303132333435363738393031323334'"; + + QTest::newRow("tag0") << raw("\xc0\x00") << "0(0)"; + QTest::newRow("tag1") << raw("\xc1\x00") << "1(0)"; + QTest::newRow("tag24") << raw("\xd8\x18\x00") << "24(0)"; + QTest::newRow("tagUINT64_MAX") << raw("\xdb" "\xff\xff\xff\xff" "\xff\xff\xff\xff" "\x00") + << QString::number(std::numeric_limits<uint64_t>::max()) + "(0)"; + + QTest::newRow("emptyarray") << raw("\x80") << "[]"; + QTest::newRow("emptymap") << raw("\xa0") << "{}"; + QTest::newRow("_emptyarray") << raw("\x9f\xff") << "[_ ]"; + QTest::newRow("_emptymap") << raw("\xbf\xff") << "{_ }"; + + QTest::newRow("map-0-24") << raw("\xa1\0\x18\x18") << "{0: 24}"; + 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}"; +} + +void tst_ToJson::nonStringKeyMaps() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + + data = "\xa1" + data + "\1"; + compareOne(data, "{\"" + expected + "\":1}", CborConvertStringifyMapKeys); + + // and verify that they fail if we use CborConvertRequireMapStringKeys + CborParser parser; + CborValue first; + QString decoded; + cbor_parser_init(reinterpret_cast<const quint8 *>(data.constData()), data.length(), 0, &parser, &first); + CborError err = parseOne(&first, &decoded, CborConvertRequireMapStringKeys); + QCOMPARE(err, CborErrorJsonObjectKeyNotString); +} + +void tst_ToJson::tagsToObjects_data() +{ + addColumns(); + QTest::newRow("0(0)") << raw("\xc0\0") << "{\"tag0\":0}"; + QTest::newRow("0(-1)") << raw("\xc0\x20") << "{\"tag0\":-1}"; + QTest::newRow("0(\"hello\")") << raw("\xc0\x65hello") << "{\"tag0\":\"hello\"}"; + QTest::newRow("22(h'48656c6c6f')") << raw("\xd6\x45Hello") << "{\"tag22\":\"SGVsbG8\"}"; + QTest::newRow("0([1,2,3])") << raw("\xc0\x83\1\2\3") << "{\"tag0\":[1,2,3]}"; + QTest::newRow("0({\"z\":true,\"y\":1})") << raw("\xc0\xa2\x61z\xf5\x61y\1") << "{\"tag0\":{\"z\":true,\"y\":1}}"; + + // large tags + QTest::newRow("55799(0)") << raw("\xd9\xd9\xf7\0") << "{\"tag55799\":0}"; + QTest::newRow("4294967295") << raw("\xda\xff\xff\xff\xff\0") << "{\"tag4294967295\":0}"; + QTest::newRow("18446744073709551615(0)") << raw("\xdb\xff\xff\xff\xff""\xff\xff\xff\xff\0") + << "{\"tag18446744073709551615\":0}"; + + // nested tags + QTest::newRow("0(1(2))") << raw("\xc0\xc1\2") << "{\"tag0\":{\"tag1\":2}}"; + QTest::newRow("0({\"z\":1(2)})") << raw("\xc0\xa1\x61z\xc1\2") << "{\"tag0\":{\"z\":{\"tag1\":2}}}"; +} + +void tst_ToJson::tagsToObjects() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + + compareOne(data, expected, CborConvertTagsToObjects); +} + +void tst_ToJson::taggedByteStringsToBase16_data() +{ + QTest::addColumn<QByteArray>("data"); + QTest::addColumn<QString>("base64url"); + QTest::addColumn<QString>("base64"); + QTest::addColumn<QString>("base16"); + + QTest::newRow("emptybytestring") << raw("\x40") << "" << "" << ""; + QTest::newRow("bytestring1") << raw("\x41 ") << "IA" << "IA==" << "20"; + QTest::newRow("bytestring1-nul") << raw("\x41\0") << "AA" << "AA==" << "00"; + QTest::newRow("bytestring1-ff") << raw("\x41\xff") << "_w" << "/w==" << "ff"; + QTest::newRow("bytestring2") << raw("\x42Hi") << "SGk" << "SGk=" << "4869"; + QTest::newRow("bytestring3") << raw("\x43Hey") << "SGV5" << "SGV5" << "486579"; + QTest::newRow("bytestring4") << raw("\x44Hola") << "SG9sYQ" << "SG9sYQ==" << "486f6c61"; + QTest::newRow("bytestring5") << raw("\x45Hello") << "SGVsbG8" << "SGVsbG8=" << "48656c6c6f"; + QTest::newRow("bytestring24") << raw("\x58\x18""123456789012345678901234") + << "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0" + << "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0" + << "313233343536373839303132333435363738393031323334"; + + // strings with undefined length + QTest::newRow("_emptybytestring") << raw("\x5f\xff") << "" << "" << ""; + QTest::newRow("_emptybytestring2") << raw("\x5f\x40\xff") << "" << "" << ""; + QTest::newRow("_emptybytestring3") << raw("\x5f\x40\x40\xff") << "" << "" << ""; + QTest::newRow("_bytestring5*2") << raw("\x5f\x43Hel\x42lo\xff") << "SGVsbG8" << "SGVsbG8=" << "48656c6c6f"; + QTest::newRow("_bytestring5*5") << raw("\x5f\x41H\x41""e\x41l\x41l\x41o\xff") + << "SGVsbG8" << "SGVsbG8=" << "48656c6c6f"; + QTest::newRow("_bytestring5*6") << raw("\x5f\x41H\x41""e\x40\x41l\x41l\x41o\xff") + << "SGVsbG8" << "SGVsbG8=" << "48656c6c6f"; +} + +void tst_ToJson::taggedByteStringsToBase16() +{ + QFETCH(QByteArray, data); + QFETCH(QString, base16); + + compareOne('\xd7' + data, '"' + base16 + '"', 0); +} + +void tst_ToJson::taggedByteStringsToBase64() +{ + QFETCH(QByteArray, data); + QFETCH(QString, base64); + + compareOne('\xd6' + data, '"' + base64 + '"', 0); +} + +void tst_ToJson::taggedByteStringsToBigNum() +{ + QFETCH(QByteArray, data); + QFETCH(QString, base64url); + + compareOne('\xc3' + data, "\"~" + base64url + '"', 0); +} + +void tst_ToJson::otherTags_data() +{ + addColumns(); + addFixedData(); + addTextStringsData(); + addNonJsonData(); + addByteStringsData(); + addEmptyContainersData(); +} + +void tst_ToJson::otherTags() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + + // other tags produce no change in output + compareOne("\xc0" + data, expected, 0); + compareOne("\xc1" + data, expected, 0); + compareOne("\xc2" + data, expected, 0); + compareOne("\xc4" + data, expected, 0); + compareOne("\xc5" + data, expected, 0); + compareOne("\xd8\x20" + data, expected, 0); + compareOne("\xd8\x21" + data, expected, 0); + compareOne("\xd8\x22" + data, expected, 0); + compareOne("\xd8\x23" + data, expected, 0); + compareOne("\xd8\x24" + data, expected, 0); + compareOne("\xd9\xd9\xf7" + data, expected, 0); +} + +void tst_ToJson::metaData_data() +{ + addColumns(); + + // booleans, null, strings, double precision numbers, regular maps, arrays and integers that + // didn't get rounded don't have metadata + QTest::newRow("0") << raw("\x00") << QString(); + QTest::newRow("1") << raw("\x01") << QString(); + QTest::newRow("2^53-1") << raw("\x1b\0\x1f\xff\xff""\xff\xff\xff\xff") << QString(); + QTest::newRow("2^64-epsilon") << raw("\x1b\xff\xff\xff\xff""\xff\xff\xf8\x00") << QString(); + QTest::newRow("-1") << raw("\x20") << QString(); + QTest::newRow("-2") << raw("\x21") << QString(); + QTest::newRow("-2^53+1") << raw("\x3b\0\x1f\xff\xff""\xff\xff\xff\xfe") << QString(); + QTest::newRow("-2^64+epsilon") << raw("\x3b\xff\xff\xff\xff""\xff\xff\xf8\x00") << QString(); + QTest::newRow("emptytextstring") << raw("\x60") << QString(); + QTest::newRow("textstring1") << raw("\x61 ") << QString(); + QTest::newRow("0.5") << raw("\xfb\x3f\xe0\0\0\0\0\0\0") << QString(); + QTest::newRow("2.^64") << raw("\xfb\x43\xf0\0\0\0\0\0\0") << QString(); + QTest::newRow("false") << raw("\xf4") << QString(); + QTest::newRow("true") << raw("\xf5") << QString(); + QTest::newRow("null") << raw("\xf6") << QString(); + QTest::newRow("emptyarray") << raw("\x80") << QString(); + QTest::newRow("emptymap") << raw("\xa0") << QString(); + QTest::newRow("array*1") << raw("\x81\xf6") << QString(); + QTest::newRow("map*1") << raw("\xa1\x61z\xf4") << QString(); + + // ---- everything from here on has at least the type ---- + QTest::newRow("emptybytestring") << raw("\x40") << "\"t\":64"; + QTest::newRow("bytestring1") << raw("\x41 ") << "\"t\":64"; + QTest::newRow("undefined") << raw("\xf7") << "\"t\":247"; + QTest::newRow("0.f16") << raw("\xf9\0\0") << "\"t\":249"; + QTest::newRow("-1.f16") << raw("\xf9\xbc\x00") << "\"t\":249"; + QTest::newRow("0.f") << raw("\xfa\0\0\0\0") << "\"t\":250"; + QTest::newRow("-1.f") << raw("\xfa\xbf\x80\0\0") << "\"t\":250"; + QTest::newRow("16777215.f") << raw("\xfa\x4b\x7f\xff\xff") << "\"t\":250"; + QTest::newRow("-16777215.f") << raw("\xfa\xcb\x7f\xff\xff") << "\"t\":250"; + QTest::newRow("0.") << raw("\xfb\0\0\0\0\0\0\0\0") << "\"t\":251"; + QTest::newRow("-1.") << raw("\xfb\xbf\xf0\0\0\0\0\0\0") << "\"t\":251"; + QTest::newRow("16777215.") << raw("\xfb\x41\x6f\xff\xff\xe0\0\0\0") << "\"t\":251"; + QTest::newRow("-16777215.") << raw("\xfb\xc1\x6f\xff\xff\xe0\0\0\0") << "\"t\":251"; + 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"; + QTest::newRow("simple32") << raw("\xf8\x20") << "\"t\":224,\"v\":32"; + QTest::newRow("simple255") << raw("\xf8\xff") << "\"t\":224,\"v\":255"; + + // infinities and NaN are not supported in JSON, they convert to null + QTest::newRow("nan_f16") << raw("\xf9\x7e\x00") << "\"t\":249,\"v\":\"nan\""; + QTest::newRow("nan_f") << raw("\xfa\x7f\xc0\0\0") << "\"t\":250,\"v\":\"nan\""; + QTest::newRow("nan") << raw("\xfb\x7f\xf8\0\0\0\0\0\0") << "\"t\":251,\"v\":\"nan\""; + QTest::newRow("-inf_f16") << raw("\xf9\xfc\x00") << "\"t\":249,\"v\":\"-inf\""; + QTest::newRow("-inf_f") << raw("\xfa\xff\x80\0\0") << "\"t\":250,\"v\":\"-inf\""; + QTest::newRow("-inf") << raw("\xfb\xff\xf0\0\0\0\0\0\0") << "\"t\":251,\"v\":\"-inf\""; + QTest::newRow("+inf_f16") << raw("\xf9\x7c\x00") << "\"t\":249,\"v\":\"inf\""; + QTest::newRow("+inf_f") << raw("\xfa\x7f\x80\0\0") << "\"t\":250,\"v\":\"inf\""; + QTest::newRow("+inf") << raw("\xfb\x7f\xf0\0\0\0\0\0\0") << "\"t\":251,\"v\":\"inf\""; + + // tags on native types + QTest::newRow("tag+0") << raw("\xc0\x00") << "\"tag\":\"0\""; + QTest::newRow("tag+-2") << raw("\xc0\x21") << "\"tag\":\"0\""; + QTest::newRow("tag+0.5") << raw("\xc0\xfb\x3f\xe0\0\0\0\0\0\0") << "\"tag\":\"0\""; + QTest::newRow("tag+emptytextstring") << raw("\xc0\x60") << "\"tag\":\"0\""; + QTest::newRow("tag+textstring1") << raw("\xc0\x61 ") << "\"tag\":\"0\""; + QTest::newRow("tag+false") << raw("\xc0\xf4") << "\"tag\":\"0\""; + QTest::newRow("tag+true") << raw("\xc0\xf5") << "\"tag\":\"0\""; + QTest::newRow("tag+null") << raw("\xc0\xf6") << "\"tag\":\"0\""; + QTest::newRow("tag+emptyarray") << raw("\xc0\x80") << "\"tag\":\"0\""; + QTest::newRow("tag+emptymap") << raw("\xc0\xa0") << "\"tag\":\"0\""; + QTest::newRow("tag+array*1") << raw("\xc0\x81\xf6") << "\"tag\":\"0\""; + QTest::newRow("tag+map*1") << raw("\xc0\xa1\x61z\xf4") << "\"tag\":\"0\""; + + // tags on non-native types + QTest::newRow("tag+emptybytestring") << raw("\xc0\x40") << "\"tag\":\"0\",\"t\":64"; + QTest::newRow("tag+bytestring1") << raw("\xc0\x41 ") << "\"tag\":\"0\",\"t\":64"; + QTest::newRow("tag+undefined") << raw("\xc0\xf7") << "\"tag\":\"0\",\"t\":247"; + QTest::newRow("tag+0.f") << raw("\xc0\xfa\0\0\0\0") << "\"tag\":\"0\",\"t\":250"; + QTest::newRow("tag+-1.f") << raw("\xc0\xfa\xbf\x80\0\0") << "\"tag\":\"0\",\"t\":250"; + QTest::newRow("tag+16777215.f") << raw("\xc0\xfa\x4b\x7f\xff\xff") << "\"tag\":\"0\",\"t\":250"; + QTest::newRow("tag+-16777215.f") << raw("\xc0\xfa\xcb\x7f\xff\xff") << "\"tag\":\"0\",\"t\":250"; + QTest::newRow("tag+0.") << raw("\xc0\xfb\0\0\0\0\0\0\0\0") << "\"tag\":\"0\",\"t\":251"; + QTest::newRow("tag+-1.") << raw("\xc0\xfb\xbf\xf0\0\0\0\0\0\0") << "\"tag\":\"0\",\"t\":251"; + QTest::newRow("tag+16777215.") << raw("\xc0\xfb\x41\x6f\xff\xff\xe0\0\0\0") << "\"tag\":\"0\",\"t\":251"; + QTest::newRow("tag+-16777215.") << raw("\xc0\xfb\xc1\x6f\xff\xff\xe0\0\0\0") << "\"tag\":\"0\",\"t\":251"; + + // big tags (don't fit in JS numbers) + QTest::newRow("bigtag1") << raw("\xdb\0\x20\0\0""\0\0\0\1\x60") << "\"tag\":\"9007199254740993\""; + QTest::newRow("bigtag2") << raw("\xdb\xff\xff\xff\xff""\xff\xff\xff\xfe\x60") + << "\"tag\":\"18446744073709551614\""; + + // specially-handled tags + QTest::newRow("negativebignum") << raw("\xc3\x41 ") << "\"tag\":\"3\",\"t\":64"; + QTest::newRow("base64") << raw("\xd6\x41 ") << "\"tag\":\"22\",\"t\":64"; + QTest::newRow("base16") << raw("\xd7\x41 ") << "\"tag\":\"23\",\"t\":64"; +} + +void compareMetaData(QByteArray data, const QString &expected, int otherFlags = 0) +{ + QString decoded; + + // needs to be in one map, with the entry called "v" + data = "\xa1\x61v" + data; + + { + CborParser parser; + CborValue first; + CborError err = cbor_parser_init(reinterpret_cast<const quint8 *>(data.constData()), data.length(), 0, &parser, &first); + QVERIFY2(!err, QByteArrayLiteral(": Got error \"") + cbor_error_string(err) + "\""); + + err = parseOne(&first, &decoded, CborConvertAddMetadata | otherFlags); + QVERIFY2(!err, QByteArrayLiteral(": Got error \"") + cbor_error_string(err) + + "\"; decoded stream:\n" + decoded.toLatin1()); + + // check that we consumed everything + QCOMPARE((void*)cbor_value_get_next_byte(&first), (void*)data.constEnd()); + } + + QVERIFY(decoded.startsWith("{\"v\":")); + QVERIFY(decoded.endsWith('}')); +// qDebug() << "was" << decoded; + + // extract just the metadata + static const char needle[] = "\"v$cbor\":{"; + int pos = decoded.indexOf(needle); + QCOMPARE(pos == -1, expected.isEmpty()); + if (pos != -1) { + decoded.chop(2); + decoded = std::move(decoded).mid(pos + strlen(needle)); + QCOMPARE(decoded, expected); + } +} + +void tst_ToJson::metaData() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + compareMetaData(data, expected); +} + +void tst_ToJson::metaDataAndTagsToObjects() +{ + QFETCH(QByteArray, data); + + // when a tag is converted to an object, the object gets metadata indicating it was a tag + compareMetaData(data, "\"t\":192", CborConvertTagsToObjects); +} + +void tst_ToJson::metaDataForKeys_data() +{ + nonStringKeyMaps_data(); + + // string keys generate no metadata + QTest::newRow("string") << raw("\x60") << QString(); +} + +void tst_ToJson::metaDataForKeys() +{ + QFETCH(QByteArray, data); + QFETCH(QString, expected); + if (expected.isEmpty()) + expected = "{\"\":false}"; + else + expected = "{\"" + expected + "\":false,\"" + expected + "$keycbordump\":true}"; + compareOne('\xa1' + data + '\xf4', expected, + CborConvertAddMetadata | CborConvertStringifyMapKeys); +} + +QTEST_MAIN(tst_ToJson) diff --git a/lib/cbor/tinycbor/tinycbor.pc.in b/lib/cbor/tinycbor/tinycbor.pc.in new file mode 100644 index 00000000..382779ab --- /dev/null +++ b/lib/cbor/tinycbor/tinycbor.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: TinyCBOR +Description: A tiny CBOR encoder and decoder library +Version: @version@ +Libs: -L${libdir} -ltinycbor +Libs.private: -lm +Cflags: -I${includedir}/tinycbor diff --git a/lib/cbor/tinycbor/tools/cbordump/cbordump.c b/lib/cbor/tinycbor/tools/cbordump/cbordump.c new file mode 100644 index 00000000..26268132 --- /dev/null +++ b/lib/cbor/tinycbor/tools/cbordump/cbordump.c @@ -0,0 +1,164 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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. +** +****************************************************************************/ + +#define _POSIX_C_SOURCE 200809L +#include "cbor.h" +#include "cborjson.h" +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +void *xrealloc(void *old, size_t size, const char *fname) +{ + old = realloc(old, size); + if (old == NULL) { + fprintf(stderr, "%s: %s\n", fname, strerror(errno)); + exit(EXIT_FAILURE); + } + return old; +} + +void printerror(CborError err, const char *fname) +{ + fprintf(stderr, "%s: %s\n", fname, cbor_error_string(err)); + exit(EXIT_FAILURE); +} + +void dumpFile(FILE *in, const char *fname, bool printJosn, int flags) +{ + static const size_t chunklen = 16 * 1024; + static size_t bufsize = 0; + static uint8_t *buffer = NULL; + + size_t buflen = 0; + do { + if (bufsize == buflen) + buffer = xrealloc(buffer, bufsize += chunklen, fname); + + size_t n = fread(buffer + buflen, 1, bufsize - buflen, in); + buflen += n; + if (n == 0) { + if (!ferror(in)) + continue; + fprintf(stderr, "%s: %s\n", fname, strerror(errno)); + exit(EXIT_FAILURE); + } + } while (!feof(in)); + + CborParser parser; + CborValue value; + CborError err = cbor_parser_init(buffer, buflen, 0, &parser, &value); + if (!err) { + if (printJosn) + err = cbor_value_to_json_advance(stdout, &value, flags); + else + err = cbor_value_to_pretty_advance_flags(stdout, &value, flags); + if (!err) + puts(""); + } + if (!err && cbor_value_get_next_byte(&value) != buffer + buflen) + err = CborErrorGarbageAtEnd; + if (err) + printerror(err, fname); +} + +int main(int argc, char **argv) +{ + bool printJson = false; + int json_flags = CborConvertDefaultFlags; + int cbor_flags = CborPrettyDefaultFlags; + int c; + while ((c = getopt(argc, argv, "MOSUcjhfn")) != -1) { + switch (c) { + case 'c': + printJson = false; + break; + case 'j': + printJson = true; + break; + + case 'f': + cbor_flags |= CborPrettyShowStringFragments; + break; + case 'n': + cbor_flags |= CborPrettyIndicateIndeterminateLength | CborPrettyNumericEncodingIndicators; + break; + + case 'M': + json_flags |= CborConvertAddMetadata; + break; + case 'O': + json_flags |= CborConvertTagsToObjects; + break; + case 'S': + json_flags |= CborConvertStringifyMapKeys; + break; + case 'U': + json_flags |= CborConvertByteStringsToBase64Url; + break; + + case '?': + fprintf(stderr, "Unknown option -%c.\n", optopt); + /* fall through */ + case 'h': + puts("Usage: cbordump [OPTION]... [FILE]...\n" + "Interprets FILEs as CBOR binary data and dumps the content to stdout.\n" + "\n" + "Options:\n" + " -c Print a CBOR dump (see RFC 7049) (default)\n" + " -j Print a JSON equivalent version\n" + " -h Print this help output and exit\n" + "When JSON output is active, the following options are recognized:\n" + " -M Add metadata so converting back to CBOR is possible\n" + " -O Convert CBOR tags to JSON objects\n" + " -S Stringify non-text string map keys\n" + " -U Convert all CBOR byte strings to Base64url regardless of tags\n" + "When CBOR dump is active, the following options are recognized:\n" + " -f Show text and byte string fragments\n" + " -n Show overlong encoding of CBOR numbers and length" + ""); + return c == '?' ? EXIT_FAILURE : EXIT_SUCCESS; + } + } + + char **fname = argv + optind; + if (!*fname) { + dumpFile(stdin, "-", printJson, printJson ? json_flags : cbor_flags); + } else { + for ( ; *fname; ++fname) { + FILE *in = fopen(*fname, "rb"); + if (!in) { + perror("open"); + return EXIT_FAILURE; + } + + dumpFile(in, *fname, printJson, printJson ? json_flags : cbor_flags); + fclose(in); + } + } + + return EXIT_SUCCESS; +} diff --git a/lib/cbor/tinycbor/tools/cbordump/cbordump.pro b/lib/cbor/tinycbor/tools/cbordump/cbordump.pro new file mode 100644 index 00000000..71ae6f76 --- /dev/null +++ b/lib/cbor/tinycbor/tools/cbordump/cbordump.pro @@ -0,0 +1,10 @@ +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/lib/cbor/tinycbor/tools/json2cbor/json2cbor.c b/lib/cbor/tinycbor/tools/json2cbor/json2cbor.c new file mode 100644 index 00000000..c13a7510 --- /dev/null +++ b/lib/cbor/tinycbor/tools/json2cbor/json2cbor.c @@ -0,0 +1,493 @@ +/**************************************************************************** +** +** 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. +** +****************************************************************************/ + +#define _POSIX_C_SOURCE 200809L +#define _GNU_SOURCE +#include "cbor.h" +#include "cborinternal_p.h" +#include "compilersupport_p.h" + +#include <cjson/cJSON.h> + +#include <errno.h> +#include <math.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static const char meta_data_marker[] = "$cbor"; +uint8_t *buffer; +size_t buffersize; +bool usingMetaData = false; + +struct MetaData { + CborTag tag; + union { + const char *v; + uint8_t simpleType; + }; + CborType t; + bool tagged; +}; + +uint8_t *decode_base64_generic(const char *string, size_t *len, const int8_t reverse_alphabet[256]) +{ + *len = ((strlen(string) + 3) & ~3) * 3 / 4; + uint8_t *buffer = malloc(*len); + if (buffer == NULL) + return NULL; + + uint8_t *out = buffer; + const uint8_t *in = (const uint8_t *)string; + bool done = false; + while (!done) { + if (reverse_alphabet[in[0]] < 0 || reverse_alphabet[in[1]] < 0) { + if (in[0] == '\0') + done = true; + break; + } + + uint32_t val = reverse_alphabet[in[0]] << 18; + val |= reverse_alphabet[in[1]] << 12; + if (in[2] == '=' || in[2] == '\0') { + if (in[2] == '=' && (in[3] != '=' || in[4] != '\0')) + break; + val >>= 12; + done = true; + } else if (in[3] == '=' || in[3] == '\0') { + if (in[3] == '=' && in[4] != '\0') + break; + val >>= 6; + val |= reverse_alphabet[in[2]]; + done = true; + } else { + val |= reverse_alphabet[in[2]] << 6; + val |= reverse_alphabet[in[3]]; + } + + *out++ = val >> 16; + *out++ = val >> 8; + *out++ = val; + in += 4; + } + + if (!done) { + free(buffer); + return NULL; + } + *len = out - buffer; + return buffer; +} + +uint8_t *decode_base64(const char *string, size_t *len) +{ + static const int8_t reverse_alphabet[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 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, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + }; + return decode_base64_generic(string, len, reverse_alphabet); +} + +uint8_t *decode_base64url(const char *string, size_t *len) +{ + static const int8_t reverse_alphabet[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 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, -1, -1, -1, 63, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + }; + return decode_base64_generic(string, len, reverse_alphabet); +} + +uint8_t *decode_base16(const char *string, size_t *len) +{ + size_t i; + *len = strlen(string) / 2; + uint8_t *buffer = malloc(*len); + if (buffer == NULL) + return NULL; + + for (i = 0; i < *len; ++i) { + char c = string[i * 2]; + if (c >= '0' && c <= '9') { + buffer[i] = (c - '0') << 4; + } else if ((c | 0x20) >= 'a' && (c | 0x20) <= 'f') { + buffer[i] = ((c | 0x20) - 'a' + 10) << 4; + } else { + free(buffer); + return NULL; + } + + c = string[i * 2 + 1]; + if (c >= '0' && c <= '9') { + buffer[i] |= (c - '0'); + } else if ((c | 0x20) >= 'a' && (c | 0x20) <= 'f') { + buffer[i] |= ((c | 0x20) - 'a' + 10); + } else { + free(buffer); + return NULL; + } + } + + return buffer; +} + +size_t get_cjson_size_limited(cJSON *container) +{ + // cJSON_GetArraySize is O(n), so don't go too far + unsigned s = 0; + cJSON *item; + for (item = container->child; item; item = item->next) { + if (++s > 255) + return CborIndefiniteLength; + } + return s; +} + +cJSON *get_meta_data(cJSON *object, cJSON *item) +{ + cJSON *meta; + char *metadatakey; + + if (asprintf(&metadatakey, "%s%s", item->string, meta_data_marker) < 0 || metadatakey == NULL) + return NULL; + meta = cJSON_GetObjectItem(object, metadatakey); + free(metadatakey); + return meta; +} + +struct MetaData parse_meta_data(cJSON *md) +{ + struct MetaData result = { 0, {NULL}, CborInvalidType, false }; + if (md == NULL || md->type != cJSON_Object) + return result; + + for (md = md->child; md; md = md->next) { + if (strcmp(md->string, "tag") == 0) { + if (md->type != cJSON_String || sscanf(md->valuestring, "%" PRIu64, &result.tag) < 0) + fprintf(stderr, "json2cbor: could not parse tag: %s\n", md->valuestring); + else + result.tagged = true; + } else if (strcmp(md->string, "t") == 0) { + result.t = md->valueint; + } else if (strcmp(md->string, "v") == 0) { + if (md->type == cJSON_Number) + result.simpleType = md->valueint; + else + result.v = md->valuestring; + } + } + return result; +} + +CborError decode_json(cJSON *json, CborEncoder *encoder); +CborError decode_json_with_metadata(cJSON *item, CborEncoder *encoder, struct MetaData md) +{ + switch (md.t) { + case CborIntegerType: { + // integer that has more than 53 bits of precision + uint64_t v; + bool positive = *md.v++ == '+'; + if (sscanf(md.v, "%" PRIx64, &v) < 0) { + fprintf(stderr, "json2cbor: could not parse number: %s\n", md.v); + break; + } + return positive ? cbor_encode_uint(encoder, v) : cbor_encode_negative_int(encoder, v); + } + + case CborByteStringType: { + uint8_t *data; + size_t len; + if (md.tag == CborExpectedBase64Tag) + data = decode_base64(item->valuestring, &len); + else if (md.tag == CborExpectedBase16Tag) + data = decode_base16(item->valuestring, &len); + else if (md.tag == CborNegativeBignumTag) + data = decode_base64url(item->valuestring + 1, &len); + else + data = decode_base64url(item->valuestring, &len); + + if (data != NULL) { + CborError err = cbor_encode_byte_string(encoder, data, len); + free(data); + return err; + } + fprintf(stderr, "json2cbor: could not decode encoded byte string: %s\n", item->valuestring); + break; + } + + case CborSimpleType: + return cbor_encode_simple_value(encoder, md.simpleType); + + case CborUndefinedType: + return cbor_encode_undefined(encoder); + + case CborHalfFloatType: + case CborFloatType: + case CborDoubleType: { + unsigned short half; + double v; + if (!md.v) { + v = item->valuedouble; + } else if (strcmp(md.v, "nan") == 0) { + v = NAN; + } else if (strcmp(md.v, "-inf") == 0) { + v = -INFINITY; + } else if (strcmp(md.v, "inf") == 0) { + v = INFINITY; + } else { + fprintf(stderr, "json2cbor: invalid floating-point value: %s\n", md.v); + break; + } + + // we can't get an OOM here because the metadata makes up for space + // (the smallest metadata is "$cbor":{"t":250} (17 bytes) + return (md.t == CborDoubleType) ? cbor_encode_double(encoder, v) : + (md.t == CborFloatType) ? cbor_encode_float(encoder, v) : + (half = encode_half(v), cbor_encode_half_float(encoder, &half)); + } + + default: + fprintf(stderr, "json2cbor: invalid CBOR type: %d\n", md.t); + case CborInvalidType: + break; + } + + return decode_json(item, encoder); +} + +CborError decode_json(cJSON *json, CborEncoder *encoder) +{ + CborEncoder container; + CborError err; + cJSON *item; + + switch (json->type) { + case cJSON_False: + case cJSON_True: + return cbor_encode_boolean(encoder, json->type == cJSON_True); + + case cJSON_NULL: + return cbor_encode_null(encoder); + + case cJSON_Number: + if ((double)json->valueint == json->valuedouble) + return cbor_encode_int(encoder, json->valueint); +encode_double: + // the only exception that JSON is larger: floating point numbers + container = *encoder; // save the state + err = cbor_encode_double(encoder, json->valuedouble); + + if (err == CborErrorOutOfMemory) { + buffersize += 1024; + uint8_t *newbuffer = realloc(buffer, buffersize); + if (newbuffer == NULL) + return err; + + *encoder = container; // restore state + encoder->data.ptr = newbuffer + (container.data.ptr - buffer); + encoder->end = newbuffer + buffersize; + buffer = newbuffer; + goto encode_double; + } + return err; + + case cJSON_String: + return cbor_encode_text_stringz(encoder, json->valuestring); + + default: + return CborErrorUnknownType; + + case cJSON_Array: + err = cbor_encoder_create_array(encoder, &container, get_cjson_size_limited(json)); + if (err) + return err; + for (item = json->child; item; item = item->next) { + err = decode_json(item, &container); + if (err) + return err; + } + return cbor_encoder_close_container_checked(encoder, &container); + + case cJSON_Object: + err = cbor_encoder_create_map(encoder, &container, + usingMetaData ? CborIndefiniteLength : get_cjson_size_limited(json)); + if (err) + return err; + + for (item = json->child ; item; item = item->next) { + if (usingMetaData && strlen(item->string) > strlen(meta_data_marker) + && strcmp(item->string + strlen(item->string) - strlen(meta_data_marker), meta_data_marker) == 0) + continue; + + err = cbor_encode_text_stringz(&container, item->string); + if (err) + return err; + + if (usingMetaData) { + cJSON *meta = get_meta_data(json, item); + struct MetaData md = parse_meta_data(meta); + if (md.tagged) { + err = cbor_encode_tag(&container, md.tag); + if (err) + return err; + } + + err = decode_json_with_metadata(item, &container, md); + } else { + err = decode_json(item, &container); + } + if (err) + return err; + } + + return cbor_encoder_close_container_checked(encoder, &container); + } +} + +int main(int argc, char **argv) +{ + int c; + while ((c = getopt(argc, argv, "M")) != -1) { + switch (c) { + case 'M': + usingMetaData = true; + break; + + case '?': + fprintf(stderr, "Unknown option -%c.\n", optopt); + // fall through + case 'h': + puts("Usage: json2cbor [OPTION]... [FILE]...\n" + "Reads JSON content from FILE and converts to CBOR.\n" + "\n" + "Options:\n" + " -M Interpret metadata added by cbordump tool\n" + ""); + return c == '?' ? EXIT_FAILURE : EXIT_SUCCESS; + } + } + + FILE *in; + const char *fname = argv[optind]; + if (fname && strcmp(fname, "-") != 0) { + in = fopen(fname, "r"); + if (!in) { + perror("open"); + return EXIT_FAILURE; + } + } else { + in = stdin; + fname = "-"; + } + + /* 1. read the file */ + off_t fsize; + if (fseeko(in, 0, SEEK_END) == 0 && (fsize = ftello(in)) >= 0) { + buffersize = fsize + 1; + buffer = malloc(buffersize); + if (buffer == NULL) { + perror("malloc"); + return EXIT_FAILURE; + } + + rewind(in); + fsize = fread(buffer, 1, fsize, in); + buffer[fsize] = '\0'; + } else { + const unsigned chunk = 16384; + buffersize = 0; + buffer = NULL; + do { // it the hard way + buffer = realloc(buffer, buffersize + chunk); + if (buffer == NULL) { + perror("malloc"); + return EXIT_FAILURE; + } + + buffersize += fread(buffer + buffersize, 1, chunk, in); + } while (!feof(in) && !ferror(in)); + buffer[buffersize] = '\0'; + } + + if (ferror(in)) { + perror("read"); + return EXIT_FAILURE; + } + if (in != stdin) + fclose(in); + + /* 2. parse as JSON */ + cJSON *doc = cJSON_ParseWithOpts((char *)buffer, NULL, true); + if (doc == NULL) { + fprintf(stderr, "json2cbor: %s: could not parse.\n", fname); + return EXIT_FAILURE; + } + + /* 3. encode as CBOR */ + // We're going to reuse the buffer, as CBOR is usually shorter than the equivalent JSON + CborEncoder encoder; + cbor_encoder_init(&encoder, buffer, buffersize, 0); + CborError err = decode_json(doc, &encoder); + + cJSON_Delete(doc); + + if (err) { + fprintf(stderr, "json2cbor: %s: error encoding to CBOR: %s\n", fname, + cbor_error_string(err)); + return EXIT_FAILURE; + } + + fwrite(buffer, 1, encoder.data.ptr - buffer, stdout); + free(buffer); + return EXIT_SUCCESS; +} diff --git a/lib/cbor/tinycbor/tools/json2cbor/json2cbor.pro b/lib/cbor/tinycbor/tools/json2cbor/json2cbor.pro new file mode 100644 index 00000000..fd6bcd09 --- /dev/null +++ b/lib/cbor/tinycbor/tools/json2cbor/json2cbor.pro @@ -0,0 +1,20 @@ +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 +} |
