diff --git a/example/baremetal/qcbor/main.c b/example/baremetal/qcbor/main.c new file mode 100644 index 000000000..7abac62eb --- /dev/null +++ b/example/baremetal/qcbor/main.c @@ -0,0 +1,181 @@ +/*============================================================================== + +Copyright (c) 2018, Laurence Lundblade. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * The name "Laurence Lundblade" may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +==============================================================================*/ +// Created by Laurence Lundblade on 10/26/18. + +#include +#include "qcbor.h" + +#include "embARC.h" +#include "embARC_debug.h" + +/* + A small user of CBOR encoding and decoding + that is good as an example and for + checking code size with all the + inlining and dead stripping on. + */ + +int main(int argc, const char * argv[]) +{ + (void)argc; // Suppress unused warning + (void)argv; // Suppress unused warning + + uint8_t pBuf[300]; + // Very simple CBOR, a map with one boolean that is true in it + QCBOREncodeContext EC; + + QCBOREncode_Init(&EC, UsefulBuf_FROM_BYTE_ARRAY(pBuf)); + + QCBOREncode_OpenMap(&EC); + QCBOREncode_AddBoolToMapN(&EC, 66, true); + QCBOREncode_CloseMap(&EC); + + UsefulBufC Encoded; + if(QCBOREncode_Finish(&EC, &Encoded)) { + return -1; + } + + + // Decode it and see that is right + QCBORDecodeContext DC; + QCBORItem Item; + QCBORDecode_Init(&DC, Encoded, QCBOR_DECODE_MODE_NORMAL); + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_MAP) { + return -2; + } + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_TRUE) { + return -3; + } + + if(QCBORDecode_Finish(&DC)) { + return -4; + } + + + // Make another encoded message with the CBOR from the previous put into this one + UsefulBuf_MAKE_STACK_UB(MemoryForEncoded2, 20); + QCBOREncode_Init(&EC, MemoryForEncoded2); + QCBOREncode_OpenArray(&EC); + QCBOREncode_AddUInt64(&EC, 451); + QCBOREncode_AddEncoded(&EC, Encoded); + QCBOREncode_OpenMap(&EC); + QCBOREncode_AddEncodedToMapN(&EC, -70000, Encoded); + QCBOREncode_CloseMap(&EC); + QCBOREncode_CloseArray(&EC); + + UsefulBufC Encoded2; + if(QCBOREncode_Finish(&EC, &Encoded2)) { + return -5; + } + /* + [ // 0 1:3 + 451, // 1 1:2 + { // 1 1:2 2:1 + 66: true // 2 1:1 + }, + { // 1 1:1 2:1 + -70000: { // 2 1:1 2:1 3:1 + 66: true // 3 XXXXXX + } + } + ] + + + + 83 # array(3) + 19 01C3 # unsigned(451) + A1 # map(1) + 18 42 # unsigned(66) + F5 # primitive(21) + A1 # map(1) + 3A 0001116F # negative(69999) + A1 # map(1) + 18 42 # unsigned(66) + F5 # primitive(21) + */ + + // Decode it and see if it is OK + QCBORDecode_Init(&DC, Encoded2, QCBOR_DECODE_MODE_NORMAL); + + // 0 1:3 + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_ARRAY || Item.val.uCount != 3) { + return -6; + } + + // 1 1:2 + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_INT64 || Item.val.uint64 != 451) { + return -7; + } + + // 1 1:2 2:1 + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_MAP || Item.val.uCount != 1) { + return -8; + } + + // 2 1:1 + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_TRUE) { + return -9; + } + + // 1 1:1 2:1 + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_MAP || Item.val.uCount != 1) { + return -10; + } + + // 2 1:1 2:1 3:1 + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_MAP || Item.val.uCount != 1 || Item.uLabelType != QCBOR_TYPE_INT64 || Item.label.int64 != -70000) { + return -11; + } + + // 3 XXXXXX + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_TRUE || Item.uLabelType != QCBOR_TYPE_INT64 || Item.label.int64 != 66) { + return -12; + } + + if(QCBORDecode_Finish(&DC)) { + return -13; + } + + EMBARC_PRINTF("QCBOR test success \r\n"); + + return 0; +} diff --git a/example/baremetal/qcbor/makefile b/example/baremetal/qcbor/makefile new file mode 100644 index 000000000..00b0cc693 --- /dev/null +++ b/example/baremetal/qcbor/makefile @@ -0,0 +1,23 @@ +# Application name +APPL ?= qcbor + +# +# root dir of embARC +# +EMBARC_ROOT = ../../.. + +MID_SEL = common qcbor + +# application source dirs +APPL_CSRC_DIR = . +APPL_ASMSRC_DIR = . + +# application include dirs +APPL_INC_DIR = . + +# include current project makefile +COMMON_COMPILE_PREREQUISITES += makefile + +### Options above must be added before include options.mk ### +# include key embARC build system makefile +include $(EMBARC_ROOT)/options/options.mk \ No newline at end of file diff --git a/middleware/qcbor/Makefile b/middleware/qcbor/Makefile new file mode 100644 index 000000000..167cd1987 --- /dev/null +++ b/middleware/qcbor/Makefile @@ -0,0 +1,45 @@ +# Makefile -- UNIX-style make for qcbor as a lib and command line test +# +# Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# +# See BSD-3-Clause license in README.md +# + + +CFLAGS=-I inc -I test -Os -Wall -Werror -pedantic-errors -Wextra -Wshadow -Wparentheses -xc -std=c99 + +QCBOR_OBJ=src/UsefulBuf.o src/qcbor_encode.o src/qcbor_decode.o src/ieee754.o + +TEST_OBJ=test/UsefulBuf_Tests.o test/qcbor_encode_tests.o test/qcbor_decode_tests.o test/run_tests.o \ + test/float_tests.o test/half_to_double_from_rfc7049.o + +qcbortest: libqcbor.a $(TEST_OBJ) cmd_line_main.o + cc -o $@ $^ libqcbor.a + +qcbormin: libqcbor.a min_use_main.o + cc -dead_strip -o $@ $^ libqcbor.a + +libqcbor.a: $(QCBOR_OBJ) + ar -r $@ $^ + +src/UsefulBuf.o: inc/UsefulBuf.h +src/qcbor_decode.o: inc/UsefulBuf.h inc/qcbor.h src/ieee754.h +src/qcbor_encode.o: inc/UsefulBuf.h inc/qcbor.h src/ieee754.h +src/iee754.o: src/ieee754.h + +test/run_tests.o: test/UsefulBuf_Tests.h test/float_tests.h test/run_tests.h test/qcbor_encode_tests.h\ + test/qcbor_decode_tests.h +test/UsefulBuf_Tests.o: test/UsefulBuf_Tests.h inc/qcbor.h inc/UsefulBuf.h +test/qcbor_encode_tests.o: test/qcbor_encode_tests.h inc/qcbor.h inc/UsefulBuf.h +test/qcbor_decode_tests.o: test/qcbor_decode_tests.h inc/qcbor.h inc/UsefulBuf.h +test/float_tests.o: inc/qcbor.h inc/UsefulBuf.h test/float_tests.h test/half_to_double_from_rfc7049.h +test/half_to_double_from_rfc7049.o: test/half_to_double_from_rfc7049.h + +cmd_line_main.o: test/run_tests.h inc/qcbor.h + +min_use_main.o: inc/qcbor.h inc/UsefulBuf.h + +clean: + rm -f $(QCBOR_OBJ) $(TEST_OBJ) libqcbor.a min_use_main.o cmd_line_main.o diff --git a/middleware/qcbor/Makefile.gcc8 b/middleware/qcbor/Makefile.gcc8 new file mode 100644 index 000000000..b05f82efe --- /dev/null +++ b/middleware/qcbor/Makefile.gcc8 @@ -0,0 +1,45 @@ +# Makefile -- UNIX-style make for qcbor as a lib and command line test +# +# Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# +# See BSD-3-Clause license in README.md +# + +CFLAGS=-I inc -I test -Os -Wall -Werror -pedantic-errors -Wextra -Wshadow -Wparentheses -xc -std=c99 -Werror=maybe-uninitialized +CC=/usr/local/bin/gcc-8 + +QCBOR_OBJ=src/UsefulBuf.o src/qcbor_encode.o src/qcbor_decode.o src/ieee754.o + +TEST_OBJ=test/UsefulBuf_Tests.o test/qcbor_encode_tests.o test/qcbor_decode_tests.o test/run_tests.o \ + test/float_tests.o test/half_to_double_from_rfc7049.o + +qcbortest: libqcbor.a $(TEST_OBJ) cmd_line_main.o + $(CC) -o $@ $^ libqcbor.a + +qcbormin: libqcbor.a min_use_main.o + $(CC) -dead_strip -o $@ $^ libqcbor.a + +libqcbor.a: $(QCBOR_OBJ) + ar -r $@ $^ + +src/UsefulBuf.o: inc/UsefulBuf.h +src/qcbor_decode.o: inc/UsefulBuf.h inc/qcbor.h src/ieee754.h +src/qcbor_encode.o: inc/UsefulBuf.h inc/qcbor.h src/ieee754.h +src/iee754.o: src/ieee754.h + +test/run_tests.o: test/UsefulBuf_Tests.h test/float_tests.h test/run_tests.h test/qcbor_encode_tests.h\ + test/qcbor_decode_tests.h +test/UsefulBuf_Tests.o: test/UsefulBuf_Tests.h inc/qcbor.h inc/UsefulBuf.h +test/qcbor_encode_tests.o: test/qcbor_encode_tests.h inc/qcbor.h inc/UsefulBuf.h +test/qcbor_decode_tests.o: test/qcbor_decode_tests.h inc/qcbor.h inc/UsefulBuf.h +test/float_tests.o: inc/qcbor.h inc/UsefulBuf.h test/float_tests.h test/half_to_double_from_rfc7049.h +test/half_to_double_from_rfc7049.o: test/half_to_double_from_rfc7049.h + +cmd_line_main.o: test/run_tests.h inc/qcbor.h + +min_use_main.o: inc/qcbor.h inc/UsefulBuf.h + +clean: + rm -f $(QCBOR_OBJ) $(TEST_OBJ) libqcbor.a min_use_main.o cmd_line_main.o diff --git a/middleware/qcbor/README.md b/middleware/qcbor/README.md new file mode 100644 index 000000000..58a708050 --- /dev/null +++ b/middleware/qcbor/README.md @@ -0,0 +1,175 @@ +# QCBOR + +QCBOR encodes and decodes [RFC 7049](https://tools.ietf.org/html/rfc7049) CBOR. + +## Characteristics + +**Implemented in C with minimal dependency** – Only dependencies are + C99, , , and making it + highly portable. There are no #ifdefs to be configured at all. + +**Focused on C / native data representation** – Simpler code because + there is no support for encoding/decoding to/from JSON, pretty + printing, diagnostic notation... Only encoding from native C + representations and decoding to native C representations is supported. + +**Small simple memory model** – Malloc is not needed. The encode + context is 136 bytes, decode context is 104 bytes and the + description of decoded data item is 56 bytes. Stack use is light and + there is no recursion. The caller supplies the memory to hold the + encoded CBOR and encode/decode contexts so caller has full control + of memory usage making it good for embedded implementations that + have to run in small fixed memory. + +**Supports nearly all of RFC 7049** – Only minor, corner-case parts of + RFC 7049 are not directly supported (canonicalization, decimal + fractions, big floats). Decoding indefinite length strings is supported, + but requires a string allocator (see documentation). Encoding indefinite + length strings is not supported, but is also not necessary or + preferred. + +**Extensible and general** – Provides a way to handle data types that + are not directly supported. + +**Secure coding style** – Uses a construct called UsefulBuf as a + discipline for very safe coding the handling of binary data. + +**Small code size** – When optimized for size using the compiler -Os + option, x86 code is about 4KB (~1.1KB encode, ~2.5KB decode, + ~0.4KB common). Other decoders may be smaller, but they may + also do less for you, so overall size of the implementation may + be larger. For example, QCBOR internally tracks error status + so you don't have to check a return code on every operation. + +**Clear documented public interface** – The public interface is + separated from the implementation. It can be put to use without + reading the source. + +**Comprehensive test suite** – Easy to verify on a new platform + or OS with the test suite. The test suite dependencies are also + minimal, only additionally requiring for floating point + tests. + +## Code Status + +QCBOR was originally developed by Qualcomm. It was [open sourced +through CAF](https://source.codeaurora.org/quic/QCBOR/QCBOR/) with a +permissive Linux license, September 2018 (thanks Qualcomm!). + +This code in [Laurence's +GitHub](https://github.com/laurencelundblade/QCBOR) has diverged from +the CAF source with some small simplifications and tidying up. + +From Nov 3, 2018, the interface and code are fairly stable. Large +changes are not planned or expected, particularly in the +interface. The test coverage is pretty good. + +## Building + +There is a simple makefile for the UNIX style command line binary that +compiles everything to run the tests. + +These seven files, the contents of the src and inc directories, make +up the entire implementation. + +* inc + * UsefulBuf.h + * qcbor.h +* src + * UsefulBuf.c + * qcbor_encode.c + * qcbor_decode.c + * ieee754.h + * ieee754.c + +For most use cases you should just be able to add them to your +project. Hopefully the easy portability of this implementation makes +this work straight away, whatever your development environment is. + +The files ieee754.c and ieee754.h are support for half-precision +floating point. The encoding side of the floating point functionality +is about 500 bytes. If it is never called because no floating point +numbers are ever encoded, all 500 bytes will be dead stripped and not +impact code size. The decoding side is about 150 bytes of object +code. It is never dead stripped because it directly referenced by the +core decoder, however it doesn't add very much to the size. + +The test directory includes some tests that are nearly as portable as +the main implementation. If your development environment doesn't +support UNIX style command line and make, you should be able to make a +simple project and add the test files to it. Then just call +RunTests() to invoke them all. + + +## Changes from CAF Version +* Float support is restored +* Minimal length float encoding is added +* indefinite length arrays/maps are supported +* indefinite length strings are supported +* Tag decoding is changed; unlimited number of tags supported, any tag +value supported, tag utility function for easier tag checking +* Addition functions in UsefulBuf +* QCBOREncode_Init takes a UsefulBuf instead of a pointer and size +* QCBOREncode_Finish takes a UsefulBufC and EncodedCBOR is remove +* bstr wrapping of arrays/maps is replaced with OpenBstrwrap +* AddRaw renamed to AddEncoded and can now only add whole arrays or maps, +not partial maps and arrays (simplification; was a dangerous feature) +* Finish cannot be called repeatedly on a partial decode (some tests used +this, but it is not really a good thing to use in the first place) +* UsefulOutBuf_OutUBuf changed to work differently +* UsefulOutBuf_Init works differently +* The "_3" functions are replaced with a small number of simpler functions +* There is a new AddTag functon instead of the "_3" functions, making +the interface simpler and saving some code +* QCBOREncode_AddRawSimple_2 is removed (the macros that referenced +still exist and work the same) + +## Credits +* Ganesh Kanike for porting to QSEE +* Mark Bapst for sponsorship and release as open source by Qualcomm +* Sachin Sharma for release through CAF +* Tamas Ban for porting to TF-M and 32-bit ARM + +## Copyright and License + +QCBOR is available under what is essentially the 3-Clause BSD License. + +Files created inside Qualcomm and open-sourced through CAF (The Code +Aurora Forum) have a slightly modified 3-Clause BSD License. The +modification additionally disclaims NON-INFRINGEMENT. + +Files created after release to CAF use the standard 3-Clause BSD +License with no modification. These files have the SPDX license +identifier, "SPDX-License-Identifier: BSD-3-Clause" in them. + +### BSD-3-Clause license + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +### Copyright for this README + +Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved. + + + diff --git a/middleware/qcbor/cmd_line_main.c b/middleware/qcbor/cmd_line_main.c new file mode 100644 index 000000000..f40c9619c --- /dev/null +++ b/middleware/qcbor/cmd_line_main.c @@ -0,0 +1,40 @@ +/*============================================================================== + cmd_line_mainc.c -- Runs tests for QCBOR encoder / decoder + + Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved. + + SPDX-License-Identifier: BSD-3-Clause + + See BSD-3-Clause license in README.md + + Created on 9/13/18 + ==============================================================================*/ + +#include +#include "run_tests.h" + + +/* + This is an implementation of OutputStringCB built using stdio. If + you don't have stdio, replaces this. + */ +static void fputs_wrapper(const char *szString, void *pOutCtx, int bNewLine) +{ + fputs(szString, (FILE *)pOutCtx); + if(bNewLine) { + fputs("\n", pOutCtx); + } +} + + +int main(int argc, const char * argv[]) +{ + (void)argc; // Avoid unused parameter error + + // This call prints out sizes of data structures to remind us + // to keep them small. + PrintSizes(&fputs_wrapper, stdout); + + // This runs all the tests + return RunTests(argv+1, &fputs_wrapper, stdout, NULL); +} diff --git a/middleware/qcbor/inc/UsefulBuf.h b/middleware/qcbor/inc/UsefulBuf.h new file mode 100644 index 000000000..fa17b6c8f --- /dev/null +++ b/middleware/qcbor/inc/UsefulBuf.h @@ -0,0 +1,1536 @@ +/*============================================================================== + Copyright (c) 2016-2018, The Linux Foundation. + Copyright (c) 2018-2019, Laurence Lundblade. + All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of The Linux Foundation nor the names of its + contributors, nor the name "Laurence Lundblade" may be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ==============================================================================*/ + +/*=================================================================================== + FILE: UsefulBuf.h + + DESCRIPTION: General purpose input and output buffers + + EDIT HISTORY FOR FILE: + + This section contains comments describing changes made to the module. + Notice that changes are listed in reverse chronological order. + + when who what, where, why + -------- ---- --------------------------------------------------- + 12/17/2018 llundblade Remove const from UsefulBuf and UsefulBufC .len + 12/13/2018 llundblade Documentation improvements + 09/18/2018 llundblade Cleaner distinction between UsefulBuf and UsefulBufC + 02/02/18 llundbla Full support for integers in and out; fix pointer + alignment bug. Incompatible change: integers in/out + are now in network byte order. + 08/12/17 llundbla Added UsefulOutBuf_AtStart and UsefulBuf_Find + 06/27/17 llundbla Fix UsefulBuf_Compare() bug. Only affected comparison + for < or > for unequal length buffers. Added + UsefulBuf_Set() function. + 05/30/17 llundbla Functions for NULL UsefulBufs and const / unconst + 11/13/16 llundbla Initial Version. + + + =====================================================================================*/ + +#ifndef _UsefulBuf_h +#define _UsefulBuf_h + + +#include // for uint8_t, uint16_t.... +#include // for strlen, memcpy, memmove, memset +#include // for size_t + +/** + @file UsefulBuf.h + + The goal of this code is to make buffer and pointer manipulation + easier and safer when working with binary data. + + You use the UsefulBuf, UsefulOutBuf and UsefulInputBuf + structures to represent buffers rather than ad hoc pointers and lengths. + + With these it will often be possible to write code that does little or no + direct pointer manipulation for copying and formatting data. For example + the QCBOR encoder was rewritten using these and has no direct pointer + manipulation. + + While it is true that object code using these functions will be a little + larger and slower than a white-knuckle clever use of pointers might be, but + not by that much or enough to have an affect for most use cases. For + security-oriented code this is highly worthwhile. Clarity, simplicity, + reviewability and are more important. + + There are some extra sanity and double checks in this code to help catch + coding errors and simple memory corruption. They are helpful, but not a + substitute for proper code review, input validation and such. + + This code consists of a lot of inline functions and a few that are not. + It should not generate very much object code, especially with the + optimizer turned up to -Os or -O3. The idea is that the inline + functions are easier to review and understand and the optimizer does + the work of making the code small. + */ + + +/*...... This is a ruler that is 80 characters long...........................*/ + +/** + UsefulBufC and UsefulBuf are simple data structures to hold a pointer and + length for a binary data. In C99 this data structure can be passed on the + stack making a lot of code cleaner than carrying around a pointer and + length as two parameters. + + This is also conducive to secure code practice as the lengths are + always carried with the pointer and the convention for handling a + pointer and a length is clear. + + While it might be possible to write buffer and pointer code more + efficiently in some use cases, the thought is that unless there is an + extreme need for performance (e.g., you are building a gigabit-per-second + IP router), it is probably better to have cleaner code you can be most + certain about the security of. + + The non-const UsefulBuf is usually used to refer a buffer to be filled in. + The length is the size of the buffer. + + The const UsefulBufC is usually used to refer to some data that has been + filled in. The length is amount of valid data pointed to. + + A common use is to pass a UsefulBuf to a function, the function fills it + in, the function returns a UsefulBufC. The pointer is the same in both. + + A UsefulBuf is NULL, it has no value, when the ptr in it is NULL. + + There are utility functions for the following: + - Checking for UsefulBufs that are NULL, empty or both + - Copying, copying with offset, copying head or tail + - Comparing and finding substrings + - Initializating + - Create initialized const UsefulBufC from compiler literals + - Create initialized const UsefulBufC from NULL-terminated string + - Make an empty UsefulBuf on the stack + + See also UsefulOutBuf. It is a richer structure that has both the size of + the valid data and the size of the buffer. + + UsefulBuf is only 16 or 8 bytes on a 64- or 32-bit machine so it can go + on the stack and be a function parameter or return value. + + UsefulBuf is kind of like the Useful Pot Pooh gave Eeyore on his birthday. + Eeyore's balloon fits beautifully, "it goes in and out like anything". + +*/ +typedef struct useful_buf_c { + const void *ptr; + size_t len; +} UsefulBufC; + + +/** + The non-const UsefulBuf typically used for some allocated memory + that is to be filled in. The len is the amount of memory, + not the length of the valid data in the buffer. + */ +typedef struct useful_buf { + void *ptr; + size_t len; +} UsefulBuf; + + +/** + A "NULL" UsefulBufC is one that has no value in the same way a NULL pointer has no value. + A UsefulBuf is NULL when the ptr field is NULL. It doesn't matter what len is. + See UsefulBuf_IsEmpty() for the distinction between NULL and empty. + */ +#define NULLUsefulBufC ((UsefulBufC) {NULL, 0}) + +/** A NULL UsefulBuf is one that has no memory associated the say way + NULL points to nothing. It does not matter what len is. + */ +#define NULLUsefulBuf ((UsefulBuf) {NULL, 0}) + + +/** + @brief Check if a UsefulBuf is NULL or not + + @param[in] UB The UsefulBuf to check + + @return 1 if it is NULL, 0 if not. + */ +static inline int UsefulBuf_IsNULL(UsefulBuf UB) { + return !UB.ptr; +} + + +/** + @brief Check if a UsefulBufC is NULL or not + + @param[in] UB The UsefulBufC to check + + @return 1 if it is NULL, 0 if not. + */ +static inline int UsefulBuf_IsNULLC(UsefulBufC UB) { + return !UB.ptr; +} + + +/** + @brief Check if a UsefulBuf is empty or not + + @param[in] UB The UsefulBuf to check + + @return 1 if it is empty, 0 if not. + + An "Empty" UsefulBuf is one that has a value and can be considered to be set, + but that value is of zero length. It is empty when len is zero. It + doesn't matter what the ptr is. + + A lot of uses will not need to clearly distinguish a NULL UsefulBuf + from an empty one and can have the ptr NULL and the len 0. However + if a use of UsefulBuf needs to make a distinction then ptr should + not be NULL when the UsefulBuf is considered empty, but not NULL. + + */ +static inline int UsefulBuf_IsEmpty(UsefulBuf UB) { + return !UB.len; +} + + +/** + @brief Check if a UsefulBufC is empty or not + + @param[in] UB The UsefulBufC to check + + @return 1 if it is empty, 0 if not. + */ +static inline int UsefulBuf_IsEmptyC(UsefulBufC UB) { + return !UB.len; +} + + +/** + @brief Check if a UsefulBuf is NULL or empty + + @param[in] UB The UsefulBuf to check + + @return 1 if it is either NULL or empty, 0 if not. + */ +static inline int UsefulBuf_IsNULLOrEmpty(UsefulBuf UB) { + return UsefulBuf_IsEmpty(UB) || UsefulBuf_IsNULL(UB); +} + + +/** + @brief Check if a UsefulBufC is NULL or empty + + @param[in] UB The UsefulBufC to check + + @return 1 if it is either NULL or empty, 0 if not. + */ +static inline int UsefulBuf_IsNULLOrEmptyC(UsefulBufC UB) { + return UsefulBuf_IsEmptyC(UB) || UsefulBuf_IsNULLC(UB); +} + + +/** + @brief Convert a non const UsefulBuf to a const UsefulBufC + + @param[in] UB The UsefulBuf to convert + + Returns: a UsefulBufC struct + */ + +static inline UsefulBufC UsefulBuf_Const(const UsefulBuf UB) +{ + return (UsefulBufC){UB.ptr, UB.len}; +} + + +/** + @brief Convert a const UsefulBufC to a non-const UsefulBuf + + @param[in] UBC The UsefulBuf to convert + + Returns: a non const UsefulBuf struct + */ +static inline UsefulBuf UsefulBuf_Unconst(const UsefulBufC UBC) +{ + return (UsefulBuf){(void *)UBC.ptr, UBC.len}; +} + + +/** + Convert a literal string to a UsefulBufC. + + szString must be a literal string that you can take sizeof. + This is better for literal strings than UsefulBuf_FromSZ() + because it generates less code. It will not work on + non-literal strings. + + The terminating \0 (NULL) is NOT included in the length! + + */ +#define UsefulBuf_FROM_SZ_LITERAL(szString) \ + ((UsefulBufC) {(szString), sizeof(szString)-1}) + + +/** + Convert a literal byte array to a UsefulBufC. + + pBytes must be a literal string that you can take sizeof. + It will not work on non-literal arrays. + + */ +#define UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pBytes) \ + ((UsefulBufC) {(pBytes), sizeof(pBytes)}) + + +/** + Make an automatic variable with name of type UsefulBuf and point it to a stack + variable of the give size + */ +#define UsefulBuf_MAKE_STACK_UB(name, size) \ + uint8_t __pBuf##name[(size)];\ + UsefulBuf name = {__pBuf##name , sizeof( __pBuf##name )} + + +/** + Make a byte array in to a UsefulBuf + */ +#define UsefulBuf_FROM_BYTE_ARRAY(pBytes) \ + ((UsefulBuf) {(pBytes), sizeof(pBytes)}) + +/** + @brief Convert a NULL terminated string to a UsefulBufC. + + @param[in] szString The string to convert + + @return a UsefulBufC struct + + UsefulBufC.ptr points to the string so it's lifetime + must be maintained. + + The terminating \0 (NULL) is NOT included in the length! + + */ +static inline UsefulBufC UsefulBuf_FromSZ(const char *szString){ + return ((UsefulBufC) {szString, strlen(szString)}); +} + + +/** + @brief Copy one UsefulBuf into another at an offset + + @param[in] Dest Destiation buffer to copy into + @param[in] uOffset The byte offset in Dest at which to copy to + @param[in] Src The bytes to copy + + @return Pointer and length of the copy + + This fails and returns NULLUsefulBufC Src.len + uOffset > Dest.len. + + Like memcpy, there is no check for NULL. If NULL is passed + this will crash. + + There is an assumption that there is valid data in Dest up to + uOffset as the resulting UsefulBufC returned starts + at the beginning of Dest and goes to Src.len + uOffset. + + */ +UsefulBufC UsefulBuf_CopyOffset(UsefulBuf Dest, size_t uOffset, const UsefulBufC Src); + + +/** + @brief Copy one UsefulBuf into another + + @param[in] Dest The destination buffer to copy into + @param[out] Src The source to copy from + + @return filled in UsefulBufC on success, NULLUsefulBufC on failure + + This fails if Src.len is greater than Dest.len. + + Note that like memcpy, the pointers are not checked and + this will crash, rather than return NULLUsefulBufC if + they are NULL or invalid. + + Results are undefined if Dest and Src overlap. + + */ +static inline UsefulBufC UsefulBuf_Copy(UsefulBuf Dest, const UsefulBufC Src) { + return UsefulBuf_CopyOffset(Dest, 0, Src); +} + + +/** + @brief Set all bytes in a UsefulBuf to a value, for example 0 + + @param[in] pDest The destination buffer to copy into + @param[in] value The value to set the bytes to + + Note that like memset, the pointer in pDest is not checked and + this will crash if NULL or invalid. + + */ +static inline UsefulBufC UsefulBuf_Set(UsefulBuf pDest, uint8_t value) +{ + memset(pDest.ptr, value, pDest.len); + return (UsefulBufC){pDest.ptr, pDest.len}; +} + + +/** + @brief Copy a pointer into a UsefulBuf + + @param[in,out] Dest The destination buffer to copy into + @param[in] ptr The source to copy from + @param[in] len Length of the source; amoutn to copy + + @return 0 on success, 1 on failure + + This fails and returns NULLUsefulBufC if len is greater than + pDest->len. + + Note that like memcpy, the pointers are not checked and + this will crash, rather than return 1 if they are NULL + or invalid. + + */ +inline static UsefulBufC UsefulBuf_CopyPtr(UsefulBuf Dest, const void *ptr, size_t len) +{ + return UsefulBuf_Copy(Dest, (UsefulBufC){ptr, len}); +} + + +/** + @brief Returns a truncation of a UsefulBufC + + @param[in] UB The buffer to get the head of + @param[in] uAmount The number of bytes in the head + + @return A UsefulBufC that is the head of UB + + */ +static inline UsefulBufC UsefulBuf_Head(UsefulBufC UB, size_t uAmount) +{ + if(uAmount > UB.len) { + return NULLUsefulBufC; + } + return (UsefulBufC){UB.ptr, uAmount}; +} + + +/** + @brief Returns bytes from the end of a UsefulBufC + + @param[in] UB The buffer to get the tail of + @param[in] uAmount The offset from the start where the tail is to begin + + @return A UsefulBufC that is the tail of UB or NULLUsefulBufC if + uAmount is greater than the length of the UsefulBufC + + If the input UsefulBufC is NULL, but the len is not, then the + length of the tail will be calculated and returned along + with a NULL ptr. + */ +static inline UsefulBufC UsefulBuf_Tail(UsefulBufC UB, size_t uAmount) +{ + UsefulBufC ReturnValue; + + if(uAmount > UB.len) { + ReturnValue = NULLUsefulBufC; + } else if(UB.ptr == NULL) { + ReturnValue = (UsefulBufC){NULL, UB.len - uAmount}; + } else { + ReturnValue = (UsefulBufC){(uint8_t *)UB.ptr + uAmount, UB.len - uAmount}; + } + + return ReturnValue; +} + + +/** + @brief Compare two UsefulBufCs + + @param[in] UB1 The destination buffer to copy into + @param[in] UB2 The source to copy from + + @return 0 if equal... + + Returns a negative value if UB1 if is less than UB2. UB1 is + less than UB2 if it is shorter or the first byte that is not + the same is less. + + Returns 0 if the UsefulBufs are the same. + + Returns a positive value if UB2 is less than UB1. + + All that is of significance is that the result is positive, + negative or 0. (This doesn't return the difference between + the first non-matching byte like memcmp). + + */ +int UsefulBuf_Compare(const UsefulBufC UB1, const UsefulBufC UB2); + + +/** + @brief Find one UsefulBuf in another + + @param[in] BytesToSearch UsefulBuf to search through + @param[in] BytesToFind UsefulBuf with bytes to be found + + @return position of found bytes or SIZE_MAX if not found. + + */ +size_t UsefulBuf_FindBytes(UsefulBufC BytesToSearch, UsefulBufC BytesToFind); + + + + +#if 0 // NOT_DEPRECATED +/** Deprecated macro; use UsefulBuf_FROM_SZ_LITERAL instead */ +#define SZLiteralToUsefulBufC(szString) \ + ((UsefulBufC) {(szString), sizeof(szString)-1}) + +/** Deprecated macro; use UsefulBuf_MAKE_STACK_UB instead */ +#define MakeUsefulBufOnStack(name, size) \ + uint8_t __pBuf##name[(size)];\ + UsefulBuf name = {__pBuf##name , sizeof( __pBuf##name )} + +/** Deprecated macro; use UsefulBuf_FROM_BYTE_ARRAY_LITERAL instead */ +#define ByteArrayLiteralToUsefulBufC(pBytes) \ + ((UsefulBufC) {(pBytes), sizeof(pBytes)}) + +/** Deprecated function; use UsefulBuf_Unconst() instead */ +static inline UsefulBuf UsefulBufC_Unconst(const UsefulBufC UBC) +{ + return (UsefulBuf){(void *)UBC.ptr, UBC.len}; +} +#endif + + + +/* + Convenient functions to avoid type punning, compiler warnings and such + The optimizer reduces them to a simple assignment + This is a crusty corner of C. It shouldn't be this hard. + */ +static inline uint32_t UsefulBufUtil_CopyFloatToUint32(float f) +{ + uint32_t u32; + memcpy(&u32, &f, sizeof(uint32_t)); + return u32; +} + +static inline uint64_t UsefulBufUtil_CopyDoubleToUint64(double d) +{ + uint64_t u64; + memcpy(&u64, &d, sizeof(uint64_t)); + return u64; +} + +static inline double UsefulBufUtil_CopyUint64ToDouble(uint64_t u64) +{ + double d; + memcpy(&d, &u64, sizeof(uint64_t)); + return d; +} + +static inline float UsefulBufUtil_CopyUint32ToFloat(uint32_t u32) +{ + float f; + memcpy(&f, &u32, sizeof(uint32_t)); + return f; +} + + + + + +/** + UsefulOutBuf is a structure and functions (an object) that are good + for serializing data into a buffer such as is often done with network + protocols or data written to files. + + The main idea is that all the pointer manipulation for adding data is + done by UsefulOutBuf functions so the caller doesn't have to do any. + All the pointer manipulation is centralized here. This code will + have been reviewed and written carefully so it spares the caller of + much of this work and results in much safer code with much less work. + + The functions to add data to the output buffer always check the + length and will never write off the end of the output buffer. If an + attempt to add data that will not fit is made, an internal error flag + will be set and further attempts to add data will not do anything. + + Basically, if you initialized with the correct buffer, there is no + way to ever write off the end of that buffer when calling the Add + and Insert functions here. + + The functions to add data do not return an error. The working model + is that the caller just makes all the calls to add data without any + error checking on each one. The error is instead checked after all the + data is added when the result is to be used. This makes the caller's + code cleaner. + + There is a utility function to get the error status anytime along the + way if the caller wants. There are functions to see how much room is + left and see if some data will fit too, but their use is generally + not necessary. + + The general call flow is like this: + + - Initialize the UsefulOutBuf with the buffer that is to have the + data added. The caller allocates the buffer. It can be heap + or stack or shared memory (or other). + + - Make calls to add data to the output buffer. Insert and append + are both supported. The append and insert calls will never write + off the end of the buffer. + + - When all data is added, check the error status to make sure + everything fit. + + - Get the resulting serialized data either as a UsefulBuf (a + pointer and length) or have it copied to another buffer. + + UsefulOutBuf can be initialized with just a buffer length by passing + NULL as the pointer to the output buffer. This is useful if you want + to go through the whole serialization process to either see if it + will fit into a given buffer or compute the size of the buffer + needed. Pass a very large buffer size when calling Init, if you want + just to compute the size. + + Some inexpensive simple sanity checks are performed before every data + addition to guard against use of an uninitialized or corrupted + UsefulOutBuf. + + This has been used to create a CBOR encoder. The CBOR encoder has + almost no pointer manipulation in it, is much easier to read, and + easier to review. + + A UsefulOutBuf is 27 bytes or 15 bytes on 64- or 32-bit machines so it + can go on the stack or be a C99 function parameter. + */ + +typedef struct useful_out_buf { + UsefulBuf UB; // Memory that is being output to + size_t data_len; // length of the data + uint16_t magic; // Used to detect corruption and lack of initialization + uint8_t err; +} UsefulOutBuf; + + +/** + @brief Initialize and supply the actual output buffer + + @param[out] me The UsefulOutBuf to initialize + @param[in] Storage Buffer to output into + + Intializes the UsefulOutBuf with storage. Sets the current position + to the beginning of the buffer clears the error. + + This must be called before the UsefulOutBuf is used. + */ +void UsefulOutBuf_Init(UsefulOutBuf *me, UsefulBuf Storage); + + + + +/** Convenience marco to make a UsefulOutBuf on the stack and + initialize it with stack buffer + */ +#define UsefulOutBuf_MakeOnStack(name, size) \ + uint8_t __pBuf##name[(size)];\ + UsefulOutBuf name;\ + UsefulOutBuf_Init(&(name), (UsefulBuf){__pBuf##name, (size)}); + + + +/** + @brief Reset a UsefulOutBuf for re use + + @param[in] me Pointer to the UsefulOutBuf + + This sets the amount of data in the output buffer to none and + clears the error state. + + The output buffer is still the same one and size as from the + UsefulOutBuf_Init() call. + + It doesn't zero the data, just resets to 0 bytes of valid data. + */ +static inline void UsefulOutBuf_Reset(UsefulOutBuf *me) +{ + me->data_len = 0; + me->err = 0; +} + + +/** + @brief Returns position of end of data in the UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBuf + + @return position of end of data + + On a freshly initialized UsefulOutBuf with no data added, this will + return 0. After ten bytes have been added, it will return 10 and so + on. + + Generally callers will not need this function for most uses of + UsefulOutBuf. + + */ +static inline size_t UsefulOutBuf_GetEndPosition(UsefulOutBuf *me) +{ + return me->data_len; +} + + +/** + @brief Returns whether any data has been added to the UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBuf + + @return 1 if output position is at start + + */ +static inline int UsefulOutBuf_AtStart(UsefulOutBuf *me) +{ + return 0 == me->data_len; +} + + +/** + @brief Inserts bytes into the UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBuf + @param[in] NewData UsefulBuf with the bytes to insert + @param[in] uPos Index in output buffer at which to insert + + NewData is the pointer and length for the bytes to be added to the + output buffer. There must be room in the output buffer for all of + NewData or an error will occur. + + The insertion point must be between 0 and the current valid data. If + not an error will occur. Appending data to the output buffer is + achieved by inserting at the end of the valid data. This can be + retrieved by calling UsefulOutBuf_GetEndPosition(). + + When insertion is performed, the bytes between the insertion point and + the end of data previously added to the output buffer is slid to the + right to make room for the new data. + + Overlapping buffers are OK. NewData can point to data in the output + buffer. + + If an error occurs an error state is set in the UsefulOutBuf. No + error is returned. All subsequent attempts to add data will do + nothing. + + Call UsefulOutBuf_GetError() to find out if there is an error. This + is usually not needed until all additions of data are complete. + + */ +void UsefulOutBuf_InsertUsefulBuf(UsefulOutBuf *me, UsefulBufC NewData, size_t uPos); + + +/** + @brief Insert a data buffer into the UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBul + @param[in] pBytes Pointer to the bytes to insert + @param[in] uLen Length of the bytes to insert + @param[in] uPos Index in output buffer at which to insert + + See UsefulOutBuf_InsertUsefulBuf() for details. This is the same with + the difference being a pointer and length is passed in rather than an + UsefulBuf. + + */ +static inline void UsefulOutBuf_InsertData(UsefulOutBuf *me, const void *pBytes, size_t uLen, size_t uPos) +{ + UsefulBufC Data = {pBytes, uLen}; + UsefulOutBuf_InsertUsefulBuf(me, Data, uPos); +} + + +/** + @brief Insert a NULL-terminated string into the UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBuf + @param[in] szString string to append + + */ +static inline void UsefulOutBuf_InsertString(UsefulOutBuf *me, const char *szString, size_t uPos) +{ + UsefulOutBuf_InsertUsefulBuf(me, (UsefulBufC){szString, strlen(szString)}, uPos); +} + + +/** + @brief Insert a byte into the UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBul + @param[in] byte Bytes to insert + @param[in] uPos Index in output buffer at which to insert + + See UsefulOutBuf_InsertUsefulBuf() for details. This is the same with + the difference being a single byte is to be inserted. + */ +static inline void UsefulOutBuf_InsertByte(UsefulOutBuf *me, uint8_t byte, size_t uPos) +{ + UsefulOutBuf_InsertData(me, &byte, 1, uPos); +} + + +/** + @brief Insert a 16-bit integer into the UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBul + @param[in] uInteger16 Integer to insert + @param[in] uPos Index in output buffer at which to insert + + See UsefulOutBuf_InsertUsefulBuf() for details. This is the same with + the difference being a single byte is to be inserted. + + The integer will be inserted in network byte order (big endian) + */ +static inline void UsefulOutBuf_InsertUint16(UsefulOutBuf *me, uint16_t uInteger16, size_t uPos) +{ + // Converts native integer format to network byte order (big endian) + uint8_t tmp[2]; + tmp[0] = (uInteger16 & 0xff00) >> 8; + tmp[1] = (uInteger16 & 0xff); + UsefulOutBuf_InsertData(me, tmp, 2, uPos); +} + + +/** + @brief Insert a 32-bit integer into the UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBul + @param[in] uInteger32 Integer to insert + @param[in] uPos Index in output buffer at which to insert + + See UsefulOutBuf_InsertUsefulBuf() for details. This is the same with + the difference being a single byte is to be inserted. + + The integer will be inserted in network byte order (big endian) + */ +static inline void UsefulOutBuf_InsertUint32(UsefulOutBuf *me, uint32_t uInteger32, size_t uPos) +{ + // Converts native integer format to network byte order (big endian) + uint8_t tmp[4]; + tmp[0] = (uInteger32 & 0xff000000) >> 24; + tmp[1] = (uInteger32 & 0xff0000) >> 16; + tmp[2] = (uInteger32 & 0xff00) >> 8; + tmp[3] = (uInteger32 & 0xff); + UsefulOutBuf_InsertData(me, tmp, 4, uPos); +} + + +/** + @brief Insert a 64-bit integer into the UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBul + @param[in] uInteger64 Integer to insert + @param[in] uPos Index in output buffer at which to insert + + See UsefulOutBuf_InsertUsefulBuf() for details. This is the same with + the difference being a single byte is to be inserted. + + The integer will be inserted in network byte order (big endian) + */ +static inline void UsefulOutBuf_InsertUint64(UsefulOutBuf *me, uint64_t uInteger64, size_t uPos) +{ + // Converts native integer format to network byte order (big endian) + uint8_t tmp[8]; + tmp[0] = (uInteger64 & 0xff00000000000000) >> 56; + tmp[1] = (uInteger64 & 0xff000000000000) >> 48; + tmp[2] = (uInteger64 & 0xff0000000000) >> 40; + tmp[3] = (uInteger64 & 0xff00000000) >> 32; + tmp[4] = (uInteger64 & 0xff000000) >> 24; + tmp[5] = (uInteger64 & 0xff0000) >> 16; + tmp[6] = (uInteger64 & 0xff00) >> 8; + tmp[7] = (uInteger64 & 0xff); + UsefulOutBuf_InsertData(me, tmp, 8, uPos); +} + + +/** + @brief Insert a float into the UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBul + @param[in] f Integer to insert + @param[in] uPos Index in output buffer at which to insert + + See UsefulOutBuf_InsertUsefulBuf() for details. This is the same with + the difference being a single byte is to be inserted. + + The float will be inserted in network byte order (big endian) + */ +static inline void UsefulOutBuf_InsertFloat(UsefulOutBuf *me, float f, size_t uPos) +{ + UsefulOutBuf_InsertUint32(me, UsefulBufUtil_CopyFloatToUint32(f), uPos); +} + + +/** + @brief Insert a double into the UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBul + @param[in] d Integer to insert + @param[in] uPos Index in output buffer at which to insert + + See UsefulOutBuf_InsertUsefulBuf() for details. This is the same with + the difference being a single byte is to be inserted. + + The double will be inserted in network byte order (big endian) + */ +static inline void UsefulOutBuf_InsertDouble(UsefulOutBuf *me, double d, size_t uPos) +{ + UsefulOutBuf_InsertUint64(me, UsefulBufUtil_CopyDoubleToUint64(d), uPos); +} + + + +/** + Append a UsefulBuf into the UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBuf + @param[in] NewData UsefulBuf with the bytes to append + + See UsefulOutBuf_InsertUsefulBuf() for details. This does the same + with the insertion point at the end of the valid data. + +*/ +static inline void UsefulOutBuf_AppendUsefulBuf(UsefulOutBuf *me, UsefulBufC NewData) +{ + // An append is just a insert at the end + UsefulOutBuf_InsertUsefulBuf(me, NewData, UsefulOutBuf_GetEndPosition(me)); +} + + +/** + Append bytes to the UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBuf + @param[in] pBytes Pointer to bytes to append + @param[in] uLen Index in output buffer at which to append + + See UsefulOutBuf_InsertUsefulBuf() for details. This does the same + with the insertion point at the end of the valid data. + */ + +static inline void UsefulOutBuf_AppendData(UsefulOutBuf *me, const void *pBytes, size_t uLen) +{ + UsefulBufC Data = {pBytes, uLen}; + UsefulOutBuf_AppendUsefulBuf(me, Data); +} + + +/** + Append a NULL-terminated string to the UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBuf + @param[in] szString string to append + + */ +static inline void UsefulOutBuf_AppendString(UsefulOutBuf *me, const char *szString) +{ + UsefulOutBuf_AppendUsefulBuf(me, (UsefulBufC){szString, strlen(szString)}); +} + + +/** + @brief Append a byte to the UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBuf + @param[in] byte Bytes to append + + See UsefulOutBuf_InsertUsefulBuf() for details. This does the same + with the insertion point at the end of the valid data. + */ +static inline void UsefulOutBuf_AppendByte(UsefulOutBuf *me, uint8_t byte) +{ + UsefulOutBuf_AppendData(me, &byte, 1); +} + +/** + @brief Append an integer to the UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBuf + @param[in] uInteger16 Integer to append + + See UsefulOutBuf_InsertUsefulBuf() for details. This does the same + with the insertion point at the end of the valid data. + + The integer will be appended in network byte order (big endian). + */ +static inline void UsefulOutBuf_AppendUint16(UsefulOutBuf *me, uint16_t uInteger16){ + UsefulOutBuf_InsertUint16(me, uInteger16, UsefulOutBuf_GetEndPosition(me)); +} + +/** + @brief Append an integer to the UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBuf + @param[in] uInteger32 Integer to append + + See UsefulOutBuf_InsertUsefulBuf() for details. This does the same + with the insertion point at the end of the valid data. + + The integer will be appended in network byte order (big endian). + */ +static inline void UsefulOutBuf_AppendUint32(UsefulOutBuf *me, uint32_t uInteger32){ + UsefulOutBuf_InsertUint32(me, uInteger32, UsefulOutBuf_GetEndPosition(me)); +} + +/** + @brief Append an integer to the UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBuf + @param[in] uInteger64 Integer to append + + See UsefulOutBuf_InsertUsefulBuf() for details. This does the same + with the insertion point at the end of the valid data. + + The integer will be appended in network byte order (big endian). + */ +static inline void UsefulOutBuf_AppendUint64(UsefulOutBuf *me, uint64_t uInteger64){ + UsefulOutBuf_InsertUint64(me, uInteger64, UsefulOutBuf_GetEndPosition(me)); +} + + +/** + @brief Append a float to the UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBuf + @param[in] f Float to append + + See UsefulOutBuf_InsertUsefulBuf() for details. This does the same + with the insertion point at the end of the valid data. + + The float will be appended in network byte order (big endian). + */ +static inline void UsefulOutBuf_AppendFloat(UsefulOutBuf *me, float f){ + UsefulOutBuf_InsertFloat(me, f, UsefulOutBuf_GetEndPosition(me)); +} + +/** + @brief Append a float to the UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBuf + @param[in] d Double to append + + See UsefulOutBuf_InsertUsefulBuf() for details. This does the same + with the insertion point at the end of the valid data. + + The double will be appended in network byte order (big endian). + */ +static inline void UsefulOutBuf_AppendDouble(UsefulOutBuf *me, double d){ + UsefulOutBuf_InsertDouble(me, d, UsefulOutBuf_GetEndPosition(me)); +} + +/** + @brief Returns the current error status + + @param[in] me Pointer to the UsefulOutBuf + + @return 0 if all OK, 1 on error + + This is the error status since the call to either + UsefulOutBuf_Reset() of UsefulOutBuf_Init(). Once it goes into error + state it will stay until one of those functions is called. + + Possible error conditions are: + - bytes to be inserted will not fit + - insertion point is out of buffer or past valid data + - current position is off end of buffer (probably corruption or uninitialized) + - detect corruption / uninitialized by bad magic number + */ + +static inline int UsefulOutBuf_GetError(UsefulOutBuf *me) +{ + return me->err; +} + + +/** + @brief Returns number of bytes unused used in the output buffer + + @param[in] me Pointer to the UsefulOutBuf + + @return Number of unused bytes or zero + + Because of the error handling strategy and checks in UsefulOutBuf_InsertUsefulBuf() + it is usually not necessary to use this. + */ + +static inline size_t UsefulOutBuf_RoomLeft(UsefulOutBuf *me) +{ + return me->UB.len - me->data_len; +} + + +/** + @brief Returns true / false if some number of bytes will fit in the UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBuf + @param[in] uLen Number of bytes for which to check + + @return 1 or 0 if nLen bytes would fit + + Because of the error handling strategy and checks in UsefulOutBuf_InsertUsefulBuf() + it is usually not necessary to use this. + */ + +static inline int UsefulOutBuf_WillItFit(UsefulOutBuf *me, size_t uLen) +{ + return uLen <= UsefulOutBuf_RoomLeft(me); +} + + +/** + @brief Returns the resulting valid data in a UsefulOutBuf + + @param[in] me Pointer to the UsefulOutBuf. + + @return The valid data in UsefulOutBuf. + + The storage for the returned data is Storage parameter passed + to UsefulOutBuf_Init(). See also UsefulOutBuf_CopyOut(). + + This can be called anytime and many times to get intermediate + results. It doesn't change the data or reset the current position + so you can keep adding data. + */ + +UsefulBufC UsefulOutBuf_OutUBuf(UsefulOutBuf *me); + + +/** + @brief Copies the valid data out into a supplied buffer + + @param[in] me Pointer to the UsefulOutBuf + @param[out] Dest The destination buffer to copy into + + @return Pointer and length of copied data. + + This is the same as UsefulOutBuf_OutUBuf() except it copies the data. +*/ + +UsefulBufC UsefulOutBuf_CopyOut(UsefulOutBuf *me, UsefulBuf Dest); + + + + + + + + + + + + + +/** + UsefulInputBuf is the counterpart to UsefulOutBuf and is for parsing + data read or received. Initialize it with the data + from the network and its length. Then use the functions + here to get the various data types out of it. It maintains a position + for getting the next item. This means you don't have to track a + pointer as you get each object. UsefulInputBuf does that for you and + makes sure it never goes off the end of the buffer. The QCBOR + implementation parser makes use of this for all its pointer math and + length checking. + + UsefulInputBuf also maintains an internal error state so you do not have + to. Once data has been requested off the end of the buffer, it goes + into an error state. You can keep calling functions to get more data + but they will either return 0 or NULL. As long as you don't + dereference the NULL, you can wait until all data items have been + fetched before checking for the error and this can simplify your + code. + + The integer and float parsing expects network byte order (big endian). + Network byte order is what is used by TCP/IP, CBOR and most internet + protocols. + + Lots of inlining is used to keep code size down. The code optimizer, + particularly with the -Os, also reduces code size a lot. The only + non-inline code is UsefulInputBuf_GetBytes() which is less than 100 + bytes so use of UsefulInputBuf doesn't add much code for all the messy + hard-to-get right issues with parsing in C that is solves. + + The parse context size is: + 64-bit machine: 16 + 8 + 2 + 1 (5 bytes padding to align) = 32 bytes + 32-bit machine: 8 + 4 + 2 + 1 (1 byte padding to align) = 16 bytes + + */ + +#define UIB_MAGIC (0xB00F) + +typedef struct useful_input_buf { + // Private data structure + UsefulBufC UB; // Data being parsed + size_t cursor; // Current offset in data being parse + uint16_t magic; // Check for corrupted or uninitialized UsefulInputBuf + uint8_t err; // Set request goes off end or magic number is bad +} UsefulInputBuf; + + + +/** + @brief Initialize the UsefulInputBuf structure before use. + + @param[in] me Pointer to the UsefulInputBuf instance. + @param[in] UB Pointer to the data to parse. + + */ +static inline void UsefulInputBuf_Init(UsefulInputBuf *me, UsefulBufC UB) +{ + me->cursor = 0; + me->err = 0; + me->magic = UIB_MAGIC; + me->UB = UB; +} + + +/** + @brief Returns current position in input buffer + + @param[in] me Pointer to the UsefulInputBuf. + + @return Integer position of the cursor + + The position that the next bytes will be returned from. + + */ +static inline size_t UsefulInputBuf_Tell(UsefulInputBuf *me) +{ + return me->cursor; +} + + +/** + @brief Sets current position in input buffer + + @param[in] me Pointer to the UsefulInputBuf. + @param[in] uPos Position to set to + + If the position is off the end of the input buffer, the error state + is entered and all functions will do nothing. + + Seeking to a valid position in the buffer will not reset the error + state. Only re initialization will do that. + + */ +static inline void UsefulInputBuf_Seek(UsefulInputBuf *me, size_t uPos) +{ + if(uPos > me->UB.len) { + me->err = 1; + } else { + me->cursor = uPos; + } +} + + +/** + @brief Returns the number of bytes from the cursor to the end of the buffer, + the uncomsummed bytes. + + @param[in] me Pointer to the UsefulInputBuf. + + @return number of bytes unconsumed or 0 on error. + + This is a critical function for input length validation. This does + some pointer / offset math. + + Returns 0 if the cursor it invalid or corruption of the structure is + detected. + + Code Reviewers: THIS FUNCTION DOES POINTER MATH + */ +static inline size_t UsefulInputBuf_BytesUnconsumed(UsefulInputBuf *me) +{ + // Magic number is messed up. Either the structure got overwritten + // or was never initialized. + if(me->magic != UIB_MAGIC) { + return 0; + } + + // The cursor is off the end of the input buffer given + // Presuming there are no bugs in this code, this should never happen. + // If it so, the struct was corrupted. The check is retained as + // as a defense in case there is a bug in this code or the struct is corrupted. + if(me->cursor > me->UB.len) { + return 0; + } + + // subtraction can't go neative because of check above + return me->UB.len - me->cursor; +} + + +/** + @brief Check if there are any unconsumed bytes + + @param[in] me Pointer to the UsefulInputBuf. + + @return 1 if len bytes are available after the cursor, and 0 if not + + */ +static inline int UsefulInputBuf_BytesAvailable(UsefulInputBuf *me, size_t uLen) +{ + return UsefulInputBuf_BytesUnconsumed(me) >= uLen ? 1 : 0; +} + + +/** + @brief Get pointer to bytes out of the input buffer + + @param[in] me Pointer to the UsefulInputBuf. + @param[in] uNum Number of bytes to get + + @return Pointer to bytes. + + This consumes n bytes from the input buffer. It returns a pointer to + the start of the n bytes. + + If there are not n bytes in the input buffer, NULL will be returned + and an error will be set. + + It advances the current position by n bytes. + */ +const void * UsefulInputBuf_GetBytes(UsefulInputBuf *me, size_t uNum); + + +/** + @brief Get UsefulBuf out of the input buffer + + @param[in] me Pointer to the UsefulInputBuf. + @param[in] uNum Number of bytes to get + + @return UsefulBufC with ptr and length for bytes consumed. + + This consumes n bytes from the input buffer and returns the pointer + and len to them as a UsefulBufC. The len returned will always be n. + + If there are not n bytes in the input buffer, UsefulBufC.ptr will be + NULL and UsefulBufC.len will be 0. An error will be set. + + It advances the current position by n bytes. + */ +static inline UsefulBufC UsefulInputBuf_GetUsefulBuf(UsefulInputBuf *me, size_t uNum) +{ + const void *pResult = UsefulInputBuf_GetBytes(me, uNum); + if(!pResult) { + return NULLUsefulBufC; + } else { + return (UsefulBufC){pResult, uNum}; + } +} + + +/** + @brief Get a byte out of the input buffer. + + @param[in] me Pointer to the UsefulInputBuf. + + @return The byte + + This consumes 1 byte from the input buffer. It returns the byte. + + If there is not 1 byte in the buffer, 0 will be returned for the byte + and an error set internally. You must check the error at some point + to know whether the 0 was the real value or just returned in error, + but you may not have to do that right away. Check the error state + with UsefulInputBuf_GetError(). You can also know you are in the + error state if UsefulInputBuf_GetBytes() returns NULL or the ptr from + UsefulInputBuf_GetUsefulBuf() is NULL. + + It advances the current position by 1 byte. + */ +static inline uint8_t UsefulInputBuf_GetByte(UsefulInputBuf *me) +{ + const void *pResult = UsefulInputBuf_GetBytes(me, sizeof(uint8_t)); + + return pResult ? *(uint8_t *)pResult : 0; +} + + +/** + @brief Get a uint16_t out of the input buffer + + @param[in] me Pointer to the UsefulInputBuf. + + @return The uint16_t + + See UsefulInputBuf_GetByte(). This works the same, except it returns + a uint16_t and two bytes are consumed. + + The input bytes must be in network order (big endian). + */ +static inline uint16_t UsefulInputBuf_GetUint16(UsefulInputBuf *me) +{ + const uint8_t *pResult = (const uint8_t *)UsefulInputBuf_GetBytes(me, sizeof(uint16_t)); + + if(!pResult) { + return 0; + } + + return ((uint16_t)pResult[0] << 8) + (uint16_t)pResult[1]; +} + + +/** + @brief Get a uint32_t out of the input buffer + + @param[in] me Pointer to the UsefulInputBuf. + + @return The uint32_t + + See UsefulInputBuf_GetByte(). This works the same, except it returns + a uint32_t and four bytes are consumed. + + The input bytes must be in network order (big endian). + */ +static inline uint32_t UsefulInputBuf_GetUint32(UsefulInputBuf *me) +{ + const uint8_t *pResult = (const uint8_t *)UsefulInputBuf_GetBytes(me, sizeof(uint32_t)); + + if(!pResult) { + return 0; + } + + return ((uint32_t)pResult[0]<<24) + + ((uint32_t)pResult[1]<<16) + + ((uint32_t)pResult[2]<<8) + + (uint32_t)pResult[3]; +} + + +/** + @brief Get a uint64_t out of the input buffer + + @param[in] me Pointer to the UsefulInputBuf. + + @return The uint64_t + + See UsefulInputBuf_GetByte(). This works the same, except it returns + a uint64_t and eight bytes are consumed. + + The input bytes must be in network order (big endian). + */ +static inline uint64_t UsefulInputBuf_GetUint64(UsefulInputBuf *me) +{ + const uint8_t *pResult = (const uint8_t *)UsefulInputBuf_GetBytes(me, sizeof(uint64_t)); + + if(!pResult) { + return 0; + } + + return ((uint64_t)pResult[0]<<56) + + ((uint64_t)pResult[1]<<48) + + ((uint64_t)pResult[2]<<40) + + ((uint64_t)pResult[3]<<32) + + ((uint64_t)pResult[4]<<24) + + ((uint64_t)pResult[5]<<16) + + ((uint64_t)pResult[6]<<8) + + (uint64_t)pResult[7]; +} + + +/** + @brief Get a float out of the input buffer + + @param[in] me Pointer to the UsefulInputBuf. + + @return The float + + See UsefulInputBuf_GetByte(). This works the same, except it returns + a float and four bytes are consumed. + + The input bytes must be in network order (big endian). + */ +static inline float UsefulInputBuf_GetFloat(UsefulInputBuf *me) +{ + uint32_t uResult = UsefulInputBuf_GetUint32(me); + + return uResult ? UsefulBufUtil_CopyUint32ToFloat(uResult) : 0; +} + +/** + @brief Get a double out of the input buffer + + @param[in] me Pointer to the UsefulInputBuf. + + @return The double + + See UsefulInputBuf_GetByte(). This works the same, except it returns + a double and eight bytes are consumed. + + The input bytes must be in network order (big endian). + */ +static inline double UsefulInputBuf_GetDouble(UsefulInputBuf *me) +{ + uint64_t uResult = UsefulInputBuf_GetUint64(me); + + return uResult ? UsefulBufUtil_CopyUint64ToDouble(uResult) : 0; +} + + +/** + @brief Get the error status + + @param[in] me Pointer to the UsefulInputBuf. + + @return The error. + + Zero is success, non-zero is error. Once in the error state, the only + way to clear it is to call Init again. + + You may be able to only check the error state at the end after all + the Get()'s have been done, but if what you get later depends on what + you get sooner you cannot. For example if you get a length or count + of following items you will have to check the error. + + */ +static inline int UsefulInputBuf_GetError(UsefulInputBuf *me) +{ + return me->err; +} + + +#endif // _UsefulBuf_h + + diff --git a/middleware/qcbor/inc/qcbor.h b/middleware/qcbor/inc/qcbor.h new file mode 100644 index 000000000..3011cc6fa --- /dev/null +++ b/middleware/qcbor/inc/qcbor.h @@ -0,0 +1,2597 @@ +/*============================================================================== + Copyright (c) 2016-2018, The Linux Foundation. + Copyright (c) 2018-2019, Laurence Lundblade. + All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of The Linux Foundation nor the names of its + contributors, nor the name "Laurence Lundblade" may be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ==============================================================================*/ + + +/*=================================================================================== + FILE: qcbor.h + + DESCRIPTION: This is the full public API and data structures for QCBOR + + EDIT HISTORY FOR FILE: + + This section contains comments describing changes made to the module. + Notice that changes are listed in reverse chronological order. + + when who what, where, why + -------- ---- --------------------------------------------------- + 12/18/18 llundblade Move decode malloc optional code to separate repository + 12/13/18 llundblade Documentatation improvements + 11/29/18 llundblade Rework to simpler handling of tags and labels. + 11/9/18 llundblade Error codes are now enums. + 11/1/18 llundblade Floating support. + 10/31/18 llundblade Switch to one license that is almost BSD-3. + 10/15/18 llundblade Indefinite length maps and arrays supported + 10/8/18 llundblade Indefinite length strings supported + 09/28/18 llundblade Added bstr wrapping feature for COSE implementation. + 07/05/17 llundbla Add bstr wrapping of maps/arrays for COSE. + 03/01/17 llundbla More data types; decoding improvements and fixes. + 11/13/16 llundbla Integrate most TZ changes back into github version. + 09/30/16 gkanike Porting to TZ. + 03/15/16 llundbla Initial Version. + + =====================================================================================*/ + +#ifndef __QCBOR__qcbor__ +#define __QCBOR__qcbor__ + +/*...... This is a ruler that is 80 characters long...........................*/ + +/* =========================================================================== + BEGINNING OF PRIVATE PART OF THIS FILE + + Caller of QCBOR should not reference any of the details below up until + the start of the public part. + =========================================================================== */ + +/* + Standard integer types are used in the interface to be precise about + sizes to be better at preventing underflow/overflow errors. + */ +#include +#include +#include "UsefulBuf.h" + + +/* + The maxium nesting of arrays and maps when encoding or decoding. + (Further down in the file there is a definition that refers to this + that is public. This is done this way so there can be a nice + separation of public and private parts in this file. +*/ +#define QCBOR_MAX_ARRAY_NESTING1 15 // Do not increase this over 255 + + +/* The largest offset to the start of an array or map. It is slightly + less than UINT32_MAX so the error condition can be tests on 32-bit machines. + UINT32_MAX comes from uStart in QCBORTrackNesting being a uin32_t. + + This will cause trouble on a machine where size_t is less than 32-bits. + */ +#define QCBOR_MAX_ARRAY_OFFSET (UINT32_MAX - 100) + +/* + PRIVATE DATA STRUCTURE + + Holds the data for tracking array and map nesting during encoding. Pairs up with + the Nesting_xxx functions to make an "object" to handle nesting encoding. + + uStart is a uint32_t instead of a size_t to keep the size of this + struct down so it can be on the stack without any concern. It would be about + double if size_t was used instead. + + Size approximation (varies with CPU/compiler): + 64-bit machine: (15 + 1) * (4 + 2 + 1 + 1 pad) + 8 = 136 bytes + 32-bit machine: (15 + 1) * (4 + 2 + 1 + 1 pad) + 4 = 132 bytes +*/ +typedef struct __QCBORTrackNesting { + // PRIVATE DATA STRUCTURE + struct { + // See function OpenArrayInternal() for detailed comments on how this works + uint32_t uStart; // uStart is the byte position where the array starts + uint16_t uCount; // Number of items in the arrary or map; counts items in a map, not pairs of items + uint8_t uMajorType; // Indicates if item is a map or an array + } pArrays[QCBOR_MAX_ARRAY_NESTING1+1], // stored state for the nesting levels + *pCurrentNesting; // the current nesting level +} QCBORTrackNesting; + + +/* + PRIVATE DATA STRUCTURE + + Context / data object for encoding some CBOR. Used by all encode functions to + form a public "object" that does the job of encdoing. + + Size approximation (varies with CPU/compiler): + 64-bit machine: 27 + 1 (+ 4 padding) + 136 = 32 + 136 = 168 bytes + 32-bit machine: 15 + 1 + 132 = 148 bytes +*/ +struct _QCBOREncodeContext { + // PRIVATE DATA STRUCTURE + UsefulOutBuf OutBuf; // Pointer to output buffer, its length and position in it + uint8_t uError; // Error state + QCBORTrackNesting nesting; // Keep track of array and map nesting +}; + + +/* + PRIVATE DATA STRUCTURE + + Holds the data for array and map nesting for decoding work. This structure + and the DecodeNesting_xxx functions form an "object" that does the work + for arrays and maps. + + Size approximation (varies with CPU/compiler): + 64-bit machine: 4 * 16 + 8 = 72 + 32-bit machine: 4 * 16 + 4 = 68 + */ +typedef struct __QCBORDecodeNesting { + // PRIVATE DATA STRUCTURE + struct { + uint16_t uCount; + uint8_t uMajorType; + } pMapsAndArrays[QCBOR_MAX_ARRAY_NESTING1+1], + *pCurrent; +} QCBORDecodeNesting; + + +/* + PRIVATE DATA STRUCTURE + + The decode context. This data structure plus the public QCBORDecode_xxx + functions form an "object" that does CBOR decoding. + + Size approximation (varies with CPU/compiler): + 64-bit machine: 32 + 1 + 1 + 6 bytes padding + 72 + 16 = 128 bytes + 32-bit machine: 16 + 1 + 1 + 2 bytes padding + 68 + 8 = 68 bytes + */ +struct _QCBORDecodeContext { + // PRIVATE DATA STRUCTURE + UsefulInputBuf InBuf; + + uint8_t uDecodeMode; + uint8_t bStringAllocateAll; + + QCBORDecodeNesting nesting; + + // This is NULL or points to a QCBORStringAllocator. It is void + // here because _QCBORDecodeContext is defined early in the + // private part of this file and QCBORStringAllocat is defined + // later in the public part of this file. + void *pStringAllocator; + + // This is NULL or points to QCBORTagList. + // It is type void for the same reason as above. + const void *pCallerConfiguredTagList; +}; + +// Used internally in the impementation here +// Must not conflict with any of the official CBOR types +#define CBOR_MAJOR_NONE_TYPE_RAW 9 +#define CBOR_MAJOR_NONE_TAG_LABEL_REORDER 10 + + +/* =========================================================================== + END OF PRIVATE PART OF THIS FILE + + BEGINNING OF PUBLIC PART OF THIS FILE + =========================================================================== */ + + + +/* =========================================================================== + BEGINNING OF CONSTANTS THAT COME FROM THE CBOR STANDARD, RFC 7049 + + It is not necessary to use these directly when encoding or decoding + CBOR with this implementation. + =========================================================================== */ + +/* Standard CBOR Major type for positive integers of various lengths */ +#define CBOR_MAJOR_TYPE_POSITIVE_INT 0 + +/* Standard CBOR Major type for negative integer of various lengths */ +#define CBOR_MAJOR_TYPE_NEGATIVE_INT 1 + +/* Standard CBOR Major type for an array of arbitrary 8-bit bytes. */ +#define CBOR_MAJOR_TYPE_BYTE_STRING 2 + +/* Standard CBOR Major type for a UTF-8 string. Note this is true 8-bit UTF8 + with no encoding and no NULL termination */ +#define CBOR_MAJOR_TYPE_TEXT_STRING 3 + +/* Standard CBOR Major type for an ordered array of other CBOR data items */ +#define CBOR_MAJOR_TYPE_ARRAY 4 + +/* Standard CBOR Major type for CBOR MAP. Maps an array of pairs. The + first item in the pair is the "label" (key, name or identfier) and the second + item is the value. */ +#define CBOR_MAJOR_TYPE_MAP 5 + +/* Standard CBOR optional tagging. This tags things like dates and URLs */ +#define CBOR_MAJOR_TYPE_OPTIONAL 6 + +/* Standard CBOR extra simple types like floats and the values true and false */ +#define CBOR_MAJOR_TYPE_SIMPLE 7 + + +/* + These are special values for the AdditionalInfo bits that are part of the first byte. + Mostly they encode the length of the data item. + */ +#define LEN_IS_ONE_BYTE 24 +#define LEN_IS_TWO_BYTES 25 +#define LEN_IS_FOUR_BYTES 26 +#define LEN_IS_EIGHT_BYTES 27 +#define ADDINFO_RESERVED1 28 +#define ADDINFO_RESERVED2 29 +#define ADDINFO_RESERVED3 30 +#define LEN_IS_INDEFINITE 31 + + +/* + 24 is a special number for CBOR. Integers and lengths + less than it are encoded in the same byte as the major type + */ +#define CBOR_TWENTY_FOUR 24 + + +/* + Tags that are used with CBOR_MAJOR_TYPE_OPTIONAL. These are + the ones defined in the CBOR spec. + */ +/** See QCBOREncode_AddDateString() below */ +#define CBOR_TAG_DATE_STRING 0 +/** See QCBOREncode_AddDateEpoch_2() */ +#define CBOR_TAG_DATE_EPOCH 1 +#define CBOR_TAG_POS_BIGNUM 2 +#define CBOR_TAG_NEG_BIGNUM 3 +#define CBOR_TAG_FRACTION 4 +#define CBOR_TAG_BIGFLOAT 5 + +#define CBOR_TAG_COSE_ENCRYPTO 16 +#define CBOR_TAG_COSE_MAC0 17 +#define CBOR_TAG_COSE_SIGN1 18 + +/* The data in byte string should be converted in base 64 URL when encoding in JSON or similar text-based representations */ +#define CBOR_TAG_ENC_AS_B64URL 21 +/* The data in byte string should be encoded in base 64 when encoding in JSON */ +#define CBOR_TAG_ENC_AS_B64 22 +/* The data in byte string should be encoded in base 16 when encoding in JSON */ +#define CBOR_TAG_ENC_AS_B16 23 +#define CBOR_TAG_CBOR 24 +/** The data in the string is a URIs, as defined in RFC3986 */ +#define CBOR_TAG_URI 32 +/** The data in the string is a base 64'd URL */ +#define CBOR_TAG_B64URL 33 +/** The data in the string is base 64'd */ +#define CBOR_TAG_B64 34 +/** regular expressions in Perl Compatible Regular Expressions (PCRE) / JavaScript syntax ECMA262. */ +#define CBOR_TAG_REGEX 35 +/** MIME messages (including all headers), as defined in RFC2045 */ +#define CBOR_TAG_MIME 36 +/** Binary UUID */ +#define CBOR_TAG_BIN_UUID 37 + +#define CBOR_TAG_CWT 61 + +#define CBOR_TAG_ENCRYPT 96 +#define CBOR_TAG_MAC 97 +#define CBOR_TAG_SIGN 98 + +#define CBOR_TAG_GEO_COORD 103 + + +/** The data is CBOR data */ +#define CBOR_TAG_CBOR_MAGIC 55799 +#define CBOR_TAG_NONE UINT64_MAX + + +/* + Values for the 5 bits for items of major type 7 + */ +#define CBOR_SIMPLEV_FALSE 20 +#define CBOR_SIMPLEV_TRUE 21 +#define CBOR_SIMPLEV_NULL 22 +#define CBOR_SIMPLEV_UNDEF 23 +#define CBOR_SIMPLEV_ONEBYTE 24 +#define HALF_PREC_FLOAT 25 +#define SINGLE_PREC_FLOAT 26 +#define DOUBLE_PREC_FLOAT 27 +#define CBOR_SIMPLE_BREAK 31 + + + +/* =========================================================================== + + END OF CONSTANTS THAT COME FROM THE CBOR STANDARD, RFC 7049 + + BEGINNING OF PUBLIC INTERFACE FOR QCBOR ENCODER / DECODER + + =========================================================================== */ + +/** + + @file qcbor.h + + Q C B O R E n c o d e / D e c o d e + + This implements CBOR -- Concise Binary Object Representation as defined + in RFC 7049. More info is at http://cbor.io. This is a near-complete + implementation of the specification. Limitations are listed further down. + + CBOR is intentionally designed to be translatable to JSON, but not + all CBOR can convert to JSON. See RFC 7049 for more info on how to + construct CBOR that is the most JSON friendly. + + The memory model for encoding and decoding is that encoded CBOR + must be in a contiguous buffer in memory. During encoding the + caller must supply an output buffer and if the encoding would go + off the end of the buffer an error is returned. During decoding + the caller supplies the encoded CBOR in a contiguous buffer + and the decoder returns pointers and lengths into that buffer + for strings. + + This implementation does not require malloc. All data structures + passed in/out of the APIs can fit on the stack. + + Decoding of indefinite length strings is a special case that requires + a "string allocator" to allocate memory into which the segments of + the string are coalesced. Without this, decoding will error out if + an indefinite length string is encountered (indefinite length maps + and arrays do not require the string allocator). A simple string + allocator called MemPool is built-in and will work if supplied with + a block of memory to allocate. The string allocator can optionally + use malloc() or some other custom scheme. + + Here are some terms and definitions: + + - "Item", "Data Item": An integer or string or such. The basic "thing" that + CBOR is about. An array is an item itself that contains some items. + + - "Array": An ordered sequence of items, the same as JSON. + + - "Map": A collection of label/value pairs. Each pair is a data + item. A JSON "object" is the same as a CBOR "map". + + - "Label": The data item in a pair in a map that names or identifies the + pair, not the value. This implementation refers to it as a "label". + JSON refers to it as the "name". The CBOR RFC refers to it this as a "key". + This implementation chooses label instead because key is too easily confused + with a cryptographic key. The COSE standard, which uses CBOR, has also + chosen to use the term "label" rather than "key" for this same reason. + + - "Key": See "Label" above. + + - "Tag": Optional info that can be added before each data item. This is always + CBOR major type 6. + + - "Initial Byte": The first byte of an encoded item. Encoding and decoding of + this byte is taken care of by the implementation. + + - "Additional Info": In addition to the major type, all data items have some + other info. This is usually the length of the data, but can be several + other things. Encoding and decoding of this is taken care of by the + implementation. + + CBOR has two mechanisms for tagging and labeling the data + values like integers and strings. For example, an integer that + represents someone's birthday in epoch seconds since Jan 1, 1970 + could be encoded like this: + + - First it is CBOR_MAJOR_TYPE_POSITIVE_INT, the primitive positive + integer. + - Next it has a "tag" CBOR_TAG_DATE_EPOCH indicating the integer + represents a date in the form of the number of seconds since + Jan 1, 1970. + - Last it has a string "label" like "BirthDate" indicating + the meaning of the data. + + The encoded binary looks like this: + a1 # Map of 1 item + 69 # Indicates text string of 9 bytes + 426972746844617465 # The text "BirthDate" + c1 # Tags next int as epoch date + 1a # Indicates 4 byte integer + 580d4172 # unsigned integer date 1477263730 + + Implementors using this API will primarily work with labels. Generally + tags are only needed for making up new data types. This implementation + covers most of the data types defined in the RFC using tags. It also, + allows for the creation of news tags if necessary. + + This implementation explicitly supports labels that are text strings + and integers. Text strings translate nicely into JSON objects and + are very readable. Integer labels are much less readable, but + can be very compact. If they are in the range of -23 to + 23 they take up only one byte. + + CBOR allows a label to be any type of data including an array or + a map. It is possible to use this API to construct and + parse such labels, but it is not explicitly supported. + + A common encoding usage mode is to invoke the encoding twice. First + with no output buffer to compute the length of the needed output + buffer. Then the correct sized output buffer is allocated. Last the + encoder is invoked again, this time with the output buffer. + + The double invocation is not required if the max output buffer size + can be predicted. This is usually possible for simple CBOR structures. + If the double invocation is implemented, it can be + in a loop or function as in the example code so that the code doesn't + have to actually be written twice, saving code size. + + If a buffer too small to hold the encoded output is given, the error + QCBOR_ERR_BUFFER_TOO_SMALL will be returned. Data will never be + written off the end of the output buffer no matter which functions + here are called or what parameters are passed to them. + + The error handling is simple. The only possible errors are trying to + encode structures that are too large or too complex. There are no + internal malloc calls so there will be no failures for out of memory. + Only the final call, QCBOREncode_Finish(), returns an error code. + Once an error happens, the encoder goes into an error state and calls + to it will do nothing so the encoding can just go on. An error + check is not needed after every data item is added. + + Encoding generally proceeds by calling QCBOREncode_Init(), calling + lots of "Add" functions and calling QCBOREncode_Finish(). There + are many "Add" functions for various data types. The input + buffers need only to be valid during the "Add" calls. The + data is copied into the output buf during the "Add" call. + + There are three `Add` functions for each data type. The first + / main one for the type is for adding the data item to an array. + The second one's name ends in `ToMap`, is used for adding + data items to maps and takes a string + argument that is its label in the map. The third one ends in + `ToMapN`, is also used for adding data items to maps, and + takes an integer argument that is its label in the map. + + The simplest aggregate type is an array, which is a simple ordered + set of items without labels the same as JSON arrays. Call + QCBOREncode_OpenArray() to open a new array, then "Add" to + put items in the array and then QCBOREncode_CloseArray(). Nesting + to a limit is allowed. All opens must be matched by closes or an + encoding error will be returned. + + The other aggregate type is a map which does use labels. The + `Add` functions that end in `ToMap` and `ToMapN` are convenient + ways to add labeled data items to a map. You can also call + any type of `Add` function once to add a label of any time and + then call any type of `Add` again to add its value. + + Note that when you nest arrays or maps in a map, the nested + array or map has a label. + + Usually it is not necessary to add tags explicitly as most + tagged types have functions here, but they can be added by + calling QCBOREncode_AddTag(). There is an IANA registry for new tags that are + for broad use and standardization as per RFC 7049. It is also + allowed for protocols to make up new tags in the range above 256. + Note that even arrays and maps can be tagged. + + Summary Limits of this implementation: + - The entire encoded CBOR must fit into contiguous memory. + - Max size of encoded / decoded CBOR data is UINT32_MAX (4GB). + - Max array / map nesting level when encoding / decoding is + QCBOR_MAX_ARRAY_NESTING (this is typically 15). + - Max items in an array or map when encoding / decoding is + QCBOR_MAX_ITEMS_IN_ARRAY (typically 65,536). + - Does not support encoding indefinite lengths (decoding is supported). + - Does not directly support some tagged types: decimal fractions, big floats + - Does not directly support labels in maps other than text strings and ints. + - Does not directly support int labels greater than INT64_MAX + - Epoch dates limited to INT64_MAX (+/- 292 billion years) + - Tags on labels are ignored during decoding + + This implementation is intended to run on 32 and 64-bit CPUs. Minor + modifications are needed for it to work on 16-bit CPUs. + + The public interface uses size_t for all lengths. Internally the + implementation uses 32-bit lengths by design to use less memory and + fit structures on the stack. This limits the encoded + CBOR it can work with to size UINT32_MAX (4GB) which should be + enough. + + This implementation assumes two's compliment integer + machines. Stdint.h also requires this. It of course would be easy to + fix this implementation for another integer representation, but all + modern machines seem to be two's compliment. + + */ + + +/** + The maximum number of items in a single array or map when encoding of decoding. +*/ +// -1 is because the value UINT16_MAX is used to track indefinite length arraysUINT16_MAX +#define QCBOR_MAX_ITEMS_IN_ARRAY (UINT16_MAX-1) + +/** + The maximum nesting of arrays and maps when encoding or decoding. The + error QCBOR_ERR_ARRAY_NESTING_TOO_DEEP will be returned on encoding + of decoding if it is exceeded +*/ +#define QCBOR_MAX_ARRAY_NESTING QCBOR_MAX_ARRAY_NESTING1 + +/** + The maximum number of tags that can be in QCBORTagListIn and passed to + QCBORDecode_SetCallerConfiguredTagList() + */ +#define QCBOR_MAX_CUSTOM_TAGS 16 + + +typedef enum { + /** The encode or decode completely correctly. */ + QCBOR_SUCCESS = 0, + + /** The buffer provided for the encoded output when doing encoding was + too small and the encoded output will not fit. Also, when the buffer + given to QCBORDecode_SetMemPool() is too small. */ + QCBOR_ERR_BUFFER_TOO_SMALL, + + /** During encoding or decoding, the array or map nesting was deeper than + this implementation can handle. Note that in the interest of code size + and memory use, this implementation has a hard limit on array nesting. The + limit is defined as the constant QCBOR_MAX_ARRAY_NESTING. */ + QCBOR_ERR_ARRAY_NESTING_TOO_DEEP, + + /** During decoding or encoding, the array or map had too many items in it. + This limit QCBOR_MAX_ITEMS_IN_ARRAY, typically 65,535. */ + QCBOR_ERR_ARRAY_TOO_LONG, + + /** During encoding, more arrays or maps were closed than opened. This is a + coding error on the part of the caller of the encoder. */ + QCBOR_ERR_TOO_MANY_CLOSES, + + /** During decoding, some CBOR construct was encountered that this decoder + doesn't support, primarily this is the reserved additional info values, + 28 through 30. */ + QCBOR_ERR_UNSUPPORTED, + + /** During decoding, hit the end of the given data to decode. For example, + a byte string of 100 bytes was expected, but the end of the input was + hit before finding those 100 bytes. Corrupted CBOR input will often + result in this error. */ + QCBOR_ERR_HIT_END, + + /** During encoding, the length of the encoded CBOR exceeded UINT32_MAX. + */ + QCBOR_ERR_BUFFER_TOO_LARGE, + + /** During decoding, an integer smaller than INT64_MIN was received (CBOR + can represent integers smaller than INT64_MIN, but C cannot). */ + QCBOR_ERR_INT_OVERFLOW, + + /** During decoding, the label for a map entry is bad. What causes this + error depends on the decoding mode. */ + QCBOR_ERR_MAP_LABEL_TYPE, + + /** During encoding or decoding, the number of array or map opens was not + matched by the number of closes. */ + QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN, + + /** During encoding, the simple value is not between CBOR_SIMPLEV_FALSE + and CBOR_SIMPLEV_UNDEF. */ + QCBOR_ERR_BAD_SIMPLE, + + /** During decoding, a date greater than +- 292 billion years from Jan 1 + 1970 encountered during parsing. */ + QCBOR_ERR_DATE_OVERFLOW, + + /** During decoding, the CBOR is not valid, primarily a simple type is encoded in + a prohibited way. */ + QCBOR_ERR_INVALID_CBOR, + + /** Optional tagging that doesn't make sense (an int is tagged as a + date string) or can't be handled. */ + QCBOR_ERR_BAD_OPT_TAG, + + /** Returned by QCBORDecode_Finish() if all the inputs bytes have not + been consumed. */ + QCBOR_ERR_EXTRA_BYTES, + + /** During encoding, QCBOREncode_Close() call with a different type than + is currently open. */ + QCBOR_ERR_CLOSE_MISMATCH, + + /** Unable to decode an indefinite length string because no string + allocator was configured. */ + QCBOR_ERR_NO_STRING_ALLOCATOR, + + /** One of the chunks in an indefinite length string is not of the type of + the string. */ + QCBOR_ERR_INDEFINITE_STRING_CHUNK, + + /** Error allocating space for a string, usually for an indefinite length + string. */ + QCBOR_ERR_STRING_ALLOCATE, + + /** During decoding, a break occurred outside an indefinite length item. */ + QCBOR_ERR_BAD_BREAK, + + /** During decoding, too many tags in the caller-configured tag list, or not + enough space in QCBORTagListOut. */ + QCBOR_ERR_TOO_MANY_TAGS, + + /** Returned by QCBORDecode_SetMemPool() when xx is too small. This should + never happen on a machine with 64-bit or smaller pointers. Fixing + it is probably by increasing QCBOR_DECODE_MIN_MEM_POOL_SIZE. */ + QCBOR_ERR_MEM_POOL_INTERNAL + +} QCBORError; + + +typedef enum { + /** See QCBORDecode_Init() */ + QCBOR_DECODE_MODE_NORMAL = 0, + /** See QCBORDecode_Init() */ + QCBOR_DECODE_MODE_MAP_STRINGS_ONLY = 1, + /** See QCBORDecode_Init() */ + QCBOR_DECODE_MODE_MAP_AS_ARRAY = 2 +} QCBORDecodeMode; + + + + + +/* Do not renumber these. Code depends on some of these values. */ +/** The type is unknown, unset or invalid */ +#define QCBOR_TYPE_NONE 0 +/** Type for an integer that decoded either between INT64_MIN and INT32_MIN or INT32_MAX and INT64_MAX; val.int64 */ +#define QCBOR_TYPE_INT64 2 +/** Type for an integer that decoded to a more than INT64_MAX and UINT64_MAX; val.uint64 */ +#define QCBOR_TYPE_UINT64 3 +/** Type for an array. The number of items in the array is in val.uCount. */ +#define QCBOR_TYPE_ARRAY 4 +/** Type for a map; number of items in map is in val.uCount */ +#define QCBOR_TYPE_MAP 5 +/** Type for a buffer full of bytes. Data is in val.string. */ +#define QCBOR_TYPE_BYTE_STRING 6 +/** Type for a UTF-8 string. It is not NULL terminated. Data is in val.string. */ +#define QCBOR_TYPE_TEXT_STRING 7 +/** Type for a positive big number. Data is in val.bignum, a pointer and a length. */ +#define QCBOR_TYPE_POSBIGNUM 9 +/** Type for a negative big number. Data is in val.bignum, a pointer and a length. */ +#define QCBOR_TYPE_NEGBIGNUM 10 +/** Type for RFC 3339 date string, possibly with time zone. Data is in val.dateString */ +#define QCBOR_TYPE_DATE_STRING 11 +/** Type for integer seconds since Jan 1970 + floating point fraction. Data is in val.epochDate */ +#define QCBOR_TYPE_DATE_EPOCH 12 +/** A simple type that this CBOR implementation doesn't know about; Type is in val.uSimple. */ +#define QCBOR_TYPE_UKNOWN_SIMPLE 13 +/** Type for the simple value false; nothing more; nothing in val union. */ +#define QCBOR_TYPE_FALSE 20 +/** Type for the simple value true; nothing more; nothing in val union. */ +#define QCBOR_TYPE_TRUE 21 +/** Type for the simple value null; nothing more; nothing in val union. */ +#define QCBOR_TYPE_NULL 22 +/** Type for the simple value undef; nothing more; nothing in val union. */ +#define QCBOR_TYPE_UNDEF 23 +/** Type for a floating point number. Data is in val.float. */ +#define QCBOR_TYPE_FLOAT 26 +/** Type for a double floating point number. Data is in val.double. */ +#define QCBOR_TYPE_DOUBLE 27 +/** For QCBOR_DECODE_MODE_MAP_AS_ARRAY decode mode, a map that is being traversed as an array. See QCBORDecode_Init() */ +#define QCBOR_TYPE_MAP_AS_ARRAY 32 + +#define QCBOR_TYPE_BREAK 31 // Used internally; never returned + +#define QCBOR_TYPE_OPTTAG 254 // Used internally; never returned + + + +/* + Approx Size of this: + 8 + 8 + 1 + 1 + 1 + (1 padding) + (4 padding on 64-bit machine) = 24 for first part (20 on a 32-bit machine) + 16 bytes for the val union + 16 bytes for label union + total = 56 bytes (52 bytes on 32-bit machine) + */ + +/** + QCBORItem holds the type, value and other info for a decoded item returned by GetNextItem(). + */ +typedef struct _QCBORItem { + uint8_t uDataType; /** Tells what element of the val union to use. One of QCBOR_TYPE_XXXX */ + uint8_t uNestingLevel; /** How deep the nesting from arrays and maps are. 0 is the top level with no arrays or maps entered */ + uint8_t uLabelType; /** Tells what element of the label union to use */ + uint8_t uDataAlloc; /** 1 if allocated with string allocator, 0 if not. See QCBORDecode_MakeMallocStringAllocator() */ + uint8_t uLabelAlloc; /** Like uDataAlloc, but for label */ + uint8_t uNextNestLevel; /** If not equal to uNestingLevel, this item closed out at least one map/array */ + + union { + int64_t int64; /** The value for uDataType QCBOR_TYPE_INT64 */ + uint64_t uint64; /** The value for uDataType QCBOR_TYPE_UINT64 */ + + UsefulBufC string; /** The value for uDataType QCBOR_TYPE_BYTE_STRING and QCBOR_TYPE_TEXT_STRING */ + uint16_t uCount; /** The "value" for uDataType QCBOR_TYPE_ARRAY or QCBOR_TYPE_MAP -- the number of items in the array or map + UINT16_MAX when decoding indefinite lengths maps and arrays. */ + double dfnum; /** The value for uDataType QCBOR_TYPE_DOUBLE */ + struct { + int64_t nSeconds; + double fSecondsFraction; + } epochDate; /** The value for uDataType QCBOR_TYPE_DATE_EPOCH */ + UsefulBufC dateString; /** The value for uDataType QCBOR_TYPE_DATE_STRING */ + UsefulBufC bigNum; /** The value for uDataType QCBOR_TYPE_BIGNUM */ + uint8_t uSimple; /** The integer value for unknown simple types */ + uint64_t uTagV; + + } val; /** The union holding the item's value. Select union member based on uDataType */ + + union { + UsefulBufC string; /** The label for uLabelType QCBOR_TYPE_BYTE_STRING and QCBOR_TYPE_TEXT_STRING */ + int64_t int64; /** The label for uLabelType for QCBOR_TYPE_INT64 */ + uint64_t uint64; /** The label for uLabelType for QCBOR_TYPE_UINT64 */ + } label; /** Union holding the different label types selected based on uLabelType */ + + uint64_t uTagBits; /** Bit indicating which tags (major type 6) on this item. */ + +} QCBORItem; + + +/** + This is a set of functions and pointer context (in object-oriented parlance, + an "object") used to allocate memory for coalescing the segments of an indefinite + length string into one. + + The fAllocate function works as an initial allocator and a reallocator to + expand the string for each new segment. When it is an initial allocator + pOldMem is NULL. + + The fFree function is called to clean up an individual allocation when an error occurs. + + The fDesctructor function is called when QCBORDecode_Finish is called. + + Any memory allocated with this will be marked by setting uDataAlloc + or uLabelAlloc in the QCBORItem structure so the caller knows they + have to free it. + + fAllocate is only ever called to increase the single most recent + allocation made, making implementation of a memory pool very simple. + + fFree is also only called on the single most recent allocation. + */ +typedef struct { + void *pAllocaterContext; + UsefulBuf (*fAllocate)(void *pAllocaterContext, void *pOldMem, size_t uNewSize); + void (*fFree)(void *pAllocaterContext, void *pMem); + void (*fDestructor)(void *pAllocaterContext); +} QCBORStringAllocator; + + +/** + This only matters if you use a string allocator + and and set it up with QCBORDecode_SetMemPool(). It is + the size of the overhead needed needed by + QCBORDecode_SetMemPool(). If you write your own + string allocator or use the separately available malloc + based string allocator, this size will not apply + */ +#define QCBOR_DECODE_MIN_MEM_POOL_SIZE 72 + + +/** + This is used to tell the decoder about tags that it should + record in uTagBits in QCBORItem beyond the built-in + tags. puTags points to an + array of uint64_t integers that are the tags. uNumTags + is the number of integers in the array. The maximum + size is QCBOR_MAX_CUSTOM_TAGS. See QCBORDecode_IsTagged() + and QCBORDecode_SetCallerAddedTagMap(). + */ +typedef struct { + uint8_t uNumTags; + const uint64_t *puTags; +} QCBORTagListIn; + + +/** + This is for QCBORDecode_GetNextWithTags() to be able to return the + full list of tags on an item. It not needed for most CBOR protocol + implementations. Its primary use is for pretty-printing CBOR or + protocol conversion to another format. + + On input, puTags points to a buffer to be filled in + and uNumAllocated is the number of uint64_t values + in the buffer. + + On output the buffer contains the tags for the item. + uNumUsed tells how many there are. + */ +typedef struct { + uint8_t uNumUsed; + uint8_t uNumAllocated; + uint64_t *puTags; +} QCBORTagListOut; + + +/** + QCBOREncodeContext is the data type that holds context for all the + encoding functions. It is less than 200 bytes, so it can go on + the stack. The contents are opaque, and the caller should not access + any internal items. A context may be re used serially as long as + it is re initialized. + */ +typedef struct _QCBOREncodeContext QCBOREncodeContext; + + +/** + Initialize the the encoder to prepare to encode some CBOR. + + @param[in,out] pCtx The encoder context to initialize. + @param[in] Storage The buffer into which this encoded result will be placed. + + Call this once at the start of an encoding of a CBOR structure. Then + call the various QCBOREncode_AddXXX() functions to add the data + items. Then call QCBOREncode_Finish(). + + The maximum output buffer is UINT32_MAX (4GB). This is not a practical + limit in any way and reduces the memory needed by the implementation. + The error QCBOR_ERR_BUFFER_TOO_LARGE will be returned by QCBOR_Finish() + if a larger buffer length is passed in. + + If this is called with pBuf as NULL and uBufLen a large value like + UINT32_MAX, all the QCBOREncode_AddXXXX() functions and + QCBORE_Encode_Finish() can still be called. No data will be encoded, + but the length of what would be encoded will be calculated. The + length of the encoded structure will be handed back in the call to + QCBOREncode_Finish(). You can then allocate a buffer of that size + and call all the encoding again, this time to fill in the buffer. + + A QCBORContext can be reused over and over as long as + QCBOREncode_Init() is called. + */ +void QCBOREncode_Init(QCBOREncodeContext *pCtx, UsefulBuf Storage); + + +/** + @brief Add a signed 64-bit integer to the encoded output. + + @param[in] pCtx The encoding context to add the integer to. + @param[in] nNum The integer to add. + + The integer will be encoded and added to the CBOR output. + + This function figures out the size and the sign and encodes in the + correct minimal CBOR. Specifically, it will select CBOR major type 0 or 1 + based on sign and will encode to 1, 2, 4 or 8 bytes depending on the + value of the integer. Values less than 24 effectively encode to one + byte because they are encoded in with the CBOR major type. This is + a neat and efficient characteristic of CBOR that can be taken + advantage of when designing CBOR-based protocols. If integers like + tags can be kept between -23 and 23 they will be encoded in one byte + including the major type. + + If you pass a smaller int, say an int16_t or a small value, say 100, + the encoding will still be CBOR's most compact that can represent the + value. For example, CBOR always encodes the value 0 as one byte, + 0x00. The representation as 0x00 includes identification of the type + as an integer too as the major type for an integer is 0. See RFC 7049 + Appendix A for more examples of CBOR encoding. This compact encoding + is also canonical CBOR as per section 3.9 in RFC 7049. + + There are no functions to add int16_t or int32_t because they are + not necessary because this always encodes to the smallest number + of bytes based on the value (If this code is running on a 32-bit + machine having a way to add 32-bit integers would reduce code size some). + + If the encoding context is in an error state, this will do + nothing. If an error occurs when adding this integer, the internal + error flag will be set, and the error will be returned when + QCBOREncode_Finish() is called. + + See also QCBOREncode_AddUInt64(). + */ +void QCBOREncode_AddInt64(QCBOREncodeContext *pCtx, int64_t nNum); + +static void QCBOREncode_AddInt64ToMap(QCBOREncodeContext *pCtx, const char *szLabel, int64_t uNum); + +static void QCBOREncode_AddInt64ToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, int64_t uNum); + + +/** + @brief Add an unsigned 64-bit integer to the encoded output. + + @param[in] pCtx The encoding context to add the integer to. + @param[in] uNum The integer to add. + + The integer will be encoded and added to the CBOR output. + + The only reason so use this function is for integers larger than + INT64_MAX and smaller than UINT64_MAX. Otherwise QCBOREncode_AddInt64() + will work fine. + + Error handling is the same as for QCBOREncode_AddInt64(). + */ +void QCBOREncode_AddUInt64(QCBOREncodeContext *pCtx, uint64_t uNum); + +static void QCBOREncode_AddUInt64ToMap(QCBOREncodeContext *pCtx, const char *szLabel, uint64_t uNum); + +static void QCBOREncode_AddUInt64ToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, uint64_t uNum); + + +/** + + @brief Add a UTF-8 text string to the encoded output + + @param[in] pCtx The context to initialize. + @param[in] Text Pointer and length of text to add. + + The text passed in must be unencoded UTF-8 according to RFC + 3629. There is no NULL termination. The text is added as CBOR + major type 3. + + If called with nBytesLen equal to 0, an empty string will be + added. When nBytesLen is 0, pBytes may be NULL. + + Note that the restriction of the buffer length to an uint32_t is + entirely intentional as this encoder is not capable of encoding + lengths greater. This limit to 4GB for a text string should not be a + problem. + + Error handling is the same as QCBOREncode_AddInt64(). + */ +static void QCBOREncode_AddText(QCBOREncodeContext *pCtx, UsefulBufC Text); + +static void QCBOREncode_AddTextToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC Text); + +static void QCBOREncode_AddTextToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC Text); + + +/** + @brief Add a UTF-8 text string to the encoded output + + @param[in] pCtx The context to initialize. + @param[in] szString Null-terminated text to add. + + This works the same as QCBOREncode_AddText(). + */ +static void QCBOREncode_AddSZString(QCBOREncodeContext *pCtx, const char *szString); + +static void QCBOREncode_AddSZStringToMap(QCBOREncodeContext *pCtx, const char *szLabel, const char *szString); + +static void QCBOREncode_AddSZStringToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, const char *szString); + + +/** + @brief Add a floating-point number to the encoded output + + @param[in] pCtx The encoding context to add the float to. + @param[in] dNum The double precision number to add. + + This outputs a floating-point number with CBOR major type 7. + + This will selectively encode the double-precision floating point + number as either double-precision, single-precision or + half-precision. It will always encode infinity, NaN and 0 has half + precision. If no precision will be lost in the conversion to + half-precision then it will be converted and encoded. If not and no + precision will be lost in conversion to single-precision, then it + will be converted and encoded. If not, then no conversion is + performed, and it encoded as a double. + + Half-precision floating point numbers take up 2 bytes, half that of + single-precision, one quarter of double-precision + + This automatically reduces the size of encoded messages a lot, maybe + even by four if most of values are 0, infinity or NaN. + + On decode, these will always be returned as a double. + + Error handling is the same as QCBOREncode_AddInt64(). + */ +void QCBOREncode_AddDouble(QCBOREncodeContext *pCtx, double dNum); + +static void QCBOREncode_AddDoubleToMap(QCBOREncodeContext *pCtx, const char *szLabel, double dNum); + +static void QCBOREncode_AddDoubleToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, double dNum); + + +/** + @brief[in] Add an optional tag + + @param[in] pCtx The encoding context to add the integer to. + @param[in] uTag The tag to add + + This outputs a CBOR major type 6 optional tag. + + The tag is applied to the next data item added to the encoded + output. That data item that is to be tagged can be of any major + CBOR type. Any number of tags can be added to a data item by calling + this multiple times before the data item is added. + + For many of the common standard tags a function to encode + data using it already exists and this is not needed. For example, + QCBOREncode_AddDateEpoch() already exists to output + integers representing dates with the right tag. +*/ +void QCBOREncode_AddTag(QCBOREncodeContext *pCtx,uint64_t uTag); + + +/** + @brief Add an epoch-based date + + @param[in] pCtx The encoding context to add the simple value to. + @param[in] date Number of seconds since 1970-01-01T00:00Z in UTC time. + + As per RFC 7049 this is similar to UNIX/Linux/POSIX dates. This is + the most compact way to specify a date and time in CBOR. Note that this + is always UTC and does not include the time zone. Use + QCBOREncode_AddDateString() if you want to include the time zone. + + The integer encoding rules apply here so the date will be encoded in a + minimal number of 1, 2 4 or 8 bytes. Until about the year 2106 these + dates should encode in 6 bytes -- one byte for the tag, one byte for the type + and 4 bytes for the integer. + + If you care about leap-seconds and that level of accuracy, make sure the + system you are running this code on does it correctly. This code just takes + the value passed in. + + This implementation cannot encode fractional seconds using float or double + even though that is allowed by CBOR, but you can encode them if you + want to by calling QCBOREncode_AddDouble() + with the right parameters. + + Error handling is the same as QCBOREncode_AddInt64(). + */ +static void QCBOREncode_AddDateEpoch(QCBOREncodeContext *pCtx, int64_t date); + +static void QCBOREncode_AddDateEpochToMap(QCBOREncodeContext *pCtx, const char *szLabel, int64_t date); + +static void QCBOREncode_AddDateEpochToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, int64_t date); + + +/** + @brief Add a byte string to the encoded output. + + @param[in] pCtx The context to initialize. + @param[in] Bytes Pointer and length of the input data. + + Simply adds the bytes to the encoded output as CBOR major type 2. + + If called with Bytes.len equal to 0, an empty string will be + added. When Bytes.len is 0, Bytes.ptr may be NULL. + + Error handling is the same as QCBOREncode_AddInt64(). + */ +static void QCBOREncode_AddBytes(QCBOREncodeContext *pCtx, UsefulBufC Bytes); + +static void QCBOREncode_AddBytesToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC Bytes); + +static void QCBOREncode_AddBytesToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC Bytes); + + + +/** + @brief Add a binary UUID to the encoded output. + + @param[in] pCtx The context to initialize. + @param[in] Bytes Pointer and length of the binary UUID. + + A binary UUID as defined in RFC 4122 is added to the ouput. + + It is output as CBOR major type 2, a binary string, with + optional tag 36 indicating the binary string is a UUID. + */ +static void QCBOREncode_AddBinaryUUID(QCBOREncodeContext *pCtx, UsefulBufC Bytes); + +static void QCBOREncode_AddBinaryUUIDToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC Bytes); + +static void QCBOREncode_AddBinaryUUIDToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC Bytes); + + +/** + @brief Add a positive big number to the encoded output. + + @param[in] pCtx The context to initialize. + @param[in] Bytes Pointer and length of the big number. + + Big numbers are integers larger than 64-bits. Their format + is described in RFC 7049. + + It is output as CBOR major type 2, a binary string, with + optional tag 2 indicating the binary string is a positive big + number. + + Often big numbers are used to represent cryptographic keys, + however, COSE which defines representations for keys chose not + to use this particular type. + */ +static void QCBOREncode_AddPositiveBignum(QCBOREncodeContext *pCtx, UsefulBufC Bytes); + +static void QCBOREncode_AddPositiveBignumToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC Bytes); + +static void QCBOREncode_AddPositiveBignumToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC Bytes); + + +/** + @brief Add a negative big number to the encoded output. + + @param[in] pCtx The context to initialize. + @param[in] Bytes Pointer and length of the big number. + + Big numbers are integers larger than 64-bits. Their format + is described in RFC 7049. + + It is output as CBOR major type 2, a binary string, with + optional tag 2 indicating the binary string is a negative big + number. + + Often big numbers are used to represent cryptographic keys, + however, COSE which defines representations for keys chose not + to use this particular type. + */ +static void QCBOREncode_AddNegativeBignum(QCBOREncodeContext *pCtx, UsefulBufC Bytes); + +static void QCBOREncode_AddNegativeBignumToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC Bytes); + +static void QCBOREncode_AddNegativeBignumToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC Bytes); + + +/** + @brief Add a text URI to the encoded output. + + @param[in] pCtx The context to initialize. + @param[in] URI Pointer and length of the URI. + + The format of URI is RFC 3986. + + It is output as CBOR major type 3, a text string, with + optional tag 32 indicating the text string is a URI. + */ +static void QCBOREncode_AddURI(QCBOREncodeContext *pCtx, UsefulBufC URI); + +static void QCBOREncode_AddURIToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC URI); + +static void QCBOREncode_AddURIToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC URI); + + +/** + @brief Add base 64-encoded text to encoded output. + + @param[in] pCtx The context to initialize. + @param[in] B64Text Pointer and length of the base-64 encoded text. + + The text content is base 64 encoded data per RFC 4648. + + It is output as CBOR major type 3, a text string, with + optional tag 34 indicating the text string is a URI. + */ +static void QCBOREncode_AddB64Text(QCBOREncodeContext *pCtx, UsefulBufC B64Text); + +static void QCBOREncode_AddB64TextToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC B64Text); + +static void QCBOREncode_AddB64TextToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC B64Text); + + +/** + @brief Add base 64URL -encoded URL to encoded output. + + @param[in] pCtx The context to initialize. + @param[in] B64Text Pointer and length of the base-64 encoded text. + + The text content is base 64 URL format encoded text as per RFC 4648. + + It is output as CBOR major type 3, a text string, with + optional tag 33 indicating the text string is a URI. + */ +static void QCBOREncode_AddB64URLText(QCBOREncodeContext *pCtx, UsefulBufC B64Text); + +static void QCBOREncode_AddB64URLTextToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC B64Text); + +static void QCBOREncode_AddB64URLTextToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC B64Text); + + +/** + @brief Add Perl Compatible Regular Expression + + @param[in] pCtx The context to initialize. + @param[in] Regex Pointer and length of the regular expression. + + The text content is Perl Compatible Regular + Expressions (PCRE) / JavaScript syntax [ECMA262]. + + It is output as CBOR major type 3, a text string, with + optional tag 35 indicating the text string is a regular expression. + */ +static void QCBOREncode_AddRegex(QCBOREncodeContext *pCtx, UsefulBufC Regex); + +static void QCBOREncode_AddRegexToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC Regex); + +static void QCBOREncode_AddRegexToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC Regex); + + +/** + @brief MIME encoded text to the encoded output. + + @param[in] pCtx The context to initialize. + @param[in] MIMEData Pointer and length of the regular expression. + + The text content is in MIME format per RFC 2045 including the headers. + + It is output as CBOR major type 3, a text string, with + optional tag 36 indicating the text string is MIME data. + */ +static void QCBOREncode_AddMIMEData(QCBOREncodeContext *pCtx, UsefulBufC MIMEData); + +static void QCBOREncode_AddMIMEDataToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC MIMEData); + +static void QCBOREncode_AddMIMEDataToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC MIMEData); + + +/** + @brief Add an RFC 3339 date string + + @param[in] pCtx The encoding context to add the simple value to. + @param[in] szDate Null-terminated string with date to add + + The string szDate should be in the form of RFC 3339 as defined by section + 3.3 in RFC 4287. This is as described in section 2.4.1 in RFC 7049. + + Note that this function doesn't validate the format of the date string + at all. If you add an incorrect format date string, the generated + CBOR will be incorrect and the receiver may not be able to handle it. + + Error handling is the same as QCBOREncode_AddInt64(). + */ +static void QCBOREncode_AddDateString(QCBOREncodeContext *pCtx, const char *szDate); + +static void QCBOREncode_AddDateStringToMap(QCBOREncodeContext *pCtx, const char *szLabel, const char *szDate); + +static void QCBOREncode_AddDateStringToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, const char *szDate); + + +/** + @brief Add a standard boolean. + + @param[in] pCtx The encoding context to add the simple value to. + @param[in] b true or false from stdbool. Anything will result in an error. + + Adds a boolean value as CBOR major type 7. + + Error handling is the same as QCBOREncode_AddInt64(). + */ +static void QCBOREncode_AddBool(QCBOREncodeContext *pCtx, bool b); + +static void QCBOREncode_AddBoolToMap(QCBOREncodeContext *pCtx, const char *szLabel, bool b); + +static void QCBOREncode_AddBoolToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, bool b); + + + +/** + @brief Add a NULL to the encoded output. + + @param[in] pCtx The encoding context to add the simple value to. + + Adds the NULL value as CBOR major type 7. + + This NULL doesn't have any special meaning in CBOR such as a terminating + value for a string or an empty value. + + Error handling is the same as QCBOREncode_AddInt64(). + */ +static void QCBOREncode_AddNULL(QCBOREncodeContext *pCtx); + +static void QCBOREncode_AddNULLToMap(QCBOREncodeContext *pCtx, const char *szLabel); + +static void QCBOREncode_AddNULLToMapN(QCBOREncodeContext *pCtx, int64_t nLabel); + + +/** + @brief Add an "undef" to the encoded output. + + @param[in] pCtx The encoding context to add the simple value to. + + Adds the undef value as CBOR major type 7. + + Note that this value will not translate to JSON. + + This Undef doesn't have any special meaning in CBOR such as a terminating + value for a string or an empty value. + + Error handling is the same as QCBOREncode_AddInt64(). + */ +static void QCBOREncode_AddUndef(QCBOREncodeContext *pCtx); + +static void QCBOREncode_AddUndefToMap(QCBOREncodeContext *pCtx, const char *szLabel); + +static void QCBOREncode_AddUndefToMapN(QCBOREncodeContext *pCtx, int64_t nLabel); + + +/** + @brief Indicates that the next items added are in an array. + + @param[in] pCtx The encoding context to open the array in. + + Arrays are the basic CBOR aggregate or structure type. Call this + function to start or open an array. Then call the various AddXXX + functions to add the items that go into the array. Then call + QCBOREncode_CloseArray() when all items have been added. The data + items in the array can be of any type and can be of mixed types. + + Nesting of arrays and maps is allowed and supported just by calling + QCBOREncode_OpenArray() again before calling CloseArray. While CBOR + has no limit on nesting, this implementation does in order to keep it + smaller and simpler. The limit is QCBOR_MAX_ARRAY_NESTING. This is + the max number of times this can be called without calling + QCBOREncode_CloseArray(). QCBOREncode_Finish() will return + QCBOR_ERR_ARRAY_NESTING_TOO_DEEP when it is called as this function + just sets an error state and returns no value when this occurs. + + If you try to add more than QCBOR_MAX_ITEMS_IN_ARRAY items to a + single array or map, QCBOR_ERR_ARRAY_TOO_LONG will be returned when + QCBOREncode_Finish() is called. + + An array itself must have a label if it is being added to a map. + Note that array elements do not have labels (but map elements do). + + An array itself may be tagged. + */ +static void QCBOREncode_OpenArray(QCBOREncodeContext *pCtx); + +static void QCBOREncode_OpenArrayInMap(QCBOREncodeContext *pCtx, const char *szLabel); + +static void QCBOREncode_OpenArrayInMapN(QCBOREncodeContext *pCtx, int64_t nLabel); + + +/** + @brief Close an open array. + + @param[in] pCtx The context to add to. + + The closes an array opened by QCBOREncode_OpenArray(). It reduces + nesting level by one. All arrays (and maps) must be closed before + calling QCBOREncode_Finish(). + + When an error occurs as a result of this call, the encoder records + the error and enters the error state. The error will be returned when + QCBOREncode_Finish() is called. + + If this has been called more times than QCBOREncode_OpenArray(), then + QCBOR_ERR_TOO_MANY_CLOSES will be returned when QCBOREncode_Finish() + is called. + + If this is called and it is not an array that is currently open, + QCBOR_ERR_CLOSE_MISMATCH will be returned when QCBOREncode_Finish() + is called. + */ +static void QCBOREncode_CloseArray(QCBOREncodeContext *pCtx); + + +/** + @brief Indicates that the next items added are in a map. + + @param[in] pCtx The context to add to. + + See QCBOREncode_OpenArray() for more information, particularly error + handling. + + CBOR maps are an aggregate type where each item in the map consists + of a label and a value. They are similar to JSON objects. + + The value can be any CBOR type including another map. + + The label can also be any CBOR type, but in practice they are + typically, integers as this gives the most compact output. They might + also be text strings which gives readability and translation to JSON. + + Every QCBOREncode_AddXXX() call has once version that is "InMap" for + adding items to maps with string labels and on that is "InMapN" that + is for adding with integer labels. + + RFC 7049 uses the term "key" instead of "label". + + If you wish to use map labels that are neither integer labels or + text strings, then just call the QCBOREncode_AddXXX() function + explicitly to add the label. Then call it again to add the value. + + See the RFC7049 for a lot more information on creating maps. + */ +static void QCBOREncode_OpenMap(QCBOREncodeContext *pCtx); + +static void QCBOREncode_OpenMapInMap(QCBOREncodeContext *pCtx, const char *szLabel); + +static void QCBOREncode_OpenMapInMapN(QCBOREncodeContext *pCtx, int64_t nLabel); + + + +/** + @brief Close an open map. + + @param[in] pCtx The context to add to. + + The closes a map opened by QCBOREncode_OpenMap(). It reduces nesting + level by one. + + When an error occurs as a result of this call, the encoder records + the error and enters the error state. The error will be returned when + QCBOREncode_Finish() is called. + + If this has been called more times than QCBOREncode_OpenMap(), + then QCBOR_ERR_TOO_MANY_CLOSES will be returned when + QCBOREncode_Finish() is called. + + If this is called and it is not a map that is currently + open, QCBOR_ERR_CLOSE_MISMATCH will be returned when QCBOREncode_Finish() + is called. + */ +static void QCBOREncode_CloseMap(QCBOREncodeContext *pCtx); + + +/** + @brief Indicate start of encoded CBOR to be wrapped in a bstr. + + @param[in] pCtx The context to add to. + + All added encoded items between this call and a call to + QCBOREncode_CloseBstrWrap() will be wrapped in a bstr. They will + appear in the final output as a byte string. That byte string will + contain encoded CBOR. + + The typical use case is for encoded CBOR that is to be + cryptographically hashed, as part of a COSE (RFC 8152) + implementation. This avoids having to encode the items first in one + buffer (e.g., the COSE payload) and then add that buffer as a bstr to + another encoding (e.g. the COSE to-be-signed bytes, the + Sig_structure) potentially saving a lot of memory. + + When constructing cryptographically signed CBOR objects, maps or + arrays, they typically are encoded normally and then wrapped as a + byte string. The COSE standard for example does this. The wrapping is + simply treating the encoded CBOR map as a byte string. + + The stated purpose of this wrapping is to prevent code relaying the + signed data but not verifying it from tampering with the signed data + thus making the signature unverifiable. It is also quite beneficial + for the signature verification code. Standard CBOR parsers usually do + not give access to partially parsed CBOR as would be need to check + the signature of some CBOR. With this wrapping, standard CBOR parsers + can be used to get to all the data needed for a signature + verification. + */ +static void QCBOREncode_BstrWrap(QCBOREncodeContext *pCtx); + +static void QCBOREncode_BstrWrapInMap(QCBOREncodeContext *pCtx, const char *szLabel); + +static void QCBOREncode_BstrWrapInMapN(QCBOREncodeContext *pCtx, int64_t nLabel); + + +/** + @brief Close a wrapping bstr. + + @param[in] pCtx The context to add to. + @param[out] pWrappedCBOR UsefulBufC containing wrapped bytes + + The closes a wrapping bstr opened by QCBOREncode_BstrWrap(). It reduces + nesting level by one. + + A pointer and length of the enclosed encoded CBOR is returned in + *pWrappedCBOR if it is not NULL. The main purpose of this is so this + data can be hashed (e.g., with SHA-256) as part of a COSE (RFC 8152) + implementation. **WARNING**, this pointer and length should be used + right away before any other calls to QCBOREncode_xxxx() as they will + move data around and the pointer and length will no longer be to the + correct encoded CBOR. + + When an error occurs as a result of this call, the encoder records + the error and enters the error state. The error will be returned when + QCBOREncode_Finish() is called. + + If this has been called more times then QCBOREncode_BstrWrap(), + then QCBOR_ERR_TOO_MANY_CLOSES will be returned when + QCBOREncode_Finish() is called. + + If this is called and it is not a wrapping bstr that is currently + open, QCBOR_ERR_CLOSE_MISMATCH will be returned when QCBOREncode_Finish() + is called. + */ +static void QCBOREncode_CloseBstrWrap(QCBOREncodeContext *pCtx, UsefulBufC *pWrappedCBOR); + + +/** + @brief Add some already-encoded CBOR bytes. + + @param[in] pCtx The context to add to. + @param[in] Encoded The already-encoded CBOR to add to the context. + + The encoded CBOR being added must be fully conforming CBOR. It must + be complete with no arrays or maps that are incomplete. While this + encoder doesn't ever produce indefinite lengths, it is OK for the + raw CBOR added here to have indefinite lengths. + + The raw CBOR added here is not checked in anyway. If it is not + conforming or has open arrays or such, the final encoded CBOR + will probably be wrong or not what was intended. + + If the encoded CBOR being added here contains multiple items, they + must be enclosed in a map or array. At the top level the raw + CBOR must be a single data item. + */ +static void QCBOREncode_AddEncoded(QCBOREncodeContext *pCtx, UsefulBufC Encoded); + +static void QCBOREncode_AddEncodedToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC Encoded); + +static void QCBOREncode_AddEncodedToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC Encoded); + + +/** + @brief Get the encoded result. + + @param[in] pCtx The context to finish encoding with. + @param[out] pEncodedCBOR Pointer and length of encoded CBOR. + + @return + One of the CBOR error codes. + + If this returns success QCBOR_SUCCESS the encoding was a success and + the return length is correct and complete. + + If no buffer was passed to QCBOR_Init(), then only the length and + number of items was computed. The length is in + pEncodedCBOR->Bytes.len. pEncodedCBOR->Bytes.ptr is NULL. + + If a buffer was passed, then pEncodedCBOR->Bytes.ptr is the same as + the buffer passed to QCBOR_Init() and contains the encoded CBOR + and the length is filled in. + + If an error is returned, the buffer may have partially encoded + incorrect CBOR in it and it should not be used. Likewise, the length + may be incorrect and should not be used. + + Note that the error could have occurred in one of the many + QCBOR_AddXXX calls long before QCBOREncode_Finish() was called. This + error handling approach reduces the CBOR implementation size, but makes + debugging a problem a little more difficult. + */ +QCBORError QCBOREncode_Finish(QCBOREncodeContext *pCtx, UsefulBufC *pEncodedCBOR); + + +/** + @brief Get the encoded CBOR and error status. + + @param[in] pCtx The context to finish encoding with. + @param[out] uEncodedLen The length of the encoded or potentially encoded CBOR in bytes. + + @return + One of the CBOR error codes. + + If this returns success QCBOR_SUCCESS the encoding was a success and + the return length is correct and complete. + + If no buffer was passed to QCBOR_Init(), then only the length was + computed. If a buffer was passed, then the encoded CBOR is in the + buffer. + + If an error is returned, the buffer may have partially encoded + incorrect CBOR in it and it should not be used. Likewise, the length + may be incorrect and should not be used. + + Note that the error could have occurred in one of the many + QCBOR_AddXXX calls long before QCBOREncode_Finish() was called. This + error handling reduces the CBOR implementation size, but makes + debugging harder. + */ +QCBORError QCBOREncode_FinishGetSize(QCBOREncodeContext *pCtx, size_t *uEncodedLen); + + + + + + +/** + QCBORDecodeContext is the data type that holds context decoding the + data items for some received CBOR. It is about 100 bytes, so it can go + on the stack. The contents are opaque, and the caller should not + access any internal items. A context may be re used serially as long + as it is re initialized. + */ +typedef struct _QCBORDecodeContext QCBORDecodeContext; + + +/** + Initialize the CBOR decoder context. + + @param[in] pCtx The context to initialize. + @param[in] EncodedCBOR The buffer with CBOR encoded bytes to be decoded. + @param[in] nMode One of QCBOR_DECODE_MODE_xxx + + Initialize context for a pre-order travesal of the encoded CBOR tree. + + Most CBOR decoding can be completed by calling this function to start + and QCBORDecode_GetNext() in a loop. + + If indefinite length strings are to be decoded, then + QCBORDecode_SetMemPool() or QCBORDecode_SetUpAllocator() must be + called to set up a string allocator. + + If tags other than built-in tags are to be recognized, then + QCBORDecode_SetCallerAddedTagMap() must be called. The built-in tags + are those for which a macro of the form CBOR_TAG_XXX is defined. + + Three decoding modes are supported. In normal mode, + QCBOR_DECODE_MODE_NORMAL, maps are decoded and strings and ints are + accepted as map labels. If a label is other than these, the error + QCBOR_ERR_MAP_LABEL_TYPE is returned by QCBORDecode_GetNext(). + + In strings-only mode, QCBOR_DECODE_MODE_MAP_STRINGS_ONLY, only text + strings are accepted for map labels. This lines up with CBOR that + converts to JSON. The error QCBOR_ERR_MAP_LABEL_TYPE is returned by + QCBORDecode_GetNext() if anything but a text string label is + encountered. + + In QCBOR_DECODE_MODE_MAP_AS_ARRAY maps are treated as special arrays. + They will be return with special uDataType QCBOR_TYPE_MAP_AS_ARRAY + and uCount, the number of items, will be double what it would be + for a normal map because the labels are also counted. This mode + is useful for decoding CBOR that has labels that are not + integers or text strings, but the caller must manage much of + the map decoding. + */ +void QCBORDecode_Init(QCBORDecodeContext *pCtx, UsefulBufC EncodedCBOR, QCBORDecodeMode nMode); + + +/** + @brief Set up the MemPool string allocator for indefinite length strings. + + @param[in] pCtx The decode context. + @param[in] MemPool The pointer and length of the memory pool. + @param[in] bAllStrings true means to put even definite length strings in the pool. + + @return error if the MemPool was less than QCBOR_DECODE_MIN_MEM_POOL_SIZE. + + Indefinite length strings (text and byte) cannot be decoded unless + there is a string allocator configured. MemPool is a simple built-in + string allocator that allocates bytes from a memory pool handed to it + by calling this function. The memory pool is just a pointer and + length for some block of memory that is to be used for string + allocation. It can come from the stack, heap or other. + + The memory pool must be QCBOR_DECODE_MIN_MEM_POOL_SIZE plus space for + all the strings allocated. There is no overhead per string allocated + + This memory pool is used for all indefinite length strings that are + text strings or byte strings, including strings used as labels. + + The pointers to strings in QCBORItem will point into the memory pool set + here. They do not need to be individually freed. Just discard the buffer + when they are no longer needed. + + If bAllStrings is set, then the size will be the overhead plus the + space to hold **all** strings, definite and indefinite length, value + or label. The advantage of this is that after the decode is complete, + the original memory holding the encoded CBOR does not need to remain + valid. + + If this function is never called because there is no need to support + indefinite length strings, the MemPool implementation should be + dead-stripped by the loader and not add to code size. + */ +QCBORError QCBORDecode_SetMemPool(QCBORDecodeContext *pCtx, UsefulBuf MemPool, bool bAllStrings); + + +/** + @brief Sets up a custom string allocator for indefinite length strings + + @param[in] pCtx The decoder context to set up an allocator for + @param[in] pAllocator The string allocator "object" + + See QCBORStringAllocator for the requirements of the string allocator. + + Typically, this is used if the simple MemPool allocator isn't desired. + + A malloc based string allocator can be obtained by calling + QCBOR_DMalloc(). This function is supply separately from qcbor + to keep qcbor smaller and neater. It is in a separate + GitHub repository. + + You can also write your own allocator. Create the allocate, free, + and destroy functions and put pointers to them in a QCBORStringAllocator. + */ +void QCBORDecode_SetUpAllocator(QCBORDecodeContext *pCtx, const QCBORStringAllocator *pAllocator, bool bAllStrings); + + +/** + @brief Configure list of caller selected tags to be recognized + + @param[in] pCtx The decode context. + @param[out] pTagList Structure holding the list of tags to configure + + This is used to tell the decoder about tags beyond those that are + built-in that should be recognized. The built-in tags are those + with macros of the form CBOR_TAG_XXX. + + See description of QCBORTagListIn. + */ +void QCBORDecode_SetCallerConfiguredTagList(QCBORDecodeContext *pCtx, const QCBORTagListIn *pTagList); + + +/** + @brief Gets the next item (integer, byte string, array...) in pre order traversal of CBOR tree + + @param[in] pCtx The decoder context. + @param[out] pDecodedItem Holds the CBOR item just decoded. + + @return 0 or error. All errors except QCBOR_ERR_TOO_MANY_TAGS + and QCBOR_ERR_STRING_ALLOCATE indicate that the CBOR input + could not be decoded. In most cases + this is because the CBOR is invalid. In a few cases + (QCBOR_ERR_ARRAY_NESTING_TOO_DEEP, QCBOR_ERR_INT_OVERFLOW, + QCBOR_ERR_DATE_OVERFLOW) it is because the CBOR is beyond + the limits of what this implementation can handle. + QCBOR_ERR_NO_STRING_ALLOCATOR indicates CBOR that cannot + be handled unless a string allocator is configured. + QCBOR_ERR_MAP_LABEL_TYPE is in a way a limitation of + this implementation, but can be avoided by decoding + in QCBOR_DECODE_MODE_MAP_AS_ARRAY mode. + + pDecodedItem is filled in with the value parsed. Generally, the + following data is returned in the structure. + + - The data type in uDataType which indicates which member of the val + union the data is in. This decoder figures out the type based on the + CBOR major type, the CBOR "additionalInfo", the CBOR optional tags + and the value of the integer. + + - The value of the item, which might be an integer, a pointer and a + length, the count of items in an array, a floating-point number or + other. + + - The nesting level for maps and arrays. + + - The label for an item in a map, which may be a text or byte string or an integer. + + - The CBOR optional tag or tags. + + See documentation on in the data type QCBORItem for all the details + on what is returned. + + This function also handles arrays and maps. When first encountered a + QCBORItem will be returned with major type CBOR_MAJOR_TYPE_ARRAY or + CBOR_MAJOR_TYPE_ARRAY_MAP. QCBORItem.val.uCount will indicate the number + of Items in the array or map. Typically, an implementation will call + QCBORDecode_GetNext() in a for loop to fetch them all. When decoding + indefinite length maps and arrays, QCBORItem.val.uCount is UINT16_MAX + and uNextNestLevel must be used to know when the end of a map + or array is reached. + + Nesting level 0 is the outside top-most nesting level. For example, in + a CBOR structure with two items, an integer and a byte string only, + both would be at nesting level 0. A CBOR structure with an array + open, an integer and a byte string, would have the integer and byte + string as nesting level 1. + + Here is an example of how the nesting level is reported with no arrays + or maps at all + + @verbatim + CBOR Structure Nesting Level + Integer 0 + Byte String 0 + @endverbatim + + Here is an example of how the nesting level is reported with an a simple + array and some top-level items. + + @verbatim + Integer 0 + Array (with 2 items) 0 + Byte String 1 + Byte string 1 + Integer 0 + @endverbatim + + + Here's a more complex example + @verbatim + + Map with 2 items 0 + Text string 1 + Array with 3 integers 1 + integer 2 + integer 2 + integer 2 + text string 1 + byte string 1 + @endverbatim + + In QCBORItem, uNextNestLevel is the nesting level for the next call + to QCBORDecode_GetNext(). It indicates if any maps or arrays were closed + out during the processing of the just-fecthed QCBORItem. This processing + includes a look-ahead for any breaks that close out indefinite length + arrays or maps. This value is needed to be able to understand the + hierarchical structure. If uNextNestLevel is not equal to uNestLevel + the end of the current map or array has been encountered. This + works the same for both definite and indefinite length arrays. + + Most uses of this decoder will not need to do anything extra for + tag handling. The built-in tags, those with a macro of the form + CBOR_TAG_XXXX, will be enough. + + If tags beyond built-in tags are to be recognized, they must be + configured by calling QCBORDecode_SetCallerConfiguredTags(). If + a tag is not recognized it is silently ignored. + + Several tagged types are automatically recognized and decoded and + returned in their decoded form. + + To find out if a QCBORItem was tagged with a particular tag + call QCBORDecode_IsTagged(). This works only for built-in + tags and caller-configured tags. + + To get the full list of tags on an Item without having to + pre-configure any predetermined list of tags use + QCBORDecode_GetNextWithTags(). + */ +QCBORError QCBORDecode_GetNext(QCBORDecodeContext *pCtx, QCBORItem *pDecodedItem); + + +/** + @brief Gets the next item including full list of tags for item + + @param[in] pCtx The decoder context. + @param[out] pDecodedItem Holds the CBOR item just decoded. + @param[in,out] pTagList On input array to put tags in; on output the tags on this item. + + @return 0 or error. + + This works the same as QCBORDecode_GetNext() except that it also returns + the full list of tags for the data item. This function should only + be needed when parsing CBOR to print it out or convert it to some other + format. It should not be needed in an actual CBOR protocol implementation. + + Tags will be returned here whether or not they are in the built-in or + caller-configured tag lists. + + CBOR has no upper bound of limit on the number of tags that can be + associated with a data item. In practice the number of tags on an item + will usually be small, perhaps less than five. This will return an error + if the array in pTagList is too small to hold all the tags for an item. + + (This function is separate from QCBORDecode_GetNext() so as to not have to + make QCBORItem large enough to be able to hold a full list of tags. Even a list of + five tags would nearly double its size because tags can be a uint64_t). + */ +QCBORError QCBORDecode_GetNextWithTags(QCBORDecodeContext *pCtx, QCBORItem *pDecodedItem, QCBORTagListOut *pTagList); + + +/** + @brief Determine if a CBOR item was tagged with a particular tag + + @param[in] pCtx The decoder context. + @param[in] pItem The CBOR item to check + @param[in] uTag The tag to check + + @return 1 if it was tagged, 0 if not + + QCBORDecode_GetNext() processes tags by looking them up + in two lists and setting a bit corresponding to the tag + in uTagBits in the QCBORItem. To find out if a + QCBORItem was tagged with a particular tag, call + this function. It handles the mapping between + the two lists of tags and the bits set for it. + + The first tag list is the built-in tags, those + with a macro of the form CBOR_TAG_XXX in this + header file. There are up to 48 of these, + corresponding to the lower 48 tag bits. + + The other optional tag list is the ones + the caller configured using QCBORDecode_SetCallerConfiguredTagList() + There are QCBOR_MAX_CUSTOM_TAGS (16) of these corresponding to the + upper 16 tag bits. + + See also QCBORDecode_GetTags() and QCBORDecode_GetNextWithTags(). + */ +int QCBORDecode_IsTagged(QCBORDecodeContext *pCtx, const QCBORItem *pItem, uint64_t uTag); + + +/** + Check whether all the bytes have been decoded and maps and arrays closed. + + @param[in] pCtx The context to check + + @return QCBOR_SUCCESS or error + + This tells you if all the bytes given to QCBORDecode_Init() have + been consumed and whether all maps and arrays were closed. + The decode is considered to be incorrect or incomplete if not + and an error will be returned. + */ +QCBORError QCBORDecode_Finish(QCBORDecodeContext *pCtx); + + + + +/** + Convert int64_t to smaller int's safely + + @param [in] src An int64_t + @param [out] dest A smaller sized int to convert to + + @return 0 on success -1 if not + + When decoding an integer, the CBOR decoder will return the value as an + int64_t unless the integer is in the range of INT64_MAX and + UINT64_MAX. That is, unless the value is so large that it can only be + represented as a uint64_t, it will be an int64_t. + + CBOR itself doesn't size the individual integers it carries at + all. The only limits it puts on the major integer types is that they + are 8 bytes or less in length. Then encoders like this one use the + smallest number of 1, 2, 4 or 8 bytes to represent the integer based + on its value. There is thus no notion that one data item in CBOR is + an 1 byte integer and another is a 4 byte integer. + + The interface to this CBOR encoder only uses 64-bit integers. Some + CBOR protocols or implementations of CBOR protocols may not want to + work with something smaller than a 64-bit integer. Perhaps an array + of 1000 integers needs to be sent and none has a value larger than + 50,000 and are represented as uint16_t. + + The sending / encoding side is easy. Integers are temporarily widened + to 64-bits as a parameter passing through QCBOREncode_AddInt64() and + encoded in the smallest way possible for their value, possibly in + less than an uint16_t. + + On the decoding side the integers will be returned at int64_t even if + they are small and were represented by only 1 or 2 bytes in the + encoded CBOR. The functions here will convert integers to a small + representation with an overflow check. + + (The decoder could have support 8 different integer types and + represented the integer with the smallest type automatically, but + this would have made the decoder more complex and code calling the + decoder more complex in most use cases. In most use cases on 64-bit + machines it is no burden to carry around even small integers as + 64-bit values). + */ +static inline int QCBOR_Int64ToInt32(int64_t src, int32_t *dest) +{ + if(src > INT32_MAX || src < INT32_MIN) { + return -1; + } else { + *dest = (int32_t) src; + } + return 0; +} + +static inline int QCBOR_Int64ToInt16(int64_t src, int16_t *dest) +{ + if(src > INT16_MAX || src < INT16_MIN) { + return -1; + } else { + *dest = (int16_t) src; + } + return 0; +} + +static inline int QCBOR_Int64ToInt8(int64_t src, int8_t *dest) +{ + if(src > INT8_MAX || src < INT8_MIN) { + return -1; + } else { + *dest = (int8_t) src; + } + return 0; +} + +static inline int QCBOR_Int64ToUInt32(int64_t src, uint32_t *dest) +{ + if(src > UINT32_MAX || src < 0) { + return -1; + } else { + *dest = (uint32_t) src; + } + return 0; +} + +static inline int QCBOR_Int64UToInt16(int64_t src, uint16_t *dest) +{ + if(src > UINT16_MAX || src < 0) { + return -1; + } else { + *dest = (uint16_t) src; + } + return 0; +} + +static inline int QCBOR_Int64ToUInt8(int64_t src, uint8_t *dest) +{ + if(src > UINT8_MAX || src < 0) { + return -1; + } else { + *dest = (uint8_t) src; + } + return 0; +} + +static inline int QCBOR_Int64ToUInt64(int64_t src, uint64_t *dest) +{ + if(src > 0) { + return -1; + } else { + *dest = (uint64_t) src; + } + return 0; +} + + + + + +/* =========================================================================== + BEGINNING OF PRIVATE INLINE IMPLEMENTATION + + =========================================================================== */ + +/** + @brief Semi-private method to add a buffer full of bytes to encoded output + + @param[in] pCtx The encoding context to add the integer to. + @param[in] uMajorType The CBOR major type of the bytes. + @param[in] Bytes The bytes to add. + + Use QCBOREncode_AddText() or QCBOREncode_AddBytes() or + QCBOREncode_AddEncoded() instead. They are inline functions + that call this and supply the correct major type. This function + is public to make the inline functions work to keep the overall + code size down and because the C language has no way to make + it private. + + If this is called the major type should be CBOR_MAJOR_TYPE_TEXT_STRING, + CBOR_MAJOR_TYPE_BYTE_STRING or CBOR_MAJOR_NONE_TYPE_RAW. The last + one is special for adding already-encoded CBOR. + */ +void QCBOREncode_AddBuffer(QCBOREncodeContext *pCtx, uint8_t uMajorType, UsefulBufC Bytes); + + +/** + @brief Semi-private method to open a map, array or bstr wrapped CBOR + + @param[in] pCtx The context to add to. + @param[in] uMajorType The major CBOR type to close + + Call QCBOREncode_OpenArray(), QCBOREncode_OpenMap() or + QCBOREncode_BstrWrap() instead of this. + */ +void QCBOREncode_OpenMapOrArray(QCBOREncodeContext *pCtx, uint8_t uMajorType); + + +/** + @brief Semi-private method to close a map, array or bstr wrapped CBOR + + @param[in] pCtx The context to add to. + @param[in] uMajorType The major CBOR type to close + @param[out] pWrappedCBOR UsefulBufC containing wrapped bytes + + Call QCBOREncode_CloseArray(), QCBOREncode_CloseMap() or + QCBOREncode_CloseBstrWrap() instead of this. + */ +void QCBOREncode_CloseMapOrArray(QCBOREncodeContext *pCtx, uint8_t uMajorType, UsefulBufC *pWrappedCBOR); + + +/** + @brief Semi-private method to add simple types. + + @param[in] pCtx The encoding context to add the simple value to. + @param[in] uSize Minimum encoding size for uNum. Usually 0. + @param[in] uNum One of CBOR_SIMPLEV_FALSE through _UNDEF or other. + + This is used to add simple types like true and false. + + Call QCBOREncode_AddBool(), QCBOREncode_AddNULL(), QCBOREncode_AddUndef() + instead of this. + + This function can add simple values that are not defined by CBOR yet. This expansion + point in CBOR should not be used unless they are standardized. + + Error handling is the same as QCBOREncode_AddInt64(). + */ +void QCBOREncode_AddType7(QCBOREncodeContext *pCtx, size_t uSize, uint64_t uNum); + + +static inline void QCBOREncode_AddInt64ToMap(QCBOREncodeContext *pCtx, const char *szLabel, int64_t uNum) +{ + QCBOREncode_AddBuffer(pCtx, CBOR_MAJOR_TYPE_TEXT_STRING, UsefulBuf_FromSZ(szLabel)); // AddSZString not defined yet + QCBOREncode_AddInt64(pCtx, uNum); +} + +static inline void QCBOREncode_AddInt64ToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, int64_t uNum) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_AddInt64(pCtx, uNum); +} + + +static inline void QCBOREncode_AddUInt64ToMap(QCBOREncodeContext *pCtx, const char *szLabel, uint64_t uNum) +{ + QCBOREncode_AddBuffer(pCtx, CBOR_MAJOR_TYPE_TEXT_STRING, UsefulBuf_FromSZ(szLabel)); // AddSZString not defined yet + QCBOREncode_AddUInt64(pCtx, uNum); +} + +static inline void QCBOREncode_AddUInt64ToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, uint64_t uNum) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_AddUInt64(pCtx, uNum); +} + + +static inline void QCBOREncode_AddText(QCBOREncodeContext *pCtx, UsefulBufC Text) +{ + QCBOREncode_AddBuffer(pCtx, CBOR_MAJOR_TYPE_TEXT_STRING, Text); +} + +static inline void QCBOREncode_AddTextToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC Text) +{ + QCBOREncode_AddText(pCtx, UsefulBuf_FromSZ(szLabel)); // AddSZString not defined yet + QCBOREncode_AddText(pCtx, Text); +} + +static inline void QCBOREncode_AddTextToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC Text) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_AddText(pCtx, Text); +} + + +inline static void QCBOREncode_AddSZString(QCBOREncodeContext *pCtx, const char *szString) +{ + QCBOREncode_AddText(pCtx, UsefulBuf_FromSZ(szString)); +} + +static inline void QCBOREncode_AddSZStringToMap(QCBOREncodeContext *pCtx, const char *szLabel, const char *szString) +{ + QCBOREncode_AddSZString(pCtx, szLabel); + QCBOREncode_AddSZString(pCtx, szString); +} + +static inline void QCBOREncode_AddSZStringToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, const char *szString) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_AddSZString(pCtx, szString); +} + + +static inline void QCBOREncode_AddDoubleToMap(QCBOREncodeContext *pCtx, const char *szLabel, double dNum) +{ + QCBOREncode_AddSZString(pCtx, szLabel); + QCBOREncode_AddDouble(pCtx, dNum); +} + +static inline void QCBOREncode_AddDoubleToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, double dNum) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_AddDouble(pCtx, dNum); +} + + +static inline void QCBOREncode_AddDateEpoch(QCBOREncodeContext *pCtx, int64_t date) +{ + QCBOREncode_AddTag(pCtx, CBOR_TAG_DATE_EPOCH); + QCBOREncode_AddInt64(pCtx, date); +} + +static inline void QCBOREncode_AddDateEpochToMap(QCBOREncodeContext *pCtx, const char *szLabel, int64_t date) +{ + QCBOREncode_AddSZString(pCtx, szLabel); + QCBOREncode_AddTag(pCtx, CBOR_TAG_DATE_EPOCH); + QCBOREncode_AddInt64(pCtx, date); +} + +static inline void QCBOREncode_AddDateEpochToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, int64_t date) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_AddTag(pCtx, CBOR_TAG_DATE_EPOCH); + QCBOREncode_AddInt64(pCtx, date); +} + + +static inline void QCBOREncode_AddBytes(QCBOREncodeContext *pCtx, UsefulBufC Bytes) +{ + QCBOREncode_AddBuffer(pCtx, CBOR_MAJOR_TYPE_BYTE_STRING, Bytes); +} + +static inline void QCBOREncode_AddBytesToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC Bytes) +{ + QCBOREncode_AddSZString(pCtx, szLabel); + QCBOREncode_AddBytes(pCtx, Bytes); +} + +static inline void QCBOREncode_AddBytesToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC Bytes) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_AddBytes(pCtx, Bytes); +} + + +static inline void QCBOREncode_AddBinaryUUID(QCBOREncodeContext *pCtx, UsefulBufC Bytes) +{ + QCBOREncode_AddTag(pCtx, CBOR_TAG_BIN_UUID); + QCBOREncode_AddBytes(pCtx, Bytes); +} + +static inline void QCBOREncode_AddBinaryUUIDToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC Bytes) +{ + QCBOREncode_AddSZString(pCtx, szLabel); + QCBOREncode_AddTag(pCtx, CBOR_TAG_BIN_UUID); + QCBOREncode_AddBytes(pCtx, Bytes); +} + +static inline void QCBOREncode_AddBinaryUUIDToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC Bytes) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_AddTag(pCtx, CBOR_TAG_BIN_UUID); + QCBOREncode_AddBytes(pCtx, Bytes); +} + + +static inline void QCBOREncode_AddPositiveBignum(QCBOREncodeContext *pCtx, UsefulBufC Bytes) +{ + QCBOREncode_AddTag(pCtx, CBOR_TAG_POS_BIGNUM); + QCBOREncode_AddBytes(pCtx, Bytes); +} + +static inline void QCBOREncode_AddPositiveBignumToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC Bytes) +{ + QCBOREncode_AddSZString(pCtx, szLabel); + QCBOREncode_AddTag(pCtx, CBOR_TAG_POS_BIGNUM); + QCBOREncode_AddBytes(pCtx, Bytes); +} + +static inline void QCBOREncode_AddPositiveBignumToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC Bytes) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_AddTag(pCtx, CBOR_TAG_POS_BIGNUM); + QCBOREncode_AddBytes(pCtx, Bytes); +} + + +static inline void QCBOREncode_AddNegativeBignum(QCBOREncodeContext *pCtx, UsefulBufC Bytes) +{ + QCBOREncode_AddTag(pCtx, CBOR_TAG_NEG_BIGNUM); + QCBOREncode_AddBytes(pCtx, Bytes); +} + +static inline void QCBOREncode_AddNegativeBignumToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC Bytes) +{ + QCBOREncode_AddSZString(pCtx, szLabel); + QCBOREncode_AddTag(pCtx, CBOR_TAG_NEG_BIGNUM); + QCBOREncode_AddBytes(pCtx, Bytes); +} + +static inline void QCBOREncode_AddNegativeBignumToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC Bytes) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_AddTag(pCtx, CBOR_TAG_NEG_BIGNUM); + QCBOREncode_AddBytes(pCtx, Bytes); +} + + +static inline void QCBOREncode_AddURI(QCBOREncodeContext *pCtx, UsefulBufC URI) +{ + QCBOREncode_AddTag(pCtx, CBOR_TAG_URI); + QCBOREncode_AddText(pCtx, URI); +} + +static inline void QCBOREncode_AddURIToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC URI) +{ + QCBOREncode_AddSZString(pCtx, szLabel); + QCBOREncode_AddTag(pCtx, CBOR_TAG_URI); + QCBOREncode_AddText(pCtx, URI); +} + +static inline void QCBOREncode_AddURIToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC URI) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_AddTag(pCtx, CBOR_TAG_URI); + QCBOREncode_AddText(pCtx, URI); +} + + + +static inline void QCBOREncode_AddB64Text(QCBOREncodeContext *pCtx, UsefulBufC B64Text) +{ + QCBOREncode_AddTag(pCtx, CBOR_TAG_B64); + QCBOREncode_AddText(pCtx, B64Text); +} + +static inline void QCBOREncode_AddB64TextToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC B64Text) +{ + QCBOREncode_AddSZString(pCtx, szLabel); + QCBOREncode_AddTag(pCtx, CBOR_TAG_B64); + QCBOREncode_AddText(pCtx, B64Text); +} + +static inline void QCBOREncode_AddB64TextToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC B64Text) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_AddTag(pCtx, CBOR_TAG_B64); + QCBOREncode_AddText(pCtx, B64Text); +} + + +static inline void QCBOREncode_AddB64URLText(QCBOREncodeContext *pCtx, UsefulBufC B64Text) +{ + QCBOREncode_AddTag(pCtx, CBOR_TAG_B64URL); + QCBOREncode_AddText(pCtx, B64Text); +} + +static inline void QCBOREncode_AddB64URLTextToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC B64Text) +{ + QCBOREncode_AddSZString(pCtx, szLabel); + QCBOREncode_AddTag(pCtx, CBOR_TAG_B64URL); + QCBOREncode_AddText(pCtx, B64Text); +} + +static inline void QCBOREncode_AddB64URLTextToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC B64Text) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_AddTag(pCtx, CBOR_TAG_B64URL); + QCBOREncode_AddText(pCtx, B64Text); +} + + +static inline void QCBOREncode_AddRegex(QCBOREncodeContext *pCtx, UsefulBufC Bytes) +{ + QCBOREncode_AddTag(pCtx, CBOR_TAG_REGEX); + QCBOREncode_AddText(pCtx, Bytes); +} + +static inline void QCBOREncode_AddRegexToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC Bytes) +{ + QCBOREncode_AddSZString(pCtx, szLabel); + QCBOREncode_AddTag(pCtx, CBOR_TAG_REGEX); + QCBOREncode_AddText(pCtx, Bytes); +} + +static inline void QCBOREncode_AddRegexToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC Bytes) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_AddTag(pCtx, CBOR_TAG_REGEX); + QCBOREncode_AddText(pCtx, Bytes); +} + + +static inline void QCBOREncode_AddMIMEData(QCBOREncodeContext *pCtx, UsefulBufC MIMEData) +{ + QCBOREncode_AddTag(pCtx, CBOR_TAG_MIME); + QCBOREncode_AddText(pCtx, MIMEData); +} + +static inline void QCBOREncode_AddMIMEDataToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC MIMEData) +{ + QCBOREncode_AddSZString(pCtx, szLabel); + QCBOREncode_AddTag(pCtx, CBOR_TAG_MIME); + QCBOREncode_AddText(pCtx, MIMEData); +} + +static inline void QCBOREncode_AddMIMEDataToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC MIMEData) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_AddTag(pCtx, CBOR_TAG_MIME); + QCBOREncode_AddText(pCtx, MIMEData); +} + + +static inline void QCBOREncode_AddDateString(QCBOREncodeContext *pCtx, const char *szDate) +{ + QCBOREncode_AddTag(pCtx, CBOR_TAG_DATE_STRING); + QCBOREncode_AddSZString(pCtx, szDate); +} + +static inline void QCBOREncode_AddDateStringToMap(QCBOREncodeContext *pCtx, const char *szLabel, const char *szDate) +{ + QCBOREncode_AddSZString(pCtx, szLabel); + QCBOREncode_AddTag(pCtx, CBOR_TAG_DATE_STRING); + QCBOREncode_AddSZString(pCtx, szDate); +} + +static inline void QCBOREncode_AddDateStringToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, const char *szDate) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_AddTag(pCtx, CBOR_TAG_DATE_STRING); + QCBOREncode_AddSZString(pCtx, szDate); +} + + +static inline void QCBOREncode_AddSimple(QCBOREncodeContext *pCtx, uint64_t uNum) +{ + QCBOREncode_AddType7(pCtx, 0, uNum); +} + +static inline void QCBOREncode_AddSimpleToMap(QCBOREncodeContext *pCtx, const char *szLabel, uint8_t uSimple) +{ + QCBOREncode_AddSZString(pCtx, szLabel); + QCBOREncode_AddSimple(pCtx, uSimple); +} + +static inline void QCBOREncode_AddSimpleToMapN(QCBOREncodeContext *pCtx, int nLabel, uint8_t uSimple) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_AddSimple(pCtx, uSimple); +} + + +static inline void QCBOREncode_AddBool(QCBOREncodeContext *pCtx, bool b) +{ + uint8_t uSimple = CBOR_SIMPLEV_FALSE; + if(b) { + uSimple = CBOR_SIMPLEV_TRUE; + } + QCBOREncode_AddSimple(pCtx, uSimple); +} + +static inline void QCBOREncode_AddBoolToMap(QCBOREncodeContext *pCtx, const char *szLabel, bool b) +{ + QCBOREncode_AddSZString(pCtx, szLabel); + QCBOREncode_AddBool(pCtx, b); +} + +static inline void QCBOREncode_AddBoolToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, bool b) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_AddBool(pCtx, b); +} + + +static inline void QCBOREncode_AddNULL(QCBOREncodeContext *pCtx) +{ + QCBOREncode_AddSimple(pCtx, CBOR_SIMPLEV_NULL); +} + +static inline void QCBOREncode_AddNULLToMap(QCBOREncodeContext *pCtx, const char *szLabel) +{ + QCBOREncode_AddSZString(pCtx, szLabel); + QCBOREncode_AddNULL(pCtx); +} + +static inline void QCBOREncode_AddNULLToMapN(QCBOREncodeContext *pCtx, int64_t nLabel) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_AddNULL(pCtx); +} + + +static inline void QCBOREncode_AddUndef(QCBOREncodeContext *pCtx) +{ + QCBOREncode_AddSimple(pCtx, CBOR_SIMPLEV_UNDEF); +} + +static inline void QCBOREncode_AddUndefToMap(QCBOREncodeContext *pCtx, const char *szLabel) +{ + QCBOREncode_AddSZString(pCtx, szLabel); + QCBOREncode_AddUndef(pCtx); +} + +static inline void QCBOREncode_AddUndefToMapN(QCBOREncodeContext *pCtx, int64_t nLabel) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_AddUndef(pCtx); +} + + +static inline void QCBOREncode_OpenArray(QCBOREncodeContext *pCtx) +{ + QCBOREncode_OpenMapOrArray(pCtx, CBOR_MAJOR_TYPE_ARRAY); +} + +static inline void QCBOREncode_OpenArrayInMap(QCBOREncodeContext *pCtx, const char *szLabel) +{ + QCBOREncode_AddSZString(pCtx, szLabel); + QCBOREncode_OpenArray(pCtx); +} + +static inline void QCBOREncode_OpenArrayInMapN(QCBOREncodeContext *pCtx, int64_t nLabel) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_OpenArray(pCtx); +} + +static inline void QCBOREncode_CloseArray(QCBOREncodeContext *pCtx) +{ + QCBOREncode_CloseMapOrArray(pCtx, CBOR_MAJOR_TYPE_ARRAY, NULL); +} + + +static inline void QCBOREncode_OpenMap(QCBOREncodeContext *pCtx) +{ + QCBOREncode_OpenMapOrArray(pCtx, CBOR_MAJOR_TYPE_MAP); +} + +static inline void QCBOREncode_OpenMapInMap(QCBOREncodeContext *pCtx, const char *szLabel) +{ + QCBOREncode_AddSZString(pCtx, szLabel); + QCBOREncode_OpenMap(pCtx); +} + +static inline void QCBOREncode_OpenMapInMapN(QCBOREncodeContext *pCtx, int64_t nLabel) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_OpenMap(pCtx); +} + +static inline void QCBOREncode_CloseMap(QCBOREncodeContext *pCtx) +{ + QCBOREncode_CloseMapOrArray(pCtx, CBOR_MAJOR_TYPE_MAP, NULL); +} + + +static inline void QCBOREncode_BstrWrap(QCBOREncodeContext *pCtx) +{ + QCBOREncode_OpenMapOrArray(pCtx, CBOR_MAJOR_TYPE_BYTE_STRING); +} + +static inline void QCBOREncode_BstrWrapInMap(QCBOREncodeContext *pCtx, const char *szLabel) +{ + QCBOREncode_AddSZString(pCtx, szLabel); + QCBOREncode_BstrWrap(pCtx); +} + +static inline void QCBOREncode_BstrWrapInMapN(QCBOREncodeContext *pCtx, int64_t nLabel) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_BstrWrap(pCtx); +} + +static inline void QCBOREncode_CloseBstrWrap(QCBOREncodeContext *pCtx, UsefulBufC *pWrappedCBOR) +{ + QCBOREncode_CloseMapOrArray(pCtx, CBOR_MAJOR_TYPE_BYTE_STRING, pWrappedCBOR); +} + + +static inline void QCBOREncode_AddEncoded(QCBOREncodeContext *pCtx, UsefulBufC Encoded) +{ + QCBOREncode_AddBuffer(pCtx, CBOR_MAJOR_NONE_TYPE_RAW, Encoded); +} + +static inline void QCBOREncode_AddEncodedToMap(QCBOREncodeContext *pCtx, const char *szLabel, UsefulBufC Encoded) +{ + QCBOREncode_AddSZString(pCtx, szLabel); + QCBOREncode_AddEncoded(pCtx, Encoded); +} + +static inline void QCBOREncode_AddEncodedToMapN(QCBOREncodeContext *pCtx, int64_t nLabel, UsefulBufC Encoded) +{ + QCBOREncode_AddInt64(pCtx, nLabel); + QCBOREncode_AddEncoded(pCtx, Encoded); +} + + +/* =========================================================================== + END OF PRIVATE INLINE IMPLEMENTATION + + =========================================================================== */ + +#endif /* defined(__QCBOR__qcbor__) */ + diff --git a/middleware/qcbor/min_use_main.c b/middleware/qcbor/min_use_main.c new file mode 100644 index 000000000..7d6f5d94c --- /dev/null +++ b/middleware/qcbor/min_use_main.c @@ -0,0 +1,179 @@ +/*============================================================================== + +Copyright (c) 2018, Laurence Lundblade. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * The name "Laurence Lundblade" may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +==============================================================================*/ +// Created by Laurence Lundblade on 10/26/18. + +#include +#include "qcbor.h" + + + +/* + A small user of CBOR encoding and decoding + that is good as an example and for + checking code size with all the + inlining and dead stripping on. + + */ + +int main(int argc, const char * argv[]) +{ + (void)argc; // Suppress unused warning + (void)argv; // Suppress unused warning + + uint8_t pBuf[300]; + // Very simple CBOR, a map with one boolean that is true in it + QCBOREncodeContext EC; + + QCBOREncode_Init(&EC, UsefulBuf_FROM_BYTE_ARRAY(pBuf)); + + QCBOREncode_OpenMap(&EC); + QCBOREncode_AddBoolToMapN(&EC, 66, true); + QCBOREncode_CloseMap(&EC); + + UsefulBufC Encoded; + if(QCBOREncode_Finish(&EC, &Encoded)) { + return -1; + } + + + // Decode it and see that is right + QCBORDecodeContext DC; + QCBORItem Item; + QCBORDecode_Init(&DC, Encoded, QCBOR_DECODE_MODE_NORMAL); + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_MAP) { + return -2; + } + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_TRUE) { + return -3; + } + + if(QCBORDecode_Finish(&DC)) { + return -4; + } + + + // Make another encoded message with the CBOR from the previous put into this one + UsefulBuf_MAKE_STACK_UB(MemoryForEncoded2, 20); + QCBOREncode_Init(&EC, MemoryForEncoded2); + QCBOREncode_OpenArray(&EC); + QCBOREncode_AddUInt64(&EC, 451); + QCBOREncode_AddEncoded(&EC, Encoded); + QCBOREncode_OpenMap(&EC); + QCBOREncode_AddEncodedToMapN(&EC, -70000, Encoded); + QCBOREncode_CloseMap(&EC); + QCBOREncode_CloseArray(&EC); + + UsefulBufC Encoded2; + if(QCBOREncode_Finish(&EC, &Encoded2)) { + return -5; + } + /* + [ // 0 1:3 + 451, // 1 1:2 + { // 1 1:2 2:1 + 66: true // 2 1:1 + }, + { // 1 1:1 2:1 + -70000: { // 2 1:1 2:1 3:1 + 66: true // 3 XXXXXX + } + } + ] + + + + 83 # array(3) + 19 01C3 # unsigned(451) + A1 # map(1) + 18 42 # unsigned(66) + F5 # primitive(21) + A1 # map(1) + 3A 0001116F # negative(69999) + A1 # map(1) + 18 42 # unsigned(66) + F5 # primitive(21) + */ + + // Decode it and see if it is OK + QCBORDecode_Init(&DC, Encoded2, QCBOR_DECODE_MODE_NORMAL); + + // 0 1:3 + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_ARRAY || Item.val.uCount != 3) { + return -6; + } + + // 1 1:2 + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_INT64 || Item.val.uint64 != 451) { + return -7; + } + + // 1 1:2 2:1 + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_MAP || Item.val.uCount != 1) { + return -8; + } + + // 2 1:1 + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_TRUE) { + return -9; + } + + // 1 1:1 2:1 + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_MAP || Item.val.uCount != 1) { + return -10; + } + + // 2 1:1 2:1 3:1 + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_MAP || Item.val.uCount != 1 || Item.uLabelType != QCBOR_TYPE_INT64 || Item.label.int64 != -70000) { + return -11; + } + + // 3 XXXXXX + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_TRUE || Item.uLabelType != QCBOR_TYPE_INT64 || Item.label.int64 != 66) { + return -12; + } + + if(QCBORDecode_Finish(&DC)) { + return -13; + } + + return 0; +} diff --git a/middleware/qcbor/qcbor.mk b/middleware/qcbor/qcbor.mk new file mode 100644 index 000000000..f1b55bd7b --- /dev/null +++ b/middleware/qcbor/qcbor.mk @@ -0,0 +1,52 @@ +# dir declaration +MID_QCBOR_DIR = $(MIDDLEWARES_ROOT)/qcbor + +MID_QCBOR_ASMSRCDIR = +MID_QCBOR_CSRCDIR = $(MID_QCBOR_DIR)/src +MID_QCBOR_INCDIR = $(MID_QCBOR_DIR)/inc + +# find all the source files in the target directories +MID_QCBOR_CSRCS = $(call get_csrcs, $(MID_QCBOR_CSRCDIR)) +MID_QCBOR_ASMSRCS = $(call get_asmsrcs, $(MID_QCBOR_ASMSRCDIR)) + +# get object files +MID_QCBOR_COBJS = $(call get_relobjs, $(MID_QCBOR_CSRCS)) +MID_QCBOR_ASMOBJS = $(call get_relobjs, $(MID_QCBOR_ASMSRCS)) +MID_QCBOR_OBJS = $(MID_QCBOR_COBJS) $(MID_QCBOR_ASMOBJS) + +# get dependency files +MID_QCBOR_DEPS = $(call get_deps, $(MID_QCBOR_OBJS)) + +# extra macros to be defined +MID_QCBOR_DEFINES = -DMID_QCBOR + +# genearte library +MID_LIB_QCBOR = $(OUT_DIR)/libmidqcbor.a + +# library generation rule +$(MID_LIB_QCBOR): $(MID_QCBOR_OBJS) + $(TRACE_ARCHIVE) + $(Q)$(AR) $(AR_OPT) $@ $(MID_QCBOR_OBJS) + +# specific compile rules +# user can add rules to compile this middleware +# if not rules specified to this middleware, it will use default compiling rules + +# Middleware Definitions +MID_INCDIR += $(MID_QCBOR_INCDIR) +MID_CSRCDIR += $(MID_QCBOR_CSRCDIR) +MID_ASMSRCDIR += $(MID_QCBOR_ASMSRCDIR) + +MID_CSRCS += $(MID_QCBOR_CSRCS) +MID_CXXSRCS += +MID_ASMSRCS += $(MID_QCBOR_ASMSRCS) +MID_ALLSRCS += $(MID_QCBOR_CSRCS) $(MID_QCBOR_ASMSRCS) + +MID_COBJS += $(MID_QCBOR_COBJS) +MID_CXXOBJS += +MID_ASMOBJS += $(MID_QCBOR_ASMOBJS) +MID_ALLOBJS += $(MID_QCBOR_OBJS) + +MID_DEFINES += $(MID_QCBOR_DEFINES) +MID_DEPS += $(MID_QCBOR_DEPS) +MID_LIBS += $(MID_LIB_QCBOR) \ No newline at end of file diff --git a/middleware/qcbor/src/UsefulBuf.c b/middleware/qcbor/src/UsefulBuf.c new file mode 100644 index 000000000..5a7d37bef --- /dev/null +++ b/middleware/qcbor/src/UsefulBuf.c @@ -0,0 +1,329 @@ +/*============================================================================== + Copyright (c) 2016-2018, The Linux Foundation. + Copyright (c) 2018-2019, Laurence Lundblade. + All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of The Linux Foundation nor the names of its + contributors, nor the name "Laurence Lundblade" may be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ==============================================================================*/ + +/*=================================================================================== + FILE: UsefulBuf.c + + DESCRIPTION: General purpose input and output buffers + + EDIT HISTORY FOR FILE: + + This section contains comments describing changes made to the module. + Notice that changes are listed in reverse chronological order. + + when who what, where, why + -------- ---- --------------------------------------------------- + 09/07/17 llundbla Fix critical bug in UsefulBuf_Find() -- a read off + the end of memory when the bytes to find is longer + than the bytes to search. + 06/27/17 llundbla Fix UsefulBuf_Compare() bug. Only affected comparison + for < or > for unequal length buffers. Added + UsefulBuf_Set() function. + 05/30/17 llundbla Functions for NULL UsefulBufs and const / unconst + 11/13/16 llundbla Initial Version. + + =====================================================================================*/ + +#include "UsefulBuf.h" + +#define USEFUL_OUT_BUF_MAGIC (0x0B0F) // used to catch use of uninitialized or corrupted UOBs + + +/* + Public function -- see UsefulBuf.h + */ +UsefulBufC UsefulBuf_CopyOffset(UsefulBuf Dest, size_t uOffset, const UsefulBufC Src) +{ + // Do this with subtraction so it doesn't give erroneous result if uOffset + Src.len overflows + if(uOffset > Dest.len || Src.len > Dest.len - uOffset) { // uOffset + Src.len > Dest.len + return NULLUsefulBufC; + } + + memcpy((uint8_t *)Dest.ptr + uOffset, Src.ptr, Src.len); + + return (UsefulBufC){Dest.ptr, Src.len + uOffset}; +} + + +/* + Public function -- see UsefulBuf.h + */ +int UsefulBuf_Compare(const UsefulBufC UB1, const UsefulBufC UB2) +{ + // use the comparisons rather than subtracting lengths to + // return an int instead of a size_t + if(UB1.len < UB2.len) { + return -1; + } else if (UB1.len > UB2.len) { + return 1; + } // else UB1.len == UB2.len + + return memcmp(UB1.ptr, UB2.ptr, UB1.len); +} + + + +/* + Public function -- see UsefulBuf.h + */ +size_t UsefulBuf_FindBytes(UsefulBufC BytesToSearch, UsefulBufC BytesToFind) +{ + if(BytesToSearch.len < BytesToFind.len) { + return SIZE_MAX; + } + + for(size_t uPos = 0; uPos <= BytesToSearch.len - BytesToFind.len; uPos++) { + if(!UsefulBuf_Compare((UsefulBufC){((uint8_t *)BytesToSearch.ptr) + uPos, BytesToFind.len}, BytesToFind)) { + return uPos; + } + } + + return SIZE_MAX; +} + + +/* + Public function -- see UsefulBuf.h + + Code Reviewers: THIS FUNCTION DOES POINTER MATH + */ +void UsefulOutBuf_Init(UsefulOutBuf *me, UsefulBuf Storage) +{ + me->magic = USEFUL_OUT_BUF_MAGIC; + UsefulOutBuf_Reset(me); + me->UB = Storage; + +#if 0 + // This check is off by default. + + // The following check fails on ThreadX + + // Sanity check on the pointer and size to be sure we are not + // passed a buffer that goes off the end of the address space. + // Given this test, we know that all unsigned lengths less than + // me->size are valid and won't wrap in any pointer additions + // based off of pStorage in the rest of this code. + const uintptr_t ptrM = UINTPTR_MAX - Storage.len; + if(Storage.ptr && (uintptr_t)Storage.ptr > ptrM) // Check #0 + me->err = 1; +#endif +} + + + +/* + Public function -- see UsefulBuf.h + + The core of UsefulOutBuf -- put some bytes in the buffer without writing off the end of it. + + Code Reviewers: THIS FUNCTION DOES POINTER MATH + + This function inserts the source buffer, NewData, into the destination buffer, me->UB.ptr. + + Destination is represented as: + me->UB.ptr -- start of the buffer + me->UB.len -- size of the buffer UB.ptr + me->data_len -- length of value data in UB + + Source is data: + NewData.ptr -- start of source buffer + NewData.len -- length of source buffer + + Insertion point: + uInsertionPos. + + Steps: + + 0. Corruption checks on UsefulOutBuf + + 1. Figure out if the new data will fit or not + + 2. Is insertion position in the range of valid data? + + 3. If insertion point is not at the end, slide data to the right of the insertion point to the right + + 4. Put the new data in at the insertion position. + + */ +void UsefulOutBuf_InsertUsefulBuf(UsefulOutBuf *me, UsefulBufC NewData, size_t uInsertionPos) +{ + if(me->err) { + // Already in error state. + return; + } + + /* 0. Sanity check the UsefulOutBuf structure */ + // A "counter measure". If magic number is not the right number it + // probably means me was not initialized or it was corrupted. Attackers + // can defeat this, but it is a hurdle and does good with very + // little code. + if(me->magic != USEFUL_OUT_BUF_MAGIC) { + me->err = 1; + return; // Magic number is wrong due to uninitalization or corrption + } + + // Make sure valid data is less than buffer size. This would only occur + // if there was corruption of me, but it is also part of the checks to + // be sure there is no pointer arithmatic under/overflow. + if(me->data_len > me->UB.len) { // Check #1 + me->err = 1; + return; // Offset of valid data is off the end of the UsefulOutBuf due to uninitialization or corruption + } + + /* 1. Will it fit? */ + // WillItFit() is the same as: NewData.len <= (me->size - me->data_len) + // Check #1 makes sure subtraction in RoomLeft will not wrap around + if(! UsefulOutBuf_WillItFit(me, NewData.len)) { // Check #2 + // The new data will not fit into the the buffer. + me->err = 1; + return; + } + + /* 2. Check the Insertion Position */ + // This, with Check #1, also confirms that uInsertionPos <= me->data_len + if(uInsertionPos > me->data_len) { // Check #3 + // Off the end of the valid data in the buffer. + me->err = 1; + return; + } + + /* 3. Slide existing data to the right */ + uint8_t *pSourceOfMove = ((uint8_t *)me->UB.ptr) + uInsertionPos; // PtrMath #1 + size_t uNumBytesToMove = me->data_len - uInsertionPos; // PtrMath #2 + uint8_t *pDestinationOfMove = pSourceOfMove + NewData.len; // PtrMath #3 + + if(uNumBytesToMove && me->UB.ptr) { + // To know memmove won't go off end of destination, see PtrMath #4 + memmove(pDestinationOfMove, pSourceOfMove, uNumBytesToMove); + } + + /* 4. Put the new data in */ + uint8_t *pInsertionPoint = ((uint8_t *)me->UB.ptr) + uInsertionPos; // PtrMath #5 + if(me->UB.ptr) { + // To know memmove won't go off end of destination, see PtrMath #6 + memmove(pInsertionPoint, NewData.ptr, NewData.len); + } + me->data_len += NewData.len ; +} + + +/* + Rationale that describes why the above pointer math is safe + + PtrMath #1 will never wrap around over because + Check #0 in UsefulOutBuf_Init makes sure me->UB.ptr + me->UB.len doesn't wrap + Check #1 makes sure me->data_len is less than me->UB.len + Check #3 makes sure uInsertionPos is less than me->data_len + + PtrMath #2 will never wrap around under because + Check #3 makes sure uInsertionPos is less than me->data_len + + PtrMath #3 will never wrap around over because todo + PtrMath #1 is checked resulting in pSourceOfMove being between me->UB.ptr and a maximum valid ptr + Check #2 that NewData.len will fit + + PtrMath #4 will never wrap under because + Calculation for extent or memmove is uRoomInDestination = me->UB.len - (uInsertionPos + NewData.len) + Check #3 makes sure uInsertionPos is less than me->data_len + Check #3 allows Check #2 to be refactored as NewData.Len > (me->size - uInsertionPos) + This algebraically rearranges to me->size > uInsertionPos + NewData.len + + PtrMath #5 is exactly the same as PtrMath #1 + + PtrMath #6 will never wrap under because + Calculation for extent of memove is uRoomInDestination = me->UB.len - uInsertionPos; + Check #1 makes sure me->data_len is less than me->size + Check #3 makes sure uInsertionPos is less than me->data_len + */ + + +/* + Public function -- see UsefulBuf.h + */ +UsefulBufC UsefulOutBuf_OutUBuf(UsefulOutBuf *me) +{ + if(me->err) { + return NULLUsefulBufC; + } + + if(me->magic != USEFUL_OUT_BUF_MAGIC) { + me->err = 1; + return NULLUsefulBufC; + } + + return (UsefulBufC){me->UB.ptr,me->data_len}; +} + + +/* + Public function -- see UsefulBuf.h + + Copy out the data accumulated in to the output buffer. + */ +UsefulBufC UsefulOutBuf_CopyOut(UsefulOutBuf *me, UsefulBuf pDest) +{ + const UsefulBufC Tmp = UsefulOutBuf_OutUBuf(me); + if(UsefulBuf_IsNULLC(Tmp)) { + return NULLUsefulBufC; + } + return UsefulBuf_Copy(pDest, Tmp); +} + + + + +/* + Public function -- see UsefulBuf.h + + The core of UsefulInputBuf -- consume some bytes without going off the end of the buffer. + + Code Reviewers: THIS FUNCTION DOES POINTER MATH + */ +const void * UsefulInputBuf_GetBytes(UsefulInputBuf *me, size_t uAmount) +{ + // Already in error state. Do nothing. + if(me->err) { + return NULL; + } + + if(!UsefulInputBuf_BytesAvailable(me, uAmount)) { + // The number of bytes asked for at current position are more than available + me->err = 1; + return NULL; + } + + // This is going to succeed + const void * const result = ((uint8_t *)me->UB.ptr) + me->cursor; + me->cursor += uAmount; // this will not overflow because of check using UsefulInputBuf_BytesAvailable() + return result; +} + diff --git a/middleware/qcbor/src/ieee754.c b/middleware/qcbor/src/ieee754.c new file mode 100644 index 000000000..6fdfda8f4 --- /dev/null +++ b/middleware/qcbor/src/ieee754.c @@ -0,0 +1,497 @@ +/*============================================================================== + ieee754.c -- floating point conversion between half, double and single precision + + Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved. + + SPDX-License-Identifier: BSD-3-Clause + + See BSD-3-Clause license in README.md + + Created on 7/23/18 + ==============================================================================*/ + +#include "ieee754.h" +#include // For memcpy() + + +/* + This code is written for clarity and verifiability, not for size, on the assumption + that the optimizer will do a good job. The LLVM optimizer, -Os, does seem to do the + job and the resulting object code is smaller from combining code for the many different + cases (normal, subnormal, infinity, zero...) for the conversions. + + Dead stripping is also really helpful to get code size down when floating point + encoding is not needed. + + This code works solely using shifts and masks and thus has no dependency on + any math libraries. It can even work if the CPU doesn't have any floating + point support, though that isn't the most useful thing to do. + + The memcpy() dependency is only for CopyFloatToUint32() and friends which only + is needed to avoid type punning when converting the actual float bits to + an unsigned value so the bit shifts and masks can work. + */ + +/* + The references used to write this code: + + - IEEE 754-2008, particularly section 3.6 and 6.2.1 + + - https://en.wikipedia.org/wiki/IEEE_754 and subordinate pages + + - https://stackoverflow.com/questions/19800415/why-does-ieee-754-reserve-so-many-nan-values + */ + + +// ----- Half Precsion ----------- +#define HALF_NUM_SIGNIFICAND_BITS (10) +#define HALF_NUM_EXPONENT_BITS (5) +#define HALF_NUM_SIGN_BITS (1) + +#define HALF_SIGNIFICAND_SHIFT (0) +#define HALF_EXPONENT_SHIFT (HALF_NUM_SIGNIFICAND_BITS) +#define HALF_SIGN_SHIFT (HALF_NUM_SIGNIFICAND_BITS + HALF_NUM_EXPONENT_BITS) + +#define HALF_SIGNIFICAND_MASK (0x3ff) // The lower 10 bits // 0x03ff +#define HALF_EXPONENT_MASK (0x1f << HALF_EXPONENT_SHIFT) // 0x7c00 5 bits of exponent +#define HALF_SIGN_MASK (0x01 << HALF_SIGN_SHIFT) // // 0x80001 bit of sign +#define HALF_QUIET_NAN_BIT (0x01 << (HALF_NUM_SIGNIFICAND_BITS-1)) // 0x0200 + +/* Biased Biased Unbiased Use + 0x00 0 -15 0 and subnormal + 0x01 1 -14 Smallest normal exponent + 0x1e 30 15 Largest normal exponent + 0x1F 31 16 NaN and Infinity */ +#define HALF_EXPONENT_BIAS (15) +#define HALF_EXPONENT_MAX (HALF_EXPONENT_BIAS) // 15 Unbiased +#define HALF_EXPONENT_MIN (-HALF_EXPONENT_BIAS+1) // -14 Unbiased +#define HALF_EXPONENT_ZERO (-HALF_EXPONENT_BIAS) // -15 Unbiased +#define HALF_EXPONENT_INF_OR_NAN (HALF_EXPONENT_BIAS+1) // 16 Unbiased + + +// ------ Single Precision -------- +#define SINGLE_NUM_SIGNIFICAND_BITS (23) +#define SINGLE_NUM_EXPONENT_BITS (8) +#define SINGLE_NUM_SIGN_BITS (1) + +#define SINGLE_SIGNIFICAND_SHIFT (0) +#define SINGLE_EXPONENT_SHIFT (SINGLE_NUM_SIGNIFICAND_BITS) +#define SINGLE_SIGN_SHIFT (SINGLE_NUM_SIGNIFICAND_BITS + SINGLE_NUM_EXPONENT_BITS) + +#define SINGLE_SIGNIFICAND_MASK (0x7fffffUL) // The lower 23 bits +#define SINGLE_EXPONENT_MASK (0xffUL << SINGLE_EXPONENT_SHIFT) // 8 bits of exponent +#define SINGLE_SIGN_MASK (0x01UL << SINGLE_SIGN_SHIFT) // 1 bit of sign +#define SINGLE_QUIET_NAN_BIT (0x01UL << (SINGLE_NUM_SIGNIFICAND_BITS-1)) + +/* Biased Biased Unbiased Use + 0x0000 0 -127 0 and subnormal + 0x0001 1 -126 Smallest normal exponent + 0x7f 127 0 1 + 0xfe 254 127 Largest normal exponent + 0xff 255 128 NaN and Infinity */ +#define SINGLE_EXPONENT_BIAS (127) +#define SINGLE_EXPONENT_MAX (SINGLE_EXPONENT_BIAS) // 127 unbiased +#define SINGLE_EXPONENT_MIN (-SINGLE_EXPONENT_BIAS+1) // -126 unbiased +#define SINGLE_EXPONENT_ZERO (-SINGLE_EXPONENT_BIAS) // -127 unbiased +#define SINGLE_EXPONENT_INF_OR_NAN (SINGLE_EXPONENT_BIAS+1) // 128 unbiased + + +// --------- Double Precision ---------- +#define DOUBLE_NUM_SIGNIFICAND_BITS (52) +#define DOUBLE_NUM_EXPONENT_BITS (11) +#define DOUBLE_NUM_SIGN_BITS (1) + +#define DOUBLE_SIGNIFICAND_SHIFT (0) +#define DOUBLE_EXPONENT_SHIFT (DOUBLE_NUM_SIGNIFICAND_BITS) +#define DOUBLE_SIGN_SHIFT (DOUBLE_NUM_SIGNIFICAND_BITS + DOUBLE_NUM_EXPONENT_BITS) + +#define DOUBLE_SIGNIFICAND_MASK (0xfffffffffffffULL) // The lower 52 bits +#define DOUBLE_EXPONENT_MASK (0x7ffULL << DOUBLE_EXPONENT_SHIFT) // 11 bits of exponent +#define DOUBLE_SIGN_MASK (0x01ULL << DOUBLE_SIGN_SHIFT) // 1 bit of sign +#define DOUBLE_QUIET_NAN_BIT (0x01ULL << (DOUBLE_NUM_SIGNIFICAND_BITS-1)) + + +/* Biased Biased Unbiased Use + 0x00000000 0 -1023 0 and subnormal + 0x00000001 1 -1022 Smallest normal exponent + 0x000007fe 2046 1023 Largest normal exponent + 0x000007ff 2047 1024 NaN and Infinity */ +#define DOUBLE_EXPONENT_BIAS (1023) +#define DOUBLE_EXPONENT_MAX (DOUBLE_EXPONENT_BIAS) // unbiased +#define DOUBLE_EXPONENT_MIN (-DOUBLE_EXPONENT_BIAS+1) // unbiased +#define DOUBLE_EXPONENT_ZERO (-DOUBLE_EXPONENT_BIAS) // unbiased +#define DOUBLE_EXPONENT_INF_OR_NAN (DOUBLE_EXPONENT_BIAS+1) // unbiased + + + +/* + Convenient functions to avoid type punning, compiler warnings and such + The optimizer reduces them to a simple assignment. + This is a crusty corner of C. It shouldn't be this hard. + + These are also in UsefulBuf.h under a different name. They are copied + here to avoid a dependency on UsefulBuf.h. There is no + object code size impact because these always optimze down to a + simple assignment. + */ +static inline uint32_t CopyFloatToUint32(float f) +{ + uint32_t u32; + memcpy(&u32, &f, sizeof(uint32_t)); + return u32; +} + +static inline uint64_t CopyDoubleToUint64(double d) +{ + uint64_t u64; + memcpy(&u64, &d, sizeof(uint64_t)); + return u64; +} + +static inline float CopyUint32ToFloat(uint32_t u32) +{ + float f; + memcpy(&f, &u32, sizeof(uint32_t)); + return f; +} + +static inline double CopyUint64ToDouble(uint64_t u64) +{ + double d; + memcpy(&d, &u64, sizeof(uint64_t)); + return d; +} + + +// Public function; see ieee754.h +uint16_t IEEE754_FloatToHalf(float f) +{ + // Pull the three parts out of the single-precision float + const uint32_t uSingle = CopyFloatToUint32(f); + const int32_t nSingleUnbiasedExponent = ((uSingle & SINGLE_EXPONENT_MASK) >> SINGLE_EXPONENT_SHIFT) - SINGLE_EXPONENT_BIAS; + const uint32_t uSingleSign = (uSingle & SINGLE_SIGN_MASK) >> SINGLE_SIGN_SHIFT; + const uint32_t uSingleSignificand = uSingle & SINGLE_SIGNIFICAND_MASK; + + + // Now convert the three parts to half-precision. + uint16_t uHalfSign, uHalfSignificand, uHalfBiasedExponent; + if(nSingleUnbiasedExponent == SINGLE_EXPONENT_INF_OR_NAN) { + // +/- Infinity and NaNs -- single biased exponent is 0xff + uHalfBiasedExponent = HALF_EXPONENT_INF_OR_NAN + HALF_EXPONENT_BIAS; + if(!uSingleSignificand) { + // Infinity + uHalfSignificand = 0; + } else { + // Copy the LBSs of the NaN payload that will fit from the single to the half + uHalfSignificand = uSingleSignificand & (HALF_SIGNIFICAND_MASK & ~HALF_QUIET_NAN_BIT); + if(uSingleSignificand & SINGLE_QUIET_NAN_BIT) { + // It's a qNaN; copy the qNaN bit + uHalfSignificand |= HALF_QUIET_NAN_BIT; + } else { + // It's a sNaN; make sure the significand is not zero so it stays a NaN + // This is needed because not all significand bits are copied from single + if(!uHalfSignificand) { + // Set the LSB. This is what wikipedia shows for sNAN. + uHalfSignificand |= 0x01; + } + } + } + } else if(nSingleUnbiasedExponent == SINGLE_EXPONENT_ZERO) { + // 0 or a subnormal number -- singled biased exponent is 0 + uHalfBiasedExponent = 0; + uHalfSignificand = 0; // Any subnormal single will be too small to express as a half precision + } else if(nSingleUnbiasedExponent > HALF_EXPONENT_MAX) { + // Exponent is too large to express in half-precision; round up to infinity + uHalfBiasedExponent = HALF_EXPONENT_INF_OR_NAN + HALF_EXPONENT_BIAS; + uHalfSignificand = 0; + } else if(nSingleUnbiasedExponent < HALF_EXPONENT_MIN) { + // Exponent is too small to express in half-precision normal; make it a half-precision subnormal + uHalfBiasedExponent = (uint16_t)(HALF_EXPONENT_ZERO + HALF_EXPONENT_BIAS); + // Difference between single normal exponent and the base exponent of a half subnormal + const uint32_t nExpDiff = -(nSingleUnbiasedExponent - HALF_EXPONENT_MIN); + // Also have to shift the significand by the difference in number of bits between a single and a half significand + const int32_t nSignificandBitsDiff = SINGLE_NUM_SIGNIFICAND_BITS - HALF_NUM_SIGNIFICAND_BITS; + // Add in the 1 that is implied in the significand of a normal number; it needs to be present in a subnormal + const uint32_t uSingleSignificandSubnormal = uSingleSignificand + (0x01L << SINGLE_NUM_SIGNIFICAND_BITS); + uHalfSignificand = uSingleSignificandSubnormal >> (nExpDiff + nSignificandBitsDiff); + } else { + // The normal case + uHalfBiasedExponent = nSingleUnbiasedExponent + HALF_EXPONENT_BIAS; + uHalfSignificand = uSingleSignificand >> (SINGLE_NUM_SIGNIFICAND_BITS - HALF_NUM_SIGNIFICAND_BITS); + } + uHalfSign = uSingleSign; + + // Put the 3 values in the right place for a half precision + const uint16_t uHalfPrecision = uHalfSignificand | + (uHalfBiasedExponent << HALF_EXPONENT_SHIFT) | + (uHalfSign << HALF_SIGN_SHIFT); + return uHalfPrecision; +} + + +// Public function; see ieee754.h +uint16_t IEEE754_DoubleToHalf(double d) +{ + // Pull the three parts out of the double-precision float + const uint64_t uDouble = CopyDoubleToUint64(d); + const int64_t nDoubleUnbiasedExponent = ((uDouble & DOUBLE_EXPONENT_MASK) >> DOUBLE_EXPONENT_SHIFT) - DOUBLE_EXPONENT_BIAS; + const uint64_t uDoubleSign = (uDouble & DOUBLE_SIGN_MASK) >> DOUBLE_SIGN_SHIFT; + const uint64_t uDoubleSignificand = uDouble & DOUBLE_SIGNIFICAND_MASK; + + + // Now convert the three parts to half-precision. + uint16_t uHalfSign, uHalfSignificand, uHalfBiasedExponent; + if(nDoubleUnbiasedExponent == DOUBLE_EXPONENT_INF_OR_NAN) { + // +/- Infinity and NaNs -- single biased exponent is 0xff + uHalfBiasedExponent = HALF_EXPONENT_INF_OR_NAN + HALF_EXPONENT_BIAS; + if(!uDoubleSignificand) { + // Infinity + uHalfSignificand = 0; + } else { + // Copy the LBSs of the NaN payload that will fit from the double to the half + uHalfSignificand = uDoubleSignificand & (HALF_SIGNIFICAND_MASK & ~HALF_QUIET_NAN_BIT); + if(uDoubleSignificand & DOUBLE_QUIET_NAN_BIT) { + // It's a qNaN; copy the qNaN bit + uHalfSignificand |= HALF_QUIET_NAN_BIT; + } else { + // It's an sNaN; make sure the significand is not zero so it stays a NaN + // This is needed because not all significand bits are copied from single + if(!uHalfSignificand) { + // Set the LSB. This is what wikipedia shows for sNAN. + uHalfSignificand |= 0x01; + } + } + } + } else if(nDoubleUnbiasedExponent == DOUBLE_EXPONENT_ZERO) { + // 0 or a subnormal number -- double biased exponent is 0 + uHalfBiasedExponent = 0; + uHalfSignificand = 0; // Any subnormal single will be too small to express as a half precision; TODO, is this really true? + } else if(nDoubleUnbiasedExponent > HALF_EXPONENT_MAX) { + // Exponent is too large to express in half-precision; round up to infinity; TODO, is this really true? + uHalfBiasedExponent = HALF_EXPONENT_INF_OR_NAN + HALF_EXPONENT_BIAS; + uHalfSignificand = 0; + } else if(nDoubleUnbiasedExponent < HALF_EXPONENT_MIN) { + // Exponent is too small to express in half-precision; round down to zero + uHalfBiasedExponent = (uint16_t)(HALF_EXPONENT_ZERO + HALF_EXPONENT_BIAS); + // Difference between double normal exponent and the base exponent of a half subnormal + const uint64_t nExpDiff = -(nDoubleUnbiasedExponent - HALF_EXPONENT_MIN); + // Also have to shift the significand by the difference in number of bits between a double and a half significand + const int64_t nSignificandBitsDiff = DOUBLE_NUM_SIGNIFICAND_BITS - HALF_NUM_SIGNIFICAND_BITS; + // Add in the 1 that is implied in the significand of a normal number; it needs to be present in a subnormal + const uint64_t uDoubleSignificandSubnormal = uDoubleSignificand + (0x01ULL << DOUBLE_NUM_SIGNIFICAND_BITS); + uHalfSignificand = uDoubleSignificandSubnormal >> (nExpDiff + nSignificandBitsDiff); + } else { + // The normal case + uHalfBiasedExponent = nDoubleUnbiasedExponent + HALF_EXPONENT_BIAS; + uHalfSignificand = uDoubleSignificand >> (DOUBLE_NUM_SIGNIFICAND_BITS - HALF_NUM_SIGNIFICAND_BITS); + } + uHalfSign = uDoubleSign; + + + // Put the 3 values in the right place for a half precision + const uint16_t uHalfPrecision = uHalfSignificand | + (uHalfBiasedExponent << HALF_EXPONENT_SHIFT) | + (uHalfSign << HALF_SIGN_SHIFT); + return uHalfPrecision; +} + + +// Public function; see ieee754.h +float IEEE754_HalfToFloat(uint16_t uHalfPrecision) +{ + // Pull out the three parts of the half-precision float + const uint16_t uHalfSignificand = uHalfPrecision & HALF_SIGNIFICAND_MASK; + const int16_t nHalfUnBiasedExponent = ((uHalfPrecision & HALF_EXPONENT_MASK) >> HALF_EXPONENT_SHIFT) - HALF_EXPONENT_BIAS; + const uint16_t uHalfSign = (uHalfPrecision & HALF_SIGN_MASK) >> HALF_SIGN_SHIFT; + + + // Make the three parts of the single-precision number + uint32_t uSingleSignificand, uSingleSign, uSingleBiasedExponent; + if(nHalfUnBiasedExponent == HALF_EXPONENT_ZERO) { + // 0 or subnormal + if(uHalfSignificand) { + // Subnormal case + uSingleBiasedExponent = -HALF_EXPONENT_BIAS + SINGLE_EXPONENT_BIAS +1; + // A half-precision subnormal can always be converted to a normal single-precision float because the ranges line up + uSingleSignificand = uHalfSignificand; + // Shift bits from right of the decimal to left, reducing the exponent by 1 each time + do { + uSingleSignificand <<= 1; + uSingleBiasedExponent--; + } while ((uSingleSignificand & 0x400) == 0); + uSingleSignificand &= HALF_SIGNIFICAND_MASK; + uSingleSignificand <<= (SINGLE_NUM_SIGNIFICAND_BITS - HALF_NUM_SIGNIFICAND_BITS); + } else { + // Just zero + uSingleBiasedExponent = SINGLE_EXPONENT_ZERO + SINGLE_EXPONENT_BIAS; + uSingleSignificand = 0; + } + } else if(nHalfUnBiasedExponent == HALF_EXPONENT_INF_OR_NAN) { + // NaN or Inifinity + uSingleBiasedExponent = SINGLE_EXPONENT_INF_OR_NAN + SINGLE_EXPONENT_BIAS; + if(uHalfSignificand) { + // NaN + // First preserve the NaN payload from half to single + uSingleSignificand = uHalfSignificand & ~HALF_QUIET_NAN_BIT; + if(uHalfSignificand & HALF_QUIET_NAN_BIT) { + // Next, set qNaN if needed since half qNaN bit is not copied above + uSingleSignificand |= SINGLE_QUIET_NAN_BIT; + } + } else { + // Infinity + uSingleSignificand = 0; + } + } else { + // Normal number + uSingleBiasedExponent = nHalfUnBiasedExponent + SINGLE_EXPONENT_BIAS; + uSingleSignificand = uHalfSignificand << (SINGLE_NUM_SIGNIFICAND_BITS - HALF_NUM_SIGNIFICAND_BITS); + } + uSingleSign = uHalfSign; + + + // Shift the three parts of the single precision into place + const uint32_t uSinglePrecision = uSingleSignificand | + (uSingleBiasedExponent << SINGLE_EXPONENT_SHIFT) | + (uSingleSign << SINGLE_SIGN_SHIFT); + + return CopyUint32ToFloat(uSinglePrecision); +} + + +// Public function; see ieee754.h +double IEEE754_HalfToDouble(uint16_t uHalfPrecision) +{ + // Pull out the three parts of the half-precision float + const uint16_t uHalfSignificand = uHalfPrecision & HALF_SIGNIFICAND_MASK; + const int16_t nHalfUnBiasedExponent = ((uHalfPrecision & HALF_EXPONENT_MASK) >> HALF_EXPONENT_SHIFT) - HALF_EXPONENT_BIAS; + const uint16_t uHalfSign = (uHalfPrecision & HALF_SIGN_MASK) >> HALF_SIGN_SHIFT; + + + // Make the three parts of hte single-precision number + uint64_t uDoubleSignificand, uDoubleSign, uDoubleBiasedExponent; + if(nHalfUnBiasedExponent == HALF_EXPONENT_ZERO) { + // 0 or subnormal + uDoubleBiasedExponent = DOUBLE_EXPONENT_ZERO + DOUBLE_EXPONENT_BIAS; + if(uHalfSignificand) { + // Subnormal case + uDoubleBiasedExponent = -HALF_EXPONENT_BIAS + DOUBLE_EXPONENT_BIAS +1; + // A half-precision subnormal can always be converted to a normal double-precision float because the ranges line up + uDoubleSignificand = uHalfSignificand; + // Shift bits from right of the decimal to left, reducing the exponent by 1 each time + do { + uDoubleSignificand <<= 1; + uDoubleBiasedExponent--; + } while ((uDoubleSignificand & 0x400) == 0); + uDoubleSignificand &= HALF_SIGNIFICAND_MASK; + uDoubleSignificand <<= (DOUBLE_NUM_SIGNIFICAND_BITS - HALF_NUM_SIGNIFICAND_BITS); + } else { + // Just zero + uDoubleSignificand = 0; + } + } else if(nHalfUnBiasedExponent == HALF_EXPONENT_INF_OR_NAN) { + // NaN or Inifinity + uDoubleBiasedExponent = DOUBLE_EXPONENT_INF_OR_NAN + DOUBLE_EXPONENT_BIAS; + if(uHalfSignificand) { + // NaN + // First preserve the NaN payload from half to single + uDoubleSignificand = uHalfSignificand & ~HALF_QUIET_NAN_BIT; + if(uHalfSignificand & HALF_QUIET_NAN_BIT) { + // Next, set qNaN if needed since half qNaN bit is not copied above + uDoubleSignificand |= DOUBLE_QUIET_NAN_BIT; + } + } else { + // Infinity + uDoubleSignificand = 0; + } + } else { + // Normal number + uDoubleBiasedExponent = nHalfUnBiasedExponent + DOUBLE_EXPONENT_BIAS; + uDoubleSignificand = (uint64_t)uHalfSignificand << (DOUBLE_NUM_SIGNIFICAND_BITS - HALF_NUM_SIGNIFICAND_BITS); + } + uDoubleSign = uHalfSign; + + + // Shift the 3 parts into place as a double-precision + const uint64_t uDouble = uDoubleSignificand | + (uDoubleBiasedExponent << DOUBLE_EXPONENT_SHIFT) | + (uDoubleSign << DOUBLE_SIGN_SHIFT); + return CopyUint64ToDouble(uDouble); +} + + +// Public function; see ieee754.h +IEEE754_union IEEE754_FloatToSmallest(float f) +{ + IEEE754_union result; + + // Pull the neeed two parts out of the single-precision float + const uint32_t uSingle = CopyFloatToUint32(f); + const int32_t nSingleExponent = ((uSingle & SINGLE_EXPONENT_MASK) >> SINGLE_EXPONENT_SHIFT) - SINGLE_EXPONENT_BIAS; + const uint32_t uSingleSignificand = uSingle & SINGLE_SIGNIFICAND_MASK; + + // Bit mask that is the significand bits that would be lost when converting + // from single-precision to half-precision + const uint64_t uDroppedSingleBits = SINGLE_SIGNIFICAND_MASK >> HALF_NUM_SIGNIFICAND_BITS; + + // Optimizer will re organize so there is only one call to IEEE754_FloatToHalf() + if(uSingle == 0) { + // Value is 0.0000, not a a subnormal + result.uSize = IEEE754_UNION_IS_HALF; + result.uValue = IEEE754_FloatToHalf(f); + } else if(nSingleExponent == SINGLE_EXPONENT_INF_OR_NAN) { + // NaN, +/- infinity + result.uSize = IEEE754_UNION_IS_HALF; + result.uValue = IEEE754_FloatToHalf(f); + } else if((nSingleExponent >= HALF_EXPONENT_MIN) && nSingleExponent <= HALF_EXPONENT_MAX && (!(uSingleSignificand & uDroppedSingleBits))) { + // Normal number in exponent range and precision won't be lost + result.uSize = IEEE754_UNION_IS_HALF; + result.uValue = IEEE754_FloatToHalf(f); + } else { + // Subnormal, exponent out of range, or precision will be lost + result.uSize = IEEE754_UNION_IS_SINGLE; + result.uValue = uSingle; + } + + return result; +} + +// Public function; see ieee754.h +IEEE754_union IEEE754_DoubleToSmallestInternal(double d, int bAllowHalfPrecision) +{ + IEEE754_union result; + + // Pull the needed two parts out of the double-precision float + const uint64_t uDouble = CopyDoubleToUint64(d); + const int64_t nDoubleExponent = ((uDouble & DOUBLE_EXPONENT_MASK) >> DOUBLE_EXPONENT_SHIFT) - DOUBLE_EXPONENT_BIAS; + const uint64_t uDoubleSignificand = uDouble & DOUBLE_SIGNIFICAND_MASK; + + // Masks to check whether dropped significand bits are zero or not + const uint64_t uDroppedDoubleBits = DOUBLE_SIGNIFICAND_MASK >> HALF_NUM_SIGNIFICAND_BITS; + const uint64_t uDroppedSingleBits = DOUBLE_SIGNIFICAND_MASK >> SINGLE_NUM_SIGNIFICAND_BITS; + + // The various cases + if(d == 0.0) { // Take care of positive and negative zero + // Value is 0.0000, not a a subnormal + result.uSize = IEEE754_UNION_IS_HALF; + result.uValue = IEEE754_DoubleToHalf(d); + } else if(nDoubleExponent == DOUBLE_EXPONENT_INF_OR_NAN) { + // NaN, +/- infinity + result.uSize = IEEE754_UNION_IS_HALF; + result.uValue = IEEE754_DoubleToHalf(d); + } else if(bAllowHalfPrecision && (nDoubleExponent >= HALF_EXPONENT_MIN) && nDoubleExponent <= HALF_EXPONENT_MAX && (!(uDoubleSignificand & uDroppedDoubleBits))) { + // Can convert to half without precision loss + result.uSize = IEEE754_UNION_IS_HALF; + result.uValue = IEEE754_DoubleToHalf(d); + } else if((nDoubleExponent >= SINGLE_EXPONENT_MIN) && nDoubleExponent <= SINGLE_EXPONENT_MAX && (!(uDoubleSignificand & uDroppedSingleBits))) { + // Can convert to single without precision loss + result.uSize = IEEE754_UNION_IS_SINGLE; + result.uValue = CopyFloatToUint32((float)d); + } else { + // Can't convert without precision loss + result.uSize = IEEE754_UNION_IS_DOUBLE; + result.uValue = uDouble; + } + + return result; +} + diff --git a/middleware/qcbor/src/ieee754.h b/middleware/qcbor/src/ieee754.h new file mode 100644 index 000000000..2530f984e --- /dev/null +++ b/middleware/qcbor/src/ieee754.h @@ -0,0 +1,168 @@ +/*============================================================================== + ieee754.c -- floating point conversion between half, double and single precision + + Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved. + + SPDX-License-Identifier: BSD-3-Clause + + See BSD-3-Clause license in README.md + + Created on 7/23/18 + ==============================================================================*/ + +#ifndef ieee754_h +#define ieee754_h + +#include + + + +/* + General comments + + This is a complete in that it handles all conversion cases + including +/- infinity, +/- zero, subnormal numbers, qNaN, sNaN + and NaN payloads. + + This confirms to IEEE 754-2008, but note that this doesn't + specify conversions, just the encodings. + + NaN payloads are preserved with alignment on the LSB. The + qNaN bit is handled differently and explicity copied. It + is always the MSB of the significand. The NaN payload MSBs + (except the qNaN bit) are truncated when going from + double or single to half. + + TODO: what does the C cast do with NaN payloads from + double to single? + + + + */ + +/* + Most simply just explicilty encode the type you want, single or double. + This works easily everywhere since standard C supports both + these types and so does qcbor. This encoder also supports + half precision and there's a few ways to use it to encode + floating point numbers in less space. + + Without losing precision, you can encode a single or double + such that the special values of 0, NaN and Infinity encode + as half-precision. This CBOR decodoer and most others + should handle this properly. + + If you don't mind losing precision, then you can use half-precision. + One way to do this is to set up your environment to use + ___fp_16. Some compilers and CPUs support it even though it is not + standard C. What is nice about this is that your program + will use less memory and floating point operations like + multiplying, adding and such will be faster. + + Another way to make use of half-precision is to represent + the values in your program as single or double, but encode + them in CBOR as half-precision. This cuts the size + of the encoded messages by 2 or 4, but doesn't reduce + memory needs or speed because you are still using + single or double in your code. + + + encode: + - float as float + - double as double + - half as half + - float as half_precision, for environments that don't support a half-precision type + - double as half_precision, for environments that don't support a half-precision type + - float with NaN, Infinity and 0 as half + - double with NaN, Infinity and 0 as half + + + + + */ + + + +/* + Convert single precision float to half-precision float. + Precision and NaN payload bits will be lost. Too large + values will round up to infinity and too small to zero. + */ +uint16_t IEEE754_FloatToHalf(float f); + + +/* + Convert half precision float to single precision float. + This is a loss-less conversion. + */ +float IEEE754_HalfToFloat(uint16_t uHalfPrecision); + + +/* + Convert double precision float to half-precision float. + Precision and NaN payload bits will be lost. Too large + values will round up to infinity and too small to zero. + */ +uint16_t IEEE754_DoubleToHalf(double d); + + +/* + Convert half precision float to double precision float. + This is a loss-less conversion. + */ +double IEEE754_HalfToDouble(uint16_t uHalfPrecision); + + + +// Both tags the value and gives the size +#define IEEE754_UNION_IS_HALF 2 +#define IEEE754_UNION_IS_SINGLE 4 +#define IEEE754_UNION_IS_DOUBLE 8 + +typedef struct { + uint8_t uSize; // One of IEEE754_IS_xxxx + uint64_t uValue; +} IEEE754_union; + + +/* + Converts double-precision to single-precision or half-precision if possible without + loss of precisions. If not, leaves it as a double. Only converts to single-precision + unless bAllowHalfPrecision is set. + */ +IEEE754_union IEEE754_DoubleToSmallestInternal(double d, int bAllowHalfPrecision); + +/* + Converts double-precision to single-precision if possible without + loss of precision. If not, leaves it as a double. + */ +static inline IEEE754_union IEEE754_DoubleToSmall(double d) +{ + return IEEE754_DoubleToSmallestInternal(d, 0); +} + + +/* + Converts double-precision to single-precision or half-precision if possible without + loss of precisions. If not, leaves it as a double. + */ +static inline IEEE754_union IEEE754_DoubleToSmallest(double d) +{ + return IEEE754_DoubleToSmallestInternal(d, 1); +} + +/* + Converts single-precision to half-precision if possible without + loss of precision. If not leaves as single-precision. + */ +IEEE754_union IEEE754_FloatToSmallest(float f); + + +#endif /* ieee754_h */ + + + + + + + diff --git a/middleware/qcbor/src/qcbor_decode.c b/middleware/qcbor/src/qcbor_decode.c new file mode 100644 index 000000000..22860382d --- /dev/null +++ b/middleware/qcbor/src/qcbor_decode.c @@ -0,0 +1,1319 @@ +/*============================================================================== + Copyright (c) 2016-2018, The Linux Foundation. + Copyright (c) 2018-2019, Laurence Lundblade. + All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of The Linux Foundation nor the names of its + contributors, nor the name "Laurence Lundblade" may be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ==============================================================================*/ + +/*=================================================================================== + FILE: qcbor_decode.c + + DESCRIPTION: This file contains the implementation of QCBOR. + + EDIT HISTORY FOR FILE: + + This section contains comments describing changes made to the module. + Notice that changes are listed in reverse chronological order. + + when who what, where, why + -------- ---- --------------------------------------------------- + 01/10/19 llundblade Clever type and argument decoder is 250 bytes smaller + 11/9/18 llundblade Error codes are now enums. + 11/2/18 llundblade Simplify float decoding and align with preferred + float encoding + 10/31/18 llundblade Switch to one license that is almost BSD-3. + 10/28/18 llundblade Reworked tag decoding + 10/15/18 llundblade Indefinite length maps and arrays supported + 10/8/18 llundblade Indefinite length strings supported + 02/04/17 llundbla Work on CPUs that don's require pointer alignment + by making use of changes in UsefulBuf + 03/01/17 llundbla More data types; decoding improvements and fixes + 11/13/16 llundbla Integrate most TZ changes back into github version. + 09/30/16 gkanike Porting to TZ. + 03/15/16 llundbla Initial Version. + + =====================================================================================*/ + +#include "qcbor.h" +#include "ieee754.h" + + +/* + This casts away the const-ness of a pointer, usually so it can be + freed or realloced. + */ +#define UNCONST_POINTER(ptr) ((void *)(ptr)) + + +/* + Collection of functions to track the map/array nesting for decoding + */ + +inline static int IsMapOrArray(uint8_t uDataType) +{ + return uDataType == QCBOR_TYPE_MAP || uDataType == QCBOR_TYPE_ARRAY; +} + +inline static int DecodeNesting_IsNested(const QCBORDecodeNesting *pNesting) +{ + return pNesting->pCurrent != &(pNesting->pMapsAndArrays[0]); +} + +inline static int DecodeNesting_IsIndefiniteLength(const QCBORDecodeNesting *pNesting) +{ + return pNesting->pCurrent->uCount == UINT16_MAX; +} + +inline static uint8_t DecodeNesting_GetLevel(QCBORDecodeNesting *pNesting) +{ + return pNesting->pCurrent - &(pNesting->pMapsAndArrays[0]); +} + +inline static int DecodeNesting_TypeIsMap(const QCBORDecodeNesting *pNesting) +{ + if(!DecodeNesting_IsNested(pNesting)) { + return 0; + } + + return CBOR_MAJOR_TYPE_MAP == pNesting->pCurrent->uMajorType; +} + +// Process a break. This will either ascend the nesting or error out +inline static QCBORError DecodeNesting_BreakAscend(QCBORDecodeNesting *pNesting) +{ + // breaks must always occur when there is nesting + if(!DecodeNesting_IsNested(pNesting)) { + return QCBOR_ERR_BAD_BREAK; + } + + // breaks can only occur when the map/array is indefinite length + if(!DecodeNesting_IsIndefiniteLength(pNesting)) { + return QCBOR_ERR_BAD_BREAK; + } + + // if all OK, the break reduces the level of nesting + pNesting->pCurrent--; + + return QCBOR_SUCCESS; +} + +// Called on every single item except breaks including the opening of a map/array +inline static void DecodeNesting_DecrementCount(QCBORDecodeNesting *pNesting) +{ + if(!DecodeNesting_IsNested(pNesting)) { + // at top level where there is no tracking + return; + } + + if(DecodeNesting_IsIndefiniteLength(pNesting)) { + // There is no count for indefinite length arrays/maps + return; + } + + // Decrement the count of items in this array/map + pNesting->pCurrent->uCount--; + + // Pop up nesting levels if the counts at the levels are zero + while(DecodeNesting_IsNested(pNesting) && 0 == pNesting->pCurrent->uCount) { + pNesting->pCurrent--; + if(!DecodeNesting_IsIndefiniteLength(pNesting)) { + pNesting->pCurrent->uCount--; + } + } +} + +// Called on every map/array +inline static QCBORError DecodeNesting_Descend(QCBORDecodeNesting *pNesting, QCBORItem *pItem) +{ + QCBORError nReturn = QCBOR_SUCCESS; + + if(pItem->val.uCount == 0) { + // Nothing to do for empty definite lenth arrays. They are just are + // effectively the same as an item that is not a map or array + goto Done; + // Empty indefinite length maps and arrays are handled elsewhere + } + + // Error out if arrays is too long to handle + if(pItem->val.uCount != UINT16_MAX && pItem->val.uCount > QCBOR_MAX_ITEMS_IN_ARRAY) { + nReturn = QCBOR_ERR_ARRAY_TOO_LONG; + goto Done; + } + + // Error out if nesting is too deep + if(pNesting->pCurrent >= &(pNesting->pMapsAndArrays[QCBOR_MAX_ARRAY_NESTING])) { + nReturn = QCBOR_ERR_ARRAY_NESTING_TOO_DEEP; + goto Done; + } + + // The actual descend + pNesting->pCurrent++; + + // Record a few details for this nesting level + pNesting->pCurrent->uMajorType = pItem->uDataType; + pNesting->pCurrent->uCount = pItem->val.uCount; + +Done: + return nReturn;; +} + +inline static void DecodeNesting_Init(QCBORDecodeNesting *pNesting) +{ + pNesting->pCurrent = &(pNesting->pMapsAndArrays[0]); +} + + + +/* + This list of built-in tags. Only add tags here that are + clearly established and useful. Once a tag is added here + it can't be taken out as that would break backwards compatibility. + There are only 48 slots available forever. + */ +static const uint16_t spBuiltInTagMap[] = { + CBOR_TAG_DATE_STRING, // See TAG_MAPPER_FIRST_FOUR + CBOR_TAG_DATE_EPOCH, // See TAG_MAPPER_FIRST_FOUR + CBOR_TAG_POS_BIGNUM, // See TAG_MAPPER_FIRST_FOUR + CBOR_TAG_NEG_BIGNUM, // See TAG_MAPPER_FIRST_FOUR + CBOR_TAG_FRACTION, + CBOR_TAG_BIGFLOAT, + CBOR_TAG_COSE_ENCRYPTO, + CBOR_TAG_COSE_MAC0, + CBOR_TAG_COSE_SIGN1, + CBOR_TAG_ENC_AS_B64URL, + CBOR_TAG_ENC_AS_B64, + CBOR_TAG_ENC_AS_B16, + CBOR_TAG_CBOR, + CBOR_TAG_URI, + CBOR_TAG_B64URL, + CBOR_TAG_B64, + CBOR_TAG_REGEX, + CBOR_TAG_MIME, + CBOR_TAG_BIN_UUID, + CBOR_TAG_CWT, + CBOR_TAG_ENCRYPT, + CBOR_TAG_MAC, + CBOR_TAG_SIGN, + CBOR_TAG_GEO_COORD, + CBOR_TAG_CBOR_MAGIC +}; + +// This is used in a bit of cleverness in GetNext_TaggedItem() to +// keep code size down and switch for the internal processing of +// these types. This will break if the first four items in +// spBuiltInTagMap don't have values 0,1,2,3. That is the +// mapping is 0 to 0, 1 to 1, 2 to 2 and 3 to 3. +#define QCBOR_TAGFLAG_DATE_STRING (0x01LL << CBOR_TAG_DATE_STRING) +#define QCBOR_TAGFLAG_DATE_EPOCH (0x01LL << CBOR_TAG_DATE_EPOCH) +#define QCBOR_TAGFLAG_POS_BIGNUM (0x01LL << CBOR_TAG_POS_BIGNUM) +#define QCBOR_TAGFLAG_NEG_BIGNUM (0x01LL << CBOR_TAG_NEG_BIGNUM) + +#define TAG_MAPPER_FIRST_FOUR (QCBOR_TAGFLAG_DATE_STRING |\ + QCBOR_TAGFLAG_DATE_EPOCH |\ + QCBOR_TAGFLAG_POS_BIGNUM |\ + QCBOR_TAGFLAG_NEG_BIGNUM) + +#define TAG_MAPPER_TOTAL_TAG_BITS 64 // Number of bits in a uint64_t +#define TAG_MAPPER_CUSTOM_TAGS_BASE_INDEX (TAG_MAPPER_TOTAL_TAG_BITS - QCBOR_MAX_CUSTOM_TAGS) // 48 +#define TAG_MAPPER_MAX_SIZE_BUILT_IN_TAGS (TAG_MAPPER_TOTAL_TAG_BITS - QCBOR_MAX_CUSTOM_TAGS ) // 48 + +static inline int TagMapper_LookupBuiltIn(uint64_t uTag) +{ + if(sizeof(spBuiltInTagMap)/sizeof(uint16_t) > TAG_MAPPER_MAX_SIZE_BUILT_IN_TAGS) { + // This is a cross-check to make sure the above array doesn't + // accidentally get made too big. + // In normal conditions the above test should optimize out + // as all the values are known at compile time. + return -1; + } + + if(uTag > UINT16_MAX) { + // This tag map works only on 16-bit tags + return -1; + } + + for(int nTagBitIndex = 0; nTagBitIndex < (int)(sizeof(spBuiltInTagMap)/sizeof(uint16_t)); nTagBitIndex++) { + if(spBuiltInTagMap[nTagBitIndex] == uTag) { + return nTagBitIndex; + } + } + return -1; // Indicates no match +} + +static inline int TagMapper_LookupCallerConfigured(const QCBORTagListIn *pCallerConfiguredTagMap, uint64_t uTag) +{ + for(int nTagBitIndex = 0; nTagBitIndex < pCallerConfiguredTagMap->uNumTags; nTagBitIndex++) { + if(pCallerConfiguredTagMap->puTags[nTagBitIndex] == uTag) { + return nTagBitIndex + TAG_MAPPER_CUSTOM_TAGS_BASE_INDEX; + } + } + + return -1; // Indicates no match +} + +/* + Find the tag bit index for a given tag value, or error out + + This and the above functions could probably be optimized and made + clearer and neater. + */ +static QCBORError TagMapper_Lookup(const QCBORTagListIn *pCallerConfiguredTagMap, uint64_t uTag, uint8_t *puTagBitIndex) +{ + int nTagBitIndex = TagMapper_LookupBuiltIn(uTag); + if(nTagBitIndex >= 0) { + // Cast is safe because TagMapper_LookupBuiltIn never returns > 47 + *puTagBitIndex = (uint8_t)nTagBitIndex; + return QCBOR_SUCCESS; + } + + if(pCallerConfiguredTagMap) { + if(pCallerConfiguredTagMap->uNumTags > QCBOR_MAX_CUSTOM_TAGS) { + return QCBOR_ERR_TOO_MANY_TAGS; + } + nTagBitIndex = TagMapper_LookupCallerConfigured(pCallerConfiguredTagMap, uTag); + if(nTagBitIndex >= 0) { + // Cast is safe because TagMapper_LookupBuiltIn never returns > 63 + + *puTagBitIndex = (uint8_t)nTagBitIndex; + return QCBOR_SUCCESS; + } + } + + return QCBOR_ERR_BAD_OPT_TAG; +} + + + + +/* + Public function, see header file + */ +void QCBORDecode_Init(QCBORDecodeContext *me, UsefulBufC EncodedCBOR, QCBORDecodeMode nDecodeMode) +{ + memset(me, 0, sizeof(QCBORDecodeContext)); + UsefulInputBuf_Init(&(me->InBuf), EncodedCBOR); + // Don't bother with error check on decode mode. If a bad value is passed it will just act as + // if the default normal mode of 0 was set. + me->uDecodeMode = nDecodeMode; + DecodeNesting_Init(&(me->nesting)); +} + + +/* + Public function, see header file + */ +void QCBORDecode_SetUpAllocator(QCBORDecodeContext *pCtx, const QCBORStringAllocator *pAllocator, bool bAllocAll) +{ + pCtx->pStringAllocator = (void *)pAllocator; + pCtx->bStringAllocateAll = bAllocAll; +} + +void QCBORDecode_SetCallerConfiguredTagList(QCBORDecodeContext *me, const QCBORTagListIn *pTagList) +{ + me->pCallerConfiguredTagList = pTagList; +} + + +/* + This decodes the fundamental part of a CBOR data item, the type and number + + This is the Counterpart to InsertEncodedTypeAndNumber(). + + This does the network->host byte order conversion. The conversion here + also results in the conversion for floats in addition to that for + lengths, tags and integer values. + + This returns: + pnMajorType -- the major type for the item + puNumber -- the "number" which is used a the value for integers, tags and floats and length for strings and arrays + puAdditionalInfo -- Pass this along to know what kind of float or if length is indefinite + + */ +inline static QCBORError DecodeTypeAndNumber(UsefulInputBuf *pUInBuf, + int *pnMajorType, + uint64_t *puArgument, + uint8_t *puAdditionalInfo) +{ + QCBORError nReturn; + + // Get the initial byte that every CBOR data item has + const uint8_t uInitialByte = UsefulInputBuf_GetByte(pUInBuf); + + // Break down the initial byte + const uint8_t uTmpMajorType = uInitialByte >> 5; + const uint8_t uAdditionalInfo = uInitialByte & 0x1f; + + // Where the number or argument accumulates + uint64_t uArgument; + + if(uAdditionalInfo >= LEN_IS_ONE_BYTE && uAdditionalInfo <= LEN_IS_EIGHT_BYTES) { + // Need to get 1,2,4 or 8 additional argument bytes + // Map LEN_IS_ONE_BYTE.. LEN_IS_EIGHT_BYTES to actual length + static const uint8_t aIterate[] = {1,2,4,8}; + + // Loop getting all the bytes in the argument + uArgument = 0; + for(int i = aIterate[uAdditionalInfo - LEN_IS_ONE_BYTE]; i; i--) { + // This shift and add gives the endian conversion + uArgument = (uArgument << 8) + UsefulInputBuf_GetByte(pUInBuf); + } + } else if(uAdditionalInfo >= ADDINFO_RESERVED1 && uAdditionalInfo <= ADDINFO_RESERVED3) { + // The reserved and thus-far unused additional info values + nReturn = QCBOR_ERR_UNSUPPORTED; + goto Done; + } else { + // Less than 24, additional info is argument or 31, an indefinite length + // No more bytes to get + uArgument = uAdditionalInfo; + } + + if(UsefulInputBuf_GetError(pUInBuf)) { + nReturn = QCBOR_ERR_HIT_END; + goto Done; + } + + // All successful if we got here. + nReturn = QCBOR_SUCCESS; + *pnMajorType = uTmpMajorType; + *puArgument = uArgument; + *puAdditionalInfo = uAdditionalInfo; + +Done: + return nReturn; +} + +/* + CBOR doesn't explicitly specify two's compliment for integers but all CPUs + use it these days and the test vectors in the RFC are so. All integers in the CBOR + structure are positive and the major type indicates positive or negative. + CBOR can express positive integers up to 2^x - 1 where x is the number of bits + and negative integers down to 2^x. Note that negative numbers can be one + more away from zero than positive. + Stdint, as far as I can tell, uses two's compliment to represent + negative integers. + + See http://www.unix.org/whitepapers/64bit.html for reasons int isn't + used here in any way including in the interface + */ +inline static QCBORError DecodeInteger(int nMajorType, uint64_t uNumber, QCBORItem *pDecodedItem) +{ + // Stack usage: int/ptr 1 -- 8 + QCBORError nReturn = QCBOR_SUCCESS; + + if(nMajorType == CBOR_MAJOR_TYPE_POSITIVE_INT) { + if (uNumber <= INT64_MAX) { + pDecodedItem->val.int64 = (int64_t)uNumber; + pDecodedItem->uDataType = QCBOR_TYPE_INT64; + + } else { + pDecodedItem->val.uint64 = uNumber; + pDecodedItem->uDataType = QCBOR_TYPE_UINT64; + + } + } else { + if(uNumber <= INT64_MAX) { + pDecodedItem->val.int64 = -uNumber-1; + pDecodedItem->uDataType = QCBOR_TYPE_INT64; + + } else { + // C can't represent a negative integer in this range + // so it is an error. todo -- test this condition + nReturn = QCBOR_ERR_INT_OVERFLOW; + } + } + + return nReturn; +} + +// Make sure #define value line up as DecodeSimple counts on this. +#if QCBOR_TYPE_FALSE != CBOR_SIMPLEV_FALSE +#error QCBOR_TYPE_FALSE macro value wrong +#endif + +#if QCBOR_TYPE_TRUE != CBOR_SIMPLEV_TRUE +#error QCBOR_TYPE_TRUE macro value wrong +#endif + +#if QCBOR_TYPE_NULL != CBOR_SIMPLEV_NULL +#error QCBOR_TYPE_NULL macro value wrong +#endif + +#if QCBOR_TYPE_UNDEF != CBOR_SIMPLEV_UNDEF +#error QCBOR_TYPE_UNDEF macro value wrong +#endif + +#if QCBOR_TYPE_BREAK != CBOR_SIMPLE_BREAK +#error QCBOR_TYPE_BREAK macro value wrong +#endif + +#if QCBOR_TYPE_DOUBLE != DOUBLE_PREC_FLOAT +#error QCBOR_TYPE_DOUBLE macro value wrong +#endif + +#if QCBOR_TYPE_FLOAT != SINGLE_PREC_FLOAT +#error QCBOR_TYPE_FLOAT macro value wrong +#endif + +/* + Decode true, false, floats, break... + */ + +inline static QCBORError DecodeSimple(uint8_t uAdditionalInfo, uint64_t uNumber, QCBORItem *pDecodedItem) +{ + // Stack usage: 0 + QCBORError nReturn = QCBOR_SUCCESS; + + // uAdditionalInfo is 5 bits from the initial byte + // compile time checks above make sure uAdditionalInfo values line up with uDataType values + pDecodedItem->uDataType = uAdditionalInfo; + + switch(uAdditionalInfo) { + case ADDINFO_RESERVED1: // 28 + case ADDINFO_RESERVED2: // 29 + case ADDINFO_RESERVED3: // 30 + nReturn = QCBOR_ERR_UNSUPPORTED; + break; + + case HALF_PREC_FLOAT: + pDecodedItem->val.dfnum = IEEE754_HalfToDouble((uint16_t)uNumber); + pDecodedItem->uDataType = QCBOR_TYPE_DOUBLE; + break; + case SINGLE_PREC_FLOAT: + pDecodedItem->val.dfnum = (double)UsefulBufUtil_CopyUint32ToFloat((uint32_t)uNumber); + pDecodedItem->uDataType = QCBOR_TYPE_DOUBLE; + break; + case DOUBLE_PREC_FLOAT: + pDecodedItem->val.dfnum = UsefulBufUtil_CopyUint64ToDouble(uNumber); + pDecodedItem->uDataType = QCBOR_TYPE_DOUBLE; + break; + + case CBOR_SIMPLEV_FALSE: // 20 + case CBOR_SIMPLEV_TRUE: // 21 + case CBOR_SIMPLEV_NULL: // 22 + case CBOR_SIMPLEV_UNDEF: // 23 + case CBOR_SIMPLE_BREAK: // 31 + break; // nothing to do + + case CBOR_SIMPLEV_ONEBYTE: // 24 + if(uNumber <= CBOR_SIMPLE_BREAK) { + // This takes out f8 00 ... f8 1f which should be encoded as e0 … f7 + nReturn = QCBOR_ERR_INVALID_CBOR; + goto Done; + } + /* FALLTHROUGH */ + // fall through intentionally + + default: // 0-19 + pDecodedItem->uDataType = QCBOR_TYPE_UKNOWN_SIMPLE; + // DecodeTypeAndNumber will make uNumber equal to uAdditionalInfo when uAdditionalInfo is < 24 + // This cast is safe because the 2, 4 and 8 byte lengths of uNumber are in the double/float cases above + pDecodedItem->val.uSimple = (uint8_t)uNumber; + break; + } + +Done: + return nReturn; +} + + + +/* + Decode text and byte strings. Call the string allocator if asked to. + */ +inline static QCBORError DecodeBytes(const QCBORStringAllocator *pAlloc, int nMajorType, uint64_t uStrLen, UsefulInputBuf *pUInBuf, QCBORItem *pDecodedItem) +{ + // Stack usage: UsefulBuf 2, int/ptr 1 40 + QCBORError nReturn = QCBOR_SUCCESS; + + const UsefulBufC Bytes = UsefulInputBuf_GetUsefulBuf(pUInBuf, uStrLen); + if(UsefulBuf_IsNULLC(Bytes)) { + // Failed to get the bytes for this string item + nReturn = QCBOR_ERR_HIT_END; + goto Done; + } + + if(pAlloc) { + // We are asked to use string allocator to make a copy + UsefulBuf NewMem = pAlloc->fAllocate(pAlloc->pAllocaterContext, NULL, uStrLen); + if(UsefulBuf_IsNULL(NewMem)) { + nReturn = QCBOR_ERR_STRING_ALLOCATE; + goto Done; + } + pDecodedItem->val.string = UsefulBuf_Copy(NewMem, Bytes); + } else { + // Normal case with no string allocator + pDecodedItem->val.string = Bytes; + } + pDecodedItem->uDataType = (nMajorType == CBOR_MAJOR_TYPE_BYTE_STRING) ? QCBOR_TYPE_BYTE_STRING : QCBOR_TYPE_TEXT_STRING; + +Done: + return nReturn; +} + + +/* + Mostly just assign the right data type for the date string. + */ +inline static QCBORError DecodeDateString(QCBORItem *pDecodedItem) +{ + // Stack Use: UsefulBuf 1 16 + if(pDecodedItem->uDataType != QCBOR_TYPE_TEXT_STRING) { + return QCBOR_ERR_BAD_OPT_TAG; + } + + const UsefulBufC Temp = pDecodedItem->val.string; + pDecodedItem->val.dateString = Temp; + pDecodedItem->uDataType = QCBOR_TYPE_DATE_STRING; + return QCBOR_SUCCESS; +} + + +/* + Mostly just assign the right data type for the bignum. + */ +inline static QCBORError DecodeBigNum(QCBORItem *pDecodedItem) +{ + // Stack Use: UsefulBuf 1 -- 16 + if(pDecodedItem->uDataType != QCBOR_TYPE_BYTE_STRING) { + return QCBOR_ERR_BAD_OPT_TAG; + } + const UsefulBufC Temp = pDecodedItem->val.string; + pDecodedItem->val.bigNum = Temp; + pDecodedItem->uDataType = pDecodedItem->uTagBits & QCBOR_TAGFLAG_POS_BIGNUM ? QCBOR_TYPE_POSBIGNUM : QCBOR_TYPE_NEGBIGNUM; + return QCBOR_SUCCESS; +} + + +/* + The epoch formatted date. Turns lots of different forms of encoding date into uniform one + */ +static int DecodeDateEpoch(QCBORItem *pDecodedItem) +{ + // Stack usage: 1 + QCBORError nReturn = QCBOR_SUCCESS; + + pDecodedItem->val.epochDate.fSecondsFraction = 0; + + switch (pDecodedItem->uDataType) { + + case QCBOR_TYPE_INT64: + pDecodedItem->val.epochDate.nSeconds = pDecodedItem->val.int64; + break; + + case QCBOR_TYPE_UINT64: + if(pDecodedItem->val.uint64 > INT64_MAX) { + nReturn = QCBOR_ERR_DATE_OVERFLOW; + goto Done; + } + pDecodedItem->val.epochDate.nSeconds = pDecodedItem->val.uint64; + break; + + case QCBOR_TYPE_DOUBLE: + { + const double d = pDecodedItem->val.dfnum; + if(d > INT64_MAX) { + nReturn = QCBOR_ERR_DATE_OVERFLOW; + goto Done; + } + pDecodedItem->val.epochDate.nSeconds = d; // Float to integer conversion happening here. + pDecodedItem->val.epochDate.fSecondsFraction = d - pDecodedItem->val.epochDate.nSeconds; + } + break; + + default: + nReturn = QCBOR_ERR_BAD_OPT_TAG; + goto Done; + } + pDecodedItem->uDataType = QCBOR_TYPE_DATE_EPOCH; + +Done: + return nReturn; +} + + + + +// Make sure the constants align as this is assumed by the GetAnItem() implementation +#if QCBOR_TYPE_ARRAY != CBOR_MAJOR_TYPE_ARRAY +#error QCBOR_TYPE_ARRAY value not lined up with major type +#endif +#if QCBOR_TYPE_MAP != CBOR_MAJOR_TYPE_MAP +#error QCBOR_TYPE_MAP value not lined up with major type +#endif + +/* + This gets a single data item and decodes it including preceding optional tagging. This does not + deal with arrays and maps and nesting except to decode the data item introducing them. Arrays and + maps are handled at the next level up in GetNext(). + + Errors detected here include: an array that is too long to decode, hit end of buffer unexpectedly, + a few forms of invalid encoded CBOR + */ +static QCBORError GetNext_Item(UsefulInputBuf *pUInBuf, QCBORItem *pDecodedItem, const QCBORStringAllocator *pAlloc) +{ + // Stack usage: int/ptr 3 -- 24 + QCBORError nReturn; + + // Get the major type and the number. Number could be length of more bytes or the value depending on the major type + // nAdditionalInfo is an encoding of the length of the uNumber and is needed to decode floats and doubles + int uMajorType; + uint64_t uNumber; + uint8_t uAdditionalInfo; + + nReturn = DecodeTypeAndNumber(pUInBuf, &uMajorType, &uNumber, &uAdditionalInfo); + + // Error out here if we got into trouble on the type and number. + // The code after this will not work if the type and number is not good. + if(nReturn) + goto Done; + + memset(pDecodedItem, 0, sizeof(QCBORItem)); + + // At this point the major type and the value are valid. We've got the type and the number that + // starts every CBOR data item. + switch (uMajorType) { + case CBOR_MAJOR_TYPE_POSITIVE_INT: // Major type 0 + case CBOR_MAJOR_TYPE_NEGATIVE_INT: // Major type 1 + nReturn = DecodeInteger(uMajorType, uNumber, pDecodedItem); + break; + + case CBOR_MAJOR_TYPE_BYTE_STRING: // Major type 2 + case CBOR_MAJOR_TYPE_TEXT_STRING: // Major type 3 + if(uAdditionalInfo == LEN_IS_INDEFINITE) { + pDecodedItem->uDataType = (uMajorType == CBOR_MAJOR_TYPE_BYTE_STRING) ? QCBOR_TYPE_BYTE_STRING : QCBOR_TYPE_TEXT_STRING; + pDecodedItem->val.string = (UsefulBufC){NULL, SIZE_MAX}; + } else { + nReturn = DecodeBytes(pAlloc, uMajorType, uNumber, pUInBuf, pDecodedItem); + } + break; + + case CBOR_MAJOR_TYPE_ARRAY: // Major type 4 + case CBOR_MAJOR_TYPE_MAP: // Major type 5 + // Record the number of items in the array or map + if(uNumber > QCBOR_MAX_ITEMS_IN_ARRAY) { + nReturn = QCBOR_ERR_ARRAY_TOO_LONG; + goto Done; + } + if(uAdditionalInfo == LEN_IS_INDEFINITE) { + pDecodedItem->val.uCount = UINT16_MAX; // Indicate indefinite length + } else { + pDecodedItem->val.uCount = (uint16_t)uNumber; // type conversion OK because of check above + } + pDecodedItem->uDataType = uMajorType; // C preproc #if above makes sure constants align + break; + + case CBOR_MAJOR_TYPE_OPTIONAL: // Major type 6, optional prepended tags + pDecodedItem->val.uTagV = uNumber; + pDecodedItem->uDataType = QCBOR_TYPE_OPTTAG; + break; + + case CBOR_MAJOR_TYPE_SIMPLE: // Major type 7, float, double, true, false, null... + nReturn = DecodeSimple(uAdditionalInfo, uNumber, pDecodedItem); + break; + + default: // Should never happen because DecodeTypeAndNumber() should never return > 7 + nReturn = QCBOR_ERR_UNSUPPORTED; + break; + } + +Done: + return nReturn; +} + + + +/* + This layer deals with indefinite length strings. It pulls all the + individual chunk items together into one QCBORItem using the + string allocator. + + Code Reviewers: THIS FUNCTION DOES A LITTLE POINTER MATH + */ +static inline QCBORError GetNext_FullItem(QCBORDecodeContext *me, QCBORItem *pDecodedItem) +{ + // Stack usage; int/ptr 2 UsefulBuf 2 QCBORItem -- 96 + QCBORError nReturn; + QCBORStringAllocator *pAlloc = (QCBORStringAllocator *)me->pStringAllocator; + UsefulBufC FullString = NULLUsefulBufC; + + nReturn = GetNext_Item(&(me->InBuf), pDecodedItem, me->bStringAllocateAll ? pAlloc: NULL); + if(nReturn) { + goto Done; + } + + // To reduce code size by removing support for indefinite length strings, the + // code in this function from here down can be eliminated. Run tests, except + // indefinite length string tests, to be sure all is OK if this is removed. + + // Only do indefinite length processing on strings + if(pDecodedItem->uDataType != QCBOR_TYPE_BYTE_STRING && pDecodedItem->uDataType != QCBOR_TYPE_TEXT_STRING) { + goto Done; // no need to do any work here on non-string types + } + + // Is this a string with an indefinite length? + if(pDecodedItem->val.string.len != SIZE_MAX) { + goto Done; // length is not indefinite, so no work to do here + } + + // Can't do indefinite length strings without a string allocator + if(pAlloc == NULL) { + nReturn = QCBOR_ERR_NO_STRING_ALLOCATOR; + goto Done; + } + + // There is an indefinite length string to work on... + // Track which type of string it is + const uint8_t uStringType = pDecodedItem->uDataType; + + // Loop getting chunk of indefinite string + for(;;) { + // Get item for next chunk + QCBORItem StringChunkItem; + // NULL passed to never string alloc chunk of indefinite length strings + nReturn = GetNext_Item(&(me->InBuf), &StringChunkItem, NULL); + if(nReturn) { + break; // Error getting the next chunk + } + + // See if it is a marker at end of indefinite length string + if(StringChunkItem.uDataType == QCBOR_TYPE_BREAK) { + // String is complete + pDecodedItem->val.string = FullString; + pDecodedItem->uDataAlloc = 1; + break; + } + + // Match data type of chunk to type at beginning. + // Also catches error of other non-string types that don't belong. + if(StringChunkItem.uDataType != uStringType) { + nReturn = QCBOR_ERR_INDEFINITE_STRING_CHUNK; + break; + } + + // Alloc new buffer or expand previously allocated buffer so it can fit + UsefulBuf NewMem = (*pAlloc->fAllocate)(pAlloc->pAllocaterContext, + UNCONST_POINTER(FullString.ptr), + FullString.len + StringChunkItem.val.string.len); + if(UsefulBuf_IsNULL(NewMem)) { + // Allocation of memory for the string failed + nReturn = QCBOR_ERR_STRING_ALLOCATE; + break; + } + + // Copy new string chunk at the end of string so far. + FullString = UsefulBuf_CopyOffset(NewMem, FullString.len, StringChunkItem.val.string); + } + +Done: + if(pAlloc && nReturn && !UsefulBuf_IsNULLC(FullString)) { + // Getting item failed, clean up the allocated memory + (pAlloc->fFree)(pAlloc->pAllocaterContext, UNCONST_POINTER(FullString.ptr)); + } + + return nReturn; +} + + +/* + Returns an error if there was something wrong with the optional item or it couldn't + be handled. + */ +static QCBORError GetNext_TaggedItem(QCBORDecodeContext *me, QCBORItem *pDecodedItem, QCBORTagListOut *pTags) +{ + // Stack usage: int/ptr: 3 -- 24 + QCBORError nReturn; + uint64_t uTagBits = 0; + if(pTags) { + pTags->uNumUsed = 0; + } + + for(;;) { + nReturn = GetNext_FullItem(me, pDecodedItem); + if(nReturn) { + goto Done; // Error out of the loop + } + + if(pDecodedItem->uDataType != QCBOR_TYPE_OPTTAG) { + // Successful exit from loop; maybe got some tags, maybe not + pDecodedItem->uTagBits = uTagBits; + break; + } + + uint8_t uTagBitIndex; + // Tag was mapped, tag was not mapped, error with tag list + switch(TagMapper_Lookup(me->pCallerConfiguredTagList, pDecodedItem->val.uTagV, &uTagBitIndex)) { + + case QCBOR_SUCCESS: + // Successfully mapped the tag + uTagBits |= 0x01ULL << uTagBitIndex; + break; + + case QCBOR_ERR_BAD_OPT_TAG: + // Tag is not recognized. Do nothing + break; + + default: + // Error Condition + goto Done; + } + + if(pTags) { + // Caller wants all tags recorded in the provided buffer + if(pTags->uNumUsed >= pTags->uNumAllocated) { + nReturn = QCBOR_ERR_TOO_MANY_TAGS; + goto Done; + } + pTags->puTags[pTags->uNumUsed] = pDecodedItem->val.uTagV; + pTags->uNumUsed++; + } + } + + switch(pDecodedItem->uTagBits & TAG_MAPPER_FIRST_FOUR) { + case 0: + // No tags at all or none we know about. Nothing to do. + // This is part of the pass-through path of this function + // that will mostly be taken when decoding any item. + break; + + case QCBOR_TAGFLAG_DATE_STRING: + nReturn = DecodeDateString(pDecodedItem); + break; + + case QCBOR_TAGFLAG_DATE_EPOCH: + nReturn = DecodeDateEpoch(pDecodedItem); + break; + + case QCBOR_TAGFLAG_POS_BIGNUM: + case QCBOR_TAGFLAG_NEG_BIGNUM: + nReturn = DecodeBigNum(pDecodedItem); + break; + + default: + // Encountering some mixed up CBOR like something that + // is tagged as both a string and integer date. + nReturn = QCBOR_ERR_BAD_OPT_TAG; + } + +Done: + return nReturn; +} + + +/* + This layer takes care of map entries. It combines the label and data items into one QCBORItem. + */ +static inline QCBORError GetNext_MapEntry(QCBORDecodeContext *me, QCBORItem *pDecodedItem, QCBORTagListOut *pTags) +{ + // Stack use: int/ptr 1, QCBORItem -- 56 + QCBORError nReturn = GetNext_TaggedItem(me, pDecodedItem, pTags); + if(nReturn) + goto Done; + + if(pDecodedItem->uDataType == QCBOR_TYPE_BREAK) { + // Break can't be a map entry + goto Done; + } + + if(me->uDecodeMode != QCBOR_DECODE_MODE_MAP_AS_ARRAY) { + // In a map and caller wants maps decoded, not treated as arrays + + if(DecodeNesting_TypeIsMap(&(me->nesting))) { + // If in a map and the right decoding mode, get the label + + // Get the next item which will be the real data; Item will be the label + QCBORItem LabelItem = *pDecodedItem; + nReturn = GetNext_TaggedItem(me, pDecodedItem, pTags); + if(nReturn) + goto Done; + + pDecodedItem->uLabelAlloc = LabelItem.uDataAlloc; + + if(LabelItem.uDataType == QCBOR_TYPE_TEXT_STRING) { + // strings are always good labels + pDecodedItem->label.string = LabelItem.val.string; + pDecodedItem->uLabelType = QCBOR_TYPE_TEXT_STRING; + } else if (QCBOR_DECODE_MODE_MAP_STRINGS_ONLY == me->uDecodeMode) { + // It's not a string and we only want strings, probably for easy translation to JSON + nReturn = QCBOR_ERR_MAP_LABEL_TYPE; + goto Done; + } else if(LabelItem.uDataType == QCBOR_TYPE_INT64) { + pDecodedItem->label.int64 = LabelItem.val.int64; + pDecodedItem->uLabelType = QCBOR_TYPE_INT64; + } else if(LabelItem.uDataType == QCBOR_TYPE_UINT64) { + pDecodedItem->label.uint64 = LabelItem.val.uint64; + pDecodedItem->uLabelType = QCBOR_TYPE_UINT64; + } else if(LabelItem.uDataType == QCBOR_TYPE_BYTE_STRING) { + pDecodedItem->label.string = LabelItem.val.string; + pDecodedItem->uLabelAlloc = LabelItem.uDataAlloc; + pDecodedItem->uLabelType = QCBOR_TYPE_BYTE_STRING; + } else { + // label is not an int or a string. It is an arrray + // or a float or such and this implementation doesn't handle that. + // Also, tags on labels are ignored. + nReturn = QCBOR_ERR_MAP_LABEL_TYPE; + goto Done; + } + } + } else { + if(pDecodedItem->uDataType == QCBOR_TYPE_MAP) { + // Decoding a map as an array + pDecodedItem->uDataType = QCBOR_TYPE_MAP_AS_ARRAY; + pDecodedItem->val.uCount *= 2; + } + } + +Done: + return nReturn; +} + + +/* + Public function, see header qcbor.h file + */ +QCBORError QCBORDecode_GetNextWithTags(QCBORDecodeContext *me, QCBORItem *pDecodedItem, QCBORTagListOut *pTags) +{ + // Stack ptr/int: 2, QCBORItem : 64 + + // The public entry point for fetching and parsing the next QCBORItem. + // All the CBOR parsing work is here and in subordinate calls. + QCBORError nReturn; + + nReturn = GetNext_MapEntry(me, pDecodedItem, pTags); + if(nReturn) { + goto Done; + } + + // Break ending arrays/maps are always processed at the end of this function. + // They should never show up here. + if(pDecodedItem->uDataType == QCBOR_TYPE_BREAK) { + nReturn = QCBOR_ERR_BAD_BREAK; + goto Done; + } + + // Record the nesting level for this data item before processing any of + // decrementing and descending. + pDecodedItem->uNestingLevel = DecodeNesting_GetLevel(&(me->nesting)); + + // Process the item just received for descent or decrement, and + // ascent if decrements are enough to close out a definite length array/map + if(IsMapOrArray(pDecodedItem->uDataType)) { + // If the new item is array or map, the nesting level descends + nReturn = DecodeNesting_Descend(&(me->nesting), pDecodedItem); + // Maps and arrays do count in as items in the map/array that encloses + // them so a decrement needs to be done for them too, but that is done + // only when all the items in them have been processed, not when they + // are opened. + } else { + // Decrement the count of items in the enclosing map/array + // If the count in the enclosing map/array goes to zero, that + // triggers a decrement in the map/array above that and + // an ascend in nesting level. + DecodeNesting_DecrementCount(&(me->nesting)); + } + if(nReturn) { + goto Done; + } + + // For indefinite length maps/arrays, looking at any and + // all breaks that might terminate them. The equivalent + // for definite length maps/arrays happens in + // DecodeNesting_DecrementCount(). + if(DecodeNesting_IsNested(&(me->nesting)) && DecodeNesting_IsIndefiniteLength(&(me->nesting))) { + while(UsefulInputBuf_BytesUnconsumed(&(me->InBuf))) { + // Peek forward one item to see if it is a break. + QCBORItem Peek; + size_t uPeek = UsefulInputBuf_Tell(&(me->InBuf)); + nReturn = GetNext_Item(&(me->InBuf), &Peek, NULL); + if(nReturn) { + goto Done; + } + if(Peek.uDataType != QCBOR_TYPE_BREAK) { + // It is not a break, rewind so it can be processed normally. + UsefulInputBuf_Seek(&(me->InBuf), uPeek); + break; + } + // It is a break. Ascend one nesting level. + // The break is consumed. + nReturn = DecodeNesting_BreakAscend(&(me->nesting)); + if(nReturn) { + // break occured outside of an indefinite length array/map + goto Done; + } + } + } + + // Tell the caller what level is next. This tells them what maps/arrays + // were closed out and makes it possible for them to reconstruct + // the tree with just the information returned by GetNext + pDecodedItem->uNextNestLevel = DecodeNesting_GetLevel(&(me->nesting)); + +Done: + return nReturn; +} + + +QCBORError QCBORDecode_GetNext(QCBORDecodeContext *me, QCBORItem *pDecodedItem) +{ + return QCBORDecode_GetNextWithTags(me, pDecodedItem, NULL); +} + + +/* + Decoding items is done in 5 layered functions, one calling the + next one down. If a layer has no work to do for a particular item + it returns quickly. + + - QCBORDecode_GetNext -- The top layer manages the beginnings and + ends of maps and arrays. It tracks descending into and ascending + out of maps/arrays. It processes all breaks that terminate + maps and arrays. + + - GetNext_MapEntry -- This handles the combining of two + items, the label and the data, that make up a map entry. + It only does work on maps. It combines the label and data + items into one labeled item. + + - GetNext_TaggedItem -- This handles the type 6 tagged items. + It accumulates all the tags and combines them with the following + non-tagged item. If the tagged item is something that is understood + like a date, the decoding of that item is invoked. + + - GetNext_FullItem -- This assembles the sub items that make up + an indefinte length string into one string item. It uses the + string allocater to create contiguous space for the item. It + processes all breaks that are part of indefinite length strings. + + - GetNext_Item -- This gets and decodes the most atomic + item in CBOR, the thing with an initial byte containing + the major type. + + Roughly this takes 300 bytes of stack for vars. Need to + evaluate this more carefully and correctly. + + */ + + +/* + Public function, see header qcbor.h file + */ +int QCBORDecode_IsTagged(QCBORDecodeContext *me, const QCBORItem *pItem, uint64_t uTag) +{ + const QCBORTagListIn *pCallerConfiguredTagMap = me->pCallerConfiguredTagList; + + uint8_t uTagBitIndex; + // Do not care about errors in pCallerConfiguredTagMap here. They are + // caught during GetNext() before this is called. + if(TagMapper_Lookup(pCallerConfiguredTagMap, uTag, &uTagBitIndex)) { + return 0; + } + + const uint64_t uTagBit = 0x01ULL << uTagBitIndex; + return (uTagBit & pItem->uTagBits) != 0; +} + + +/* + Public function, see header qcbor.h file + */ +QCBORError QCBORDecode_Finish(QCBORDecodeContext *me) +{ + int nReturn = QCBOR_SUCCESS; + + // Error out if all the maps/arrays are not closed out + if(DecodeNesting_IsNested(&(me->nesting))) { + nReturn = QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN; + goto Done; + } + + // Error out if not all the bytes are consumed + if(UsefulInputBuf_BytesUnconsumed(&(me->InBuf))) { + nReturn = QCBOR_ERR_EXTRA_BYTES; + } + +Done: + // Call the destructor for the string allocator if there is one. + // Always called, even if there are errors; always have to clean up + if(me->pStringAllocator) { + QCBORStringAllocator *pAllocator = (QCBORStringAllocator *)me->pStringAllocator; + if(pAllocator->fDestructor) { + (pAllocator->fDestructor)(pAllocator->pAllocaterContext); + } + } + + return nReturn; +} + + + +/* + +Decoder errors handled in this file + + - Hit end of input before it was expected while decoding type and number QCBOR_ERR_HIT_END + + - negative integer that is too large for C QCBOR_ERR_INT_OVERFLOW + + - Hit end of input while decoding a text or byte string QCBOR_ERR_HIT_END + + - Encountered conflicting tags -- e.g., an item is tagged both a date string and an epoch date QCBOR_ERR_UNSUPPORTED + + - Encontered an array or mapp that has too many items QCBOR_ERR_ARRAY_TOO_LONG + + - Encountered array/map nesting that is too deep QCBOR_ERR_ARRAY_NESTING_TOO_DEEP + + - An epoch date > INT64_MAX or < INT64_MIN was encountered QCBOR_ERR_DATE_OVERFLOW + + - The type of a map label is not a string or int QCBOR_ERR_MAP_LABEL_TYPE + + - Hit end with arrays or maps still open -- QCBOR_ERR_EXTRA_BYTES + + */ + + + + +/* + This is a very primitive memory allocator. It does not track individual + allocations, only a high-water mark. A free or reallotcation must be of + the last chunk allocated. + + All of this following code will get dead-stripped if QCBORDecode_SetMemPool() + is not called. + */ + +typedef struct { + QCBORStringAllocator StringAllocator; + uint8_t *pStart; // First byte that can be allocated + uint8_t *pEnd; // One past the last byte that can be allocated + uint8_t *pFree; // Where the next free chunk is +} MemPool; + + +/* + Internal function for an allocation + + Code Reviewers: THIS FUNCTION DOES POINTER MATH + */ +static UsefulBuf MemPool_Alloc(void *ctx, void *pMem, size_t uNewSize) +{ + MemPool *me = (MemPool *)ctx; + void *pReturn = NULL; + + if(pMem) { + // Realloc case + // This check will work even if uNewSize is a super-large value like UINT64_MAX + if((uNewSize <= (size_t)(me->pEnd - (uint8_t *)pMem)) && ((uint8_t *)pMem >= me->pStart)) { + me->pFree = (uint8_t *)pMem + uNewSize; + pReturn = pMem; + } + } else { + // New chunk case + // This check will work even if uNewSize is a super large value like UINT64_MAX + if(uNewSize <= (size_t)(me->pEnd - me->pFree)) { + pReturn = me->pFree; + me->pFree += uNewSize; + } + } + + return (UsefulBuf){pReturn, uNewSize}; +} + +/* + Internal function to free memory + */ +static void MemPool_Free(void *ctx, void *pOldMem) +{ + MemPool *me = (MemPool *)ctx; + me->pFree = pOldMem; +} + +/* + Public function, see header qcbor.h file + */ +QCBORError QCBORDecode_SetMemPool(QCBORDecodeContext *me, UsefulBuf Pool, bool bAllStrings) +{ + // The idea behind QCBOR_MIN_MEM_POOL_SIZE is + // that the caller knows exactly what size to + // allocate and that the tests can run conclusively + // no matter what size MemPool is + // even though it wastes some memory. MemPool + // will vary depending on pointer size of the + // the machine. QCBOR_MIN_MEM_POOL_SIZE is + // set for pointers up to 64-bits. This + // wastes about 50 bytes on a 32-bit machine. + // This check makes sure things don't go + // horribly wrong. It should optimize out + // when there is no problem as the sizes are + // known at compile time. + if(sizeof(MemPool) > QCBOR_DECODE_MIN_MEM_POOL_SIZE) { + return QCBOR_ERR_MEM_POOL_INTERNAL; + } + + // The first bytes of the Pool passed in are used + // as the context (vtable of sorts) for the memory pool + // allocator. + if(Pool.len < QCBOR_DECODE_MIN_MEM_POOL_SIZE) { + return QCBOR_ERR_BUFFER_TOO_SMALL; + } + MemPool *pMP = (MemPool *)Pool.ptr; + + // Fill in the "vtable" + pMP->StringAllocator.fAllocate = MemPool_Alloc; + pMP->StringAllocator.fFree = MemPool_Free; + pMP->StringAllocator.fDestructor = NULL; + + // Set up the pointers to the memory to be allocated + pMP->pStart = (uint8_t *)Pool.ptr + QCBOR_DECODE_MIN_MEM_POOL_SIZE; + pMP->pFree = pMP->pStart; + pMP->pEnd = (uint8_t *)Pool.ptr + Pool.len; + + // More book keeping of context + pMP->StringAllocator.pAllocaterContext = pMP; + me->pStringAllocator = pMP; + + // The flag indicating when to use the allocator + me->bStringAllocateAll = bAllStrings; + + return QCBOR_SUCCESS; +} + + +/* + Extra little hook to make MemPool testing work right + without adding any code size or overhead to non-test + uses. This will get dead-stripped for non-test use. + + This is not a public function. + */ +size_t MemPoolTestHook_GetPoolSize(void *ctx) +{ + MemPool *me = (MemPool *)ctx; + + return me->pEnd - me->pStart; +} + diff --git a/middleware/qcbor/src/qcbor_encode.c b/middleware/qcbor/src/qcbor_encode.c new file mode 100644 index 000000000..460dd8520 --- /dev/null +++ b/middleware/qcbor/src/qcbor_encode.c @@ -0,0 +1,650 @@ +/*============================================================================== + Copyright (c) 2016-2018, The Linux Foundation. + Copyright (c) 2018-2019, Laurence Lundblade. + All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of The Linux Foundation nor the names of its + contributors, nor the name "Laurence Lundblade" may be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ==============================================================================*/ + +/*=================================================================================== + FILE: qcbor_encode.c + + DESCRIPTION: This file contains the implementation of QCBOR. + + EDIT HISTORY FOR FILE: + + This section contains comments describing changes made to the module. + Notice that changes are listed in reverse chronological order. + + when who what, where, why + -------- ---- --------------------------------------------------- + 12/30/18 llundblade Small efficient clever encode of type & argument. + 11/29/18 llundblade Rework to simpler handling of tags and labels. + 11/9/18 llundblade Error codes are now enums. + 11/1/18 llundblade Floating support. + 10/31/18 llundblade Switch to one license that is almost BSD-3. + 09/28/18 llundblade Added bstr wrapping feature for COSE implementation. + 02/05/18 llundbla Works on CPUs which require integer alignment. + Requires new version of UsefulBuf. + 07/05/17 llundbla Add bstr wrapping of maps/arrays for COSE + 03/01/17 llundbla More data types + 11/13/16 llundbla Integrate most TZ changes back into github version. + 09/30/16 gkanike Porting to TZ. + 03/15/16 llundbla Initial Version. + + =====================================================================================*/ + +#include "qcbor.h" +#include "ieee754.h" + + +/*...... This is a ruler that is 80 characters long...........................*/ + + +/* + CBOR's two nesting types, arrays and maps, are tracked here. There is a + limit of QCBOR_MAX_ARRAY_NESTING to the number of arrays and maps + that can be nested in one encoding so the encoding context stays + small enough to fit on the stack. + + When an array / map is opened, pCurrentNesting points to the element + in pArrays that records the type, start position and accumluates a + count of the number of items added. When closed the start position is + used to go back and fill in the type and number of items in the array + / map. + + Encoded output be just items like ints and strings that are + not part of any array / map. That is, the first thing encoded + does not have to be an array or a map. + */ +inline static void Nesting_Init(QCBORTrackNesting *pNesting) +{ + // assumes pNesting has been zeroed + pNesting->pCurrentNesting = &pNesting->pArrays[0]; + // Implied CBOR array at the top nesting level. This is never returned, + // but makes the item count work correctly. + pNesting->pCurrentNesting->uMajorType = CBOR_MAJOR_TYPE_ARRAY; +} + +inline static QCBORError Nesting_Increase(QCBORTrackNesting *pNesting, + uint8_t uMajorType, + uint32_t uPos) +{ + QCBORError nReturn = QCBOR_SUCCESS; + + if(pNesting->pCurrentNesting == &pNesting->pArrays[QCBOR_MAX_ARRAY_NESTING]) { + // trying to open one too many + nReturn = QCBOR_ERR_ARRAY_NESTING_TOO_DEEP; + } else { + pNesting->pCurrentNesting++; + pNesting->pCurrentNesting->uCount = 0; + pNesting->pCurrentNesting->uStart = uPos; + pNesting->pCurrentNesting->uMajorType = uMajorType; + } + return nReturn; +} + +inline static void Nesting_Decrease(QCBORTrackNesting *pNesting) +{ + pNesting->pCurrentNesting--; +} + +inline static QCBORError Nesting_Increment(QCBORTrackNesting *pNesting) +{ + if(1 >= QCBOR_MAX_ITEMS_IN_ARRAY - pNesting->pCurrentNesting->uCount) { + return QCBOR_ERR_ARRAY_TOO_LONG; + } + + pNesting->pCurrentNesting->uCount += 1; + + return QCBOR_SUCCESS; +} + +inline static uint16_t Nesting_GetCount(QCBORTrackNesting *pNesting) +{ + // The nesting count recorded is always the actual number of individiual + // data items in the array or map. For arrays CBOR uses the actual item + // count. For maps, CBOR uses the number of pairs. This function returns + // the number needed for the CBOR encoding, so it divides the number of + // items by two for maps to get the number of pairs. This implementation + // takes advantage of the map major type being one larger the array major + // type, hence uDivisor is either 1 or 2. + const uint16_t uDivisor = pNesting->pCurrentNesting->uMajorType - CBOR_MAJOR_TYPE_ARRAY+1; + + return pNesting->pCurrentNesting->uCount / uDivisor; +} + +inline static uint32_t Nesting_GetStartPos(QCBORTrackNesting *pNesting) +{ + return pNesting->pCurrentNesting->uStart; +} + +inline static uint8_t Nesting_GetMajorType(QCBORTrackNesting *pNesting) +{ + return pNesting->pCurrentNesting->uMajorType; +} + +inline static int Nesting_IsInNest(QCBORTrackNesting *pNesting) +{ + return pNesting->pCurrentNesting == &pNesting->pArrays[0] ? 0 : 1; +} + + + + +/* + Error tracking plan -- Errors are tracked internally and not returned + until Finish is called. The CBOR errors are in me->uError. + UsefulOutBuf also tracks whether the buffer is full or not in its + context. Once either of these errors is set they are never + cleared. Only QCBOREncode_Init() resets them. Or said another way, they must + never be cleared or we'll tell the caller all is good when it is not. + + Only one error code is reported by QCBOREncode_Finish() even if there are + multiple errors. The last one set wins. The caller might have to fix + one error to reveal the next one they have to fix. This is OK. + + The buffer full error tracked by UsefulBuf is only pulled out of + UsefulBuf in Finish() so it is the one that usually wins. UsefulBuf + will never go off the end of the buffer even if it is called again + and again when full. + + It is really tempting to not check for overflow on the count in the + number of items in an array. It would save a lot of code, it is + extremely unlikely that any one will every put 65,000 items in an + array, and the only bad thing that would happen is the CBOR would be + bogus. + + Since this does not parse any input, you could in theory remove all + error checks in this code if you knew the caller called it + correctly. Maybe someday CDDL or some such language will be able to + generate the code to call this and the calling code would always be + correct. This could also automatically size some of the data + structures like array/map nesting resulting in some stack memory + savings. + + Errors returned here fall into two categories: + + Sizes + QCBOR_ERR_BUFFER_TOO_LARGE -- Encoded output exceeded UINT32_MAX + QCBOR_ERR_BUFFER_TOO_SMALL -- output buffer too small + + QCBOR_ERR_ARRAY_NESTING_TOO_DEEP -- Array/map nesting > QCBOR_MAX_ARRAY_NESTING1 + QCBOR_ERR_ARRAY_TOO_LONG -- Too many things added to an array/map + + Nesting constructed incorrectly + QCBOR_ERR_TOO_MANY_CLOSES -- more close calls than opens + QCBOR_ERR_CLOSE_MISMATCH -- Type of close does not match open + QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN -- Finish called without enough closes + */ + + + + +/* + Public function for initialization. See header qcbor.h + */ +void QCBOREncode_Init(QCBOREncodeContext *me, UsefulBuf Storage) +{ + memset(me, 0, sizeof(QCBOREncodeContext)); + UsefulOutBuf_Init(&(me->OutBuf), Storage); + Nesting_Init(&(me->nesting)); +} + + + + +/* + All CBOR data items have a type and an "argument". The argument is + either the value of the item for integer types, the length of the + content for string, byte, array and map types, a tag for major type + 6, and has several uses for major type 7. + + This function encodes the type and the argument. There are several + encodings for the argument depending on how large it is and how it is + used. + + Every encoding of the type and argument has at least one byte, the + "initial byte". + + The top three bits of the initial byte are the major type for the + CBOR data item. The eight major types defined by the standard are + defined as CBOR_MAJOR_TYPE_xxxx in qcbor.h. + + The remaining five bits, known as "additional information", and + possibly more bytes encode the argument. If the argument is less than + 24, then it is encoded entirely in the five bits. This is neat + because it allows you to encode an entire CBOR data item in 1 byte + for many values and types (integers 0-23, true, false, and tags). + + If the argument is larger than 24, then it is encoded in 1,2,4 or 8 + additional bytes, with the number of these bytes indicated by the + values of the 5 bits 24, 25, 25 and 27. + + It is possible to encode a particular argument in many ways with this + representation. This implementation always uses the smallest + possible representation. This conforms with CBOR preferred encoding. + + This function inserts them into the output buffer at the specified + position. AppendEncodedTypeAndNumber() appends to the end. + + This function takes care of converting to network byte order. + + This function is also used to insert floats and doubles. Before this + function is called the float or double must be copied into a + uint64_t. That is how they are passed in. They are then converted to + network byte order correctly. The uMinLen param makes sure that even + + if all the digits of a half, float or double are 0 it is still + correctly encoded in 2, 4 or 8 bytes. + */ + +static void InsertEncodedTypeAndNumber(QCBOREncodeContext *me, + uint8_t uMajorType, + int nMinLen, + uint64_t uNumber, + size_t uPos) +{ + /* + This code does endian conversion without hton or knowing the + endianness of the machine using masks and shifts. This avoids the + dependency on hton and the mess of figuring out how to find the + machine's endianness. + + This is a good efficient implementation on little-endian machines. + A faster and small implementation is possible on big-endian + machines because CBOR/network byte order is big endian. However + big endian machines are uncommon. + + On x86, it is about 200 bytes instead of 500 bytes for the more + formal unoptimized code. + + This also does the CBOR preferred shortest encoding for integers + and is called to do endian conversion for floats. + + It works backwards from the LSB to the MSB as needed. + + Code Reviewers: THIS FUNCTION DOES POINTER MATH + */ + // Holds up to 9 bytes of type and argument + // plus one extra so pointer always points to + // valid bytes. + uint8_t bytes[sizeof(uint64_t)+2]; + // Point to the last bytes and work backwards + uint8_t *pByte = &bytes[sizeof(bytes)-1]; + // This is the 5 bits in the initial byte that is not the major type + uint8_t uAdditionalInfo; + + if(uNumber < CBOR_TWENTY_FOUR && nMinLen == 0) { + // Simple case where argument is < 24 + uAdditionalInfo = uNumber; + } else { + /* + Encode argument in 1,2,4 or 8 bytes. Outer loop + runs once for 1 byte and 4 times for 8 bytes. + Inner loop runs 1, 2 or 4 times depending on + outer loop counter. This works backwards taking + 8 bits off the argument being encoded at a time + until all bits from uNumber have been encoded + and the minimum encoding size is reached. + Minimum encoding size is for floating point + numbers with zero bytes. + */ + static const uint8_t aIterate[] = {1,1,2,4}; + uint8_t i; + for(i = 0; uNumber || nMinLen > 0; i++) { + const uint8_t uIterations = aIterate[i]; + for(int j = 0; j < uIterations; j++) { + *--pByte = uNumber & 0xff; + uNumber = uNumber >> 8; + } + nMinLen -= uIterations; + } + // Additional info is the encoding of the + // number of additional bytes to encode + // argument. + uAdditionalInfo = LEN_IS_ONE_BYTE-1 + i; + } + *--pByte = (uMajorType << 5) + uAdditionalInfo; + + UsefulOutBuf_InsertData(&(me->OutBuf), pByte, &bytes[sizeof(bytes)-1] - pByte, uPos); +} + + +/* + Append the type and number info to the end of the buffer. + + See InsertEncodedTypeAndNumber() function above for details +*/ +inline static void AppendEncodedTypeAndNumber(QCBOREncodeContext *me, + uint8_t uMajorType, + uint64_t uNumber) +{ + // An append is an insert at the end. + InsertEncodedTypeAndNumber(me, + uMajorType, + 0, + uNumber, + UsefulOutBuf_GetEndPosition(&(me->OutBuf))); +} + + + + +/* + Public functions for closing arrays and maps. See header qcbor.h + */ +void QCBOREncode_AddUInt64(QCBOREncodeContext *me, uint64_t uValue) +{ + if(me->uError == QCBOR_SUCCESS) { + AppendEncodedTypeAndNumber(me, CBOR_MAJOR_TYPE_POSITIVE_INT, uValue); + me->uError = Nesting_Increment(&(me->nesting)); + } +} + + +/* + Public functions for closing arrays and maps. See header qcbor.h + */ +void QCBOREncode_AddInt64(QCBOREncodeContext *me, int64_t nNum) +{ + if(me->uError == QCBOR_SUCCESS) { + uint8_t uMajorType; + uint64_t uValue; + + if(nNum < 0) { + // In CBOR -1 encodes as 0x00 with major type negative int. + uValue = (uint64_t)(-nNum - 1); + uMajorType = CBOR_MAJOR_TYPE_NEGATIVE_INT; + } else { + uValue = (uint64_t)nNum; + uMajorType = CBOR_MAJOR_TYPE_POSITIVE_INT; + } + + AppendEncodedTypeAndNumber(me, uMajorType, uValue); + me->uError = Nesting_Increment(&(me->nesting)); + } +} + + +/* + Semi-private function. It is exposed to user of the interface, + but they will usually call one of the inline wrappers rather than this. + + See header qcbor.h + + Does the work of adding some bytes to the CBOR output. Works for a + byte and text strings, which are the same in in CBOR though they have + different major types. This is also used to insert raw + pre-encoded CBOR. + */ +void QCBOREncode_AddBuffer(QCBOREncodeContext *me, uint8_t uMajorType, UsefulBufC Bytes) +{ + if(me->uError == QCBOR_SUCCESS) { + // If it is not Raw CBOR, add the type and the length + if(uMajorType != CBOR_MAJOR_NONE_TYPE_RAW) { + AppendEncodedTypeAndNumber(me, uMajorType, Bytes.len); + } + + // Actually add the bytes + UsefulOutBuf_AppendUsefulBuf(&(me->OutBuf), Bytes); + + // Update the array counting if there is any nesting at all + me->uError = Nesting_Increment(&(me->nesting)); + } +} + + +/* + Public functions for closing arrays and maps. See header qcbor.h + */ +void QCBOREncode_AddTag(QCBOREncodeContext *me, uint64_t uTag) +{ + AppendEncodedTypeAndNumber(me, CBOR_MAJOR_TYPE_OPTIONAL, uTag); +} + + +/* + Semi-private function. It is exposed to user of the interface, + but they will usually call one of the inline wrappers rather than this. + + See header qcbor.h + */ +void QCBOREncode_AddType7(QCBOREncodeContext *me, size_t uSize, uint64_t uNum) +{ + if(me->uError == QCBOR_SUCCESS) { + // This function call takes care of endian swapping for the float / double + InsertEncodedTypeAndNumber(me, + // The major type for floats and doubles + CBOR_MAJOR_TYPE_SIMPLE, + // size makes sure floats with zeros encode correctly + (int)uSize, + // Bytes of the floating point number as a uint + uNum, + // end position because this is append + UsefulOutBuf_GetEndPosition(&(me->OutBuf))); + + me->uError = Nesting_Increment(&(me->nesting)); + } +} + + +/* + Public functions for closing arrays and maps. See header qcbor.h + */ +void QCBOREncode_AddDouble(QCBOREncodeContext *me, double dNum) +{ + const IEEE754_union uNum = IEEE754_DoubleToSmallest(dNum); + + QCBOREncode_AddType7(me, uNum.uSize, uNum.uValue); +} + + +/* + Semi-public function. It is exposed to user of the interface, + but they will usually call one of the inline wrappers rather than this. + + See header qcbor.h +*/ +void QCBOREncode_OpenMapOrArray(QCBOREncodeContext *me, uint8_t uMajorType) +{ + // Add one item to the nesting level we are in for the new map or array + me->uError = Nesting_Increment(&(me->nesting)); + if(me->uError == QCBOR_SUCCESS) { + // The offset where the length of an array or map will get written + // is stored in a uint32_t, not a size_t to keep stack usage smaller. This + // checks to be sure there is no wrap around when recording the offset. + // Note that on 64-bit machines CBOR larger than 4GB can be encoded as long as no + // array / map offsets occur past the 4GB mark, but the public interface + // says that the maximum is 4GB to keep the discussion simpler. + size_t uEndPosition = UsefulOutBuf_GetEndPosition(&(me->OutBuf)); + + // QCBOR_MAX_ARRAY_OFFSET is slightly less than UINT32_MAX so this + // code can run on a 32-bit machine and tests can pass on a 32-bit + // machine. If it was exactly UINT32_MAX, then this code would + // not compile or run on a 32-bit machine and an #ifdef or some + // machine size detection would be needed reducing portability. + if(uEndPosition >= QCBOR_MAX_ARRAY_OFFSET) { + me->uError = QCBOR_ERR_BUFFER_TOO_LARGE; + + } else { + // Increase nesting level because this is a map or array. + // Cast from size_t to uin32_t is safe because of check above + me->uError = Nesting_Increase(&(me->nesting), uMajorType, (uint32_t)uEndPosition); + } + } +} + + +/* + Public functions for closing arrays and maps. See header qcbor.h + */ +void QCBOREncode_CloseMapOrArray(QCBOREncodeContext *me, + uint8_t uMajorType, + UsefulBufC *pWrappedCBOR) +{ + if(me->uError == QCBOR_SUCCESS) { + if(!Nesting_IsInNest(&(me->nesting))) { + me->uError = QCBOR_ERR_TOO_MANY_CLOSES; + } else if(Nesting_GetMajorType(&(me->nesting)) != uMajorType) { + me->uError = QCBOR_ERR_CLOSE_MISMATCH; + } else { + // When the array, map or bstr wrap was started, nothing was done + // except note the position of the start of it. This code goes back + // and inserts the actual CBOR array, map or bstr and its length. + // That means all the data that is in the array, map or wrapped + // needs to be slid to the right. This is done by UsefulOutBuf's + // insert function that is called from inside + // InsertEncodedTypeAndNumber() + const size_t uInsertPosition = Nesting_GetStartPos(&(me->nesting)); + const size_t uEndPosition = UsefulOutBuf_GetEndPosition(&(me->OutBuf)); + // This can't go negative because the UsefulOutBuf always only grows + // and never shrinks. UsefulOutBut itself also has defenses such that + // it won't write were it should not even if given hostile input lengths + const size_t uLenOfEncodedMapOrArray = uEndPosition - uInsertPosition; + + // Length is number of bytes for a bstr and number of items a for map & array + const size_t uLength = uMajorType == CBOR_MAJOR_TYPE_BYTE_STRING ? + uLenOfEncodedMapOrArray : Nesting_GetCount(&(me->nesting)); + + // Actually insert + InsertEncodedTypeAndNumber(me, + uMajorType, // major type bstr, array or map + 0, // no minimum length for encoding + uLength, // either len of bstr or num map / array items + uInsertPosition); // position in out buffer + + // Return pointer and length to the enclosed encoded CBOR. The intended + // use is for it to be hashed (e.g., SHA-256) in a COSE implementation. + // This must be used right away, as the pointer and length go invalid + // on any subsequent calls to this function because of the + // InsertEncodedTypeAndNumber() call that slides data to the right. + if(pWrappedCBOR) { + const UsefulBufC PartialResult = UsefulOutBuf_OutUBuf(&(me->OutBuf)); + const size_t uBstrLen = UsefulOutBuf_GetEndPosition(&(me->OutBuf)) - uEndPosition; + *pWrappedCBOR = UsefulBuf_Tail(PartialResult, uInsertPosition+uBstrLen); + } + Nesting_Decrease(&(me->nesting)); + } + } +} + + + + +/* + Public functions to finish and get the encoded result. See header qcbor.h + */ +QCBORError QCBOREncode_Finish(QCBOREncodeContext *me, UsefulBufC *pEncodedCBOR) +{ + QCBORError uReturn = me->uError; + + if(uReturn != QCBOR_SUCCESS) { + goto Done; + } + + if (Nesting_IsInNest(&(me->nesting))) { + uReturn = QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN; + goto Done; + } + + if(UsefulOutBuf_GetError(&(me->OutBuf))) { + // Stuff didn't fit in the buffer. + // This check catches this condition for all the appends and inserts + // so checks aren't needed when the appends and inserts are performed. + // And of course UsefulBuf will never overrun the input buffer given + // to it. No complex analysis of the error handling in this file is + // needed to know that is true. Just read the UsefulBuf code. + uReturn = QCBOR_ERR_BUFFER_TOO_SMALL; + goto Done; + } + + *pEncodedCBOR = UsefulOutBuf_OutUBuf(&(me->OutBuf)); + +Done: + return uReturn; +} + + +/* + Public functions to finish and get the encoded result. See header qcbor.h + */ +QCBORError QCBOREncode_FinishGetSize(QCBOREncodeContext *me, size_t *puEncodedLen) +{ + UsefulBufC Enc; + + QCBORError nReturn = QCBOREncode_Finish(me, &Enc); + + if(nReturn == QCBOR_SUCCESS) { + *puEncodedLen = Enc.len; + } + + return nReturn; +} + + + + +/* + Notes on the code + + CBOR Major Type Public Function + 0 QCBOREncode_AddUInt64 + 0, 1 QCBOREncode_AddUInt64, QCBOREncode_AddInt64 + 2, 3 QCBOREncode_AddBuffer, Also QCBOREncode_OpenMapOrArray + 4, 5 QCBOREncode_OpenMapOrArray + 6 QCBOREncode_AddTag + 7 QCBOREncode_AddDouble, QCBOREncode_AddType7 + + Object code sizes on X86 with LLVM compiler and -Os (Dec 30, 2018) + + _QCBOREncode_Init 69 + _QCBOREncode_AddUInt64 76 + _QCBOREncode_AddInt64 87 + _QCBOREncode_AddBuffer 113 + _QCBOREncode_AddTag 27 + _QCBOREncode_AddType7 87 + _QCBOREncode_AddDouble 36 + _QCBOREncode_OpenMapOrArray 103 + _QCBOREncode_CloseMapOrArray 181 + _InsertEncodedTypeAndNumber 190 + _QCBOREncode_Finish 72 + _QCBOREncode_FinishGetSize 70 + + Total is about 1.1KB + + _QCBOREncode_CloseMapOrArray is larger because it has a lot + of nesting tracking to do and much of Nesting_ inlines + into it. It probably can't be reduced much. + + If the error returned by Nesting_Increment() can be ignored + because the limit is so high and the consequence of exceeding + is proved to be inconsequential, then a lot of if(me->uError) + instance can be removed, saving some code. + + */ + diff --git a/middleware/qcbor/test/UsefulBuf_Tests.c b/middleware/qcbor/test/UsefulBuf_Tests.c new file mode 100644 index 000000000..cfa126274 --- /dev/null +++ b/middleware/qcbor/test/UsefulBuf_Tests.c @@ -0,0 +1,700 @@ +/*============================================================================== + Copyright (c) 2016-2018, The Linux Foundation. + Copyright (c) 2018-2019, Laurence Lundblade. + All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of The Linux Foundation nor the names of its + contributors, nor the name "Laurence Lundblade" may be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ==============================================================================*/ + +#include "UsefulBuf.h" + + +/* Basic exercise... + + Call all the main public functions. + + Binary compare the result to the expected. + + There is nothing adversarial in this test + */ +const char * UOBTest_NonAdversarial() +{ + const char *szReturn = NULL; + + UsefulBuf_MAKE_STACK_UB(outbuf,50); + + UsefulOutBuf UOB; + + UsefulOutBuf_Init(&UOB, outbuf); + + if(!UsefulOutBuf_AtStart(&UOB)) { + szReturn = "Not at start"; + goto Done; + } + + // Put 7 bytes at beginning of buf + UsefulOutBuf_AppendData(&UOB, "bluster", 7); + + if(UsefulOutBuf_AtStart(&UOB)) { + szReturn = "At start"; + goto Done; + } + + // add a space to end + UsefulOutBuf_AppendByte(&UOB, ' '); + + // Add 5 bytes to the end + UsefulBufC UBC = {"hunny", 5}; + UsefulOutBuf_AppendUsefulBuf(&UOB, UBC); + + // Insert 9 bytes at the beginning, slide the previous stuff right + UsefulOutBuf_InsertData(&UOB, "heffalump", 9, 0); + UsefulOutBuf_InsertByte(&UOB, ' ', 9); + + // Put 9 bytes in at position 10 -- just after "heffalump " + UsefulBufC UBC2 = {"unbounce ", 9}; + UsefulOutBuf_InsertUsefulBuf(&UOB, UBC2, 10); + + // Make it a null terminated string (because all the appends and inserts above not strcpy !) + UsefulOutBuf_AppendByte(&UOB, '\0'); + + + UsefulBufC U = UsefulOutBuf_OutUBuf(&UOB); + + const char *expected = "heffalump unbounce bluster hunny"; + + if(UsefulBuf_IsNULLC(U) || U.len-1 != strlen(expected) || strcmp(expected, U.ptr) || UsefulOutBuf_GetError(&UOB)) { + szReturn = "OutUBuf"; + } + + UsefulBuf_MAKE_STACK_UB(buf, 50); + UsefulBufC Out = UsefulOutBuf_CopyOut(&UOB, buf); + + if(UsefulBuf_IsNULLC(Out) || Out.len-1 != strlen(expected) || strcmp(expected, Out.ptr)) { + szReturn = "CopyOut"; + } + +Done: + return szReturn; +} + + +/* + Append test utility. + pUOB is the buffer to append too + num is the amount to append + expected is the expected return code, 0 or 1 + + returns 0 if test passed + + */ +static int AppendTest(UsefulOutBuf *pUOB, size_t num, int expected) +{ + //reset + UsefulOutBuf_Reset(pUOB); + + // check status first + if(UsefulOutBuf_GetError(pUOB)) + return 1; + + // append the bytes + UsefulOutBuf_AppendData(pUOB, (const uint8_t *)"bluster", num); + + // check error status after + if(UsefulOutBuf_GetError(pUOB) != expected) + return 1; + + return 0; +} + + +/* + Same as append, but takes a position param too + */ +static int InsertTest(UsefulOutBuf *pUOB, size_t num, size_t pos, int expected) +{ + // reset + UsefulOutBuf_Reset(pUOB); + + // check + if(UsefulOutBuf_GetError(pUOB)) + return 1; + + UsefulOutBuf_InsertData(pUOB, (const uint8_t *)"bluster", num, pos); + + if(UsefulOutBuf_GetError(pUOB) != expected) + return 1; + + return 0; +} + + +/* + Boundary conditions to test + - around 0 + - around the buffer size + - around MAX size_t + + + Test these for the buffer size and the cursor, the insert amount, the append amount and the insert position + + */ + +const char *UOBTest_BoundaryConditionsTest() +{ + UsefulBuf_MAKE_STACK_UB(outbuf,2); + + UsefulOutBuf UOB; + + UsefulOutBuf_Init(&UOB, outbuf); + + // append 0 byte to a 2 byte buffer --> success + if(AppendTest(&UOB, 0, 0)) + return "Append 0 bytes failed"; + + // append 1 byte to a 2 byte buffer --> success + if(AppendTest(&UOB, 1, 0)) + return "Append of 1 byte failed"; + + // append 2 byte to a 2 byte buffer --> success + if(AppendTest(&UOB, 2, 0)) + return "Append to fill buffer failed"; + + // append 3 bytes to a 2 byte buffer --> failure + if(AppendTest(&UOB, 3, 1)) + return "Overflow of buffer not caught"; + + // append max size_t to a 2 byte buffer --> failure + if(AppendTest(&UOB, SIZE_MAX, 1)) + return "Append of SIZE_MAX error not caught"; + + if(InsertTest(&UOB, 1, 0, 0)) + return "Insert 1 byte at start failed"; + + if(InsertTest(&UOB, 2, 0, 0)) + return "Insert 2 bytes at start failed"; + + if(InsertTest(&UOB, 3, 0, 1)) + return "Insert overflow not caught"; + + if(InsertTest(&UOB, 1, 1, 1)) + return "Bad insertion point not caught"; + + + UsefulBuf_MAKE_STACK_UB(outBuf2,10); + + UsefulOutBuf_Init(&UOB, outBuf2); + + UsefulOutBuf_Reset(&UOB); + // put data in the buffer + UsefulOutBuf_AppendString(&UOB, "abc123"); + + UsefulOutBuf_InsertString(&UOB, "xyz*&^", 0); + + if(!UsefulOutBuf_GetError(&UOB)) { + return "insert with data should have failed"; + } + + + UsefulOutBuf_Init(&UOB, (UsefulBuf){NULL, SIZE_MAX - 5}); + UsefulOutBuf_AppendData(&UOB, "123456789", SIZE_MAX -6); + if(UsefulOutBuf_GetError(&UOB)) { + return "insert in huge should have succeeded"; + } + + UsefulOutBuf_Init(&UOB, (UsefulBuf){NULL, SIZE_MAX - 5}); + UsefulOutBuf_AppendData(&UOB, "123456789", SIZE_MAX -5); + if(UsefulOutBuf_GetError(&UOB)) { + return "insert in huge should have succeeded"; + } + + UsefulOutBuf_Init(&UOB, (UsefulBuf){NULL, SIZE_MAX - 5}); + UsefulOutBuf_AppendData(&UOB, "123456789", SIZE_MAX - 4); + if(!UsefulOutBuf_GetError(&UOB)) { + return "lengths near max size"; + } + + return NULL; +} + + + + + +// Test function to get size and magic number check + +const char *TestBasicSanity() +{ + UsefulBuf_MAKE_STACK_UB(outbuf,10); + + UsefulOutBuf UOB; + + // First -- make sure that the room left function returns the right amount + UsefulOutBuf_Init(&UOB, outbuf); + + if(UsefulOutBuf_RoomLeft(&UOB) != 10) + return "room left failed"; + + if(!UsefulOutBuf_WillItFit(&UOB, 9)) { + return "it did not fit"; + } + + if(UsefulOutBuf_WillItFit(&UOB, 11)) { + return "it should have not fit"; + } + + + // Next -- make sure that the magic number checking is working right + UOB.magic = 8888; // make magic bogus + + UsefulOutBuf_AppendData(&UOB, (const uint8_t *)"bluster", 7); + + if(!UsefulOutBuf_GetError(&UOB)) + return "magic corruption check failed"; + + + + // Next make sure that the valid data length check is working right + UsefulOutBuf_Init(&UOB, outbuf); + + UOB.data_len = UOB.UB.len+1; // make size bogus + + UsefulOutBuf_AppendData(&UOB, (const uint8_t *)"bluster", 7); + if(!UsefulOutBuf_GetError(&UOB)) + return "valid data check failed"; + + return NULL; +} + + + +const char *UBMacroConversionsTest() +{ + char *szFoo = "foo"; + + UsefulBufC Foo = UsefulBuf_FromSZ(szFoo); + if(Foo.len != 3 || strncmp(Foo.ptr, szFoo, 3)) + return "SZToUsefulBufC failed"; + + UsefulBufC Too = UsefulBuf_FROM_SZ_LITERAL("Toooo"); + if(Too.len != 5 || strncmp(Too.ptr, "Toooo", 5)) + return "UsefulBuf_FROM_SZ_LITERAL failed"; + + uint8_t pB[] = {0x42, 0x6f, 0x6f}; + UsefulBufC Boo = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pB); + if(Boo.len != 3 || strncmp(Boo.ptr, "Boo", 3)) + return "UsefulBuf_FROM_BYTE_ARRAY_LITERAL failed"; + + char *sz = "not const"; // some data for the test + UsefulBuf B = (UsefulBuf){sz, sizeof(sz)}; + UsefulBufC BC = UsefulBuf_Const(B); + if(BC.len != sizeof(sz) || BC.ptr != sz) + return "UsefulBufConst failed"; + + return NULL; +} + + +const char *UBUtilTests() +{ + UsefulBuf UB = NULLUsefulBuf; + + if(!UsefulBuf_IsNULL(UB)){ + return "IsNull failed"; + } + + if(!UsefulBuf_IsEmpty(UB)){ + return "IsEmpty failed"; + } + + if(!UsefulBuf_IsNULLOrEmpty(UB)) { + return "IsNULLOrEmpty failed"; + } + + const UsefulBufC UBC = UsefulBuf_Const(UB); + + if(!UsefulBuf_IsNULLC(UBC)){ + return "IsNull const failed"; + } + + if(!UsefulBuf_IsEmptyC(UBC)){ + return "IsEmptyC failed"; + } + + if(!UsefulBuf_IsNULLOrEmptyC(UBC)){ + return "IsNULLOrEmptyC failed"; + } + + const UsefulBuf UB2 = UsefulBuf_Unconst(UBC); + if(!UsefulBuf_IsEmpty(UB2)) { + return "Back to UB is Empty failed"; + } + + UB.ptr = "x"; // just some valid pointer + + if(UsefulBuf_IsNULL(UB)){ + return "IsNull failed"; + } + + if(!UsefulBuf_IsEmptyC(UBC)){ + return "IsEmpty failed"; + } + + // test the Unconst. + if(UsefulBuf_Unconst(UBC).ptr != NULL) { + return "Unconst failed"; + } + + // Set 100 bytes of '+'; validated a few tests later + UsefulBuf_MAKE_STACK_UB(Temp, 100); + const UsefulBufC TempC = UsefulBuf_Set(Temp, '+'); + + // Try to copy into a buf that is too small and see failure + UsefulBuf_MAKE_STACK_UB(Temp2, 99); + if(!UsefulBuf_IsNULLC(UsefulBuf_Copy(Temp2, TempC))) { + return "Copy should have failed"; + } + + if(UsefulBuf_IsNULLC(UsefulBuf_CopyPtr(Temp2, "xx", 2))) { + return "CopyPtr failed"; + } + + UsefulBufC xxyy = UsefulBuf_CopyOffset(Temp2, 2, UsefulBuf_FROM_SZ_LITERAL("yy")); + if(UsefulBuf_IsNULLC(xxyy)) { + return "CopyOffset Failed"; + } + + if(UsefulBuf_Compare(UsefulBuf_Head(xxyy, 3), UsefulBuf_FROM_SZ_LITERAL("xxy"))) { + return "head failed"; + } + + if(UsefulBuf_Compare(UsefulBuf_Tail(xxyy, 1), UsefulBuf_FROM_SZ_LITERAL("xyy"))) { + return "tail failed"; + } + + if(!UsefulBuf_IsNULLC(UsefulBuf_Head(xxyy, 5))) { + return "head should have failed"; + } + + if(!UsefulBuf_IsNULLC(UsefulBuf_Tail(xxyy, 5))) { + return "tail should have failed"; + } + + if(!UsefulBuf_IsNULLC(UsefulBuf_Tail(NULLUsefulBufC, 0))) { + return "tail of NULLUsefulBufC is not NULLUsefulBufC"; + } + + const UsefulBufC TailResult = UsefulBuf_Tail((UsefulBufC){NULL, 100}, 99); + if(TailResult.ptr != NULL || TailResult.len != 1) { + return "tail of NULL and length incorrect"; + } + + if(!UsefulBuf_IsNULLC(UsefulBuf_CopyOffset(Temp2, 100, UsefulBuf_FROM_SZ_LITERAL("yy")))) { + return "Copy Offset should have failed"; + } + + // Try to copy into a NULL/empty buf and see failure + const UsefulBuf UBNull = NULLUsefulBuf; + if(!UsefulBuf_IsNULLC(UsefulBuf_Copy(UBNull, TempC))) { + return "Copy to NULL should have failed"; + } + + + // Try to set a NULL/empty buf; nothing should happen + UsefulBuf_Set(UBNull, '+'); // This will crash on failure + + // Copy successfully to a buffer + UsefulBuf_MAKE_STACK_UB(Temp3, 101); + if(UsefulBuf_IsNULLC(UsefulBuf_Copy(Temp3, TempC))) { + return "Copy should not have failed"; + } + + static const uint8_t pExpected[] = { + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + }; + UsefulBufC Expected = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pExpected); + // This validates comparison for equality and the UsefulBuf_Set + if(UsefulBuf_Compare(Expected, TempC)) { + return "Set / Copy / Compare failed"; + } + + // Compare two empties and expect success + if(UsefulBuf_Compare(NULLUsefulBufC, NULLUsefulBufC)){ + return "Compare Empties failed"; + } + + // Compare with empty and expect the first to be larger + if(UsefulBuf_Compare(Expected, NULLUsefulBufC) <= 0){ + return "Compare with empty failed"; + } + + + static const uint8_t pExpectedBigger[] = { + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', ',', + }; + const UsefulBufC ExpectedBigger = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pExpectedBigger); + + // Expect -1 when the first arg is smaller + if(UsefulBuf_Compare(Expected, ExpectedBigger) >= 0){ + return "Compare with bigger"; + } + + + static const uint8_t pExpectedSmaller[] = { + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '*', + }; + const UsefulBufC ExpectedSmaller = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pExpectedSmaller); + // Expect +1 when the first arg is larger + if(UsefulBuf_Compare(Expected, ExpectedSmaller) <= 0){ + return "Compare with smaller"; + } + + + static const uint8_t pExpectedLonger[] = { + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', '+' + }; + const UsefulBufC ExpectedLonger = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pExpectedLonger); + + // Expect -1 when the first arg is smaller + if(UsefulBuf_Compare(Expected, ExpectedLonger) >= 0){ + return "Compare with longer"; + } + + + static const uint8_t pExpectedShorter[] = { + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', + '+', '+', '+', '+', '+', '+', '+', '+', '+', + }; + const UsefulBufC ExpectedShorter = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pExpectedShorter); + // Expect +1 with the first arg is larger + if(UsefulBuf_Compare(Expected, ExpectedShorter) <= 0){ + return "Compare with shorter"; + } + + + if(UsefulBuf_IsNULLC(UsefulBuf_Copy(Temp, NULLUsefulBufC))) { + return "Copy null/empty failed"; + } + + // Look for +++++... in +++++... and find it at the beginning + if(0 != UsefulBuf_FindBytes(ExpectedLonger, ExpectedShorter)){ + return "Failed to find"; + } + + // look for ++* in ....++* and find it at the end + static const uint8_t pToFind[] = {'+', '+', '*'}; + const UsefulBufC ToBeFound = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pToFind); + + if(97 != UsefulBuf_FindBytes(ExpectedSmaller, ToBeFound)){ + return "Failed to find 2"; + } + + // look for ++* in ....++, and find it near the end + if(SIZE_MAX != UsefulBuf_FindBytes(ExpectedBigger, ToBeFound)){ + return "Failed to not find"; + } + + // Look for the whole buffer in itself and succeed. + if(0 != UsefulBuf_FindBytes(ExpectedLonger, ExpectedLonger)){ + return "Failed to find 3"; + } + + return NULL; +} + + +const char * UIBTest_IntegerFormat() +{ + UsefulOutBuf_MakeOnStack(UOB,100); + + const uint32_t u32 = 0x0A0B0C0D; // from https://en.wikipedia.org/wiki/Endianness + const uint64_t u64 = 1984738472938472; + const uint16_t u16 = 40000; + const uint8_t u8 = 9; + const float f = (float)314.15; + const double d = 2.1e10; + + + UsefulOutBuf_AppendUint32(&UOB, u32); // Also tests UsefulOutBuf_InsertUint64 and UsefulOutBuf_GetEndPosition + UsefulOutBuf_AppendUint64(&UOB, u64); // Also tests UsefulOutBuf_InsertUint32 + UsefulOutBuf_AppendUint16(&UOB, u16); // Also tests UsefulOutBuf_InsertUint16 + UsefulOutBuf_AppendByte(&UOB, u8); + UsefulOutBuf_AppendFloat(&UOB, f); // Also tests UsefulOutBuf_InsertFloat + UsefulOutBuf_AppendDouble(&UOB, d); // Also tests UsefulOutBuf_InsertDouble + + const UsefulBufC O = UsefulOutBuf_OutUBuf(&UOB); + if(UsefulBuf_IsNULLC(O)) + return "Couldn't output integers"; + + // from https://en.wikipedia.org/wiki/Endianness + const uint8_t pExpectedNetworkOrder[4] = {0x0A, 0x0B, 0x0C, 0x0D}; + if(memcmp(O.ptr, pExpectedNetworkOrder, 4)) { + return "not in network order"; + } + + UsefulInputBuf UIB; + + UsefulInputBuf_Init(&UIB, O); + + if(UsefulInputBuf_Tell(&UIB) != 0) { + return "UsefulInputBuf_Tell failed"; + } + + if(UsefulInputBuf_GetUint32(&UIB) != u32) { + return "u32 out then in failed"; + } + if(UsefulInputBuf_GetUint64(&UIB) != u64) { + return "u64 out then in failed"; + } + if(UsefulInputBuf_GetUint16(&UIB) != u16) { + return "u16 out then in failed"; + } + if(UsefulInputBuf_GetByte(&UIB) != u8) { + return "u8 out then in failed"; + } + if(UsefulInputBuf_GetFloat(&UIB) != f) { + return "float out then in failed"; + } + if(UsefulInputBuf_GetDouble(&UIB) != d) { + return "double out then in failed"; + } + + // Reset and go again for a few more tests + UsefulInputBuf_Init(&UIB, O); + + const UsefulBufC Four = UsefulInputBuf_GetUsefulBuf(&UIB, 4); + if(UsefulBuf_IsNULLC(Four)) { + return "Four is NULL"; + } + if(UsefulBuf_Compare(Four, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pExpectedNetworkOrder))) { + return "Four compare failed"; + } + + if(UsefulInputBuf_BytesUnconsumed(&UIB) != 23){ + return "Wrong number of unconsumed bytes"; + } + + if(!UsefulInputBuf_BytesAvailable(&UIB, 23)){ + return "Wrong number of bytes available I"; + } + + if(UsefulInputBuf_BytesAvailable(&UIB, 24)){ + return "Wrong number of bytes available II"; + } + + UsefulInputBuf_Seek(&UIB, 0); + + if(UsefulInputBuf_GetError(&UIB)) { + return "unexpected error after seek"; + } + + const uint8_t *pGetBytes = (const uint8_t *)UsefulInputBuf_GetBytes(&UIB, 4); + if(pGetBytes == NULL) { + return "GetBytes returns NULL"; + } + + if(memcmp(pGetBytes, pExpectedNetworkOrder, 4)) { + return "Got wrong bytes"; + } + + UsefulInputBuf_Seek(&UIB, 28); + + if(!UsefulInputBuf_GetError(&UIB)) { + return "expected error after seek"; + } + + return NULL; +} + + +const char *UBUTest_CopyUtil() +{ + if(UsefulBufUtil_CopyFloatToUint32(65536.0F) != 0x47800000) { + return "CopyFloatToUint32 failed"; + } + + if(UsefulBufUtil_CopyDoubleToUint64(4e-40F) != 0X37C16C2800000000ULL) { + return "CopyDoubleToUint64 failed"; + } + + if(UsefulBufUtil_CopyUint64ToDouble(0X37C16C2800000000ULL) != 4e-40F) { + return "CopyUint64ToDouble failed"; + } + + if(UsefulBufUtil_CopyUint32ToFloat(0x47800000) != 65536.0F) { + return "CopyUint32ToFloat failed"; + } + + return NULL; +} + + + diff --git a/middleware/qcbor/test/UsefulBuf_Tests.h b/middleware/qcbor/test/UsefulBuf_Tests.h new file mode 100644 index 000000000..976de621d --- /dev/null +++ b/middleware/qcbor/test/UsefulBuf_Tests.h @@ -0,0 +1,50 @@ +/*============================================================================== + Copyright (c) 2016-2018, The Linux Foundation. + Copyright (c) 2018, Laurence Lundblade. + All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of The Linux Foundation nor the names of its + contributors, nor the name "Laurence Lundblade" may be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ==============================================================================*/ + +#ifndef UsefulBuf_UsefulBuf_Tests_h +#define UsefulBuf_UsefulBuf_Tests_h + +const char * UOBTest_NonAdversarial(void); + +const char * TestBasicSanity(void); + +const char * UOBTest_BoundaryConditionsTest(void); + +const char * UBMacroConversionsTest(void); + +const char * UBUtilTests(void); + +const char * UIBTest_IntegerFormat(void); + +const char * UBUTest_CopyUtil(void); + +#endif diff --git a/middleware/qcbor/test/float_tests.c b/middleware/qcbor/test/float_tests.c new file mode 100644 index 000000000..eaf75aa3e --- /dev/null +++ b/middleware/qcbor/test/float_tests.c @@ -0,0 +1,474 @@ +/*============================================================================== + float_tests.c -- tests for float and conversion to/from half-precision + + Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved. + + SPDX-License-Identifier: BSD-3-Clause + + See BSD-3-Clause license in README.md + + Created on 9/19/18 + ==============================================================================*/ + +#include "float_tests.h" +#include "qcbor.h" +#include "half_to_double_from_rfc7049.h" +#include // For INFINITY and NAN and isnan() + + + +static const uint8_t spExpectedHalf[] = { + 0xB1, + 0x64, + 0x7A, 0x65, 0x72, 0x6F, + 0xF9, 0x00, 0x00, // 0.000 + 0x6A, + 0x69, 0x6E, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x74, 0x79, + 0xF9, 0x7C, 0x00, // Infinity + 0x73, + 0x6E, 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20, 0x69, 0x6E, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x74, 0x79, + 0xF9, 0xFC, 0x00, // -Inifinity + 0x63, + 0x4E, 0x61, 0x4E, + 0xF9, 0x7E, 0x00, // NaN + 0x63, + 0x6F, 0x6E, 0x65, + 0xF9, 0x3C, 0x00, // 1.0 + 0x69, + 0x6F, 0x6E, 0x65, 0x20, 0x74, 0x68, 0x69, 0x72, 0x64, + 0xF9, 0x35, 0x55, // 0.333251953125 + 0x76, + 0x6C, 0x61, 0x72, 0x67, 0x65, 0x73, 0x74, 0x20, 0x68, 0x61, 0x6C, 0x66, 0x2D, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6F, 0x6E, + 0xF9, 0x7B, 0xFF, // 65504.0 + 0x78, 0x18, 0x74, 0x6F, 0x6F, 0x2D, 0x6C, 0x61, 0x72, 0x67, 0x65, 0x20, 0x68, 0x61, 0x6C, 0x66, 0x2D, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6F, 0x6E, + 0xF9, 0x7C, 0x00, // Infinity + 0x72, + 0x73, 0x6D, 0x61, 0x6C, 0x6C, 0x65, 0x73, 0x74, 0x20, 0x73, 0x75, 0x62, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C, + 0xF9, 0x00, 0x01, // 0.000000059604 + 0x6F, + 0x73, 0x6D, 0x61, 0x6C, 0x6C, 0x65, 0x73, 0x74, 0x20, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C, + 0xF9, 0x03, 0xFF, // 0.0000609755516 + 0x71, + 0x62, 0x69, 0x67, 0x67, 0x65, 0x73, 0x74, 0x20, 0x73, 0x75, 0x62, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C, + 0xF9, 0x04, 0x00, // 0.000061988 + 0x70, + 0x73, 0x75, 0x62, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C, 0x20, 0x73, 0x69, 0x6E, 0x67, 0x6C, 0x65, + 0xF9, 0x00, 0x00, + 0x03, + 0xF9, 0xC0, 0x00, // -2 + 0x04, + 0xF9, 0x7E, 0x00, // qNaN + 0x05, + 0xF9, 0x7C, 0x01, // sNaN + 0x06, + 0xF9, 0x7E, 0x0F, // qNaN with payload 0x0f + 0x07, + 0xF9, 0x7C, 0x0F, // sNaN with payload 0x0f + +}; + + +int HalfPrecisionDecodeBasicTests() +{ + UsefulBufC HalfPrecision = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spExpectedHalf); + + QCBORDecodeContext DC; + QCBORDecode_Init(&DC, HalfPrecision, 0); + + QCBORItem Item; + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_MAP) { + return -1; + } + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != 0.0F) { + return -2; + } + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != INFINITY) { + return -3; + } + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != -INFINITY) { + return -4; + } + + QCBORDecode_GetNext(&DC, &Item); // TODO, is this really converting right? It is carrying payload, but this confuses things. + if(Item.uDataType != QCBOR_TYPE_DOUBLE || !isnan(Item.val.dfnum)) { + return -5; + } + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != 1.0F) { + return -6; + } + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != 0.333251953125F) { + return -7; + } + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != 65504.0F) { + return -8; + } + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != INFINITY) { + return -9; + } + + QCBORDecode_GetNext(&DC, &Item); // TODO: check this + if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != 0.0000000596046448F) { + return -10; + } + + QCBORDecode_GetNext(&DC, &Item); // TODO: check this + if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != 0.0000609755516F) { + return -11; + } + + QCBORDecode_GetNext(&DC, &Item); // TODO check this + if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != 0.0000610351563F) { + return -12; + } + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != 0) { + return -13; + } + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != -2.0F) { + return -14; + } + + // TODO: double check these four tests + QCBORDecode_GetNext(&DC, &Item); // qNaN + if(Item.uDataType != QCBOR_TYPE_DOUBLE || UsefulBufUtil_CopyDoubleToUint64(Item.val.dfnum) != 0x7ff8000000000000ULL) { + return -15; + } + QCBORDecode_GetNext(&DC, &Item); // sNaN + if(Item.uDataType != QCBOR_TYPE_DOUBLE || UsefulBufUtil_CopyDoubleToUint64(Item.val.dfnum) != 0x7ff0000000000001ULL) { + return -16; + } + QCBORDecode_GetNext(&DC, &Item); // qNaN with payload 0x0f + if(Item.uDataType != QCBOR_TYPE_DOUBLE || UsefulBufUtil_CopyDoubleToUint64(Item.val.dfnum) != 0x7ff800000000000fULL) { + return -17; + } + QCBORDecode_GetNext(&DC, &Item); // sNaN with payload 0x0f + if(Item.uDataType != QCBOR_TYPE_DOUBLE || UsefulBufUtil_CopyDoubleToUint64(Item.val.dfnum) != 0x7ff000000000000fULL) { + return -18; + } + + if(QCBORDecode_Finish(&DC)) { + return -19; + } + + return 0; +} + + + + +int HalfPrecisionAgainstRFCCodeTest() +{ + for(uint32_t uHalfP = 0; uHalfP < 0xffff; uHalfP += 60) { + unsigned char x[2]; + x[1] = uHalfP & 0xff; + x[0] = uHalfP >> 8; + double d = decode_half(x); + + // Contruct the CBOR for the half-precision float by hand + UsefulBuf_MAKE_STACK_UB(__xx, 3); + UsefulOutBuf UOB; + UsefulOutBuf_Init(&UOB, __xx); + + const uint8_t uHalfPrecInitialByte = HALF_PREC_FLOAT + (CBOR_MAJOR_TYPE_SIMPLE << 5); // 0xf9 + UsefulOutBuf_AppendByte(&UOB, uHalfPrecInitialByte); // The initial byte for a half-precision float + UsefulOutBuf_AppendUint16(&UOB, (uint16_t)uHalfP); + + // Now parse the hand-constructed CBOR. This will invoke the conversion to a float + QCBORDecodeContext DC; + QCBORDecode_Init(&DC, UsefulOutBuf_OutUBuf(&UOB), 0); + + QCBORItem Item; + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_DOUBLE) { + return -1; + } + + //printf("%04x QCBOR:%15.15f RFC: %15.15f (%8x)\n", uHalfP,Item.val.fnum, d , UsefulBufUtil_CopyFloatToUint32(d)); + + if(isnan(d)) { + // The RFC code uses the native instructions which may or may not + // handle sNaN, qNaN and NaN payloads correctly. This test just + // makes sure it is a NaN and doesn't worry about the type of NaN + if(!isnan(Item.val.dfnum)) { + return -3; + } + } else { + if(Item.val.dfnum != d) { + return -2; + } + } + } + return 0; +} + + +/* + {"zero": 0.0, + "negative zero": -0.0, + "infinitity": Infinity, + "negative infinitity": -Infinity, + "NaN": NaN, + "one": 1.0, + "one third": 0.333251953125, + "largest half-precision": 65504.0, + "largest half-precision point one": 65504.1, + "too-large half-precision": 65536.0, + "smallest subnormal": 5.96046448e-8, + "smallest normal": 0.00006103515261202119, + "biggest subnormal": 0.00006103515625, + "subnormal single": 4.00000646641519e-40, + 3: -2.0, + "large single exp": 2.5521177519070385e+38, + "too-large single exp": 5.104235503814077e+38, + "biggest single with prec": 16777216.0, + "first single with prec loss": 16777217.0, + 1: "fin"} + */ +static const uint8_t spExpectedSmallest[] = { + 0xB4, 0x64, 0x7A, 0x65, 0x72, 0x6F, 0xF9, 0x00, 0x00, 0x6D, + 0x6E, 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20, 0x7A, + 0x65, 0x72, 0x6F, 0xF9, 0x80, 0x00, 0x6A, 0x69, 0x6E, 0x66, + 0x69, 0x6E, 0x69, 0x74, 0x69, 0x74, 0x79, 0xF9, 0x7C, 0x00, + 0x73, 0x6E, 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20, + 0x69, 0x6E, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x74, 0x79, + 0xF9, 0xFC, 0x00, 0x63, 0x4E, 0x61, 0x4E, 0xF9, 0x7E, 0x00, + 0x63, 0x6F, 0x6E, 0x65, 0xF9, 0x3C, 0x00, 0x69, 0x6F, 0x6E, + 0x65, 0x20, 0x74, 0x68, 0x69, 0x72, 0x64, 0xF9, 0x35, 0x55, + 0x76, 0x6C, 0x61, 0x72, 0x67, 0x65, 0x73, 0x74, 0x20, 0x68, + 0x61, 0x6C, 0x66, 0x2D, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, + 0x69, 0x6F, 0x6E, 0xF9, 0x7B, 0xFF, 0x78, 0x20, 0x6C, 0x61, + 0x72, 0x67, 0x65, 0x73, 0x74, 0x20, 0x68, 0x61, 0x6C, 0x66, + 0x2D, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6F, 0x6E, + 0x20, 0x70, 0x6F, 0x69, 0x6E, 0x74, 0x20, 0x6F, 0x6E, 0x65, + 0xFB, 0x40, 0xEF, 0xFC, 0x03, 0x33, 0x33, 0x33, 0x33, 0x78, + 0x18, 0x74, 0x6F, 0x6F, 0x2D, 0x6C, 0x61, 0x72, 0x67, 0x65, + 0x20, 0x68, 0x61, 0x6C, 0x66, 0x2D, 0x70, 0x72, 0x65, 0x63, + 0x69, 0x73, 0x69, 0x6F, 0x6E, 0xFA, 0x47, 0x80, 0x00, 0x00, + 0x72, 0x73, 0x6D, 0x61, 0x6C, 0x6C, 0x65, 0x73, 0x74, 0x20, + 0x73, 0x75, 0x62, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C, 0xFB, + 0x3E, 0x70, 0x00, 0x00, 0x00, 0x1C, 0x5F, 0x68, 0x6F, 0x73, + 0x6D, 0x61, 0x6C, 0x6C, 0x65, 0x73, 0x74, 0x20, 0x6E, 0x6F, + 0x72, 0x6D, 0x61, 0x6C, 0xFA, 0x38, 0x7F, 0xFF, 0xFF, 0x71, + 0x62, 0x69, 0x67, 0x67, 0x65, 0x73, 0x74, 0x20, 0x73, 0x75, + 0x62, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C, 0xF9, 0x04, 0x00, + 0x70, 0x73, 0x75, 0x62, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C, + 0x20, 0x73, 0x69, 0x6E, 0x67, 0x6C, 0x65, 0xFB, 0x37, 0xC1, + 0x6C, 0x28, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF9, 0xC0, 0x00, + 0x70, 0x6C, 0x61, 0x72, 0x67, 0x65, 0x20, 0x73, 0x69, 0x6E, + 0x67, 0x6C, 0x65, 0x20, 0x65, 0x78, 0x70, 0xFA, 0x7F, 0x40, + 0x00, 0x00, 0x74, 0x74, 0x6F, 0x6F, 0x2D, 0x6C, 0x61, 0x72, + 0x67, 0x65, 0x20, 0x73, 0x69, 0x6E, 0x67, 0x6C, 0x65, 0x20, + 0x65, 0x78, 0x70, 0xFB, 0x47, 0xF8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x78, 0x18, 0x62, 0x69, 0x67, 0x67, 0x65, 0x73, + 0x74, 0x20, 0x73, 0x69, 0x6E, 0x67, 0x6C, 0x65, 0x20, 0x77, + 0x69, 0x74, 0x68, 0x20, 0x70, 0x72, 0x65, 0x63, 0xFA, 0x4B, + 0x80, 0x00, 0x00, 0x78, 0x1B, 0x66, 0x69, 0x72, 0x73, 0x74, + 0x20, 0x73, 0x69, 0x6E, 0x67, 0x6C, 0x65, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x20, 0x70, 0x72, 0x65, 0x63, 0x20, 0x6C, 0x6F, + 0x73, 0x73, 0xFB, 0x41, 0x70, 0x00, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x01, 0x63, 0x66, 0x69, 0x6E +}; + + +int DoubleAsSmallestTest() +{ + UsefulBuf_MAKE_STACK_UB(EncodedHalfsMem, 420); + +#define QCBOREncode_AddDoubleAsSmallestToMap QCBOREncode_AddDoubleToMap +#define QCBOREncode_AddDoubleAsSmallestToMapN QCBOREncode_AddDoubleToMapN + + + QCBOREncodeContext EC; + QCBOREncode_Init(&EC, EncodedHalfsMem); + // These are mostly from https://en.wikipedia.org/wiki/Half-precision_floating-point_format + QCBOREncode_OpenMap(&EC); + // 64 # text(4) + // 7A65726F # "zero" + // F9 0000 # primitive(0) + QCBOREncode_AddDoubleAsSmallestToMap(&EC, "zero", 0.00); + + // 64 # text(4) + // 7A65726F # "negative zero" + // F9 8000 # primitive(0) + QCBOREncode_AddDoubleAsSmallestToMap(&EC, "negative zero", -0.00); + + // 6A # text(10) + // 696E66696E6974697479 # "infinitity" + // F9 7C00 # primitive(31744) + QCBOREncode_AddDoubleAsSmallestToMap(&EC, "infinitity", INFINITY); + + // 73 # text(19) + // 6E6567617469766520696E66696E6974697479 # "negative infinitity" + // F9 FC00 # primitive(64512) + QCBOREncode_AddDoubleAsSmallestToMap(&EC, "negative infinitity", -INFINITY); + + // 63 # text(3) + // 4E614E # "NaN" + // F9 7E00 # primitive(32256) + QCBOREncode_AddDoubleAsSmallestToMap(&EC, "NaN", NAN); + + // TODO: test a few NaN variants + + // 63 # text(3) + // 6F6E65 # "one" + // F9 3C00 # primitive(15360) + QCBOREncode_AddDoubleAsSmallestToMap(&EC, "one", 1.0); + + // 69 # text(9) + // 6F6E65207468697264 # "one third" + // F9 3555 # primitive(13653) + QCBOREncode_AddDoubleAsSmallestToMap(&EC, "one third", 0.333251953125); + + // 76 # text(22) + // 6C6172676573742068616C662D707265636973696F6E # "largest half-precision" + // F9 7BFF # primitive(31743) + QCBOREncode_AddDoubleAsSmallestToMap(&EC, "largest half-precision",65504.0); + + // 76 # text(22) + // 6C6172676573742068616C662D707265636973696F6E # "largest half-precision" + // F9 7BFF # primitive(31743) + QCBOREncode_AddDoubleAsSmallestToMap(&EC, "largest half-precision point one",65504.1); + + // Float 65536.0F is 0x47800000 in hex. It has an exponent of 16, which is larger than 15, the largest half-precision exponent + // 78 18 # text(24) + // 746F6F2D6C617267652068616C662D707265636973696F6E # "too-large half-precision" + // FA 47800000 # primitive(31743) + QCBOREncode_AddDoubleAsSmallestToMap(&EC, "too-large half-precision", 65536.0); + + // The smallest possible half-precision subnormal, but digitis are lost converting + // to half, so this turns into a double + // 72 # text(18) + // 736D616C6C657374207375626E6F726D616C # "smallest subnormal" + // FB 3E700000001C5F68 # primitive(4499096027744984936) + QCBOREncode_AddDoubleAsSmallestToMap(&EC, "smallest subnormal", 0.0000000596046448); + + // The smallest possible half-precision snormal, but digitis are lost converting + // to half, so this turns into a single TODO: confirm this is right + // 6F # text(15) + // 736D616C6C657374206E6F726D616C # "smallest normal" + // FA 387FFFFF # primitive(947912703) + // in hex single is 0x387fffff, exponent -15, significand 7fffff + QCBOREncode_AddDoubleAsSmallestToMap(&EC, "smallest normal", 0.0000610351526F); + + // 71 # text(17) + // 62696767657374207375626E6F726D616C # "biggest subnormal" + // F9 0400 # primitive(1024) + // in hex single is 0x38800000, exponent -14, significand 0 + QCBOREncode_AddDoubleAsSmallestToMap(&EC, "biggest subnormal", 0.0000610351563F); + + // 70 # text(16) + // 7375626E6F726D616C2073696E676C65 # "subnormal single" + // FB 37C16C2800000000 # primitive(4017611261645684736) + QCBOREncode_AddDoubleAsSmallestToMap(&EC, "subnormal single", 4e-40F); + + // 03 # unsigned(3) + // F9 C000 # primitive(49152) + QCBOREncode_AddDoubleAsSmallestToMapN(&EC, 3, -2.0); + + // 70 # text(16) + // 6C617267652073696E676C6520657870 # "large single exp" + // FA 7F400000 # primitive(2134900736) + // (0x01LL << (DOUBLE_NUM_SIGNIFICAND_BITS-1)) | ((127LL + DOUBLE_EXPONENT_BIAS) << DOUBLE_EXPONENT_SHIFT); + QCBOREncode_AddDoubleAsSmallestToMap(&EC, "large single exp", 2.5521177519070385E+38); // Exponent fits single + + // 74 # text(20) + // 746F6F2D6C617267652073696E676C6520657870 # "too-large single exp" + // FB 47F8000000000000 # primitive(5185894970917126144) + // (0x01LL << (DOUBLE_NUM_SIGNIFICAND_BITS-1)) | ((128LL + DOUBLE_EXPONENT_BIAS) << DOUBLE_EXPONENT_SHIFT); + QCBOREncode_AddDoubleAsSmallestToMap(&EC, "too-large single exp", 5.104235503814077E+38); // Exponent too large for single + + // 66 # text(6) + // 646664666465 # "dfdfde" + // FA 4B800000 # primitive(1266679808) + QCBOREncode_AddDoubleAsSmallestToMap(&EC, "biggest single with prec",16777216); // Single with no precision loss + + // 78 18 # text(24) + // 626967676573742073696E676C6520776974682070726563 # "biggest single with prec" + // FA 4B800000 # primitive(1266679808) + QCBOREncode_AddDoubleAsSmallestToMap(&EC, "first single with prec loss",16777217); // Double becuase of precision loss + + // Just a convenient marker when cutting and pasting encoded CBOR + QCBOREncode_AddSZStringToMapN(&EC, 1, "fin"); + + QCBOREncode_CloseMap(&EC); + + UsefulBufC EncodedHalfs; + int nReturn = QCBOREncode_Finish(&EC, &EncodedHalfs); + if(nReturn) { + return -1; + } + + if(UsefulBuf_Compare(EncodedHalfs, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spExpectedSmallest))) { + return -3; + } + + return 0; +} + + + +#ifdef NAN_EXPERIMENT +/* + Code for checking what the double to float cast does with + NaNs. Not run as part of tests. Keep it around to + be able to check various platforms and CPUs. + */ + +#define DOUBLE_NUM_SIGNIFICAND_BITS (52) +#define DOUBLE_NUM_EXPONENT_BITS (11) +#define DOUBLE_NUM_SIGN_BITS (1) + +#define DOUBLE_SIGNIFICAND_SHIFT (0) +#define DOUBLE_EXPONENT_SHIFT (DOUBLE_NUM_SIGNIFICAND_BITS) +#define DOUBLE_SIGN_SHIFT (DOUBLE_NUM_SIGNIFICAND_BITS + DOUBLE_NUM_EXPONENT_BITS) + +#define DOUBLE_SIGNIFICAND_MASK (0xfffffffffffffULL) // The lower 52 bits +#define DOUBLE_EXPONENT_MASK (0x7ffULL << DOUBLE_EXPONENT_SHIFT) // 11 bits of exponent +#define DOUBLE_SIGN_MASK (0x01ULL << DOUBLE_SIGN_SHIFT) // 1 bit of sign +#define DOUBLE_QUIET_NAN_BIT (0x01ULL << (DOUBLE_NUM_SIGNIFICAND_BITS-1)) + + +static int NaNExperiments() { + double dqNaN = UsefulBufUtil_CopyUint64ToDouble(DOUBLE_EXPONENT_MASK | DOUBLE_QUIET_NAN_BIT); + double dsNaN = UsefulBufUtil_CopyUint64ToDouble(DOUBLE_EXPONENT_MASK | 0x01); + double dqNaNPayload = UsefulBufUtil_CopyUint64ToDouble(DOUBLE_EXPONENT_MASK | DOUBLE_QUIET_NAN_BIT | 0xf00f); + + float f1 = (float)dqNaN; + float f2 = (float)dsNaN; + float f3 = (float)dqNaNPayload; + + + uint32_t uqNaN = UsefulBufUtil_CopyFloatToUint32((float)dqNaN); + uint32_t usNaN = UsefulBufUtil_CopyFloatToUint32((float)dsNaN); + uint32_t uqNaNPayload = UsefulBufUtil_CopyFloatToUint32((float)dqNaNPayload); + + // Result of this on x86 is that every NaN is a qNaN. The intel + // CVTSD2SS instruction ignores the NaN payload and even converts + // a sNaN to a qNaN. + + return 0; +} +#endif + + + diff --git a/middleware/qcbor/test/float_tests.h b/middleware/qcbor/test/float_tests.h new file mode 100644 index 000000000..b7174c806 --- /dev/null +++ b/middleware/qcbor/test/float_tests.h @@ -0,0 +1,23 @@ +/*============================================================================== + float_tests.h -- tests for float and conversion to/from half-precision + + Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved. + + SPDX-License-Identifier: BSD-3-Clause + + See BSD-3-Clause license in README.md + + Created on 9/19/18 + ==============================================================================*/ + +#ifndef float_tests_h +#define float_tests_h + +int HalfPrecisionDecodeBasicTests(void); + +int DoubleAsSmallestTest(void); + +int HalfPrecisionAgainstRFCCodeTest(void); + + +#endif /* float_tests_h */ diff --git a/middleware/qcbor/test/half_to_double_from_rfc7049.c b/middleware/qcbor/test/half_to_double_from_rfc7049.c new file mode 100644 index 000000000..6380e519a --- /dev/null +++ b/middleware/qcbor/test/half_to_double_from_rfc7049.c @@ -0,0 +1,45 @@ +/* + + Copyright (c) 2013 IETF Trust and the persons identified as the + document authors. All rights reserved. + + This document is subject to BCP 78 and the IETF Trust's Legal + Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info) in effect on the date of + publication of this document. Please review these documents + carefully, as they describe your rights and restrictions with respect + to this document. Code Components extracted from this document must + include Simplified BSD License text as described in Section 4.e of + the Trust Legal Provisions and are provided without warranty as + described in the Simplified BSD License. + + */ + +/* + This code is from RFC 7049. It is not used in the main implementation + because: + a) it adds a dependency on and ldexp(). + b) the license may be an issue + + QCBOR does support half-precision, but rather than using + floating point math like this, it does it with bit shifting + and masking. + + This code is here to test that code. + + */ + +#include "half_to_double_from_rfc7049.h" + +#include + +double decode_half(unsigned char *halfp) { + int half = (halfp[0] << 8) + halfp[1]; + 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; +} diff --git a/middleware/qcbor/test/half_to_double_from_rfc7049.h b/middleware/qcbor/test/half_to_double_from_rfc7049.h new file mode 100644 index 000000000..9f69e35c3 --- /dev/null +++ b/middleware/qcbor/test/half_to_double_from_rfc7049.h @@ -0,0 +1,18 @@ +/*============================================================================== + half_to_double_from_rfc7049.h -- interface to IETF float conversion code. + + Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved. + + SPDX-License-Identifier: BSD-3-Clause + + See BSD-3-Clause license in README.md + + Created on 9/23/18 + ==============================================================================*/ + +#ifndef half_to_double_from_rfc7049_h +#define half_to_double_from_rfc7049_h + +double decode_half(unsigned char *halfp); + +#endif /* half_to_double_from_rfc7049_h */ diff --git a/middleware/qcbor/test/qcbor_decode_tests.c b/middleware/qcbor/test/qcbor_decode_tests.c new file mode 100644 index 000000000..38005c03f --- /dev/null +++ b/middleware/qcbor/test/qcbor_decode_tests.c @@ -0,0 +1,2836 @@ +/*============================================================================== + Copyright (c) 2016-2018, The Linux Foundation. + Copyright (c) 2018-2019, Laurence Lundblade. + All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of The Linux Foundation nor the names of its + contributors, nor the name "Laurence Lundblade" may be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ==============================================================================*/ + +#include "qcbor_decode_tests.h" +#include "qcbor.h" +#include +#include // for fabs() + + +#ifdef PRINT_FUNCTIONS_FOR_DEBUGGING +#include + +static void PrintUsefulBufC(const char *szLabel, UsefulBufC Buf) +{ + if(szLabel) { + printf("%s ", szLabel); + } + + size_t i; + for(i = 0; i < Buf.len; i++) { + uint8_t Z = ((uint8_t *)Buf.ptr)[i]; + printf("%02x ", Z); + } + printf("\n"); + + fflush(stdout); +} + +/*static void printencoded(const char *szLabel, const uint8_t *pEncoded, size_t nLen) +{ + PrintUsefulBufC(szLabel, (UsefulBufC){pEncoded, nLen}); +}*/ +#endif + + +static const uint8_t spExpectedEncodedInts[] = { + 0x98, 0x2f, 0x3b, 0x7f, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x3b, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x3a, 0xff, 0xff, 0xff, + 0xff, 0x3a, 0xff, 0xff, 0xff, 0xfe, 0x3a, 0xff, + 0xff, 0xff, 0xfd, 0x3a, 0x7f, 0xff, 0xff, 0xff, + 0x3a, 0x7f, 0xff, 0xff, 0xfe, 0x3a, 0x00, 0x01, + 0x00, 0x01, 0x3a, 0x00, 0x01, 0x00, 0x00, 0x39, + 0xff, 0xff, 0x39, 0xff, 0xfe, 0x39, 0xff, 0xfd, + 0x39, 0x01, 0x00, 0x38, 0xff, 0x38, 0xfe, 0x38, + 0xfd, 0x38, 0x18, 0x37, 0x36, 0x20, 0x00, 0x00, + 0x01, 0x16, 0x17, 0x18, 0x18, 0x18, 0x19, 0x18, + 0x1a, 0x18, 0xfe, 0x18, 0xff, 0x19, 0x01, 0x00, + 0x19, 0x01, 0x01, 0x19, 0xff, 0xfe, 0x19, 0xff, + 0xff, 0x1a, 0x00, 0x01, 0x00, 0x00, 0x1a, 0x00, + 0x01, 0x00, 0x01, 0x1a, 0x00, 0x01, 0x00, 0x02, + 0x1a, 0x7f, 0xff, 0xff, 0xff, 0x1a, 0x7f, 0xff, + 0xff, 0xff, 0x1a, 0x80, 0x00, 0x00, 0x00, 0x1a, + 0x80, 0x00, 0x00, 0x01, 0x1a, 0xff, 0xff, 0xff, + 0xfe, 0x1a, 0xff, 0xff, 0xff, 0xff, 0x1b, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x1b, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x1b, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff}; + + +// return CBOR error or -1 if type of value doesn't match + +static int IntegerValuesParseTestInternal(QCBORDecodeContext *pDCtx) +{ + QCBORItem Item; + int nCBORError; + + if((nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_ARRAY) + return -1; + + if((nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || // Todo; fix this for 32-bit machines + Item.val.int64 != -9223372036854775807LL - 1) + return -1; + + if((nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != -4294967297) + return -1; + + if((nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != -4294967296) + return -1; + + if((nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != -4294967295) + return -1; + + if((nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != -4294967294) + return -1; + + + if((nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != -2147483648) + return -1; + + if((nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != -2147483647) + return -1; + + if((nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != -65538) + return -1; + + if((nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != -65537) + return -1; + + if((nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != -65536) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != -65535) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != -65534) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != -257) + return -1; + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != -256) + return -1; + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != -255) + return -1; + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != -254) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != -25) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != -24) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != -23) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != -1) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 0) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 0) + return -1; + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 1) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 22) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 23) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 24) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 25) + return -1; + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 26) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 254) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 255) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 256) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 257) + return -1; + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 65534) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 65535) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 65536) + return -1; + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 65537) + return -1; + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 65538) + return -1; + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 2147483647) + return -1; + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 2147483647) + return -1; + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 2147483648) + return -1; + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 2147483649) + return -1; + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 4294967294) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 4294967295) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 4294967296) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 4294967297) + return -1; + + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 9223372036854775807LL) + return -1; + + + if(( nCBORError = QCBORDecode_GetNext(pDCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_UINT64 || + Item.val.uint64 != 18446744073709551615ULL) + return -1; + + + if(QCBORDecode_Finish(pDCtx) != QCBOR_SUCCESS) { + return -1; + } + + return 0; +} + + +/* + Tests the decoding of lots of different integers sizes + and values. + */ + +int IntegerValuesParseTest() +{ + int n; + QCBORDecodeContext DCtx; + + QCBORDecode_Init(&DCtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spExpectedEncodedInts), QCBOR_DECODE_MODE_NORMAL); + + n = IntegerValuesParseTestInternal(&DCtx); + + return(n); +} + + +/* + Creates a simple CBOR array and returns it in *pEncoded. The array is malloced + and needs to be freed. This is used by several tests. + + Two of the inputs can be set. Two other items in the array are fixed. + + */ + +static uint8_t spSimpleArrayBuffer[50]; + +static int CreateSimpleArray(int nInt1, int nInt2, uint8_t **pEncoded, size_t *pEncodedLen) +{ + QCBOREncodeContext ECtx; + int nReturn = -1; + + *pEncoded = NULL; + *pEncodedLen = INT32_MAX; + + // loop runs CBOR encoding twice. First with no buffer to + // calculate the length so buffer can be allocated correctly, + // and last with the buffer to do the actual encoding + do { + QCBOREncode_Init(&ECtx, (UsefulBuf){*pEncoded, *pEncodedLen}); + QCBOREncode_OpenArray(&ECtx); + QCBOREncode_AddInt64(&ECtx, nInt1); + QCBOREncode_AddInt64(&ECtx, nInt2); + QCBOREncode_AddBytes(&ECtx, ((UsefulBufC) {"galactic", 8})); + QCBOREncode_AddBytes(&ECtx, ((UsefulBufC) {"haven token", 11})); + QCBOREncode_CloseArray(&ECtx); + + if(QCBOREncode_FinishGetSize(&ECtx, pEncodedLen)) + goto Done; + + if(*pEncoded != NULL) { + nReturn = 0; + goto Done; + } + + // Use static buffer to avoid dependency on malloc() + if(*pEncodedLen > sizeof(spSimpleArrayBuffer)) { + goto Done; + } + *pEncoded = spSimpleArrayBuffer; + + } while(1); + +Done: + return nReturn; +} + + +/* + {"first integer": 42, + "an array of two strings": [ + "string1", "string2" + ], + "map in a map": { + "bytes 1": h'78787878', + "bytes 2": h'79797979', + "another int": 98, + "text 2": "lies, damn lies and statistics" + } + } + */ + +static uint8_t pValidMapEncoded[] = { + 0xa3, 0x6d, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, 0x69, 0x6e, + 0x74, 0x65, 0x67, 0x65, 0x72, 0x18, 0x2a, 0x77, 0x61, 0x6e, + 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x20, 0x6f, 0x66, 0x20, + 0x74, 0x77, 0x6f, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x73, 0x82, 0x67, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x31, + 0x67, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x32, 0x6c, 0x6d, + 0x61, 0x70, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x6d, 0x61, + 0x70, 0xa4, 0x67, 0x62, 0x79, 0x74, 0x65, 0x73, 0x20, 0x31, + 0x44, 0x78, 0x78, 0x78, 0x78, 0x67, 0x62, 0x79, 0x74, 0x65, + 0x73, 0x20, 0x32, 0x44, 0x79, 0x79, 0x79, 0x79, 0x6b, 0x61, + 0x6e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20, 0x69, 0x6e, 0x74, + 0x18, 0x62, 0x66, 0x74, 0x65, 0x78, 0x74, 0x20, 0x32, 0x78, + 0x1e, 0x6c, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x64, 0x61, 0x6d, + 0x6e, 0x20, 0x6c, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, + 0x20, 0x73, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, + 0x73 } ; + +static int ParseOrderedArray(const uint8_t *pEncoded, size_t nLen, int64_t *pInt1, int64_t *pInt2, const uint8_t **pBuf3, size_t *pBuf3Len, const uint8_t **pBuf4, size_t *pBuf4Len) +{ + QCBORDecodeContext DCtx; + QCBORItem Item; + int nReturn = -1; // assume error until success + + QCBORDecode_Init(&DCtx, (UsefulBufC){pEncoded, nLen}, QCBOR_DECODE_MODE_NORMAL); + + // Make sure the first thing is a map + if(QCBORDecode_GetNext(&DCtx, &Item) != 0 || Item.uDataType != QCBOR_TYPE_ARRAY) + goto Done; + + // First integer + if(QCBORDecode_GetNext(&DCtx, &Item) != 0 || Item.uDataType != QCBOR_TYPE_INT64) + goto Done; + *pInt1 = Item.val.int64; + + // Second integer + if(QCBORDecode_GetNext(&DCtx, &Item) != 0 || Item.uDataType != QCBOR_TYPE_INT64) + goto Done; + *pInt2 = Item.val.int64; + + // First string + if(QCBORDecode_GetNext(&DCtx, &Item) != 0 || Item.uDataType != QCBOR_TYPE_BYTE_STRING) + goto Done; + *pBuf3 = Item.val.string.ptr; + *pBuf3Len = Item.val.string.len; + + // Second string + if(QCBORDecode_GetNext(&DCtx, &Item) != 0 || Item.uDataType != QCBOR_TYPE_BYTE_STRING) + goto Done; + *pBuf4 = Item.val.string.ptr; + *pBuf4Len = Item.val.string.len; + + nReturn = 0; + +Done: + return(nReturn); +} + + + + +int SimpleArrayTest() +{ + uint8_t *pEncoded; + size_t nEncodedLen; + + int64_t i1=0, i2=0; + size_t i3=0, i4=0; + const uint8_t *s3= (uint8_t *)""; + const uint8_t *s4= (uint8_t *)""; + + + if(CreateSimpleArray(23, 6000, &pEncoded, &nEncodedLen) < 0) { + return(-1); + } + + ParseOrderedArray(pEncoded, nEncodedLen, &i1, &i2, &s3, &i3, &s4, &i4); + + if(i1 != 23 || + i2 != 6000 || + i3 != 8 || + i4 != 11 || + memcmp("galactic", s3, 8) !=0 || + memcmp("haven token", s4, 11) !=0) { + return(-1); + } + + return(0); +} + + + +static uint8_t spDeepArrays[] = {0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x80}; + +int ParseDeepArrayTest() +{ + QCBORDecodeContext DCtx; + int nReturn = 0; + int i; + + QCBORDecode_Init(&DCtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spDeepArrays), QCBOR_DECODE_MODE_NORMAL); + + for(i = 0; i < 10; i++) { + QCBORItem Item; + + if(QCBORDecode_GetNext(&DCtx, &Item) != 0 || + Item.uDataType != QCBOR_TYPE_ARRAY || + Item.uNestingLevel != i) { + nReturn = -1; + break; + } + } + + return(nReturn); +} + +// Big enough to test nesting to the depth of 24 +static uint8_t spTooDeepArrays[] = {0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x80}; + +int ParseTooDeepArrayTest() +{ + QCBORDecodeContext DCtx; + int nReturn = 0; + int i; + QCBORItem Item; + + + QCBORDecode_Init(&DCtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spTooDeepArrays), QCBOR_DECODE_MODE_NORMAL); + + for(i = 0; i < QCBOR_MAX_ARRAY_NESTING1; i++) { + + if(QCBORDecode_GetNext(&DCtx, &Item) != 0 || + Item.uDataType != QCBOR_TYPE_ARRAY || + Item.uNestingLevel != i) { + nReturn = -1; + break; + } + } + + if(QCBORDecode_GetNext(&DCtx, &Item) != QCBOR_ERR_ARRAY_NESTING_TOO_DEEP) + nReturn = -1; + + return(nReturn); +} + + + + +int ShortBufferParseTest() +{ + int nResult = 0; + QCBORDecodeContext DCtx; + int num; + + for(num = sizeof(spExpectedEncodedInts)-1; num; num--) { + int n; + + QCBORDecode_Init(&DCtx, (UsefulBufC){spExpectedEncodedInts, num}, QCBOR_DECODE_MODE_NORMAL); + + n = IntegerValuesParseTestInternal(&DCtx); + + //printf("Len %d, result: %d\n", num, n); + + if(n != QCBOR_ERR_HIT_END) { + nResult = -1; + goto Done; + } + } +Done: + return nResult; +} + + + +int ShortBufferParseTest2() +{ + uint8_t *pEncoded; + int nReturn; + size_t nEncodedLen; + + int64_t i1, i2; + size_t i3, i4; + const uint8_t *s3, *s4; + + nReturn = 0; + + if(CreateSimpleArray(23, 6000, &pEncoded, &nEncodedLen) < 0) { + return(-1); + } + + for(nEncodedLen--; nEncodedLen; nEncodedLen--) { + int nResult = ParseOrderedArray(pEncoded, (uint32_t)nEncodedLen, &i1, &i2, &s3, &i3, &s4, &i4); + if(nResult == 0) { + nReturn = -1; + } + } + + return(nReturn); +} + +/* + Decode and thoroughly check a moderately complex + set of maps + */ +static int ParseMapTest1(QCBORDecodeMode nMode) +{ + QCBORDecodeContext DCtx; + QCBORItem Item; + int nCBORError; + + QCBORDecode_Init(&DCtx, (UsefulBufC){pValidMapEncoded, sizeof(pValidMapEncoded)}, nMode); + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uDataType != QCBOR_TYPE_MAP || + Item.val.uCount != 3) + return -1; + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_TEXT_STRING || + Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 42 || + Item.uDataAlloc || + Item.uLabelAlloc || + UsefulBuf_Compare(Item.label.string, UsefulBuf_FromSZ("first integer"))) { + return -1; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_TEXT_STRING || + Item.uDataAlloc || + Item.uLabelAlloc || + UsefulBuf_Compare(Item.label.string, UsefulBuf_FromSZ("an array of two strings")) || + Item.uDataType != QCBOR_TYPE_ARRAY || + Item.val.uCount != 2) + return -1; + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uDataType != QCBOR_TYPE_TEXT_STRING || + Item.uDataAlloc || + Item.uLabelAlloc || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("string1"))) { + return -1; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uDataType != QCBOR_TYPE_TEXT_STRING || + Item.uDataAlloc || + Item.uLabelAlloc || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("string2"))) { + return -1; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_TEXT_STRING || + Item.uDataAlloc || + Item.uLabelAlloc || + UsefulBuf_Compare(Item.label.string, UsefulBuf_FromSZ("map in a map")) || + Item.uDataType != QCBOR_TYPE_MAP || + Item.val.uCount != 4) { + return -1; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_TEXT_STRING || + UsefulBuf_Compare(Item.label.string, UsefulBuf_FromSZ("bytes 1"))|| + Item.uDataType != QCBOR_TYPE_BYTE_STRING || + Item.uDataAlloc || + Item.uLabelAlloc || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("xxxx"))) { + return -1; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_TEXT_STRING || + UsefulBuf_Compare(Item.label.string, UsefulBuf_FromSZ("bytes 2")) || + Item.uDataType != QCBOR_TYPE_BYTE_STRING || + Item.uDataAlloc || + Item.uLabelAlloc || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("yyyy"))) { + return -1; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_TEXT_STRING || + Item.uDataAlloc || + Item.uLabelAlloc || + UsefulBuf_Compare(Item.label.string, UsefulBuf_FromSZ("another int")) || + Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 98) + return -1; + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_TEXT_STRING || + UsefulBuf_Compare(Item.label.string, UsefulBuf_FromSZ("text 2"))|| + Item.uDataType != QCBOR_TYPE_TEXT_STRING || + Item.uDataAlloc || + Item.uLabelAlloc || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("lies, damn lies and statistics"))) { + return -1; + } + + return 0; +} + + +/* + Decode and thoroughly check a moderately complex + set of maps + */ +int ParseMapAsArrayTest() +{ + QCBORDecodeContext DCtx; + QCBORItem Item; + int nCBORError; + + QCBORDecode_Init(&DCtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pValidMapEncoded), QCBOR_DECODE_MODE_MAP_AS_ARRAY); + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uDataType != QCBOR_TYPE_MAP_AS_ARRAY || + Item.val.uCount != 6) { + return -1; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uDataType != QCBOR_TYPE_TEXT_STRING || + Item.uDataAlloc || + Item.uLabelAlloc || + Item.uLabelType != QCBOR_TYPE_NONE || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("first integer"))) { + return -2; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_NONE || + Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 42 || + Item.uDataAlloc || + Item.uLabelAlloc) { + return -3; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_NONE || + Item.uDataAlloc || + Item.uLabelAlloc || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("an array of two strings")) || + Item.uDataType != QCBOR_TYPE_TEXT_STRING) { + return -4; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_NONE || + Item.uDataAlloc || + Item.uLabelAlloc || + Item.uDataType != QCBOR_TYPE_ARRAY || + Item.val.uCount != 2) { + return -5; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uDataType != QCBOR_TYPE_TEXT_STRING || + Item.val.string.len != 7 || + Item.uDataAlloc || + Item.uLabelAlloc || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("string1"))) { + return -6; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uDataType != QCBOR_TYPE_TEXT_STRING || + Item.uDataAlloc || + Item.uLabelAlloc || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("string2"))) { + return -7; + } + + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_NONE || + Item.uDataAlloc || + Item.uLabelAlloc || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("map in a map"))) { + return -8; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_NONE || + Item.uDataAlloc || + Item.uLabelAlloc || + Item.uDataType != QCBOR_TYPE_MAP_AS_ARRAY || + Item.val.uCount != 8) { + return -9; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_NONE || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("bytes 1"))|| + Item.uDataType != QCBOR_TYPE_TEXT_STRING || + Item.uDataAlloc || + Item.uLabelAlloc) { + return -10; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_NONE || + Item.uDataType != QCBOR_TYPE_BYTE_STRING || + Item.uDataAlloc || + Item.uLabelAlloc || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("xxxx"))) { + return -11; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_NONE || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("bytes 2")) || + Item.uDataType != QCBOR_TYPE_TEXT_STRING || + Item.uDataAlloc || + Item.uLabelAlloc) { + return -12; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_NONE || + Item.uDataType != QCBOR_TYPE_BYTE_STRING || + Item.uDataAlloc || + Item.uLabelAlloc || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("yyyy"))) { + return -13; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_NONE || + Item.uDataAlloc || + Item.uLabelAlloc || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("another int")) || + Item.uDataType != QCBOR_TYPE_TEXT_STRING) { + return -14; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_NONE || + Item.uDataAlloc || + Item.uLabelAlloc || + Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 98) { + return -15; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_NONE || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("text 2"))|| + Item.uDataType != QCBOR_TYPE_TEXT_STRING || + Item.uDataAlloc || + Item.uLabelAlloc) { + return -16; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_NONE || + Item.uDataType != QCBOR_TYPE_TEXT_STRING || + Item.uDataAlloc || + Item.uLabelAlloc || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("lies, damn lies and statistics"))) { + return -17; + } + + return 0; +} + + +/* + Fully or partially decode pValidMapEncoded. When + partially decoding check for the right error code. + How much partial decoding depends on nLevel. + + The partial decodes test error conditions of + incomplete encoded input. + + This could be combined with the above test + and made prettier and maybe a little more + thorough. + */ +static int ExtraBytesTest(int nLevel) +{ + QCBORDecodeContext DCtx; + QCBORItem Item; + int nCBORError; + + QCBORDecode_Init(&DCtx, (UsefulBufC){pValidMapEncoded, sizeof(pValidMapEncoded)}, QCBOR_DECODE_MODE_NORMAL); + + if(nLevel < 1) { + if(QCBORDecode_Finish(&DCtx) != QCBOR_ERR_EXTRA_BYTES) { + return -1; + } else { + return 0; + } + } + + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uDataType != QCBOR_TYPE_MAP || + Item.val.uCount != 3) + return -1; + + if(nLevel < 2) { + if(QCBORDecode_Finish(&DCtx) != QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN) { + return -1; + } else { + return 0; + } + } + + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_TEXT_STRING || + Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.uCount != 42 || + UsefulBuf_Compare(Item.label.string, UsefulBuf_FromSZ("first integer"))) { + return -1; + } + + if(nLevel < 3) { + if(QCBORDecode_Finish(&DCtx) != QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN) { + return -1; + } else { + return 0; + } + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_TEXT_STRING || + UsefulBuf_Compare(Item.label.string, UsefulBuf_FromSZ("an array of two strings")) || + Item.uDataType != QCBOR_TYPE_ARRAY || + Item.val.uCount != 2) { + return -1; + } + + + if(nLevel < 4) { + if(QCBORDecode_Finish(&DCtx) != QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN) { + return -1; + } else { + return 0; + } + } + + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uDataType != QCBOR_TYPE_TEXT_STRING || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("string1"))) { + return -1; + } + + if(nLevel < 5) { + if(QCBORDecode_Finish(&DCtx) != QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN) { + return -1; + } else { + return 0; + } + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uDataType != QCBOR_TYPE_TEXT_STRING || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("string2"))) { + return -1; + } + + if(nLevel < 6) { + if(QCBORDecode_Finish(&DCtx) != QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN) { + return -1; + } else { + return 0; + } + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_TEXT_STRING || + UsefulBuf_Compare(Item.label.string, UsefulBuf_FromSZ("map in a map")) || + Item.uDataType != QCBOR_TYPE_MAP || + Item.val.uCount != 4) + return -1; + + if(nLevel < 7) { + if(QCBORDecode_Finish(&DCtx) != QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN) { + return -1; + } else { + return 0; + } + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_TEXT_STRING || + UsefulBuf_Compare(Item.label.string, UsefulBuf_FromSZ("bytes 1")) || + Item.uDataType != QCBOR_TYPE_BYTE_STRING || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("xxxx"))) { + return -1; + } + + if(nLevel < 8) { + if(QCBORDecode_Finish(&DCtx) != QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN) { + return -1; + } else { + return 0; + } + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_TEXT_STRING || + UsefulBuf_Compare(Item.label.string, UsefulBuf_FromSZ("bytes 2")) || + Item.uDataType != QCBOR_TYPE_BYTE_STRING || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("yyyy"))) { + return -1; + } + + if(nLevel < 9) { + if(QCBORDecode_Finish(&DCtx) != QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN) { + return -1; + } else { + return 0; + } + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_TEXT_STRING || + UsefulBuf_Compare(Item.label.string, UsefulBuf_FromSZ("another int")) || + Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != 98) + return -1; + + if(nLevel < 10) { + if(QCBORDecode_Finish(&DCtx) != QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN) { + return -1; + } else { + return 0; + } + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) { + return nCBORError; + } + if(Item.uLabelType != QCBOR_TYPE_TEXT_STRING || + UsefulBuf_Compare(Item.label.string, UsefulBuf_FromSZ("text 2"))|| + Item.uDataType != QCBOR_TYPE_TEXT_STRING || + UsefulBuf_Compare(Item.val.string, UsefulBuf_FromSZ("lies, damn lies and statistics"))) { + return -1; + } + + if(QCBORDecode_Finish(&DCtx)) { + return -1; + } + + return 0; +} + + + + +int ParseMapTest() +{ + // Parse a moderatly complex map structure very thoroughl + int n = ParseMapTest1(QCBOR_DECODE_MODE_NORMAL); + + n = ParseMapTest1(QCBOR_DECODE_MODE_MAP_STRINGS_ONLY); + + if(!n) { + for(int i = 0; i < 10; i++) { + n = ExtraBytesTest(i); + if(n) { + break; + } + } + } + + return(n); +} + + +static uint8_t spSimpleValues[] = {0x8a, 0xf4, 0xf5, 0xf6, 0xf7, 0xff, 0xe0, 0xf3, 0xf8, 0x00, 0xf8, 0x13, 0xf8, 0x1f, 0xf8, 0x20, 0xf8, 0xff}; + +int ParseSimpleTest() +{ + QCBORDecodeContext DCtx; + QCBORItem Item; + int nCBORError; + + + QCBORDecode_Init(&DCtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spSimpleValues), QCBOR_DECODE_MODE_NORMAL); + + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_ARRAY || + Item.val.uCount != 10) + return -1; + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_FALSE) + return -1; + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_TRUE) + return -1; + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_NULL) + return -1; + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_UNDEF) + return -1; + + // A break + if(QCBORDecode_GetNext(&DCtx, &Item) != QCBOR_ERR_BAD_BREAK) + return -1; + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_UKNOWN_SIMPLE || Item.val.uSimple != 0) + return -1; + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_UKNOWN_SIMPLE || Item.val.uSimple != 19) + return -1; + + if(QCBORDecode_GetNext(&DCtx, &Item) != QCBOR_ERR_INVALID_CBOR) + return -1; + + if(QCBORDecode_GetNext(&DCtx, &Item) != QCBOR_ERR_INVALID_CBOR) + return -1; + + if(QCBORDecode_GetNext(&DCtx, &Item) != QCBOR_ERR_INVALID_CBOR) + return -1; + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_UKNOWN_SIMPLE || Item.val.uSimple != 32) + return -1; + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_UKNOWN_SIMPLE || Item.val.uSimple != 255) + return -1; + + return 0; + +} + + +struct FailInput { + UsefulBufC Input; + int nError; +}; + + +struct FailInput Failures[] = { + { {(uint8_t[]){0x18}, 1}, QCBOR_ERR_HIT_END }, // 1 byte integer missing the byte + { {(uint8_t[]){0x1c}, 1}, QCBOR_ERR_UNSUPPORTED }, // Reserved additional info = 28 + { {(uint8_t[]){0x1d}, 1}, QCBOR_ERR_UNSUPPORTED }, // Reserved additional info = 29 + { {(uint8_t[]){0x1e}, 1}, QCBOR_ERR_UNSUPPORTED }, // Reserved additional info = 30 + { {(uint8_t[]){0x1f}, 1}, QCBOR_ERR_UNSUPPORTED }, // Indefinite length integer + { {(uint8_t[]){0x3c}, 1}, QCBOR_ERR_UNSUPPORTED }, // 1 byte integer missing the byte + { {(uint8_t[]){0x3d}, 1}, QCBOR_ERR_UNSUPPORTED }, // 1 byte integer missing the byte + { {(uint8_t[]){0x3e}, 1}, QCBOR_ERR_UNSUPPORTED }, // 1 byte integer missing the byte + { {(uint8_t[]){0x3f}, 1}, QCBOR_ERR_UNSUPPORTED }, // Indefinite length negative integer + { {(uint8_t[]){0x41}, 1}, QCBOR_ERR_HIT_END }, // Short byte string + { {(uint8_t[]){0x5c}, 1}, QCBOR_ERR_UNSUPPORTED }, // Reserved additional info = 28 + { {(uint8_t[]){0x5f}, 1}, QCBOR_ERR_UNSUPPORTED }, // Indefinite length byte string + { {(uint8_t[]){0x61}, 1}, QCBOR_ERR_HIT_END }, // Short UTF-8 string + { {(uint8_t[]){0x7c}, 1}, QCBOR_ERR_UNSUPPORTED }, // Reserved additional info = 28 + { {(uint8_t[]){0x7f}, 1}, QCBOR_ERR_UNSUPPORTED }, // Indefinite length UTF-8 string + { {(uint8_t[]){0xff}, 1}, QCBOR_ERR_UNSUPPORTED } , // break + { {(uint8_t[]){0xf8, 0x00}, 2}, QCBOR_ERR_INVALID_CBOR }, // An invalid encoding of a simple type + { {(uint8_t[]){0xf8, 0x1f}, 2}, QCBOR_ERR_INVALID_CBOR }, // An invalid encoding of a simple type + { {(uint8_t[]){0xc0, 0x00}, 2}, QCBOR_ERR_BAD_OPT_TAG }, // Text-based date, with an integer + { {(uint8_t[]){0xc1, 0x41, 0x33}, 3}, QCBOR_ERR_BAD_OPT_TAG }, // Epoch date, with an byte string + { {(uint8_t[]){0xc1, 0xc0, 0x00}, 3}, QCBOR_ERR_BAD_OPT_TAG }, // tagged as both epoch and string dates + { {(uint8_t[]){0xc2, 0x00}, 2}, QCBOR_ERR_BAD_OPT_TAG } // big num tagged an int, not a byte string + +}; + + +int FailureTests() +{ + int nResult = 0; + + struct FailInput *pFEnd = &Failures[0] + sizeof(Failures)/sizeof(struct FailInput); + + for(struct FailInput *pF = &Failures[0]; pF < pFEnd ;pF++) { + QCBORDecodeContext DCtx; + QCBORItem Item; + int nCBORError; + + QCBORDecode_Init(&DCtx, pF->Input, QCBOR_DECODE_MODE_NORMAL); + + while(1) { + nCBORError = QCBORDecode_GetNext(&DCtx, &Item); + if(QCBOR_ERR_HIT_END == nCBORError) { + break; + } + if(nCBORError != pF->nError) { + nResult = 1; + break; + } + } + } + + { + QCBORDecodeContext DCtx; + QCBORItem Item; + int nCBORError; + + QCBORDecode_Init(&DCtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spSimpleValues), QCBOR_DECODE_MODE_NORMAL); + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return nCBORError; + if(Item.uDataType != QCBOR_TYPE_ARRAY || + Item.val.uCount != 10) + return -1; + + DCtx.InBuf.magic = 0; // Corrupt the UsefulInputBuf + + nCBORError = QCBORDecode_GetNext(&DCtx, &Item); + if(nCBORError != QCBOR_ERR_HIT_END) + return -1; + } + + + return nResult; +} + + +/* Try all 256 values of the byte at nLen including recursing for + each of the values to try values at nLen+1 ... up to nLenMax + */ +static void ComprehensiveInputRecurser(uint8_t *pBuf, int nLen, int nLenMax) +{ + if(nLen >= nLenMax) { + return; + } + + for(int inputByte = 0; inputByte < 256; inputByte++) { + // Set up the input + pBuf[nLen] = inputByte; + const UsefulBufC Input = {pBuf, nLen+1}; + + // Get ready to parse + QCBORDecodeContext DCtx; + QCBORDecode_Init(&DCtx, Input, QCBOR_DECODE_MODE_NORMAL); + + // Parse by getting the next item until an error occurs + // Just about every possible decoder error can occur here + // The goal of this test is not to check for the correct + // error since that is not really possible. It is to + // see that there is no crash on hostile input. + while(1) { + QCBORItem Item; + QCBORError nCBORError = QCBORDecode_GetNext(&DCtx, &Item); + if(nCBORError != QCBOR_SUCCESS) { + break; + } + } + + ComprehensiveInputRecurser(pBuf, nLen+1, nLenMax); + } +} + + +/* + Public function for initialization. See header qcbor.h + */ +int ComprehensiveInputTest() +{ + // Size 2 tests 64K inputs and runs quickly + uint8_t pBuf[2]; + + ComprehensiveInputRecurser(pBuf, 0, sizeof(pBuf)); + + return 0; +} + + +/* + Public function for initialization. See header qcbor.h + */ +int BigComprehensiveInputTest() +{ + // size 3 tests 16 million inputs and runs OK + // in seconds on fast machines. Size 4 takes + // 10+ minutes and 5 half a day on fast + // machines. This test is kept separate from + // the others so as to no slow down the use + // of them as a very frequent regression. + uint8_t pBuf[3]; // + + ComprehensiveInputRecurser(pBuf, 0, sizeof(pBuf)); + + return 0; +} + + +static uint8_t spDateTestInput[] = { + 0xc0, // tag for string date + 0x6a, '1','9','8','5','-','0','4','-','1','2', // Date string + + 0xc1, // tag for epoch date + 0x1a, 0x53, 0x72, 0x4E, 0x00, // Epoch date 1400000000; Tue, 13 May 2014 16:53:20 GMT + + // CBOR_TAG_B64 + 0xc1, 0xcf, 0xd8, 0x22, // 0xee, // Epoch date with extra tags TODO: fix this test + 0x1a, 0x53, 0x72, 0x4E, 0x01, + + 0xc1, // tag for epoch date + 0x1b, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // Too large integer + + 0xc1, // tag for epoch date + 0xfa, 0x3f, 0x8c, 0xcc, 0xcd, // double with value 1.1 + + 0xc1, // tag for epoch date + 0xfa, 0x7f, 0x7f, 0xff, 0xff // 3.4028234663852886e+38 too large + +}; + + +// have to check float expected only to within an epsilon +int CHECK_EXPECTED_DOUBLE(double val, double expected) { + + double diff = val - expected; + + diff = fabs(diff); + + return diff > 0.0000001; +} + + +int DateParseTest() +{ + QCBORDecodeContext DCtx; + QCBORItem Item; + int nCBORError; + + QCBORDecode_Init(&DCtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spDateTestInput), QCBOR_DECODE_MODE_NORMAL); + + const uint64_t uTags[] = {15}; + QCBORTagListIn TagList = {1, uTags}; + + QCBORDecode_SetCallerConfiguredTagList(&DCtx, &TagList); + + // String date + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return -1; + if(Item.uDataType != QCBOR_TYPE_DATE_STRING || + UsefulBuf_Compare(Item.val.dateString, UsefulBuf_FromSZ("1985-04-12"))){ + return -2; + } + + // Epoch date + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return -3; + if(Item.uDataType != QCBOR_TYPE_DATE_EPOCH || + Item.val.epochDate.nSeconds != 1400000000 || + Item.val.epochDate.fSecondsFraction != 0 ) { + return -4; + } + + // Epoch date with extra CBOR_TAG_B64 tag that doesn't really mean anything + // but want to be sure extra tag doesn't cause a problem + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return -5; + if(Item.uDataType != QCBOR_TYPE_DATE_EPOCH || + Item.val.epochDate.nSeconds != 1400000001 || + Item.val.epochDate.fSecondsFraction != 0 || + !QCBORDecode_IsTagged(&DCtx, &Item, CBOR_TAG_B64)) { + return -6; + } + + // Epoch date that is too large for our representation + if(QCBORDecode_GetNext(&DCtx, &Item) != QCBOR_ERR_DATE_OVERFLOW) { + return -7; + } + + // Epoch date in float format with fractional seconds + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return -8; + if(Item.uDataType != QCBOR_TYPE_DATE_EPOCH || + Item.val.epochDate.nSeconds != 1 || + CHECK_EXPECTED_DOUBLE(Item.val.epochDate.fSecondsFraction, 0.1 )) { + return -9; + } + + // Epoch date float that is too large for our representation + if(QCBORDecode_GetNext(&DCtx, &Item) != QCBOR_ERR_DATE_OVERFLOW) { + return -10; + } + + // TODO: could use a few more tests with float, double, and half precsion and negative (but coverage is still pretty good) + + return 0; +} + +// Really simple basic input for tagging test +static uint8_t spOptTestInput[] = { + 0xd9, 0xd9, 0xf7, // CBOR magic number + 0x81, // Array of one + 0xd8, 0x04, // non-preferred serialization of tag 4 + 0x82, 0x01, 0x03}; // fraction 1/3 + +static uint8_t spEncodedLargeTag[] = {0xdb, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x80}; + +// 0x9192939495969798, 0x88, 0x01, 0x04 +static uint8_t spLotsOfTags[] = {0xdb, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0xd8, 0x88, 0xc5, 0xc4, 0x80}; + +/* + The cbor.me parse of this. + 55799(55799(55799({6(7(-23)): 5859837686836516696(7({7(-20): 11({17(-18): 17(17(17("Organization"))), + 9(-17): 773("SSG"), -15: 4(5(6(7(8(9(10(11(12(13(14(15("Confusion")))))))))))), 17(-16): 17("San Diego"), + 17(-14): 17("US")}), 23(-19): 19({-11: 9({-9: -7}), + 90599561(90599561(90599561(-10))): 12(h'0102030405060708090A')})})), + 16(-22): 23({11(8(7(-5))): 8(-3)})}))) + */ +static uint8_t spCSRWithTags[] = { + 0xd9, 0xd9, 0xf7, 0xd9, 0xd9, 0xf7, 0xd9, 0xd9, 0xf7, 0xa2, + 0xc6, 0xc7, 0x36, + 0xdb, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0xc7, 0xa2, + 0xda, 0x00, 0x00, 0x00, 0x07, 0x33, + 0xcb, 0xa5, + 0xd1, 0x31, + 0xd1, 0xd1, 0xd1, 0x6c, + 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0xc9, 0x30, + 0xd9, 0x03, 0x05, 0x63, + 0x53, 0x53, 0x47, + 0x2e, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0x69, + 0x43, 0x6f, 0x6e, 0x66, 0x75, 0x73, 0x69, 0x6f, 0x6e, + 0xd1, 0x2f, + 0xd1, 0x69, + 0x53, 0x61, 0x6e, 0x20, 0x44, 0x69, 0x65, 0x67, 0x6f, + 0xd1, 0x2d, + 0xd1, 0x62, + 0x55, 0x53, + 0xd7, 0x32, + 0xd3, 0xa2, + 0x2a, + 0xc9, 0xa1, + 0x28, + 0x26, + 0xda, 0x05, 0x66, 0x70, 0x89, 0xda, 0x05, 0x66, 0x70, 0x89, 0xda, 0x05, 0x66, 0x70, 0x89, 0x29, + 0xcc, 0x4a, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,0x07, 0x08, 0x09, 0x0a, + 0xd0, 0x35, + 0xd7, 0xa1, + 0xcb, 0xc8, 0xc7, 0x24, + 0xc8, 0x22}; + +static int CheckCSRMaps(QCBORDecodeContext *pDC); + + +int OptTagParseTest() +{ + QCBORDecodeContext DCtx; + QCBORItem Item; + + QCBORDecode_Init(&DCtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spOptTestInput), QCBOR_DECODE_MODE_NORMAL); + + //------------------------- + // This text matches the magic number tag and the fraction tag + if(QCBORDecode_GetNext(&DCtx, &Item)) { + return -2; + } + if(Item.uDataType != QCBOR_TYPE_ARRAY || + !QCBORDecode_IsTagged(&DCtx, &Item, CBOR_TAG_CBOR_MAGIC)) { + return -3; + } + + if(QCBORDecode_GetNext(&DCtx, &Item)) { + return -4; + } + if(Item.uDataType != QCBOR_TYPE_ARRAY || + !QCBORDecode_IsTagged(&DCtx, &Item, CBOR_TAG_FRACTION) || + Item.val.uCount != 2) { + return -5; + } + + // -------------------------------- + // This test decodes the very large tag, but it is not in + // any list so it is ignored. + QCBORDecode_Init(&DCtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spEncodedLargeTag), QCBOR_DECODE_MODE_NORMAL); + if(QCBORDecode_GetNext(&DCtx, &Item)) { + return -6; + } + if(Item.uTagBits) { + return -7; + } + + // ---------------------------------- + // This test sets up a caller-config list that includes the very large tage and then matches it. + QCBORDecode_Init(&DCtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spEncodedLargeTag), QCBOR_DECODE_MODE_NORMAL); + const uint64_t puList[] = {0x9192939495969798, 257}; + const QCBORTagListIn TL = {2, puList}; + QCBORDecode_SetCallerConfiguredTagList(&DCtx, &TL); + + if(QCBORDecode_GetNext(&DCtx, &Item)) { + return -8; + } + if(Item.uDataType != QCBOR_TYPE_ARRAY || + !QCBORDecode_IsTagged(&DCtx, &Item, 0x9192939495969798) || + QCBORDecode_IsTagged(&DCtx, &Item, 257) || + QCBORDecode_IsTagged(&DCtx, &Item, CBOR_TAG_BIGFLOAT) || + Item.val.uCount != 0) { + return -9; + } + + //------------------------ + // This test sets up a caller-configured list, and looks up something not in it + const uint64_t puLongList[17] = {1,2,1}; + const QCBORTagListIn TLLong = {17, puLongList}; + QCBORDecode_Init(&DCtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spEncodedLargeTag), QCBOR_DECODE_MODE_NORMAL); + QCBORDecode_SetCallerConfiguredTagList(&DCtx, &TLLong); + if(QCBORDecode_GetNext(&DCtx, &Item)) { + return -11; + } + + // ----------------------- + // This tests retrievel of the full tag list + QCBORDecode_Init(&DCtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spLotsOfTags), QCBOR_DECODE_MODE_NORMAL); + uint64_t puTags[16]; + QCBORTagListOut Out = {0, 4, puTags}; + if(QCBORDecode_GetNextWithTags(&DCtx, &Item, &Out)) { + return -12; + } + if(puTags[0] != 0x9192939495969798 || + puTags[1] != 0x88 || + puTags[2] != 0x05 || + puTags[3] != 0x04) { + return -13; + } + + // ---------------------- + // This text if too small of an out list + QCBORDecode_Init(&DCtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spLotsOfTags), QCBOR_DECODE_MODE_NORMAL); + QCBORTagListOut OutSmall = {0, 3, puTags}; + if(QCBORDecode_GetNextWithTags(&DCtx, &Item, &OutSmall) != QCBOR_ERR_TOO_MANY_TAGS) { + return -14; + } + + // --------------- + // Parse a version of the "CSR" that has had a ton of tags randomly inserted + QCBORDecode_Init(&DCtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spCSRWithTags), QCBOR_DECODE_MODE_NORMAL); + int n = CheckCSRMaps(&DCtx); + if(n) { + return n-2000; + } + + Out = (QCBORTagListOut){0,16, puTags}; + QCBORDecode_Init(&DCtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spCSRWithTags), QCBOR_DECODE_MODE_NORMAL); + + const uint64_t puTagList[] = {773, 1, 90599561}; + const QCBORTagListIn TagList = {3, puTagList}; + QCBORDecode_SetCallerConfiguredTagList(&DCtx, &TagList); + + + if(QCBORDecode_GetNextWithTags(&DCtx, &Item, &Out)) { + return -100; + } + if(Item.uDataType != QCBOR_TYPE_MAP || + !QCBORDecode_IsTagged(&DCtx, &Item, CBOR_TAG_CBOR_MAGIC) || + QCBORDecode_IsTagged(&DCtx, &Item, 90599561) || + QCBORDecode_IsTagged(&DCtx, &Item, CBOR_TAG_DATE_EPOCH) || + Item.val.uCount != 2 || + puTags[0] != CBOR_TAG_CBOR_MAGIC || + puTags[1] != CBOR_TAG_CBOR_MAGIC || + puTags[2] != CBOR_TAG_CBOR_MAGIC || + Out.uNumUsed != 3) { + return -101; + } + + if(QCBORDecode_GetNextWithTags(&DCtx, &Item, &Out)) { + return -102; + } + if(Item.uDataType != QCBOR_TYPE_MAP || + QCBORDecode_IsTagged(&DCtx, &Item, CBOR_TAG_CBOR_MAGIC) || + QCBORDecode_IsTagged(&DCtx, &Item, 6) || + QCBORDecode_IsTagged(&DCtx, &Item, 7) || // item is tagged 7, but 7 is not configured to be recognized + Item.val.uCount != 2 || + puTags[0] != 5859837686836516696 || + puTags[1] != 7 || + Out.uNumUsed != 2) { + return -103; + } + + if(QCBORDecode_GetNextWithTags(&DCtx, &Item, &Out)) { + return -104; + } + if(Item.uDataType != QCBOR_TYPE_MAP || + Item.uTagBits || + Item.val.uCount != 5 || + puTags[0] != 0x0b || + Out.uNumUsed != 1) { + return -105; + } + + if(QCBORDecode_GetNextWithTags(&DCtx, &Item, &Out)) { + return -106; + } + if(Item.uDataType != QCBOR_TYPE_TEXT_STRING || + !QCBORDecode_IsTagged(&DCtx, &Item, CBOR_TAG_COSE_MAC0) || + Item.val.string.len != 12 || + puTags[0] != CBOR_TAG_COSE_MAC0 || + puTags[1] != CBOR_TAG_COSE_MAC0 || + puTags[2] != CBOR_TAG_COSE_MAC0 || + Out.uNumUsed != 3) { + return -105; + } + + if(QCBORDecode_GetNextWithTags(&DCtx, &Item, &Out)) { + return -107; + } + if(Item.uDataType != QCBOR_TYPE_TEXT_STRING || + !QCBORDecode_IsTagged(&DCtx, &Item, 773) || + Item.val.string.len != 3 || + puTags[0] != 773 || + Out.uNumUsed != 1) { + return -108; + } + + if(QCBORDecode_GetNextWithTags(&DCtx, &Item, &Out)) { + return -109; + } + if(Item.uDataType != QCBOR_TYPE_TEXT_STRING || + !QCBORDecode_IsTagged(&DCtx, &Item, 4) || + Item.val.string.len != 9 || + puTags[0] != 4 || + puTags[11] != 0x0f || + Out.uNumUsed != 12) { + return -110; + } + + if(QCBORDecode_GetNextWithTags(&DCtx, &Item, &Out)) { + return -111; + } + if(Item.uDataType != QCBOR_TYPE_TEXT_STRING || + !QCBORDecode_IsTagged(&DCtx, &Item, 17) || + Item.val.string.len != 9 || + puTags[0] != 17 || + Out.uNumUsed != 1) { + return -112; + } + + if(QCBORDecode_GetNextWithTags(&DCtx, &Item, &Out)) { + return -111; + } + if(Item.uDataType != QCBOR_TYPE_TEXT_STRING || + !QCBORDecode_IsTagged(&DCtx, &Item, 17) || + Item.val.string.len != 2 || + puTags[0] != 17 || + Out.uNumUsed != 1) { + return -112; + } + + if(QCBORDecode_GetNextWithTags(&DCtx, &Item, &Out)) { + return -113; + } + if(Item.uDataType != QCBOR_TYPE_MAP || + QCBORDecode_IsTagged(&DCtx, &Item, 19) || + Item.val.uCount != 2 || + puTags[0] != 19 || + Out.uNumUsed != 1) { + return -114; + } + + if(QCBORDecode_GetNextWithTags(&DCtx, &Item, &Out)) { + return -115; + } + if(Item.uDataType != QCBOR_TYPE_MAP || + QCBORDecode_IsTagged(&DCtx, &Item, 9) || + Item.uTagBits || + Item.val.uCount != 1 || + puTags[0] != 9 || + Out.uNumUsed != 1) { + return -116; + } + + if(QCBORDecode_GetNextWithTags(&DCtx, &Item, &Out)) { + return -116; + } + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.val.int64 != -7 || + Item.uTagBits || + Out.uNumUsed != 0) { + return -117; + } + + if(QCBORDecode_GetNextWithTags(&DCtx, &Item, &Out)) { + return -118; + } + if(Item.uDataType != QCBOR_TYPE_BYTE_STRING || + Item.val.string.len != 10 || + Item.uTagBits || + puTags[0] != 12 || + Out.uNumUsed != 1) { + return -119; + } + + if(QCBORDecode_GetNextWithTags(&DCtx, &Item, &Out)) { + return -120; + } + if(Item.uDataType != QCBOR_TYPE_MAP || + !QCBORDecode_IsTagged(&DCtx, &Item, CBOR_TAG_ENC_AS_B16) || + Item.val.uCount != 1 || + puTags[0] != 0x17 || + Out.uNumUsed != 1) { + return -121; + } + + if(QCBORDecode_GetNextWithTags(&DCtx, &Item, &Out)) { + return -122; + } + if(Item.uDataType != QCBOR_TYPE_INT64 || + QCBORDecode_IsTagged(&DCtx, &Item, 8) || + Item.val.int64 != -3 || + puTags[0] != 8 || + Out.uNumUsed != 1) { + return -123; + } + + if(QCBORDecode_Finish(&DCtx)) { + return -124; + } + + return 0; +} + + + + +static uint8_t spBigNumInput[] = { + 0x83, + 0xC2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xC3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xA4, + 0x63, 0x42, 0x4E, 0x2B, + 0xC2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x40, + 0xC2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x63, 0x42, 0x4E, 0x2D, + 0xC3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x38, 0x3F, + 0xC3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + +static uint8_t spBigNum[] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + +int BignumParseTest() +{ + QCBORDecodeContext DCtx; + QCBORItem Item; + int nCBORError; + + QCBORDecode_Init(&DCtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spBigNumInput), QCBOR_DECODE_MODE_NORMAL); + + + // + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return -1; + if(Item.uDataType != QCBOR_TYPE_ARRAY) { + return -1; + } + + // + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return -1; + if(Item.uDataType != QCBOR_TYPE_POSBIGNUM || + UsefulBuf_Compare(Item.val.bigNum, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spBigNum))){ + return -1; + } + + // + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return -1; + if(Item.uDataType != QCBOR_TYPE_NEGBIGNUM || + UsefulBuf_Compare(Item.val.bigNum, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spBigNum))){ + return -1; + } + + // + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return -1; + if(Item.uDataType != QCBOR_TYPE_MAP) { + return -1; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return -1; + if(Item.uDataType != QCBOR_TYPE_POSBIGNUM || + Item.uLabelType != QCBOR_TYPE_TEXT_STRING || + UsefulBuf_Compare(Item.val.bigNum, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spBigNum))){ + return -1; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return -1; + if(Item.uDataType != QCBOR_TYPE_POSBIGNUM || + Item.uLabelType != QCBOR_TYPE_INT64 || + Item.label.int64 != 64 || + UsefulBuf_Compare(Item.val.bigNum, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spBigNum))){ + return -1; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return -1; + if(Item.uDataType != QCBOR_TYPE_NEGBIGNUM || + Item.uLabelType != QCBOR_TYPE_TEXT_STRING || + UsefulBuf_Compare(Item.val.bigNum, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spBigNum))){ + return -1; + } + + if((nCBORError = QCBORDecode_GetNext(&DCtx, &Item))) + return -1; + if(Item.uDataType != QCBOR_TYPE_NEGBIGNUM || + Item.uLabelType != QCBOR_TYPE_INT64 || + Item.label.int64 != -64 || + UsefulBuf_Compare(Item.val.bigNum, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spBigNum))){ + return -1; + } + + return 0; +} + + + +static int CheckItemWithIntLabel(QCBORDecodeContext *pCtx, uint8_t uDataType, uint8_t uNestingLevel, uint8_t uNextNest, int64_t nLabel, QCBORItem *pItem) +{ + QCBORItem Item; + int nCBORError; + + if((nCBORError = QCBORDecode_GetNext(pCtx, &Item))) return -1; + if(Item.uDataType != uDataType) return -1; + if(uNestingLevel > 0) { + if(Item.uLabelType != QCBOR_TYPE_INT64 && Item.uLabelType != QCBOR_TYPE_UINT64) return -1; + if(Item.uLabelType == QCBOR_TYPE_INT64) { + if(Item.label.int64 != nLabel) return -1; + } else { + if(Item.label.uint64 != (uint64_t)nLabel) return -1; + } + } + if(Item.uNestingLevel != uNestingLevel) return -1; + if(Item.uNextNestLevel != uNextNest) return -1; + + if(pItem) { + *pItem = Item; + } + return 0; +} + + +// Same code checks definite and indefinite length versions of the map +static int CheckCSRMaps(QCBORDecodeContext *pDC) +{ + if(CheckItemWithIntLabel(pDC, QCBOR_TYPE_MAP, 0, 1, 0, NULL)) return -1; + + if(CheckItemWithIntLabel(pDC, QCBOR_TYPE_MAP, 1, 2, -23, NULL)) return -1; + + if(CheckItemWithIntLabel(pDC, QCBOR_TYPE_MAP, 2, 3, -20, NULL)) return -1; + + if(CheckItemWithIntLabel(pDC, QCBOR_TYPE_TEXT_STRING, 3, 3, -18, NULL)) return -1; + if(CheckItemWithIntLabel(pDC, QCBOR_TYPE_TEXT_STRING, 3, 3, -17, NULL)) return -1; + if(CheckItemWithIntLabel(pDC, QCBOR_TYPE_TEXT_STRING, 3, 3, -15, NULL)) return -1; + if(CheckItemWithIntLabel(pDC, QCBOR_TYPE_TEXT_STRING, 3, 3, -16, NULL)) return -1; + if(CheckItemWithIntLabel(pDC, QCBOR_TYPE_TEXT_STRING, 3, 2, -14, NULL)) return -1; + + if(CheckItemWithIntLabel(pDC, QCBOR_TYPE_MAP, 2, 3, -19, NULL)) return -1; + if(CheckItemWithIntLabel(pDC, QCBOR_TYPE_MAP, 3, 4, -11, NULL)) return -1; + + if(CheckItemWithIntLabel(pDC, QCBOR_TYPE_INT64, 4, 3, -9, NULL)) return -1; + if(CheckItemWithIntLabel(pDC, QCBOR_TYPE_BYTE_STRING, 3, 1, -10, NULL)) return -1; + + if(CheckItemWithIntLabel(pDC, QCBOR_TYPE_MAP, 1, 2, -22, NULL)) return -1; + if(CheckItemWithIntLabel(pDC, QCBOR_TYPE_INT64, 2, 0, -5, NULL)) return -1; + + if(QCBORDecode_Finish(pDC)) return -2; + + return 0; +} + + +/* +// cbor.me decoded output +{ + -23: { + -20: { + -18: "Organization", + -17: "SSG", + -15: "Confusion", + -16: "San Diego", + -14: "US" + }, + -19: { + -11: { + -9: -7 + }, + -10: '\u0001\u0002\u0003\u0004\u0005\u0006\a\b\t\n' + } + }, + -22: { + -5: -3 + } +} + */ + + +static uint8_t spCSRInput[] = { + 0xa2, 0x36, 0xa2, 0x33, 0xa5, 0x31, 0x6c, 0x4f, + 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x30, 0x63, 0x53, 0x53, 0x47, + 0x2e, 0x69, 0x43, 0x6f, 0x6e, 0x66, 0x75, 0x73, + 0x69, 0x6f, 0x6e, 0x2f, 0x69, 0x53, 0x61, 0x6e, + 0x20, 0x44, 0x69, 0x65, 0x67, 0x6f, 0x2d, 0x62, + 0x55, 0x53, 0x32, 0xa2, 0x2a, 0xa1, 0x28, 0x26, + 0x29, 0x4a, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x35, 0xa1, 0x24, 0x22}; + +int NestedMapTest() +{ + QCBORDecodeContext DCtx; + + QCBORDecode_Init(&DCtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spCSRInput), QCBOR_DECODE_MODE_NORMAL); + + return CheckCSRMaps(&DCtx); +} + + + +int StringDecoderModeFailTest() +{ + QCBORDecodeContext DCtx; + + QCBORDecode_Init(&DCtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spCSRInput), QCBOR_DECODE_MODE_MAP_STRINGS_ONLY); + + QCBORItem Item; + QCBORError nCBORError; + + if(QCBORDecode_GetNext(&DCtx, &Item)) { + return -1; + } + if(Item.uDataType != QCBOR_TYPE_MAP) { + return -2; + } + + nCBORError = QCBORDecode_GetNext(&DCtx, &Item); + if(nCBORError != QCBOR_ERR_MAP_LABEL_TYPE) { + return -3; + } + + return 0; +} + + +// Same map as above, but using indefinite lengths +static uint8_t spCSRInputIndefLen[] = { + 0xbf, 0x36, 0xbf, 0x33, 0xbf, 0x31, 0x6c, 0x4f, + 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x30, 0x63, 0x53, 0x53, 0x47, + 0x2e, 0x69, 0x43, 0x6f, 0x6e, 0x66, 0x75, 0x73, + 0x69, 0x6f, 0x6e, 0x2f, 0x69, 0x53, 0x61, 0x6e, + 0x20, 0x44, 0x69, 0x65, 0x67, 0x6f, 0x2d, 0x62, + 0x55, 0x53, 0xff, 0x32, 0xbf, 0x2a, 0xbf, 0x28, + 0x26, 0xff, 0x29, 0x4a, 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0xff, 0xff, + 0x35, 0xbf, 0x24, 0x22, 0xff, 0xff}; + +int NestedMapTestIndefLen() +{ + QCBORDecodeContext DCtx; + + QCBORDecode_Init(&DCtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spCSRInputIndefLen), QCBOR_DECODE_MODE_NORMAL); + + return CheckCSRMaps(&DCtx); +} + + + +static UsefulBufC make_nested_indefinite_arrays(int n, UsefulBuf Storage) +{ + UsefulOutBuf UOB; + UsefulOutBuf_Init(&UOB, Storage); + + int i; + for(i = 0; i < n; i++) { + UsefulOutBuf_AppendByte(&UOB, 0x9f); + } + + for(i = 0; i < n; i++) { + UsefulOutBuf_AppendByte(&UOB, 0xff); + } + return UsefulOutBuf_OutUBuf(&UOB); +} + + +static int parse_indeflen_nested(UsefulBufC Nested, int nNestLevel) +{ + QCBORDecodeContext DC; + QCBORDecode_Init(&DC, Nested, 0); + + int j; + for(j = 0; j < nNestLevel; j++) { + QCBORItem Item; + int nReturn = QCBORDecode_GetNext(&DC, &Item); + if(j >= QCBOR_MAX_ARRAY_NESTING) { + // Should be in error + if(nReturn != QCBOR_ERR_ARRAY_NESTING_TOO_DEEP) { + return -4; + } else { + return 0; // Decoding doesn't recover after an error + } + } else { + // Should be no error + if(nReturn) { + return -9; // Should not have got an error + } + } + if(Item.uDataType != QCBOR_TYPE_ARRAY) { + return -7; + } + } + int nReturn = QCBORDecode_Finish(&DC); + if(nReturn) { + return -3; + } + return 0; +} + + +int IndefiniteLengthNestTest() +{ + UsefulBuf_MAKE_STACK_UB(Storage, 50); + int i; + for(i=1; i < QCBOR_MAX_ARRAY_NESTING+4; i++) { + const UsefulBufC Nested = make_nested_indefinite_arrays(i, Storage); + int nReturn = parse_indeflen_nested(Nested, i); + if(nReturn) { + return nReturn; + } + } + return 0; +} + + + +static const uint8_t spIndefiniteArray[] = {0x9f, 0x01, 0x82, 0x02, 0x03, 0xff}; // [1, [2, 3]] +static const uint8_t spIndefiniteArrayBad1[] = {0x9f}; // No closing break +static const uint8_t spIndefiniteArrayBad2[] = {0x9f, 0x9f, 0x02, 0xff}; // Not enough closing breaks +static const uint8_t spIndefiniteArrayBad3[] = {0x9f, 0x02, 0xff, 0xff}; // Too many closing breaks +static const uint8_t spIndefiniteArrayBad4[] = {0x81, 0x9f}; // Unclosed indeflen inside def len +static const uint8_t spIndefiniteArrayBad5[] = {0x9f, 0xd1, 0xff}; // confused tag + +int IndefiniteLengthArrayMapTest() +{ + int nResult; + // --- first test ----- + UsefulBufC IndefLen = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spIndefiniteArray); + + // Decode it and see if it is OK + UsefulBuf_MAKE_STACK_UB(MemPool, 150); + QCBORDecodeContext DC; + QCBORItem Item; + QCBORDecode_Init(&DC, IndefLen, QCBOR_DECODE_MODE_NORMAL); + + QCBORDecode_SetMemPool(&DC, MemPool, false); + + QCBORDecode_GetNext(&DC, &Item); + + if(Item.uDataType != QCBOR_TYPE_ARRAY || + Item.uNestingLevel != 0 || + Item.uNextNestLevel != 1) { + return -111; + } + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.uNestingLevel != 1 || + Item.uNextNestLevel != 1) { + return -2; + } + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_ARRAY || + Item.uNestingLevel != 1 || + Item.uNextNestLevel != 2) { + return -3; + } + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.uNestingLevel != 2 || + Item.uNextNestLevel != 2) { + return -4; + } + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_INT64 || + Item.uNestingLevel != 2 || + Item.uNextNestLevel != 0) { + return -5; + } + + if(QCBORDecode_Finish(&DC)) { + return -6; + } + + // --- next test ----- + IndefLen = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spIndefiniteArrayBad1); + + QCBORDecode_Init(&DC, IndefLen, QCBOR_DECODE_MODE_NORMAL); + + QCBORDecode_SetMemPool(&DC, MemPool, false); + + nResult = QCBORDecode_GetNext(&DC, &Item); + if(nResult || Item.uDataType != QCBOR_TYPE_ARRAY) { + return -7; + } + + nResult = QCBORDecode_Finish(&DC); + if(nResult != QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN) { + return -8; + } + + + // --- next test ----- + IndefLen = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spIndefiniteArrayBad2); + + QCBORDecode_Init(&DC, IndefLen, QCBOR_DECODE_MODE_NORMAL); + + QCBORDecode_SetMemPool(&DC, MemPool, false); + + nResult = QCBORDecode_GetNext(&DC, &Item); + if(nResult || Item.uDataType != QCBOR_TYPE_ARRAY) { + return -9; + } + + nResult = QCBORDecode_GetNext(&DC, &Item); + if(nResult || Item.uDataType != QCBOR_TYPE_ARRAY) { + return -10; + } + + nResult = QCBORDecode_GetNext(&DC, &Item); + if(nResult || Item.uDataType != QCBOR_TYPE_INT64) { + return -11; + } + + nResult = QCBORDecode_Finish(&DC); + if(nResult != QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN) { + return -12; + } + + + // --- next test ----- + IndefLen = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spIndefiniteArrayBad3); + + QCBORDecode_Init(&DC, IndefLen, QCBOR_DECODE_MODE_NORMAL); + + QCBORDecode_SetMemPool(&DC, MemPool, false); + + nResult = QCBORDecode_GetNext(&DC, &Item); + if(nResult || Item.uDataType != QCBOR_TYPE_ARRAY) { + return -13; + } + + nResult = QCBORDecode_GetNext(&DC, &Item); + if(nResult != QCBOR_ERR_BAD_BREAK) { + return -14; + } + + + // --- next test ----- + IndefLen = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spIndefiniteArrayBad4); + + QCBORDecode_Init(&DC, IndefLen, QCBOR_DECODE_MODE_NORMAL); + + QCBORDecode_SetMemPool(&DC, MemPool, false); + + nResult = QCBORDecode_GetNext(&DC, &Item); + if(nResult || Item.uDataType != QCBOR_TYPE_ARRAY) { + return -15; + } + + nResult = QCBORDecode_GetNext(&DC, &Item); + if(nResult || Item.uDataType != QCBOR_TYPE_ARRAY) { + return -16; + } + + nResult = QCBORDecode_Finish(&DC); + if(nResult != QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN) { + return -17; + } + + // --- next test ----- + IndefLen = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spIndefiniteArrayBad5); + + QCBORDecode_Init(&DC, IndefLen, QCBOR_DECODE_MODE_NORMAL); + + QCBORDecode_SetMemPool(&DC, MemPool, false); + + nResult = QCBORDecode_GetNext(&DC, &Item); + if(nResult || Item.uDataType != QCBOR_TYPE_ARRAY) { + return -18; + } + + nResult = QCBORDecode_GetNext(&DC, &Item); + if(nResult != QCBOR_ERR_BAD_BREAK) { + return -19; + } + + return 0; +} + + +static const uint8_t spIndefiniteLenString[] = { + 0x81, // Array of length one + 0x7f, // text string marked with indefinite length + 0x65, 0x73, 0x74, 0x72, 0x65, 0x61, // first segment + 0x64, 0x6d, 0x69, 0x6e, 0x67, // second segment + 0xff // ending break +}; + +static const uint8_t spIndefiniteLenStringBad2[] = { + 0x81, // Array of length one + 0x7f, // text string marked with indefinite length + 0x65, 0x73, 0x74, 0x72, 0x65, 0x61, // first segment + 0x44, 0x6d, 0x69, 0x6e, 0x67, // second segment of wrong type + 0xff // ending break +}; + +static const uint8_t spIndefiniteLenStringBad3[] = { + 0x81, // Array of length one + 0x7f, // text string marked with indefinite length + 0x01, 0x02, // Not a string + 0xff // ending break +}; + +static const uint8_t spIndefiniteLenStringBad4[] = { + 0x81, // Array of length one + 0x7f, // text string marked with indefinite length + 0x65, 0x73, 0x74, 0x72, 0x65, 0x61, // first segment + 0x64, 0x6d, 0x69, 0x6e, 0x67, // second segment + // missing end of string +}; + +static const uint8_t spIndefiniteLenStringLabel[] = { + 0xa1, // Array of length one + 0x7f, // text string marked with indefinite length + 0x65, 0x73, 0x74, 0x72, 0x75, 0x75, // first segment + 0x64, 0x6d, 0x69, 0x6e, 0x67, // second segment + 0xff, // ending break + 0x01 // integer being labeled. +}; + +static UsefulBufC MakeIndefiniteBigBstr(UsefulBuf Storage) // TODO: size this +{ + UsefulOutBuf UOB; + + UsefulOutBuf_Init(&UOB, Storage); + UsefulOutBuf_AppendByte(&UOB, 0x81); + UsefulOutBuf_AppendByte(&UOB, 0x5f); + + int i = 0; + for(int nChunkSize = 1; nChunkSize <= 128; nChunkSize *= 2) { + UsefulOutBuf_AppendByte(&UOB, 0x58); + UsefulOutBuf_AppendByte(&UOB, (uint8_t)nChunkSize); + for(int j = 0; j < nChunkSize; j++ ) { + UsefulOutBuf_AppendByte(&UOB, i); + i++; + } + } + UsefulOutBuf_AppendByte(&UOB, 0xff); + + return UsefulOutBuf_OutUBuf(&UOB); +} + +static int CheckBigString(UsefulBufC BigString) +{ + if(BigString.len != 255) { + return 1; + } + + for(uint8_t i = 0; i < 255; i++){ + if(((const uint8_t *)BigString.ptr)[i] != i) { + return 1; + } + } + return 0; +} + + +int IndefiniteLengthStringTest() +{ + QCBORDecodeContext DC; + QCBORItem Item; + // big enough for MakeIndefiniteBigBstr() + MemPool overhead + UsefulBuf_MAKE_STACK_UB(MemPool, 350); + + // --- Simple normal indefinite length string ------ + UsefulBufC IndefLen = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spIndefiniteLenString); + QCBORDecode_Init(&DC, IndefLen, QCBOR_DECODE_MODE_NORMAL); + + if(QCBORDecode_SetMemPool(&DC, MemPool, false)) { + return -1; + } + + if(QCBORDecode_GetNext(&DC, &Item)) { + return -2; + } + if(Item.uDataType != QCBOR_TYPE_ARRAY || Item.uDataAlloc) { + return -3; + } + + if(QCBORDecode_GetNext(&DC, &Item)) { + return -4; + } + if(Item.uDataType != QCBOR_TYPE_TEXT_STRING || !Item.uDataAlloc) { + return -5; + } + if(QCBORDecode_Finish(&DC)) { + return -6; + } + + // ----- types mismatch --- + QCBORDecode_Init(&DC, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spIndefiniteLenStringBad2), QCBOR_DECODE_MODE_NORMAL); + + if(QCBORDecode_SetMemPool(&DC, MemPool, false)) { + return -7; + } + + if(QCBORDecode_GetNext(&DC, &Item)) { + return -8; + } + if(Item.uDataType != QCBOR_TYPE_ARRAY) { + return -9; + } + + if(QCBORDecode_GetNext(&DC, &Item) != QCBOR_ERR_INDEFINITE_STRING_CHUNK) { + return -10; + } + + // ----- not a string --- + QCBORDecode_Init(&DC, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spIndefiniteLenStringBad3), QCBOR_DECODE_MODE_NORMAL); + + if(QCBORDecode_SetMemPool(&DC, MemPool, false)) { + return -11; + } + + if(QCBORDecode_GetNext(&DC, &Item)) { + return -12; + } + if(Item.uDataType != QCBOR_TYPE_ARRAY) { + return -13; + } + + if(QCBORDecode_GetNext(&DC, &Item) != QCBOR_ERR_INDEFINITE_STRING_CHUNK) { + return -14; + } + + // ----- no end ----- + QCBORDecode_Init(&DC, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spIndefiniteLenStringBad4), QCBOR_DECODE_MODE_NORMAL); + + if(QCBORDecode_SetMemPool(&DC, MemPool, false)) { + return -15; + } + + if(QCBORDecode_GetNext(&DC, &Item)) { + return -16; + } + if(Item.uDataType != QCBOR_TYPE_ARRAY) { + return -17; + } + + if(QCBORDecode_GetNext(&DC, &Item) != QCBOR_ERR_HIT_END) { + return -18; + } + + // ------ Don't set a string allocator and see an error ----- + QCBORDecode_Init(&DC, IndefLen, QCBOR_DECODE_MODE_NORMAL); + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_ARRAY) { + return -19; + } + + if(QCBORDecode_GetNext(&DC, &Item) != QCBOR_ERR_NO_STRING_ALLOCATOR) { + return -20; + } + + // ----- Mempool is way too small ----- + UsefulBuf_MAKE_STACK_UB(MemPoolTooSmall, 20); // 20 is too small no matter what + + QCBORDecode_Init(&DC, IndefLen, QCBOR_DECODE_MODE_NORMAL); + if(!QCBORDecode_SetMemPool(&DC, MemPoolTooSmall, false)) { + return -21; + } + + // ----- Mempool is way too small ----- + UsefulBuf_MAKE_STACK_UB(BigIndefBStrStorage, 290); + const UsefulBufC BigIndefBStr = MakeIndefiniteBigBstr(BigIndefBStrStorage); + + UsefulBuf_MAKE_STACK_UB(MemPoolSmall, 80); // 80 is big enough for MemPool overhead, but not BigIndefBStr + + QCBORDecode_Init(&DC, BigIndefBStr, QCBOR_DECODE_MODE_NORMAL); + if(QCBORDecode_SetMemPool(&DC, MemPoolSmall, false)) { + return -22; + } + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_ARRAY) { + return -23; + } + if(QCBORDecode_GetNext(&DC, &Item) != QCBOR_ERR_STRING_ALLOCATE) { + return -24; + } + + // ---- big bstr ----- + QCBORDecode_Init(&DC, BigIndefBStr, QCBOR_DECODE_MODE_NORMAL); + + if(QCBORDecode_SetMemPool(&DC, MemPool, false)) { + return -25; + } + + if(QCBORDecode_GetNext(&DC, &Item)) { + return -26; + } + if(Item.uDataType != QCBOR_TYPE_ARRAY || Item.uDataAlloc) { + return -26; + } + + if(QCBORDecode_GetNext(&DC, &Item)) { + return -27; + } + if(Item.uDataType != QCBOR_TYPE_BYTE_STRING || !Item.uDataAlloc || Item.uNestingLevel != 1) { + return -28; + } + if(CheckBigString(Item.val.string)) { + return -3; + } + if(QCBORDecode_Finish(&DC)) { + return -29; + } + + // --- label is an indefinite length string ------ + QCBORDecode_Init(&DC, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spIndefiniteLenStringLabel), QCBOR_DECODE_MODE_NORMAL); + + if(QCBORDecode_SetMemPool(&DC, MemPool, false)) { + return -30; + } + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_MAP) { + return -31; + } + + if(QCBORDecode_GetNext(&DC, &Item)){ + return -32; + } + if(Item.uLabelType != QCBOR_TYPE_TEXT_STRING || Item.uDataType != QCBOR_TYPE_INT64 || + Item.uDataAlloc || !Item.uLabelAlloc || + UsefulBuf_Compare(Item.label.string, UsefulBuf_FromSZ("struuming"))) { + return -33; + } + + if(QCBORDecode_Finish(&DC)) { + return -34; + } + + return 0; +} + + +int AllocAllStringsTest() +{ + QCBORDecodeContext DC; + QCBORError nCBORError; + + + // First test, use the "CSRMap" as easy input and checking + QCBORDecode_Init(&DC, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spCSRInput), QCBOR_DECODE_MODE_NORMAL); + + UsefulBuf_MAKE_STACK_UB(Pool, sizeof(spCSRInput) + QCBOR_DECODE_MIN_MEM_POOL_SIZE); + + nCBORError = QCBORDecode_SetMemPool(&DC, Pool, 1); // Turn on copying. + if(nCBORError) { + return -1; + } + + if(CheckCSRMaps(&DC)) { + return -2; + } + + // Next parse, save pointers to a few strings, destroy original and see all is OK. + UsefulBuf_MAKE_STACK_UB(CopyOfStorage, sizeof(pValidMapEncoded) + QCBOR_DECODE_MIN_MEM_POOL_SIZE); + const UsefulBufC CopyOf = UsefulBuf_Copy(CopyOfStorage, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pValidMapEncoded)); + + QCBORDecode_Init(&DC, CopyOf, QCBOR_DECODE_MODE_NORMAL); + UsefulBuf_Set(Pool, '/'); + QCBORDecode_SetMemPool(&DC, Pool, 1); // Turn on copying. + + QCBORItem Item1, Item2, Item3, Item4; + if((nCBORError = QCBORDecode_GetNext(&DC, &Item1))) + return nCBORError; + if(Item1.uDataType != QCBOR_TYPE_MAP || + Item1.val.uCount != 3) + return -3; + if((nCBORError = QCBORDecode_GetNext(&DC, &Item1))) + return nCBORError; + if((nCBORError = QCBORDecode_GetNext(&DC, &Item2))) + return nCBORError; + if((nCBORError = QCBORDecode_GetNext(&DC, &Item3))) + return nCBORError; + if((nCBORError = QCBORDecode_GetNext(&DC, &Item4))) + return nCBORError; + + UsefulBuf_Set(CopyOfStorage, '_'); + + if(Item1.uLabelType != QCBOR_TYPE_TEXT_STRING || + Item1.uDataType != QCBOR_TYPE_INT64 || + Item1.val.int64 != 42 || + UsefulBuf_Compare(Item1.label.string, UsefulBuf_FromSZ("first integer"))) { + return -4; + } + + + if(Item2.uLabelType != QCBOR_TYPE_TEXT_STRING || + UsefulBuf_Compare(Item2.label.string, UsefulBuf_FromSZ("an array of two strings")) || + Item2.uDataType != QCBOR_TYPE_ARRAY || + Item2.val.uCount != 2) + return -5; + + if(Item3.uDataType != QCBOR_TYPE_TEXT_STRING || + UsefulBuf_Compare(Item3.val.string, UsefulBuf_FromSZ("string1"))) { + return -6; + } + + if(Item4.uDataType != QCBOR_TYPE_TEXT_STRING || + UsefulBuf_Compare(Item4.val.string, UsefulBuf_FromSZ("string2"))) { + return -7; + } + + // Next parse with a pool that is too small + UsefulBuf_MAKE_STACK_UB(SmallPool, QCBOR_DECODE_MIN_MEM_POOL_SIZE + 1); + QCBORDecode_Init(&DC, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pValidMapEncoded), QCBOR_DECODE_MODE_NORMAL); + QCBORDecode_SetMemPool(&DC, SmallPool, 1); // Turn on copying. + if((nCBORError = QCBORDecode_GetNext(&DC, &Item1))) + return -8; + if(Item1.uDataType != QCBOR_TYPE_MAP || + Item1.val.uCount != 3) { + return -9; + } + if(!(nCBORError = QCBORDecode_GetNext(&DC, &Item1))){ + if(!(nCBORError = QCBORDecode_GetNext(&DC, &Item2))) { + if(!(nCBORError = QCBORDecode_GetNext(&DC, &Item3))) { + nCBORError = QCBORDecode_GetNext(&DC, &Item4); + } + } + } + if(nCBORError != QCBOR_ERR_STRING_ALLOCATE) { + return -10; + } + + return 0; +} + +// Cheating declaration to get to the special test hook +size_t MemPoolTestHook_GetPoolSize(void *ctx); + + +int MemPoolTest(void) +{ + // Set up the decoder with a tiny bit of CBOR to parse + QCBORDecodeContext DC; + const uint8_t pMinimalCBOR[] = {0xa0}; // One empty map + QCBORDecode_Init(&DC, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pMinimalCBOR),0); + + // Set up an memory pool of 100 bytes + UsefulBuf_MAKE_STACK_UB(Pool, 100); + QCBORError nError = QCBORDecode_SetMemPool(&DC, Pool, 0); + if(nError) { + return -9; + } + + // Cheat a little to get to the string allocator object + // so we can call it directly to test it + QCBORStringAllocator *pAlloc = (QCBORStringAllocator *)DC.pStringAllocator; + // Cheat some more to know exactly the + size_t uAvailPool = MemPoolTestHook_GetPoolSize(pAlloc); + + // First test -- ask for too much in one go + UsefulBuf Allocated = (*pAlloc->fAllocate)(pAlloc->pAllocaterContext, NULL, uAvailPool+1); + if(!UsefulBuf_IsNULL(Allocated)) { + return -1; + } + + + // Re do the set up for the next test that will do a successful alloc, + // a fail, a free and then success + // This test should work on 32 and 64-bit machines if the compiler + // does the expected thing with pointer sizes for the internal + // MemPool implementation leaving 44 or 72 bytes of pool memory. + QCBORDecode_SetMemPool(&DC, Pool, 0); + + // Cheat a little to get to the string allocator object + // so we can call it directly to test it + pAlloc = (QCBORStringAllocator *)DC.pStringAllocator; + // Cheat some more to know exactly the + uAvailPool = MemPoolTestHook_GetPoolSize(pAlloc); + + Allocated = (*pAlloc->fAllocate)(pAlloc->pAllocaterContext, NULL, uAvailPool-1); + if(UsefulBuf_IsNULL(Allocated)) { // expected to succeed + return -2; + } + UsefulBuf Allocated2 = (*pAlloc->fAllocate)(pAlloc->pAllocaterContext, NULL, uAvailPool/2); + if(!UsefulBuf_IsNULL(Allocated2)) { // expected to fail + return -3; + } + (*pAlloc->fFree)(pAlloc->pAllocaterContext, Allocated.ptr); + Allocated = (*pAlloc->fAllocate)(pAlloc->pAllocaterContext, NULL, uAvailPool/2); + if(UsefulBuf_IsNULL(Allocated)) { // succeed because of the free + return -4; + } + + + // Re do set up for next test that involves a successful alloc, + // and a successful realloc and a failed realloc + QCBORDecode_SetMemPool(&DC, Pool, 0); + + // Cheat a little to get to the string allocator object + // so we can call it directly to test it + pAlloc = (QCBORStringAllocator *)DC.pStringAllocator; + Allocated = (*pAlloc->fAllocate)(pAlloc->pAllocaterContext, NULL, uAvailPool/2); + if(UsefulBuf_IsNULL(Allocated)) { // expected to succeed + return -5; + } + Allocated2 = (*pAlloc->fAllocate)(pAlloc->pAllocaterContext, Allocated.ptr, uAvailPool); + if(UsefulBuf_IsNULL(Allocated2)) { + return -6; + } + if(Allocated2.ptr != Allocated.ptr || Allocated2.len != uAvailPool) { + return -7; + } + UsefulBuf Allocated3 = (*pAlloc->fAllocate)(pAlloc->pAllocaterContext, Allocated.ptr, uAvailPool+1); + if(!UsefulBuf_IsNULL(Allocated3)) { // expected to fail + return -8; + } + + return 0; +} + diff --git a/middleware/qcbor/test/qcbor_decode_tests.h b/middleware/qcbor/test/qcbor_decode_tests.h new file mode 100644 index 000000000..2b09c55d5 --- /dev/null +++ b/middleware/qcbor/test/qcbor_decode_tests.h @@ -0,0 +1,229 @@ +/*============================================================================== + Copyright (c) 2016-2018, The Linux Foundation. + Copyright (c) 2018-2019, Laurence Lundblade. + All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of The Linux Foundation nor the names of its + contributors, nor the name "Laurence Lundblade" may be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ==============================================================================*/ + +#ifndef __QCBOR__qcbort_decode_tests__ +#define __QCBOR__qcbort_decode_tests__ + +#include "qcbor.h" + + +/* + Notes: + + - All the functions in qcbor.h are called once in the aggregation of all the tests below. + + - All the types that are supported are given as input and parsed by these tests + + - There is some hostile input such as invalid lengths and CBOR too complex + and types this parser doesn't handle + + */ + + + + +/* + Parse a well-known set of integers including those around the boundaries and + make sure the expected values come out + */ +int IntegerValuesParseTest(void); + + + + + +/* + Decode a simple CBOR encoded array and make sure it returns all the correct values. + This is a decode test. + */ +int SimpleArrayTest(void); + + +/* + Make sure a maximally deep array can be parsed and that the + reported nesting level is correct. This uses test vector + of CBOR encoded data with a depth of 10. This a parse test. + */ +int ParseDeepArrayTest(void); + + +/* + See that the correct error is reported when parsing + an array of depth 11, one too large. + */ +int ParseTooDeepArrayTest(void); + + +/* + Try to parse some legit CBOR types that this parsers + doesn't support. + */ +int UnsupportedCBORDecodeTest(void); + + +/* + This takes the encoded CBOR integers used in the above test and parses + it over and over with one more byte less each time. It should fail + every time on incorrect CBOR input. This is a hostile input decode test. + */ +int ShortBufferParseTest(void); + + +/* + Same as ShortBufferParseTest, but with a different encoded CBOR input. + It is another hostile input test + */ +int ShortBufferParseTest2(void); + + +/* + Parses the somewhat complicated CBOR MAP and makes sure all the correct + values parse out. About 15 values are tested. This is a decode test. + */ +int ParseMapTest(void); + + + +int FloatValuesTest1(void); + + + +int SimpleValuesTest1(void); + + +/* + + */ +int ParseMapAsArrayTest(void); + + + +int ParseSimpleTest(void); + + + +/* + Tests a number of failure cases on bad CBOR to get the right error code + */ +int FailureTests(void); + + +/* + Parses all possible inputs that are two bytes long. Main point + is that the test doesn't crash as it doesn't evaluate the + input for correctness in any way. + + (Parsing all possible 3 byte strings takes too long on all but + very fast machines). + */ +int ComprehensiveInputTest(void); + + +/* + Parses all possible inputs that are four bytes long. Main point + is that the test doesn't crash as it doesn't evaluate the + input for correctness in any way. This runs very slow, so it + is only practical as a once-in-a-while regression test on + fast machines. + */ +int BigComprehensiveInputTest(void); + + +/* + Thest the date types -- epoch and strings + */ +int DateParseTest(void); + + +/* + Test optional tags like the CBOR magic number. + */ +int OptTagParseTest(void); + + +/* + Parse some big numbers, positive and negative + */ +int BignumParseTest(void); + + +int StringDecoderModeFailTest(void); + + +/* + Parse some nested maps + */ +int NestedMapTest(void); + + +/* + Parse maps with indefinite lengths + */ +int NestedMapTestIndefLen(void); + + +/* + Parse some maps and arrays with indefinite lengths. + Includes some error cases. + */ +int IndefiniteLengthArrayMapTest(void); + + +/* + Parse indefinite length strings. Uses + MemPool. Includes error cases. + */ +int IndefiniteLengthStringTest(void); + + +/* + Test deep nesting of indefinite length + maps and arrays including too deep. + */ +int IndefiniteLengthNestTest(void); + + +/* + Test parsing strings were all strings, not + just indefinite length strings, are + allocated. Includes error test cases. + */ +int AllocAllStringsTest(void); + + +/* + Direct test of MemPool string allocator + */ +int MemPoolTest(void); + + +#endif /* defined(__QCBOR__qcbort_decode_tests__) */ diff --git a/middleware/qcbor/test/qcbor_encode_tests.c b/middleware/qcbor/test/qcbor_encode_tests.c new file mode 100644 index 000000000..2a22cf1bd --- /dev/null +++ b/middleware/qcbor/test/qcbor_encode_tests.c @@ -0,0 +1,2007 @@ +/*============================================================================== + Copyright (c) 2016-2018, The Linux Foundation. + Copyright (c) 2018-2019, Laurence Lundblade. + All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of The Linux Foundation nor the names of its + contributors, nor the name "Laurence Lundblade" may be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ==============================================================================*/ + +#include "qcbor.h" +#include "qcbor_encode_tests.h" + + +/* + This is the test set for CBOR encoding. + + This is largely complete for the implemented. + + A few more things to do include: + - Add a test for counting the top level items and adding it back in with AddRaw() + - Run on some different CPUs like 32-bit and maybe even 16-bit + - Test the large array count limit + - Add the CBOR diagnostic output for every expected + + */ + +//#define PRINT_FUNCTIONS_FOR_DEBUGGINGXX + +#ifdef PRINT_FUNCTIONS_FOR_DEBUGGINGXX +#include + +// ifdef these out to not have compiler warnings +static void printencoded(const uint8_t *pEncoded, size_t nLen) +{ + size_t i; + for(i = 0; i < nLen; i++) { + uint8_t Z = pEncoded[i]; + printf("%02x ", Z); + } + printf("\n"); + + fflush(stdout); +} + + +// Do the comparison and print out where it fails +static int UsefulBuf_Compare_Print(UsefulBufC U1, UsefulBufC U2) { + size_t i; + for(i = 0; i < U1.len; i++) { + if(((uint8_t *)U1.ptr)[i] != ((uint8_t *)U2.ptr)[i]) { + printf("Position: %d Actual: 0x%x Expected: 0x%x\n", i, ((uint8_t *)U1.ptr)[i], ((uint8_t *)U2.ptr)[i]); + return 1; + } + } + return 0; + +} + +#define CheckResults(Enc, Expected) \ + UsefulBuf_Compare_Print(Enc, (UsefulBufC){Expected, sizeof(Expected)}) + +#else + +#define CheckResults(Enc, Expected) \ + UsefulBuf_Compare(Enc, (UsefulBufC){Expected, sizeof(Expected)}) + +#endif + + + +// One big buffer that is used by all the tests to encode into +// Putting it in uninitialized data is better than using a lot +// of stack. The tests should run on small devices too. +static uint8_t spBigBuf[2200]; + + + +/* + Some very minimal tests. + */ +int BasicEncodeTest() +{ + // Very simple CBOR, a map with one boolean that is true in it + QCBOREncodeContext EC; + + QCBOREncode_Init(&EC, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf)); + + QCBOREncode_OpenMap(&EC); + QCBOREncode_AddBoolToMapN(&EC, 66, true); + QCBOREncode_CloseMap(&EC); + + UsefulBufC Encoded; + if(QCBOREncode_Finish(&EC, &Encoded)) { + return -1; + } + + + // Decode it and see that is right + QCBORDecodeContext DC; + QCBORItem Item; + QCBORDecode_Init(&DC, Encoded, QCBOR_DECODE_MODE_NORMAL); + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_MAP) { + return -2; + } + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_TRUE) { + return -3; + } + + if(QCBORDecode_Finish(&DC)) { + return -4; + } + + + // Make another encoded message with the CBOR from the previous put into this one + UsefulBuf_MAKE_STACK_UB(MemoryForEncoded2, 20); + QCBOREncode_Init(&EC, MemoryForEncoded2); + QCBOREncode_OpenArray(&EC); + QCBOREncode_AddUInt64(&EC, 451); + QCBOREncode_AddEncoded(&EC, Encoded); + QCBOREncode_OpenMap(&EC); + QCBOREncode_AddEncodedToMapN(&EC, -70000, Encoded); + QCBOREncode_CloseMap(&EC); + QCBOREncode_CloseArray(&EC); + + UsefulBufC Encoded2; + if(QCBOREncode_Finish(&EC, &Encoded2)) { + return -5; + } + /* + [ // 0 1:3 + 451, // 1 1:2 + { // 1 1:2 2:1 + 66: true // 2 1:1 + }, + { // 1 1:1 2:1 + -70000: { // 2 1:1 2:1 3:1 + 66: true // 3 XXXXXX + } + } + ] + + + + 83 # array(3) + 19 01C3 # unsigned(451) + A1 # map(1) + 18 42 # unsigned(66) + F5 # primitive(21) + A1 # map(1) + 3A 0001116F # negative(69999) + A1 # map(1) + 18 42 # unsigned(66) + F5 # primitive(21) + */ + + // Decode it and see if it is OK + QCBORDecode_Init(&DC, Encoded2, QCBOR_DECODE_MODE_NORMAL); + + // 0 1:3 + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_ARRAY || Item.val.uCount != 3) { + return -6; + } + + // 1 1:2 + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_INT64 || Item.val.uint64 != 451) { + return -7; + } + + // 1 1:2 2:1 + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_MAP || Item.val.uCount != 1) { + return -8; + } + + // 2 1:1 + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_TRUE) { + return -9; + } + + // 1 1:1 2:1 + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_MAP || Item.val.uCount != 1) { + return -10; + } + + // 2 1:1 2:1 3:1 + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_MAP || Item.val.uCount != 1 || Item.uLabelType != QCBOR_TYPE_INT64 || Item.label.int64 != -70000) { + return -11; + } + + // 3 XXXXXX + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_TRUE || Item.uLabelType != QCBOR_TYPE_INT64 || Item.label.int64 != 66) { + return -12; + } + + if(QCBORDecode_Finish(&DC)) { + return -13; + } + + return 0; +} + + + +static const uint8_t spExpectedEncodedAll[] = { + 0x98, 0x22, 0x66, 0x55, 0x49, 0x4e, 0x54, 0x36, 0x32, 0xd8, + 0x64, 0x1a, 0x05, 0x5d, 0x23, 0x15, 0x65, 0x49, 0x4e, 0x54, + 0x36, 0x34, 0xd8, 0x4c, 0x1b, 0x00, 0x00, 0x00, 0x12, 0x16, + 0xaf, 0x2b, 0x15, 0x00, 0x38, 0x2b, 0xa4, 0x63, 0x4c, 0x42, + 0x4c, 0x18, 0x4d, 0x23, 0x18, 0x58, 0x78, 0x1a, 0x4e, 0x45, + 0x47, 0x4c, 0x42, 0x4c, 0x54, 0x48, 0x41, 0x54, 0x20, 0x49, + 0x53, 0x20, 0x4b, 0x49, 0x4e, 0x44, 0x20, 0x4f, 0x46, 0x20, + 0x4c, 0x4f, 0x4e, 0x47, 0x3b, 0x00, 0x00, 0x02, 0x2d, 0x9a, + 0xc6, 0x94, 0x55, 0x3a, 0x05, 0xf5, 0xe0, 0xff, 0x3a, 0x2f, + 0xaf, 0x07, 0xff, 0xc1, 0x1a, 0x8e, 0x15, 0x1c, 0x8a, + 0xa3, 0x74, 0x4c, 0x6f, 0x6e, 0x67, 0x4c, 0x69, 0x76, 0x65, + 0x44, 0x65, 0x6e, 0x69, 0x73, 0x52, 0x69, 0x74, 0x63, 0x68, + 0x69, 0x65, 0xc1, 0x1a, 0x53, 0x72, 0x4e, 0x00, 0x66, 0x74, + 0x69, 0x6d, 0x65, 0x28, 0x29, 0xc1, 0x1a, 0x58, 0x0d, 0x41, + 0x72, 0x39, 0x07, 0xb0, 0xc1, 0x1a, 0x58, 0x0d, 0x3f, 0x76, + 0x42, 0xff, 0x00, 0xa3, 0x66, 0x62, 0x69, 0x6e, 0x62, 0x69, + 0x6e, 0xda, 0x00, 0x01, 0x86, 0xa0, 0x41, 0x00, 0x66, 0x62, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x43, 0x01, 0x02, 0x03, 0x00, + 0x44, 0x04, 0x02, 0x03, 0xfe, 0x6f, 0x62, 0x61, 0x72, 0x20, + 0x62, 0x61, 0x72, 0x20, 0x66, 0x6f, 0x6f, 0x20, 0x62, 0x61, + 0x72, 0x64, 0x6f, 0x6f, 0x66, 0x0a, 0xd8, 0x20, 0x78, 0x6b, + 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x73, 0x74, 0x61, + 0x63, 0x6b, 0x6f, 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x77, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x32, 0x38, 0x30, 0x35, 0x39, + 0x36, 0x39, 0x37, 0x2f, 0x68, 0x6f, 0x77, 0x2d, 0x64, 0x6f, + 0x2d, 0x69, 0x2d, 0x74, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x2d, + 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x2d, 0x64, 0x65, + 0x62, 0x75, 0x67, 0x2d, 0x61, 0x6e, 0x64, 0x2d, 0x72, 0x65, + 0x6c, 0x65, 0x61, 0x73, 0x65, 0x2d, 0x62, 0x75, 0x69, 0x6c, + 0x64, 0x73, 0x2d, 0x69, 0x6e, 0x2d, 0x78, 0x63, 0x6f, 0x64, + 0x65, 0x2d, 0x36, 0x2d, 0x37, 0x2d, 0x38, 0xd8, 0x22, 0x78, + 0x1c, 0x59, 0x57, 0x35, 0x35, 0x49, 0x47, 0x4e, 0x68, 0x63, + 0x6d, 0x35, 0x68, 0x62, 0x43, 0x42, 0x77, 0x62, 0x47, 0x56, + 0x68, 0x63, 0x33, 0x56, 0x79, 0x5a, 0x51, 0x3d, 0x3d, 0xd8, + 0x23, 0x67, 0x5b, 0x5e, 0x61, 0x62, 0x63, 0x5d, 0x2b, 0xd8, + 0x24, 0x79, 0x01, 0x57, 0x4d, 0x49, 0x4d, 0x45, 0x2d, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x31, 0x2e, + 0x30, 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, + 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x6d, 0x75, 0x6c, 0x74, + 0x69, 0x70, 0x61, 0x72, 0x74, 0x2f, 0x6d, 0x69, 0x78, 0x65, + 0x64, 0x3b, 0x0a, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, + 0x79, 0x3d, 0x22, 0x58, 0x58, 0x58, 0x58, 0x62, 0x6f, 0x75, + 0x6e, 0x64, 0x61, 0x72, 0x79, 0x20, 0x74, 0x65, 0x78, 0x74, + 0x22, 0x0a, 0x0a, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, + 0x20, 0x61, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x61, + 0x72, 0x74, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x20, 0x69, 0x6e, 0x20, 0x4d, 0x49, 0x4d, 0x45, 0x20, 0x66, + 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x0a, 0x0a, 0x2d, 0x2d, + 0x58, 0x58, 0x58, 0x58, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, + 0x72, 0x79, 0x20, 0x74, 0x65, 0x78, 0x74, 0x0a, 0x43, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, + 0x3a, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, 0x61, + 0x69, 0x6e, 0x0a, 0x0a, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, + 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x6f, 0x64, 0x79, + 0x20, 0x74, 0x65, 0x78, 0x74, 0x0a, 0x0a, 0x2d, 0x2d, 0x58, + 0x58, 0x58, 0x58, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, + 0x79, 0x20, 0x74, 0x65, 0x78, 0x74, 0x0a, 0x43, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, + 0x20, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, 0x61, 0x69, + 0x6e, 0x3b, 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x2d, 0x44, 0x69, 0x73, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x3a, 0x20, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, + 0x6d, 0x65, 0x6e, 0x74, 0x3b, 0x0a, 0x66, 0x69, 0x6c, 0x65, + 0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x73, 0x74, + 0x2e, 0x74, 0x78, 0x74, 0x22, 0x0a, 0x0a, 0x74, 0x68, 0x69, + 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, + 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x20, + 0x74, 0x65, 0x78, 0x74, 0x0a, 0x0a, 0x2d, 0x2d, 0x58, 0x58, + 0x58, 0x58, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, + 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x2d, 0xae, 0x65, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x6f, 0x66, 0x6f, 0x6f, 0x20, 0x62, + 0x61, 0x72, 0x20, 0x66, 0x6f, 0x6f, 0x20, 0x66, 0x6f, 0x6f, + 0x64, 0x5f, 0x5f, 0x5f, 0x5f, 0x67, 0x66, 0x6f, 0x6f, 0x20, + 0x62, 0x61, 0x72, 0x66, 0x28, 0x29, 0x28, 0x29, 0x28, 0x29, + 0xd9, 0x03, 0xe8, 0x6b, 0x72, 0x61, 0x62, 0x20, 0x72, 0x61, + 0x62, 0x20, 0x6f, 0x6f, 0x66, 0x16, 0x6f, 0x66, 0x6f, 0x6f, + 0x20, 0x66, 0x6f, 0x6f, 0x20, 0x66, 0x6f, 0x6f, 0x20, 0x66, + 0x6f, 0x6f, 0x62, 0x5e, 0x5e, 0x69, 0x6f, 0x6f, 0x6f, 0x6f, + 0x6f, 0x6f, 0x6f, 0x6f, 0x66, 0x18, 0x63, 0x6d, 0x66, 0x66, + 0x66, 0x66, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, + 0x66, 0x63, 0x52, 0x46, 0x43, 0xd8, 0x20, 0x78, 0x31, 0x68, + 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x74, 0x6f, 0x6f, + 0x6c, 0x73, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x6f, 0x72, + 0x67, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x2f, 0x72, 0x66, 0x63, + 0x37, 0x30, 0x34, 0x39, 0x23, 0x73, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x2d, 0x32, 0x2e, 0x34, 0x2e, 0x35, 0x18, 0x89, + 0xd8, 0x20, 0x6f, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, + 0x63, 0x62, 0x6f, 0x72, 0x2e, 0x6d, 0x65, 0x2f, 0x68, 0x77, + 0x68, 0x65, 0x6e, 0x69, 0x6d, 0x36, 0x34, 0xd8, 0x22, 0x6c, + 0x63, 0x47, 0x78, 0x6c, 0x59, 0x58, 0x4e, 0x31, 0x63, 0x6d, + 0x55, 0x75, 0x18, 0x40, 0xd8, 0x22, 0x68, 0x63, 0x33, 0x56, + 0x79, 0x5a, 0x53, 0x34, 0x3d, 0x64, 0x70, 0x6f, 0x70, 0x6f, + 0xd8, 0x23, 0x68, 0x31, 0x30, 0x30, 0x5c, 0x73, 0x2a, 0x6d, + 0x6b, 0x38, 0x32, 0xd8, 0x23, 0x66, 0x70, 0x65, 0x72, 0x6c, + 0x5c, 0x42, 0x63, 0x4e, 0x65, 0x64, 0xd8, 0x24, 0x79, 0x01, + 0x57, 0x4d, 0x49, 0x4d, 0x45, 0x2d, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x31, 0x2e, 0x30, 0x0a, 0x43, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, 0x79, 0x70, + 0x65, 0x3a, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x61, + 0x72, 0x74, 0x2f, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x3b, 0x0a, + 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x3d, 0x22, + 0x58, 0x58, 0x58, 0x58, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, + 0x72, 0x79, 0x20, 0x74, 0x65, 0x78, 0x74, 0x22, 0x0a, 0x0a, + 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, + 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x61, 0x72, 0x74, 0x20, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x69, 0x6e, + 0x20, 0x4d, 0x49, 0x4d, 0x45, 0x20, 0x66, 0x6f, 0x72, 0x6d, + 0x61, 0x74, 0x2e, 0x0a, 0x0a, 0x2d, 0x2d, 0x58, 0x58, 0x58, + 0x58, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x20, + 0x74, 0x65, 0x78, 0x74, 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x74, + 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x0a, + 0x0a, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x62, 0x6f, 0x64, 0x79, 0x20, 0x74, 0x65, + 0x78, 0x74, 0x0a, 0x0a, 0x2d, 0x2d, 0x58, 0x58, 0x58, 0x58, + 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x20, 0x74, + 0x65, 0x78, 0x74, 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x74, 0x65, + 0x78, 0x74, 0x2f, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x3b, 0x0a, + 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x44, 0x69, + 0x73, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, + 0x20, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, + 0x74, 0x3b, 0x0a, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, + 0x65, 0x3d, 0x22, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74, 0x78, + 0x74, 0x22, 0x0a, 0x0a, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, + 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x74, 0x74, 0x61, + 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x74, 0x65, 0x78, + 0x74, 0x0a, 0x0a, 0x2d, 0x2d, 0x58, 0x58, 0x58, 0x58, 0x62, + 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x20, 0x74, 0x65, + 0x78, 0x74, 0x2d, 0x2d, 0x0a, 0xd8, 0x24, 0x79, 0x01, 0x57, + 0x4d, 0x49, 0x4d, 0x45, 0x2d, 0x56, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x3a, 0x20, 0x31, 0x2e, 0x30, 0x0a, 0x43, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, + 0x3a, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x61, 0x72, + 0x74, 0x2f, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x3b, 0x0a, 0x62, + 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x3d, 0x22, 0x58, + 0x58, 0x58, 0x58, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, + 0x79, 0x20, 0x74, 0x65, 0x78, 0x74, 0x22, 0x0a, 0x0a, 0x54, + 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x6d, + 0x75, 0x6c, 0x74, 0x69, 0x70, 0x61, 0x72, 0x74, 0x20, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x69, 0x6e, 0x20, + 0x4d, 0x49, 0x4d, 0x45, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, + 0x74, 0x2e, 0x0a, 0x0a, 0x2d, 0x2d, 0x58, 0x58, 0x58, 0x58, + 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x20, 0x74, + 0x65, 0x78, 0x74, 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x74, 0x65, + 0x78, 0x74, 0x2f, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x0a, 0x0a, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x62, 0x6f, 0x64, 0x79, 0x20, 0x74, 0x65, 0x78, + 0x74, 0x0a, 0x0a, 0x2d, 0x2d, 0x58, 0x58, 0x58, 0x58, 0x62, + 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x20, 0x74, 0x65, + 0x78, 0x74, 0x0a, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x2d, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x74, 0x65, 0x78, + 0x74, 0x2f, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x3b, 0x0a, 0x43, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x44, 0x69, 0x73, + 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, + 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, + 0x3b, 0x0a, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, + 0x3d, 0x22, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74, 0x78, 0x74, + 0x22, 0x0a, 0x0a, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x74, 0x74, 0x61, 0x63, + 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x74, 0x65, 0x78, 0x74, + 0x0a, 0x0a, 0x2d, 0x2d, 0x58, 0x58, 0x58, 0x58, 0x62, 0x6f, + 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x20, 0x74, 0x65, 0x78, + 0x74, 0x2d, 0x2d, 0xc0, 0x74, 0x32, 0x30, 0x30, 0x33, 0x2d, + 0x31, 0x32, 0x2d, 0x31, 0x33, 0x54, 0x31, 0x38, 0x3a, 0x33, + 0x30, 0x3a, 0x30, 0x32, 0x5a, 0xa2, 0x68, 0x42, 0x65, 0x64, + 0x20, 0x74, 0x69, 0x6d, 0x65, 0xc0, 0x78, 0x1c, 0x32, 0x30, + 0x30, 0x33, 0x2d, 0x31, 0x32, 0x2d, 0x31, 0x33, 0x54, 0x31, + 0x38, 0x3a, 0x33, 0x30, 0x3a, 0x30, 0x32, 0x2e, 0x32, 0x35, + 0x2b, 0x30, 0x31, 0x3a, 0x30, 0x30, 0x18, 0x58, 0xc0, 0x78, + 0x1c, 0x32, 0x30, 0x30, 0x33, 0x2d, 0x31, 0x32, 0x2d, 0x31, + 0x33, 0x54, 0x31, 0x38, 0x3a, 0x33, 0x30, 0x3a, 0x30, 0x32, + 0x2e, 0x32, 0x35, 0x2b, 0x30, 0x31, 0x3a, 0x30, 0x30, 0xf7, + 0xa3, 0x64, 0x64, 0x61, 0x72, 0x65, 0xd8, 0x42, 0xf5, 0x62, + 0x75, 0x75, 0xf4, 0x1a, 0x00, 0x0b, 0x41, 0x62, 0xf6, 0x80, + 0xa3, 0x78, 0x1c, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x74, 0x61, 0x67, 0x67, 0x65, 0x64, 0x20, + 0x65, 0x6d, 0x70, 0x74, 0x79, 0x20, 0x61, 0x72, 0x72, 0x61, + 0x79, 0xd9, 0x04, 0x45, 0x80, 0x65, 0x61, 0x6c, 0x61, 0x62, + 0x6c, 0x80, 0x18, 0x2a, 0x80, 0xa1, 0x68, 0x69, 0x6e, 0x20, + 0x61, 0x20, 0x6d, 0x61, 0x70, 0xa1, 0x19, 0x15, 0xb4, 0xa1, + 0x6e, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x69, 0x6e, 0x20, 0x61, + 0x20, 0x69, 0x6e, 0x20, 0x61, 0xd9, 0x23, 0x7f, 0xa0, 0xa5, + 0x62, 0x73, 0x31, 0xd8, 0x58, 0xf8, 0xff, 0x62, 0x73, 0x32, + 0xe0, 0x62, 0x73, 0x33, 0xd8, 0x58, 0xf8, 0x21, 0x1a, 0x05, + 0x44, 0x8c, 0x06, 0xd8, 0x58, 0xf8, 0xff, 0x18, 0x59, 0xd8, + 0x58, 0xf3, 0xd8, 0x25, 0x50, 0x53, 0x4d, 0x41, 0x52, 0x54, + 0x43, 0x53, 0x4c, 0x54, 0x54, 0x43, 0x46, 0x49, 0x43, 0x41, + 0x32, 0xa2, 0x64, 0x55, 0x55, 0x55, 0x55, 0xd8, 0x25, 0x50, + 0x53, 0x4d, 0x41, 0x52, 0x54, 0x43, 0x53, 0x4c, 0x54, 0x54, + 0x43, 0x46, 0x49, 0x43, 0x41, 0x32, 0x18, 0x63, 0xd8, 0x25, + 0x50, 0x53, 0x4d, 0x41, 0x52, 0x54, 0x43, 0x53, 0x4c, 0x54, + 0x54, 0x43, 0x46, 0x49, 0x43, 0x41, 0x32, 0xf5, 0xf4, 0xa2, + 0x71, 0x47, 0x65, 0x6f, 0x72, 0x67, 0x65, 0x20, 0x69, 0x73, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x61, 0x6e, 0xf5, 0x19, + 0x10, 0x41, 0xf5, 0xC2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xC3, 0x49, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0x63, 0x42, 0x4E, 0x2B, + 0xC2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x18, 0x40, 0xC2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x63, 0x42, 0x4E, 0x2D, 0xC3, 0x49, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, + 0x3F, 0xC3, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 +}; + + +static const char *szMIME = "\ +MIME-Version: 1.0\n\ +Content-Type: multipart/mixed;\n\ +boundary=\"XXXXboundary text\"\n\ +\n\ +This is a multipart message in MIME format.\n\ +\n\ +--XXXXboundary text\n\ +Content-Type: text/plain\n\ +\n\ +this is the body text\n\ +\n\ +--XXXXboundary text\n\ +Content-Type: text/plain;\n\ +Content-Disposition: attachment;\n\ +filename=\"test.txt\"\n\ +\n\ +this is the attachment text\n\ +\n\ +--XXXXboundary text--"; + + +int AllAddMethodsTest() +{ + // TODO: this test should be broken down into several so it is more managable. Tags and labels could be more sensible + QCBOREncodeContext ECtx; + int nReturn = 0; + + QCBOREncode_Init(&ECtx, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf)); + + QCBOREncode_OpenArray(&ECtx); + + // Some ints that are tagged and have strings preceeding them (not labels becase it is not a map) + QCBOREncode_AddSZString(&ECtx, "UINT62"); + QCBOREncode_AddTag(&ECtx, 100); + QCBOREncode_AddUInt64(&ECtx, 89989909); + QCBOREncode_AddSZString(&ECtx, "INT64"); + QCBOREncode_AddTag(&ECtx, 76); + QCBOREncode_AddInt64(&ECtx, 77689989909); + QCBOREncode_AddUInt64(&ECtx,0); + QCBOREncode_AddInt64(&ECtx, -44); + + // ints that go in maps + QCBOREncode_OpenMap(&ECtx); + QCBOREncode_AddUInt64ToMap(&ECtx, "LBL", 77); + QCBOREncode_AddUInt64ToMapN(&ECtx, -4, 88); + QCBOREncode_AddInt64ToMap(&ECtx, "NEGLBLTHAT IS KIND OF LONG", -2394893489238); + QCBOREncode_AddInt64ToMapN(&ECtx, -100000000, -800000000); + QCBOREncode_CloseMap(&ECtx); + + // Epoch Date + QCBOREncode_AddDateEpoch(&ECtx, 2383748234); + + // Epoch date with labels + QCBOREncode_OpenMap(&ECtx); + QCBOREncode_AddDateEpochToMap(&ECtx, "LongLiveDenisRitchie", 1400000000); + QCBOREncode_AddDateEpochToMap(&ECtx, "time()", 1477263730); + QCBOREncode_AddDateEpochToMapN(&ECtx, -1969, 1477263222); + QCBOREncode_CloseMap(&ECtx); + + // Binary blobs + QCBOREncode_AddBytes(&ECtx, ((UsefulBufC) {(uint8_t []){0xff, 0x00}, 2})); + + // binary blobs in maps + QCBOREncode_OpenMap(&ECtx); + QCBOREncode_AddSZString(&ECtx, "binbin"); + QCBOREncode_AddTag(&ECtx, 100000); + QCBOREncode_AddBytes(&ECtx, ((UsefulBufC) {(uint8_t []){0x00}, 1})); + QCBOREncode_AddBytesToMap(&ECtx, "blabel", ((UsefulBufC) {(uint8_t []){0x01, 0x02, 0x03}, 3})); + QCBOREncode_AddBytesToMapN(&ECtx, 0, ((UsefulBufC){(uint8_t []){0x04, 0x02, 0x03, 0xfe}, 4})); + QCBOREncode_CloseMap(&ECtx); + + // text blobs + QCBOREncode_AddText(&ECtx, UsefulBuf_FROM_SZ_LITERAL("bar bar foo bar")); + QCBOREncode_AddSZString(&ECtx, "oof\n"); + QCBOREncode_AddURI(&ECtx, UsefulBuf_FROM_SZ_LITERAL("http://stackoverflow.com/questions/28059697/how-do-i-toggle-between-debug-and-release-builds-in-xcode-6-7-8")); + QCBOREncode_AddB64Text(&ECtx, UsefulBuf_FROM_SZ_LITERAL("YW55IGNhcm5hbCBwbGVhc3VyZQ==")); + QCBOREncode_AddRegex(&ECtx, UsefulBuf_FROM_SZ_LITERAL("[^abc]+")); + QCBOREncode_AddMIMEData(&ECtx, UsefulBuf_FromSZ(szMIME)); + + // text blobs in maps + QCBOREncode_OpenMap(&ECtx); + QCBOREncode_AddTextToMap(&ECtx, "#####", UsefulBuf_FROM_SZ_LITERAL("foo bar foo foo")); + QCBOREncode_AddTextToMap(&ECtx, "____", UsefulBuf_FROM_SZ_LITERAL("foo bar")); + QCBOREncode_AddSZString(&ECtx, "()()()"); + QCBOREncode_AddTag(&ECtx, 1000); + QCBOREncode_AddSZString(&ECtx, "rab rab oof"); + QCBOREncode_AddTextToMapN(&ECtx,22, UsefulBuf_FROM_SZ_LITERAL("foo foo foo foo")); + QCBOREncode_AddSZStringToMap(&ECtx, "^^", "oooooooof"); + QCBOREncode_AddSZStringToMapN(&ECtx, 99, "ffffoooooooof"); + QCBOREncode_AddURIToMap(&ECtx, "RFC", UsefulBuf_FROM_SZ_LITERAL("https://tools.ietf.org/html/rfc7049#section-2.4.5")); + QCBOREncode_AddURIToMapN(&ECtx, 0x89, UsefulBuf_FROM_SZ_LITERAL("http://cbor.me/")); + QCBOREncode_AddB64TextToMap(&ECtx, "whenim64", UsefulBuf_FROM_SZ_LITERAL("cGxlYXN1cmUu")); + QCBOREncode_AddB64TextToMapN(&ECtx, 64, UsefulBuf_FROM_SZ_LITERAL("c3VyZS4=")); + QCBOREncode_AddRegexToMap(&ECtx, "popo", UsefulBuf_FROM_SZ_LITERAL("100\\s*mk")); // x code string literal bug + QCBOREncode_AddRegexToMapN(&ECtx, -51, UsefulBuf_FROM_SZ_LITERAL("perl\\B")); // x code string literal bug + QCBOREncode_AddMIMEDataToMap(&ECtx, "Ned", UsefulBuf_FromSZ(szMIME)); + QCBOREncode_AddMIMEDataToMapN(&ECtx, 10, UsefulBuf_FromSZ(szMIME)); + QCBOREncode_CloseMap(&ECtx); + + // Date strings + QCBOREncode_AddDateString(&ECtx, "2003-12-13T18:30:02Z"); + QCBOREncode_OpenMap(&ECtx); + QCBOREncode_AddDateStringToMap(&ECtx, "Bed time", "2003-12-13T18:30:02.25+01:00"); + QCBOREncode_AddDateStringToMapN(&ECtx, 88, "2003-12-13T18:30:02.25+01:00"); + QCBOREncode_CloseMap(&ECtx); + + // true / false ... + QCBOREncode_AddSimple(&ECtx, CBOR_SIMPLEV_UNDEF); + QCBOREncode_OpenMap(&ECtx); + QCBOREncode_AddSZString(&ECtx, "dare"); + QCBOREncode_AddTag(&ECtx, 66); + QCBOREncode_AddBool(&ECtx, true); + QCBOREncode_AddBoolToMap(&ECtx, "uu", false); + QCBOREncode_AddSimpleToMapN(&ECtx, 737634, CBOR_SIMPLEV_NULL); + QCBOREncode_CloseMap(&ECtx); + + // opening an array + QCBOREncode_OpenArray(&ECtx); + QCBOREncode_CloseArray(&ECtx); + + // opening arrays in a map + QCBOREncode_OpenMap(&ECtx); + QCBOREncode_AddSZString(&ECtx, "label and tagged empty array"); + QCBOREncode_AddTag(&ECtx, 1093); + QCBOREncode_OpenArray(&ECtx); + QCBOREncode_CloseArray(&ECtx); + QCBOREncode_OpenArrayInMap(&ECtx, "alabl"); + QCBOREncode_CloseArray(&ECtx); + QCBOREncode_OpenArrayInMapN(&ECtx, 42); + QCBOREncode_CloseArray(&ECtx); + QCBOREncode_CloseMap(&ECtx); + + // opening maps with labels and tagging + QCBOREncode_OpenMap(&ECtx); + QCBOREncode_OpenMapInMap(&ECtx, "in a map"); + QCBOREncode_OpenMapInMapN(&ECtx, 5556); + QCBOREncode_AddSZString(&ECtx, "in a in a in a"); + QCBOREncode_AddTag(&ECtx, 9087); + QCBOREncode_OpenMap(&ECtx); + QCBOREncode_CloseMap(&ECtx); + QCBOREncode_CloseMap(&ECtx); + QCBOREncode_CloseMap(&ECtx); + QCBOREncode_CloseMap(&ECtx); + + + // Extended simple values (these are not standard...) + QCBOREncode_OpenMap(&ECtx); + QCBOREncode_AddSZString(&ECtx, "s1"); + QCBOREncode_AddTag(&ECtx, 88); + QCBOREncode_AddSimple(&ECtx, 255); + QCBOREncode_AddSimpleToMap(&ECtx, "s2", 0); + QCBOREncode_AddSZString(&ECtx, "s3"); + QCBOREncode_AddTag(&ECtx, 88); + QCBOREncode_AddSimple(&ECtx, 33); + QCBOREncode_AddInt64(&ECtx, 88378374); // label before tag + QCBOREncode_AddTag(&ECtx, 88); + QCBOREncode_AddSimple(&ECtx, 255); + QCBOREncode_AddInt64(&ECtx, 89); // label before tag + QCBOREncode_AddTag(&ECtx, 88); + QCBOREncode_AddSimple(&ECtx, 19); + QCBOREncode_CloseMap(&ECtx); + + // UUIDs + static const uint8_t ppppUUID[] = {0x53, 0x4D, 0x41, 0x52, 0x54, 0x43, 0x53, 0x4C, 0x54, 0x54, 0x43, 0x46, 0x49, 0x43, 0x41, 0x32}; + const UsefulBufC XXUUID = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(ppppUUID); + QCBOREncode_AddBinaryUUID(&ECtx, XXUUID); + QCBOREncode_OpenMap(&ECtx); + QCBOREncode_AddBinaryUUIDToMap(&ECtx, "UUUU", XXUUID); + QCBOREncode_AddBinaryUUIDToMapN(&ECtx, 99, XXUUID); + QCBOREncode_CloseMap(&ECtx); + + // Bool + QCBOREncode_AddBool(&ECtx, true); + QCBOREncode_AddBool(&ECtx, false); + QCBOREncode_OpenMap(&ECtx); + QCBOREncode_AddBoolToMap(&ECtx, "George is the man", true); + QCBOREncode_AddBoolToMapN(&ECtx, 010101, true); + QCBOREncode_CloseMap(&ECtx); + + + static const uint8_t pBignum[] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + const UsefulBufC BIGNUM = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pBignum); + QCBOREncode_AddPositiveBignum(&ECtx, BIGNUM); + QCBOREncode_AddNegativeBignum(&ECtx, BIGNUM); + QCBOREncode_OpenMap(&ECtx); + QCBOREncode_AddPositiveBignumToMap(&ECtx, "BN+", BIGNUM); + QCBOREncode_AddPositiveBignumToMapN(&ECtx, 64, BIGNUM); + QCBOREncode_AddNegativeBignumToMap(&ECtx, "BN-", BIGNUM); + QCBOREncode_AddNegativeBignumToMapN(&ECtx, -64, BIGNUM); + QCBOREncode_CloseMap(&ECtx); + + QCBOREncode_CloseArray(&ECtx); + + UsefulBufC Enc; + + if(QCBOREncode_Finish(&ECtx, &Enc)) { + nReturn = -1; + goto Done; + } + + if(CheckResults(Enc, spExpectedEncodedAll)) + nReturn = -2; + +Done: + return nReturn; +} + +/* + 98 2F # array(47) + 3B 7FFFFFFFFFFFFFFF # negative(9223372036854775807) + 3B 0000000100000000 # negative(4294967296) + 3A FFFFFFFF # negative(4294967295) + 3A FFFFFFFE # negative(4294967294) + 3A FFFFFFFD # negative(4294967293) + 3A 7FFFFFFF # negative(2147483647) + 3A 7FFFFFFE # negative(2147483646) + 3A 00010001 # negative(65537) + 3A 00010000 # negative(65536) + 39 FFFF # negative(65535) + 39 FFFE # negative(65534) + 39 FFFD # negative(65533) + 39 0100 # negative(256) + 38 FF # negative(255) + 38 FE # negative(254) + 38 FD # negative(253) + 38 18 # negative(24) + 37 # negative(23) + 36 # negative(22) + 20 # negative(0) + 00 # unsigned(0) + 00 # unsigned(0) + 01 # unsigned(1) + 16 # unsigned(22) + 17 # unsigned(23) + 18 18 # unsigned(24) + 18 19 # unsigned(25) + 18 1A # unsigned(26) + 18 FE # unsigned(254) + 18 FF # unsigned(255) + 19 0100 # unsigned(256) + 19 0101 # unsigned(257) + 19 FFFE # unsigned(65534) + 19 FFFF # unsigned(65535) + 1A 00010000 # unsigned(65536) + 1A 00010001 # unsigned(65537) + 1A 00010002 # unsigned(65538) + 1A 7FFFFFFF # unsigned(2147483647) + 1A 7FFFFFFF # unsigned(2147483647) + 1A 80000000 # unsigned(2147483648) + 1A 80000001 # unsigned(2147483649) + 1A FFFFFFFE # unsigned(4294967294) + 1A FFFFFFFF # unsigned(4294967295) + 1B 0000000100000000 # unsigned(4294967296) + 1B 0000000100000001 # unsigned(4294967297) + 1B 7FFFFFFFFFFFFFFF # unsigned(9223372036854775807) + 1B FFFFFFFFFFFFFFFF # unsigned(18446744073709551615) + */ +static const uint8_t spExpectedEncodedInts[] = { + 0x98, 0x2f, 0x3b, 0x7f, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x3b, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x3a, 0xff, 0xff, 0xff, + 0xff, 0x3a, 0xff, 0xff, 0xff, 0xfe, 0x3a, 0xff, + 0xff, 0xff, 0xfd, 0x3a, 0x7f, 0xff, 0xff, 0xff, + 0x3a, 0x7f, 0xff, 0xff, 0xfe, 0x3a, 0x00, 0x01, + 0x00, 0x01, 0x3a, 0x00, 0x01, 0x00, 0x00, 0x39, + 0xff, 0xff, 0x39, 0xff, 0xfe, 0x39, 0xff, 0xfd, + 0x39, 0x01, 0x00, 0x38, 0xff, 0x38, 0xfe, 0x38, + 0xfd, 0x38, 0x18, 0x37, 0x36, 0x20, 0x00, 0x00, + 0x01, 0x16, 0x17, 0x18, 0x18, 0x18, 0x19, 0x18, + 0x1a, 0x18, 0xfe, 0x18, 0xff, 0x19, 0x01, 0x00, + 0x19, 0x01, 0x01, 0x19, 0xff, 0xfe, 0x19, 0xff, + 0xff, 0x1a, 0x00, 0x01, 0x00, 0x00, 0x1a, 0x00, + 0x01, 0x00, 0x01, 0x1a, 0x00, 0x01, 0x00, 0x02, + 0x1a, 0x7f, 0xff, 0xff, 0xff, 0x1a, 0x7f, 0xff, + 0xff, 0xff, 0x1a, 0x80, 0x00, 0x00, 0x00, 0x1a, + 0x80, 0x00, 0x00, 0x01, 0x1a, 0xff, 0xff, 0xff, + 0xfe, 0x1a, 0xff, 0xff, 0xff, 0xff, 0x1b, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x1b, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x1b, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x1b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff}; + +/* + + Test the generation of integers. This also ends up testing + encoding of all the different lengths. It encodes integers + of many lengths and values, especially around the boundaries + for different types of integers. It compares the output + to expected values generated from http://cbor.me. + + */ +int IntegerValuesTest1() +{ + QCBOREncodeContext ECtx; + int nReturn = 0; + + QCBOREncode_Init(&ECtx, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf)); + QCBOREncode_OpenArray(&ECtx); + + QCBOREncode_AddInt64(&ECtx, -9223372036854775807LL - 1); + QCBOREncode_AddInt64(&ECtx, -4294967297); + QCBOREncode_AddInt64(&ECtx, -4294967296); + QCBOREncode_AddInt64(&ECtx, -4294967295); + QCBOREncode_AddInt64(&ECtx, -4294967294); + QCBOREncode_AddInt64(&ECtx, -2147483648); + QCBOREncode_AddInt64(&ECtx, -2147483647); + QCBOREncode_AddInt64(&ECtx, -65538); + QCBOREncode_AddInt64(&ECtx, -65537); + QCBOREncode_AddInt64(&ECtx, -65536); + QCBOREncode_AddInt64(&ECtx, -65535); + QCBOREncode_AddInt64(&ECtx, -65534); + QCBOREncode_AddInt64(&ECtx, -257); + QCBOREncode_AddInt64(&ECtx, -256); + QCBOREncode_AddInt64(&ECtx, -255); + QCBOREncode_AddInt64(&ECtx, -254); + QCBOREncode_AddInt64(&ECtx, -25); + QCBOREncode_AddInt64(&ECtx, -24); + QCBOREncode_AddInt64(&ECtx, -23); + QCBOREncode_AddInt64(&ECtx, -1); + QCBOREncode_AddInt64(&ECtx, 0); + QCBOREncode_AddUInt64(&ECtx, 0ULL); + QCBOREncode_AddInt64(&ECtx, 1); + QCBOREncode_AddInt64(&ECtx, 22); + QCBOREncode_AddInt64(&ECtx, 23); + QCBOREncode_AddInt64(&ECtx, 24); + QCBOREncode_AddInt64(&ECtx, 25); + QCBOREncode_AddInt64(&ECtx, 26); + QCBOREncode_AddInt64(&ECtx, 254); + QCBOREncode_AddInt64(&ECtx, 255); + QCBOREncode_AddInt64(&ECtx, 256); + QCBOREncode_AddInt64(&ECtx, 257); + QCBOREncode_AddInt64(&ECtx, 65534); + QCBOREncode_AddInt64(&ECtx, 65535); + QCBOREncode_AddInt64(&ECtx, 65536); + QCBOREncode_AddInt64(&ECtx, 65537); + QCBOREncode_AddInt64(&ECtx, 65538); + QCBOREncode_AddInt64(&ECtx, 2147483647); + QCBOREncode_AddInt64(&ECtx, 2147483647); + QCBOREncode_AddInt64(&ECtx, 2147483648); + QCBOREncode_AddInt64(&ECtx, 2147483649); + QCBOREncode_AddInt64(&ECtx, 4294967294); + QCBOREncode_AddInt64(&ECtx, 4294967295); + QCBOREncode_AddInt64(&ECtx, 4294967296); + QCBOREncode_AddInt64(&ECtx, 4294967297); + QCBOREncode_AddInt64(&ECtx, 9223372036854775807LL); + QCBOREncode_AddUInt64(&ECtx, 18446744073709551615ULL); + + QCBOREncode_CloseArray(&ECtx); + + UsefulBufC Enc; + if(QCBOREncode_Finish(&ECtx, &Enc)) { + nReturn = -1; + } + + if(CheckResults(Enc, spExpectedEncodedInts)) + return -2; + + return(nReturn); +} + + +/* + 85 # array(5) + F5 # primitive(21) + F4 # primitive(20) + F6 # primitive(22) + F7 # primitive(23) + A1 # map(1) + 65 # text(5) + 554E446566 # "UNDef" + F7 # primitive(23) + */ +static const uint8_t spExpectedEncodedSimple[] = { + 0x85, 0xf5, 0xf4, 0xf6, 0xf7, 0xa1, 0x65, 0x55, 0x4e, 0x44, 0x65, 0x66, 0xf7}; + +int SimpleValuesTest1() +{ + QCBOREncodeContext ECtx; + int nReturn = 0; + + QCBOREncode_Init(&ECtx, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf)); + QCBOREncode_OpenArray(&ECtx); + + QCBOREncode_AddSimple(&ECtx, CBOR_SIMPLEV_TRUE); + QCBOREncode_AddSimple(&ECtx, CBOR_SIMPLEV_FALSE); + QCBOREncode_AddSimple(&ECtx, CBOR_SIMPLEV_NULL); + QCBOREncode_AddSimple(&ECtx, CBOR_SIMPLEV_UNDEF); + + QCBOREncode_OpenMap(&ECtx); + + QCBOREncode_AddSimpleToMap(&ECtx, "UNDef", CBOR_SIMPLEV_UNDEF); + QCBOREncode_CloseMap(&ECtx); + + QCBOREncode_CloseArray(&ECtx); + + UsefulBufC ECBOR; + if(QCBOREncode_Finish(&ECtx, &ECBOR)) { + nReturn = -1; + } + + if(CheckResults(ECBOR, spExpectedEncodedSimple)) + return -2; + + return(nReturn); +} + + +/* + 83 # array(3) + C0 # tag(0) + 74 # text(20) + 323031332D30332D32315432303A30343A30305A # "2013-03-21T20:04:00Z" + C1 # tag(1) + 1A 514B67B0 # unsigned(1363896240) + A2 # map(2) + 78 19 # text(25) + 53616D706C6520446174652066726F6D205246432033333339 # "Sample Date from RFC 3339" + C0 # tag(0) + 77 # text(23) + 313938352D30342D31325432333A32303A35302E35325A # "1985-04-12T23:20:50.52Z" + 62 # text(2) + 5344 # "SD" + C1 # tag(1) + 19 03E7 # unsigned(999) + */ +static const uint8_t spExpectedEncodedDates[] = { + 0x83, 0xc0, 0x74, 0x32, 0x30, 0x31, 0x33, 0x2d, 0x30, 0x33, + 0x2d, 0x32, 0x31, 0x54, 0x32, 0x30, 0x3a, 0x30, 0x34, 0x3a, + 0x30, 0x30, 0x5a, 0xc1, 0x1a, 0x51, 0x4b, 0x67, 0xb0, 0xa2, + 0x78, 0x19, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x44, + 0x61, 0x74, 0x65, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x52, + 0x46, 0x43, 0x20, 0x33, 0x33, 0x33, 0x39, 0xc0, 0x77, 0x31, + 0x39, 0x38, 0x35, 0x2d, 0x30, 0x34, 0x2d, 0x31, 0x32, 0x54, + 0x32, 0x33, 0x3a, 0x32, 0x30, 0x3a, 0x35, 0x30, 0x2e, 0x35, + 0x32, 0x5a, 0x62, 0x53, 0x44, 0xc1, 0x19, 0x03, 0xe7 +}; + +int EncodeDateTest() +{ + QCBOREncodeContext ECtx; + int nReturn = 0; + + QCBOREncode_Init(&ECtx, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf)); + + QCBOREncode_OpenArray(&ECtx); + + + QCBOREncode_AddDateString(&ECtx, "2013-03-21T20:04:00Z"); // from CBOR RFC + QCBOREncode_AddDateEpoch(&ECtx, 1363896240); // from CBOR RFC + + + QCBOREncode_OpenMap(&ECtx); + + QCBOREncode_AddDateStringToMap(&ECtx, "Sample Date from RFC 3339", "1985-04-12T23:20:50.52Z"); + + QCBOREncode_AddDateEpochToMap(&ECtx, "SD", 999); + + QCBOREncode_CloseMap(&ECtx); + + QCBOREncode_CloseArray(&ECtx); + + UsefulBufC ECBOR; + + if(QCBOREncode_Finish(&ECtx, &ECBOR)) { + nReturn = -1; + } + + if(CheckResults(ECBOR, spExpectedEncodedDates)) + return -2; + + return(nReturn); +} + + +int ArrayNestingTest1() +{ + QCBOREncodeContext ECtx; + int i; + int nReturn = 0; + + QCBOREncode_Init(&ECtx, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf)); + for(i = QCBOR_MAX_ARRAY_NESTING; i; i--) { + QCBOREncode_OpenArray(&ECtx); + } + for(i = QCBOR_MAX_ARRAY_NESTING; i; i--) { + QCBOREncode_CloseArray(&ECtx); + } + UsefulBufC Encoded; + if(QCBOREncode_Finish(&ECtx, &Encoded)) { + nReturn = -1; + } + + return(nReturn); +} + + + +int ArrayNestingTest2() +{ + QCBOREncodeContext ECtx; + int i; + int nReturn = 0; + + QCBOREncode_Init(&ECtx, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf)); + for(i = QCBOR_MAX_ARRAY_NESTING+1; i; i--) { + QCBOREncode_OpenArray(&ECtx); + } + for(i = QCBOR_MAX_ARRAY_NESTING; i; i--) { + QCBOREncode_CloseArray(&ECtx); + } + + UsefulBufC Encoded; + if(QCBOREncode_Finish(&ECtx, &Encoded) != QCBOR_ERR_ARRAY_NESTING_TOO_DEEP) { + nReturn = -1; + } + + return(nReturn); +} + + + +int ArrayNestingTest3() +{ + QCBOREncodeContext ECtx; + int i; + int nReturn = 0; + + QCBOREncode_Init(&ECtx, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf)); + for(i = QCBOR_MAX_ARRAY_NESTING; i; i--) { + QCBOREncode_OpenArray(&ECtx); + } + for(i = QCBOR_MAX_ARRAY_NESTING+1 ; i; i--) { + QCBOREncode_CloseArray(&ECtx); + } + UsefulBufC Encoded; + if(QCBOREncode_Finish(&ECtx, &Encoded) != QCBOR_ERR_TOO_MANY_CLOSES) { + nReturn = -1; + } + + return(nReturn); +} + + +/* + 81 # array(1) + 81 # array(1) + 81 # array(1) + 81 # array(1) + 80 # array(0) +*/ +static const uint8_t spFiveArrarys[] = {0x81, 0x81, 0x81, 0x81, 0x80}; + +// Validated at http://cbor.me and by manually examining its output +/* + 82 # array(2) + 81 # array(1) + 81 # array(1) + 81 # array(1) + 81 # array(1) + 80 # array(0) + 98 2F # array(47) + 3B 7FFFFFFFFFFFFFFF # negative(9223372036854775807) + 3B 0000000100000000 # negative(4294967296) + 3A FFFFFFFF # negative(4294967295) + 3A FFFFFFFE # negative(4294967294) + 3A FFFFFFFD # negative(4294967293) + 3A 7FFFFFFF # negative(2147483647) + 3A 7FFFFFFE # negative(2147483646) + 3A 00010001 # negative(65537) + 3A 00010000 # negative(65536) + 39 FFFF # negative(65535) + 39 FFFE # negative(65534) + 39 FFFD # negative(65533) + 39 0100 # negative(256) + 38 FF # negative(255) + 38 FE # negative(254) + 38 FD # negative(253) + 38 18 # negative(24) + 37 # negative(23) + 36 # negative(22) + 20 # negative(0) + 00 # unsigned(0) + 00 # unsigned(0) + 01 # unsigned(1) + 16 # unsigned(22) + 17 # unsigned(23) + 18 18 # unsigned(24) + 18 19 # unsigned(25) + 18 1A # unsigned(26) + 18 FE # unsigned(254) + 18 FF # unsigned(255) + 19 0100 # unsigned(256) + 19 0101 # unsigned(257) + 19 FFFE # unsigned(65534) + 19 FFFF # unsigned(65535) + 1A 00010000 # unsigned(65536) + 1A 00010001 # unsigned(65537) + 1A 00010002 # unsigned(65538) + 1A 7FFFFFFF # unsigned(2147483647) + 1A 7FFFFFFF # unsigned(2147483647) + 1A 80000000 # unsigned(2147483648) + 1A 80000001 # unsigned(2147483649) + 1A FFFFFFFE # unsigned(4294967294) + 1A FFFFFFFF # unsigned(4294967295) + 1B 0000000100000000 # unsigned(4294967296) + 1B 0000000100000001 # unsigned(4294967297) + 1B 7FFFFFFFFFFFFFFF # unsigned(9223372036854775807) + 1B FFFFFFFFFFFFFFFF # unsigned(18446744073709551615) + */ +static const uint8_t spEncodeRawExpected[] = { + 0x82, 0x81, 0x81, 0x81, 0x81, 0x80, 0x98, 0x2f, + 0x3b, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x3b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x3a, 0xff, 0xff, 0xff, 0xff, 0x3a, + 0xff, 0xff, 0xff, 0xfe, 0x3a, 0xff, 0xff, 0xff, + 0xfd, 0x3a, 0x7f, 0xff, 0xff, 0xff, 0x3a, 0x7f, + 0xff, 0xff, 0xfe, 0x3a, 0x00, 0x01, 0x00, 0x01, + 0x3a, 0x00, 0x01, 0x00, 0x00, 0x39, 0xff, 0xff, + 0x39, 0xff, 0xfe, 0x39, 0xff, 0xfd, 0x39, 0x01, + 0x00, 0x38, 0xff, 0x38, 0xfe, 0x38, 0xfd, 0x38, + 0x18, 0x37, 0x36, 0x20, 0x00, 0x00, 0x01, 0x16, + 0x17, 0x18, 0x18, 0x18, 0x19, 0x18, 0x1a, 0x18, + 0xfe, 0x18, 0xff, 0x19, 0x01, 0x00, 0x19, 0x01, + 0x01, 0x19, 0xff, 0xfe, 0x19, 0xff, 0xff, 0x1a, + 0x00, 0x01, 0x00, 0x00, 0x1a, 0x00, 0x01, 0x00, + 0x01, 0x1a, 0x00, 0x01, 0x00, 0x02, 0x1a, 0x7f, + 0xff, 0xff, 0xff, 0x1a, 0x7f, 0xff, 0xff, 0xff, + 0x1a, 0x80, 0x00, 0x00, 0x00, 0x1a, 0x80, 0x00, + 0x00, 0x01, 0x1a, 0xff, 0xff, 0xff, 0xfe, 0x1a, + 0xff, 0xff, 0xff, 0xff, 0x1b, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x1b, 0x7f, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1b, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + + +int EncodeRawTest() +{ + QCBOREncodeContext ECtx; + + QCBOREncode_Init(&ECtx, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf)); + QCBOREncode_OpenArray(&ECtx); + QCBOREncode_AddEncoded(&ECtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spFiveArrarys)); + QCBOREncode_AddEncoded(&ECtx, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spExpectedEncodedInts)); + QCBOREncode_CloseArray(&ECtx); + + UsefulBufC EncodedRawTest; + + if(QCBOREncode_Finish(&ECtx, &EncodedRawTest)) { + return -4; + } + + if(CheckResults(EncodedRawTest, spEncodeRawExpected)) { + return -5; + } + + return 0; +} + +/* + This returns a pointer to spBigBuf + */ +static int CreateMap(uint8_t **pEncoded, size_t *pEncodedLen) +{ + QCBOREncodeContext ECtx; + int nReturn = -1; + + *pEncoded = NULL; + *pEncodedLen = INT32_MAX; + size_t uFirstSizeEstimate = 0; + + // loop runs CBOR encoding twice. First with no buffer to + // calucate the length so buffer can be allocated correctly, + // and last with the buffer to do the actual encoding + do { + QCBOREncode_Init(&ECtx, (UsefulBuf){*pEncoded, *pEncodedLen}); + QCBOREncode_OpenMap(&ECtx); + QCBOREncode_AddInt64ToMap(&ECtx, "first integer", 42); + QCBOREncode_OpenArrayInMap(&ECtx, "an array of two strings"); + QCBOREncode_AddText(&ECtx, ((UsefulBufC) {"string1", 7})); + QCBOREncode_AddText(&ECtx, ((UsefulBufC) {"string2", 7})); + QCBOREncode_CloseArray(&ECtx); + QCBOREncode_OpenMapInMap(&ECtx, "map in a map"); + QCBOREncode_AddBytesToMap(&ECtx,"bytes 1", ((UsefulBufC) { "xxxx", 4})); + QCBOREncode_AddBytesToMap(&ECtx, "bytes 2",((UsefulBufC) { "yyyy", 4})); + QCBOREncode_AddInt64ToMap(&ECtx, "another int", 98); + QCBOREncode_AddTextToMap(&ECtx, "text 2", ((UsefulBufC) {"lies, damn lies and statistics", 30})); + QCBOREncode_CloseMap(&ECtx); + QCBOREncode_CloseMap(&ECtx); + + if(QCBOREncode_FinishGetSize(&ECtx, pEncodedLen)) + goto Done; + if(*pEncoded != NULL) { + if(uFirstSizeEstimate != *pEncodedLen) { + nReturn = 1; + } else { + nReturn = 0; + } + goto Done; + } + *pEncoded = spBigBuf; + uFirstSizeEstimate = *pEncodedLen; + + } while(1); + + Done: + return(nReturn); +} + +/* + A3 # map(3) + 6D # text(13) + 666972737420696E7465676572 # "first integer" + 18 2A # unsigned(42) + 77 # text(23) + 616E206172726179206F662074776F20737472696E6773 # "an array of two strings" + 82 # array(2) + 67 # text(7) + 737472696E6731 # "string1" + 67 # text(7) + 737472696E6732 # "string2" + 6C # text(12) + 6D617020696E2061206D6170 # "map in a map" + A4 # map(4) + 67 # text(7) + 62797465732031 # "bytes 1" + 44 # bytes(4) + 78787878 # "xxxx" + 67 # text(7) + 62797465732032 # "bytes 2" + 44 # bytes(4) + 79797979 # "yyyy" + 6B # text(11) + 616E6F7468657220696E74 # "another int" + 18 62 # unsigned(98) + 66 # text(6) + 746578742032 # "text 2" + 78 1E # text(30) + 6C6965732C2064616D6E206C69657320616E642073746174697374696373 # "lies, damn lies and statistics" + */ +static const uint8_t spValidMapEncoded[] = { + 0xa3, 0x6d, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, 0x69, 0x6e, + 0x74, 0x65, 0x67, 0x65, 0x72, 0x18, 0x2a, 0x77, 0x61, 0x6e, + 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x20, 0x6f, 0x66, 0x20, + 0x74, 0x77, 0x6f, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x73, 0x82, 0x67, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x31, + 0x67, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x32, 0x6c, 0x6d, + 0x61, 0x70, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x6d, 0x61, + 0x70, 0xa4, 0x67, 0x62, 0x79, 0x74, 0x65, 0x73, 0x20, 0x31, + 0x44, 0x78, 0x78, 0x78, 0x78, 0x67, 0x62, 0x79, 0x74, 0x65, + 0x73, 0x20, 0x32, 0x44, 0x79, 0x79, 0x79, 0x79, 0x6b, 0x61, + 0x6e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20, 0x69, 0x6e, 0x74, + 0x18, 0x62, 0x66, 0x74, 0x65, 0x78, 0x74, 0x20, 0x32, 0x78, + 0x1e, 0x6c, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x64, 0x61, 0x6d, + 0x6e, 0x20, 0x6c, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, + 0x20, 0x73, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, + 0x73 } ; + + +int MapEncodeTest() +{ + uint8_t *pEncodedMaps; + size_t nEncodedMapLen; + + if(CreateMap(&pEncodedMaps, &nEncodedMapLen)) { + return -1; + } + + int nReturn = 0; + if(memcmp(spValidMapEncoded, pEncodedMaps, sizeof(spValidMapEncoded))) + nReturn = 2; + + return(nReturn); +} + + +/* + @brief Encode the RTIC results + + @param[in] nRResult CBOR_SIMPLEV_TRUE, CBOR_SIMPLEV_FALSE or CBOR_SIMPLEV_NULL + @param[in] time Time stamp in UNIX epoch time or 0 for no time stamp + @param[in] szAlexString Diagnostic code. + @param[in[ pOut Buffer to put the result in + @param[in/out] pnLen Size of pOut buffer when called; length of data output in buffer on return + + @return + One of the CBOR encoder errors. QCBOR_SUCCESS, which is has value 0, if no error. + + The size of pOut should be 30 bytes plus the length of pnLen. If you make it too + short an error will be returned. This function will never write off the end + of the buffer passed to it. + + If the result is 0, then the correct encoded CBOR is in pOut and *pnLen is the + length of the encoded CBOR. + + */ + +static UsefulBufC FormatRTICResults(int nRResult, uint64_t time, const char *szType, const char *szAlexString, UsefulBuf Storage) +{ + // Buffer that the result will be written in to + // It is fixed size and small that a stack variable will be fine + // QCBOREncode will never write off the end of this buffer. If it won't fit QCBOREncode_Finish will return an error. + + // Context for the encoder + QCBOREncodeContext ECtx; + QCBOREncode_Init(&ECtx, Storage); + + // All the RTIC results are grouped in a CBOR Map which will get turned into a JSON Object + // Contents are label / value pairs + QCBOREncode_OpenMap(&ECtx); + + { // Brace / indention just to show CBOR encoding nesting + + // The result: 0 if scan happened and found nothing; 1 if it happened and found something wrong; 2 if it didn't happen + QCBOREncode_AddSimpleToMap(&ECtx, "integrity", nRResult); + + // Add the diagnostic code + QCBOREncode_AddSZStringToMap(&ECtx, "type", szType); + + // Add a time stamp + if(time) { + QCBOREncode_AddDateEpochToMap(&ECtx, "time", time); + } + + // Add the diagnostic code + QCBOREncode_AddSZStringToMap(&ECtx, "diag", szAlexString); + + // Open a subordinate map for telemtry data + QCBOREncode_OpenMapInMap(&ECtx, "telemetry"); + + { // Brace / indention just to show CBOR encoding nesting + + // Add a few fake integers and buffers for now. + QCBOREncode_AddInt64ToMap(&ECtx, "Shoe Size", 12); + + // Add a few fake integers and buffers for now. + QCBOREncode_AddInt64ToMap(&ECtx, "IQ", 0xffffffff); + + // Add a few fake integers and buffers for now. + static const uint8_t pPV[] = {0x66, 0x67, 0x00, 0x56, 0xaa, 0xbb, 0x01, 0x01}; + const UsefulBufC WSPV = {pPV, sizeof(pPV)}; + + QCBOREncode_AddBytesToMap(&ECtx, "WhaleSharkPatternVector", WSPV); + } + } + + // Close the telemetry map + QCBOREncode_CloseMap(&ECtx); + + // Close the map + QCBOREncode_CloseMap(&ECtx); + + UsefulBufC Result; + + QCBOREncode_Finish(&ECtx, &Result); + + return Result; +} + + +/* + A5 # map(5) + 69 # text(9) + 696E74656772697479 # "integrity" + F4 # primitive(20) + 64 # text(4) + 74797065 # "type" + 66 # text(6) + 726563656E74 # "recent" + 64 # text(4) + 74696D65 # "time" + C1 # tag(1) + 1A 580D4172 # unsigned(1477263730) + 64 # text(4) + 64696167 # "diag" + 6A # text(10) + 30784131654335303031 # "0xA1eC5001" + 69 # text(9) + 74656C656D65747279 # "telemetry" + A3 # map(3) + 69 # text(9) + 53686F652053697A65 # "Shoe Size" + 0C # unsigned(12) + 62 # text(2) + 4951 # "IQ" + 1A FFFFFFFF # unsigned(4294967295) + 77 # text(23) + 5768616C65536861726B5061747465726E566563746F72 # "WhaleSharkPatternVector" + 48 # bytes(8) + 66670056AABB0101 # "fg\x00V\xAA\xBB\x01\x01" + */ +static const uint8_t spExpectedRTIC[] = { + 0xa5, 0x69, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x69, 0x74, + 0x79, 0xf4, 0x64, 0x74, 0x79, 0x70, 0x65, 0x66, 0x72, 0x65, + 0x63, 0x65, 0x6e, 0x74, 0x64, 0x74, 0x69, 0x6d, 0x65, 0xc1, + 0x1a, 0x58, 0x0d, 0x41, 0x72, 0x64, 0x64, 0x69, 0x61, 0x67, + 0x6a, 0x30, 0x78, 0x41, 0x31, 0x65, 0x43, 0x35, 0x30, 0x30, + 0x31, 0x69, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, + 0x79, 0xa3, 0x69, 0x53, 0x68, 0x6f, 0x65, 0x20, 0x53, 0x69, + 0x7a, 0x65, 0x0c, 0x62, 0x49, 0x51, 0x1a, 0xff, 0xff, 0xff, + 0xff, 0x77, 0x57, 0x68, 0x61, 0x6c, 0x65, 0x53, 0x68, 0x61, + 0x72, 0x6b, 0x50, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x56, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x48, 0x66, 0x67, 0x00, 0x56, + 0xaa, 0xbb, 0x01, 0x01}; + + +int RTICResultsTest() +{ + const UsefulBufC Encoded = FormatRTICResults(CBOR_SIMPLEV_FALSE, 1477263730, + "recent", "0xA1eC5001", + UsefulBuf_FROM_BYTE_ARRAY(spBigBuf)); + if(UsefulBuf_IsNULLC(Encoded)) { + return -1; + } + + if(CheckResults(Encoded, spExpectedRTIC)) { + return -2; + } + + return 0; +} + + +/* + 82 # array(2) + 19 01C3 # unsigned(451) + 43 # bytes(3) + 1901D2 # "\x19\x01\xD2" +*/ +static const uint8_t spExpectedBstrWrap[] = {0x82, 0x19, 0x01, 0xC3, 0x43, 0x19, 0x01, 0xD2}; + +/* + Very basic bstr wrapping test + */ +int BstrWrapTest() +{ + QCBOREncodeContext EC; + + QCBOREncode_Init(&EC, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf)); + + QCBOREncode_OpenArray(&EC); + QCBOREncode_AddUInt64(&EC, 451); + + QCBOREncode_BstrWrap(&EC); + QCBOREncode_AddUInt64(&EC, 466); + + UsefulBufC Wrapped; + QCBOREncode_CloseBstrWrap(&EC, &Wrapped); + + QCBOREncode_CloseArray(&EC); + + UsefulBufC Encoded; + if(QCBOREncode_Finish(&EC, &Encoded)) { + return -1; + } + + if(CheckResults(Encoded, spExpectedBstrWrap)) { + return -2; + } + + /* Another test; see about handling length calculation */ + QCBOREncode_Init(&EC, (UsefulBuf){NULL, INT32_MAX}); + QCBOREncode_OpenArray(&EC); + QCBOREncode_BstrWrap(&EC); + QCBOREncode_OpenArray(&EC); + QCBOREncode_AddNULL(&EC); + QCBOREncode_CloseArray(&EC); + UsefulBufC BStr; + QCBOREncode_CloseBstrWrap(&EC, &BStr); + // 2 is one byte for an array of length 1 and 1 byte for a NULL + if(BStr.ptr != NULL || BStr.len != 2) { + return -5; + } + + return 0; +} + + + +int BstrWrapErrorTest() +{ + // -------------- Test closing a bstrwrap when it is an array that is open ----------- + QCBOREncodeContext EC; + + QCBOREncode_Init(&EC, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf)); + + QCBOREncode_OpenArray(&EC); + QCBOREncode_AddUInt64(&EC, 451); + + QCBOREncode_BstrWrap(&EC); + QCBOREncode_AddUInt64(&EC, 466); + QCBOREncode_OpenArray(&EC); + + UsefulBufC Wrapped; + QCBOREncode_CloseBstrWrap(&EC, &Wrapped); + + QCBOREncode_CloseArray(&EC); + + UsefulBufC Encoded2; + if(QCBOREncode_Finish(&EC, &Encoded2) != QCBOR_ERR_CLOSE_MISMATCH) { + return -1; + } + + // ----------- test closing a bstrwrap when nothing is open --------------------- + QCBOREncode_Init(&EC, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf)); + QCBOREncode_CloseBstrWrap(&EC, &Wrapped); + if(QCBOREncode_Finish(&EC, &Encoded2) != QCBOR_ERR_TOO_MANY_CLOSES) { + return -2; + } + + // --------------- test nesting too deep ---------------------------------- + QCBOREncode_Init(&EC, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf)); + for(int i = 1; i < 18; i++) { + QCBOREncode_BstrWrap(&EC); + } + QCBOREncode_AddBool(&EC, true); + + for(int i = 1; i < 18; i++) { + QCBOREncode_CloseBstrWrap(&EC, &Wrapped); + } + + if(QCBOREncode_Finish(&EC, &Encoded2) != QCBOR_ERR_ARRAY_NESTING_TOO_DEEP) { + return -3; + } + + return 0; +} + + + +// Part of bstr_wrap_nest_test +/* + 83 array with three + 53 byte string with 19 bytes + 01 #1 + 50 byte string with 16 bytes + 02 + 4D byte string with 13 bytes + 03 + 4A byte string with 10 bytes + 04 + 47 byte string with 7 bytes + 05 + 44 byte string with 4 bytes + 06 + 41 byte string with 1 byte + 07 + 01 + 02 + 03 + 04 + 05 + 06 + 07 + A2 map with two items + 18 20 label for byte string + 54 byte string of length 20 + 82 Array with two items + 10 The integer value 10 + A2 map with two items + 18 21 label for byte string + 44 byte string with 4 bytes + 81 array with 1 item + 11 integer value 11 + 18 30 integer value 30 + 18 40 integer label 40 + 65 68 65 6C 6C 6F text string hello + 18 31 integer value 31 + 18 41 integer label 41 + 65 68 65 6C 6C 6F text string hello + + + */ + + +/* + 83 # array(3) + 56 # bytes(22) + 00530150024D034A0447054406410700010203040506 # "\x00S\x01P\x02M\x03J\x04G\x05D\x06A\a\x00\x01\x02\x03\x04\x05\x06" + 07 # unsigned(7) + A2 # map(2) + 18 20 # unsigned(32) + 54 # bytes(20) + 8210A21821448111183018406568656C6C6F1831 # "\x82\x10\xA2\x18!D\x81\x11\x180\x18@ehello\x181" + 18 41 # unsigned(65) + 65 # text(5) + 68656C6C6F # "hello" + */ +static const uint8_t spExpectedDeepBstr[] = +{ + 0x83, 0x56, 0x00, 0x53, 0x01, 0x50, 0x02, 0x4D, + 0x03, 0x4A, 0x04, 0x47, 0x05, 0x44, 0x06, 0x41, + 0x07, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0xA2, 0x18, 0x20, 0x54, 0x82, 0x10, 0xA2, + 0x18, 0x21, 0x44, 0x81, 0x11, 0x18, 0x30, 0x18, + 0x40, 0x65, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x18, + 0x31, 0x18, 0x41, 0x65, 0x68, 0x65, 0x6C, 0x6C, + 0x6F +}; + +// Part of bstr_wrap_nest_test +static int DecodeNextNested(UsefulBufC Wrapped) +{ + int nReturn; + QCBORDecodeContext DC; + QCBORDecode_Init(&DC, Wrapped, QCBOR_DECODE_MODE_NORMAL); + + QCBORItem Item; + nReturn = QCBORDecode_GetNext(&DC, &Item); + if(nReturn) { + return -11; + } + if(Item.uDataType != QCBOR_TYPE_INT64) { + return -12; + } + + nReturn = QCBORDecode_GetNext(&DC, &Item); + if(nReturn == QCBOR_ERR_HIT_END) { + return 0; + } + if(Item.uDataType != QCBOR_TYPE_BYTE_STRING) { + return -13; + } + nReturn = DecodeNextNested(Item.val.string); + if(nReturn) { + return nReturn; + } + + nReturn = QCBORDecode_GetNext(&DC, &Item); + if(nReturn) { + return -14; + } + if(Item.uDataType != QCBOR_TYPE_INT64) { + return -15; + } + + if(QCBORDecode_Finish(&DC)) { + return -16; + } + + return 0; +} + +// Part of bstr_wrap_nest_test +static int DecodeNextNested2(UsefulBufC Wrapped) +{ + int nReturn; + QCBORDecodeContext DC; + QCBORDecode_Init(&DC, Wrapped, QCBOR_DECODE_MODE_NORMAL); + + QCBORItem Item; + nReturn = QCBORDecode_GetNext(&DC, &Item); + if(nReturn) { + return -11; + } + if(Item.uDataType != QCBOR_TYPE_ARRAY) { + return -12; + } + + nReturn = QCBORDecode_GetNext(&DC, &Item); + if(nReturn) { + return -11; + } + if(Item.uDataType != QCBOR_TYPE_INT64) { + return -12; + } + + nReturn = QCBORDecode_GetNext(&DC, &Item); + if(nReturn) { + return -11; + } + if(Item.uDataType != QCBOR_TYPE_MAP) { + return 0; + } + + nReturn = QCBORDecode_GetNext(&DC, &Item); + if(nReturn) { + return -11; + } + if(Item.uDataType != QCBOR_TYPE_BYTE_STRING) { + return -13; + } + nReturn = DecodeNextNested2(Item.val.string); + if(nReturn) { + return nReturn; + } + + nReturn = QCBORDecode_GetNext(&DC, &Item); + if(nReturn) { + return -11; + } + if(Item.uDataType != QCBOR_TYPE_TEXT_STRING) { + return -12; + } + nReturn = QCBORDecode_GetNext(&DC, &Item); + if(nReturn) { + return -11; + } + if(Item.uDataType != QCBOR_TYPE_INT64) { + return -12; + } + + if(QCBORDecode_Finish(&DC)) { + return -16; + } + + return 0; +} + + +int BstrWrapNestTest() +{ + QCBOREncodeContext EC; + QCBOREncode_Init(&EC, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf)); + + // ---- Make a complicated nested CBOR structure --- +#define BSTR_TEST_DEPTH 10 + + QCBOREncode_OpenArray(&EC); + + for(int i = 0; i < BSTR_TEST_DEPTH-2; i++) { + QCBOREncode_BstrWrap(&EC); + QCBOREncode_AddUInt64(&EC, i); + } + + for(int i = 0; i < BSTR_TEST_DEPTH-2; i++) { + QCBOREncode_CloseBstrWrap(&EC, NULL); + QCBOREncode_AddUInt64(&EC, i); + } + + for(int i = 0; i < (BSTR_TEST_DEPTH-2)/3; i++) { + QCBOREncode_OpenMap(&EC); + QCBOREncode_BstrWrapInMapN(&EC, i+0x20); + QCBOREncode_OpenArray(&EC); + QCBOREncode_AddUInt64(&EC, i+0x10); + } + + for(int i = 0; i < (BSTR_TEST_DEPTH-2)/3; i++) { + QCBOREncode_CloseArray(&EC); + QCBOREncode_AddUInt64(&EC, i+0x30); + QCBOREncode_CloseBstrWrap(&EC, NULL); + QCBOREncode_AddSZStringToMapN(&EC, i+0x40, "hello"); + QCBOREncode_CloseMap(&EC); + } + QCBOREncode_CloseArray(&EC); + + UsefulBufC Encoded; + if(QCBOREncode_Finish(&EC, &Encoded)) { + return -1; + } + + // ---Compare it to expected. Expected was hand checked with use of CBOR playground ---- + if(UsefulBuf_Compare(UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spExpectedDeepBstr), Encoded)) { + return -25; + } + + + // ---- Decode it and see if it is OK ------ + QCBORDecodeContext DC; + QCBORDecode_Init(&DC, Encoded, QCBOR_DECODE_MODE_NORMAL); + + QCBORItem Item; + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_ARRAY || Item.val.uCount != 3) { + return -2; + } + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_BYTE_STRING) { + return -3; + } + + int nReturn = DecodeNextNested(Item.val.string); + if(nReturn) { + return nReturn; + } + + nReturn = QCBORDecode_GetNext(&DC, &Item); + if(nReturn) { + return -11; + } + if(Item.uDataType != QCBOR_TYPE_INT64) { + return -12; + } + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_MAP || Item.val.uCount != 2) { + return -2; + } + + QCBORDecode_GetNext(&DC, &Item); + if(Item.uDataType != QCBOR_TYPE_BYTE_STRING) { + return -3; + } + nReturn = DecodeNextNested2(Item.val.string); + if(nReturn) { + return nReturn; + } + + nReturn = QCBORDecode_GetNext(&DC, &Item); + if(nReturn) { + return -11; + } + if(Item.uDataType != QCBOR_TYPE_TEXT_STRING) { + return -12; + } + + if(QCBORDecode_Finish(&DC)) { + return -16; + } + + return 0; +} + + +static const uint8_t spSignature[] = { + 0x8e, 0xb3, 0x3e, 0x4c, 0xa3, 0x1d, 0x1c, 0x46, 0x5a, 0xb0, + 0x5a, 0xac, 0x34, 0xcc, 0x6b, 0x23, 0xd5, 0x8f, 0xef, 0x5c, + 0x08, 0x31, 0x06, 0xc4, 0xd2, 0x5a, 0x91, 0xae, 0xf0, 0xb0, + 0x11, 0x7e, 0x2a, 0xf9, 0xa2, 0x91, 0xaa, 0x32, 0xe1, 0x4a, + 0xb8, 0x34, 0xdc, 0x56, 0xed, 0x2a, 0x22, 0x34, 0x44, 0x54, + 0x7e, 0x01, 0xf1, 0x1d, 0x3b, 0x09, 0x16, 0xe5, 0xa4, 0xc3, + 0x45, 0xca, 0xcb, 0x36}; + +/* + D2 # tag(18) + 84 # array(4) + 43 # bytes(3) + A10126 # "\xA1\x01&" + A1 # map(1) + 04 # unsigned(4) + 42 # bytes(2) + 3131 # "11" + 54 # bytes(20) + 546869732069732074686520636F6E74656E742E # "This is the content." + 58 40 # bytes(64) + 8EB33E4CA31D1C465AB05AAC34CC6B23D58FEF5C083106C4D25A91AEF0B0117E2AF9A291AA32E14AB834DC56ED2A223444547E01F11D3B0916E5A4C345CACB36 # "\x8E\xB3>L\xA3\x1D\x1CFZ\xB0Z\xAC4\xCCk#\xD5\x8F\xEF\\\b1\x06\xC4\xD2Z\x91\xAE\xF0\xB0\x11~*\xF9\xA2\x91\xAA2\xE1J\xB84\xDCV\xED*\"4DT~\x01\xF1\x1D;\t\x16\xE5\xA4\xC3E\xCA\xCB6" + */ +static const uint8_t spExpected[] = { + 0xD2, 0x84, 0x43, 0xA1, 0x01, 0x26, 0xA1, 0x04, 0x42, 0x31, + 0x31, 0x54, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x63, 0x6F, 0x6E, 0x74, 0x65, 0x6E, + 0x74, 0x2E, 0x58, 0x40, 0x8E, 0xB3, 0x3E, 0x4C, 0xA3, 0x1D, + 0x1C, 0x46, 0x5A, 0xB0, 0x5A, 0xAC, 0x34, 0xCC, 0x6B, 0x23, + 0xD5, 0x8F, 0xEF, 0x5C, 0x08, 0x31, 0x06, 0xC4, 0xD2, 0x5A, + 0x91, 0xAE, 0xF0, 0xB0, 0x11, 0x7E, 0x2A, 0xF9, 0xA2, 0x91, + 0xAA, 0x32, 0xE1, 0x4A, 0xB8, 0x34, 0xDC, 0x56, 0xED, 0x2A, + 0x22, 0x34, 0x44, 0x54, 0x7E, 0x01, 0xF1, 0x1D, 0x3B, 0x09, + 0x16, 0xE5, 0xA4, 0xC3, 0x45, 0xCA, 0xCB, 0x36}; + +/* + this corresponds exactly to the example in RFC 8152 + section C.2.1. This doesn't actually verify the signature + though that would be nice as it would make the test + really good. That would require bring in ECDSA crypto + to this test. + */ +int CoseSign1TBSTest() +{ + // All of this is from RFC 8152 C.2.1 + const char *szKid = "11"; + const UsefulBufC Kid = UsefulBuf_FromSZ(szKid); + const char *szPayload = "This is the content."; + const UsefulBufC Payload = UsefulBuf_FromSZ(szPayload); + static const uint8_t pProtectedHeaders[] = {0xa1, 0x01, 0x26}; + const UsefulBufC ProtectedHeaders = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pProtectedHeaders); + + // It would be good to compare this to the output from + // a COSE implementation like COSE-C. It has been checked + // against the CBOR playground. + const UsefulBufC Signature = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spSignature); + + QCBOREncodeContext EC; + QCBOREncode_Init(&EC, UsefulBuf_FROM_BYTE_ARRAY(spBigBuf)); + + // top level array for cose sign1, 18 is the tag for COSE sign + QCBOREncode_AddTag(&EC, CBOR_TAG_COSE_SIGN1); + QCBOREncode_OpenArray(&EC); + + // Add protected headers + QCBOREncode_AddBytes(&EC, ProtectedHeaders); + + // Empty map with unprotected headers + QCBOREncode_OpenMap(&EC); + QCBOREncode_AddBytesToMapN(&EC, 4, Kid); + QCBOREncode_CloseMap(&EC); + + // The payload + UsefulBufC WrappedPayload; + QCBOREncode_BstrWrap(&EC); + QCBOREncode_AddEncoded(&EC, Payload); // Payload is not actually CBOR in example C.2.1 + QCBOREncode_CloseBstrWrap(&EC, &WrappedPayload); + + // Check we got back the actual payload expected + if(UsefulBuf_Compare(WrappedPayload, Payload)) { + return -1; + } + + // The signature + QCBOREncode_AddBytes(&EC, Signature); + QCBOREncode_CloseArray(&EC); + + // Finish and check the results + UsefulBufC COSE_Sign1; + if(QCBOREncode_Finish(&EC, &COSE_Sign1)) { + return -2; + } + + // 98 is the size from RFC 8152 C.2.1 + if(COSE_Sign1.len != 98) { + return -3; + } + + if(CheckResults(COSE_Sign1, spExpected)) { + return -4; + } + + return 0; +} + + +int EncodeErrorTests() +{ + QCBOREncodeContext EC; + + + // ------ Test for QCBOR_ERR_BUFFER_TOO_LARGE ------ + // Do all of these tests with NULL buffers so no actual large allocations are neccesary + UsefulBuf Buffer = (UsefulBuf){NULL, UINT32_MAX}; + + // First verify no error from a big buffer + QCBOREncode_Init(&EC, Buffer); + QCBOREncode_OpenArray(&EC); + // 6 is the CBOR overhead for opening the array and encodng the length + // This exactly fills the buffer. + QCBOREncode_AddBytes(&EC, (UsefulBufC){NULL, UINT32_MAX-6}); + QCBOREncode_CloseArray(&EC); + size_t xx; + if(QCBOREncode_FinishGetSize(&EC, &xx) != QCBOR_SUCCESS) { + return -1; + } + + // Second verify error from an array in encoded output too large + QCBOREncode_Init(&EC, Buffer); + QCBOREncode_OpenArray(&EC); + QCBOREncode_AddBytes(&EC, (UsefulBufC){NULL, UINT32_MAX-6}); + QCBOREncode_OpenArray(&EC); // Where QCBOR internally encounters and records error + QCBOREncode_CloseArray(&EC); + QCBOREncode_CloseArray(&EC); + if(QCBOREncode_FinishGetSize(&EC, &xx) != QCBOR_ERR_BUFFER_TOO_LARGE) { + return -2; + } + + // Third, fit an array in exactly at max position allowed + QCBOREncode_Init(&EC, Buffer); + QCBOREncode_OpenArray(&EC); + QCBOREncode_AddBytes(&EC, (UsefulBufC){NULL, QCBOR_MAX_ARRAY_OFFSET-6}); + QCBOREncode_OpenArray(&EC); + QCBOREncode_CloseArray(&EC); + QCBOREncode_CloseArray(&EC); + if(QCBOREncode_FinishGetSize(&EC, &xx) != QCBOR_SUCCESS) { + return -10; + } + + + // ----- QCBOR_ERR_BUFFER_TOO_SMALL -------------- + // Work close to the 4GB size limit for a better test + const uint32_t uLargeSize = UINT32_MAX - 1024; + UsefulBuf Large = (UsefulBuf){NULL,uLargeSize}; + + QCBOREncode_Init(&EC, Large); + QCBOREncode_OpenArray(&EC); + QCBOREncode_AddBytes(&EC, (UsefulBufC){NULL, uLargeSize/2 + 1}); + QCBOREncode_CloseArray(&EC); + if(QCBOREncode_FinishGetSize(&EC, &xx) != QCBOR_SUCCESS) { + // Making sure it succeeds when it should first + return -3; + } + + QCBOREncode_Init(&EC, Large); + QCBOREncode_OpenArray(&EC); + QCBOREncode_AddBytes(&EC, (UsefulBufC){NULL, uLargeSize/2 + 1}); + QCBOREncode_AddBytes(&EC, (UsefulBufC){NULL, uLargeSize/2}); + QCBOREncode_CloseArray(&EC); + if(QCBOREncode_FinishGetSize(&EC, &xx) != QCBOR_ERR_BUFFER_TOO_SMALL) { + // Now just 1 byte over, see that it fails + return -4; + } + + + // ----- QCBOR_ERR_ARRAY_NESTING_TOO_DEEP ------- + QCBOREncode_Init(&EC, Large); + for(int i = QCBOR_MAX_ARRAY_NESTING; i > 0; i--) { + QCBOREncode_OpenArray(&EC); + } + for(int i = QCBOR_MAX_ARRAY_NESTING; i > 0; i--) { + QCBOREncode_CloseArray(&EC); + } + if(QCBOREncode_FinishGetSize(&EC, &xx) != QCBOR_SUCCESS) { + // Making sure it succeeds when it should first + return -5; + } + + QCBOREncode_Init(&EC, Large); + for(int i = QCBOR_MAX_ARRAY_NESTING+1; i > 0; i--) { + QCBOREncode_OpenArray(&EC); + } + for(int i = QCBOR_MAX_ARRAY_NESTING+1; i > 0; i--) { + QCBOREncode_CloseArray(&EC); + } + if(QCBOREncode_FinishGetSize(&EC, &xx) != QCBOR_ERR_ARRAY_NESTING_TOO_DEEP) { + // One more level to cause error + return -6; + } + + + // ------ QCBOR_ERR_TOO_MANY_CLOSES -------- + QCBOREncode_Init(&EC, Large); + for(int i = QCBOR_MAX_ARRAY_NESTING; i > 0; i--) { + QCBOREncode_OpenArray(&EC); + } + for(int i = QCBOR_MAX_ARRAY_NESTING+1; i > 0; i--) { + QCBOREncode_CloseArray(&EC); + } + if(QCBOREncode_FinishGetSize(&EC, &xx) != QCBOR_ERR_TOO_MANY_CLOSES) { + // One more level to cause error + return -7; + } + + + // ------ QCBOR_ERR_CLOSE_MISMATCH -------- + QCBOREncode_Init(&EC, Large); + QCBOREncode_OpenArray(&EC); + UsefulBufC Wrap; + QCBOREncode_CloseBstrWrap(&EC, &Wrap); + if(QCBOREncode_FinishGetSize(&EC, &xx) != QCBOR_ERR_CLOSE_MISMATCH) { + return -8; + } + + + // ------ QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN --------- + QCBOREncode_Init(&EC, Large); + for(int i = QCBOR_MAX_ARRAY_NESTING; i > 0; i--) { + QCBOREncode_OpenArray(&EC); + } + for(int i = QCBOR_MAX_ARRAY_NESTING-1; i > 0; i--) { + QCBOREncode_CloseArray(&EC); + } + if(QCBOREncode_FinishGetSize(&EC, &xx) != QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN) { + // One more level to cause error + return -9; + } + + /* QCBOR_ERR_ARRAY_TOO_LONG is not tested here as + it would require a 64KB of RAM to test */ + + return 0; +} + diff --git a/middleware/qcbor/test/qcbor_encode_tests.h b/middleware/qcbor/test/qcbor_encode_tests.h new file mode 100644 index 000000000..33703d831 --- /dev/null +++ b/middleware/qcbor/test/qcbor_encode_tests.h @@ -0,0 +1,145 @@ +/*============================================================================== + Copyright (c) 2016-2018, The Linux Foundation. + Copyright (c) 2018-2019, Laurence Lundblade. + All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of The Linux Foundation nor the names of its + contributors, nor the name "Laurence Lundblade" may be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ==============================================================================*/ + +#ifndef __QCBOR__qcbor_encode_tests__ +#define __QCBOR__qcbor_encode_tests__ + +#include "qcbor.h" + + +/* + Notes: + + - All the functions in qcbor.h are called once in the aggregation of all the tests below. + + - All the types that are supported are given as input and parsed by these tests + + - There is some hostile input such as invalid lengths and CBOR too complex + and types this parser doesn't handle + + */ + + +/* + Most basic test. + */ +int BasicEncodeTest(void); + + +/* + Encode lots of integer values, particularly around the boundary and make sure they + Match the expected binary output. Primarily an encoding test. + */ +int IntegerValuesTest1(void); + + + +/* + Create nested arrays to the max depth allowed and make sure it succeeds. + This is an encoding test. + */ +int ArrayNestingTest1(void); + + +/* + Create nested arrays to one more than the meax depth and make sure it fails. + This is an encoding test. + */ +int ArrayNestingTest2(void); + + +/* + Encoding test. + Create arrays to max depth and close one extra time and look for correct error code + */ +int ArrayNestingTest3(void); + + +/* + This tests the QCBOREncode_AddRaw() function by adding two chunks or RAWCBOR to an + array and comparing with expected values. This is an encoding test. + */ +int EncodeRawTest(void); + + +/* + This creates a somewhat complicated CBOR MAP and verifies it against expected + data. This is an encoding test. + */ +int MapEncodeTest(void); + + + +/* + Encodes a goodly number of floats and doubles and checks encoding is right + */ +int FloatValuesTest1(void); + + +/* + Encodes true, false and the like + */ +int SimpleValuesTest1(void); + + +/* + Encodes most data formats that are supported */ +int EncodeDateTest(void); + + +/* + Encodes particular data structure that a particular app will need... + */ +int RTICResultsTest(void); + + +/* + Calls all public encode methods in qcbor.h once. + */ +int AllAddMethodsTest(void); + +/* + The binary string wrapping of maps and arrays used by COSE + */ +int BstrWrapTest(void); + +int BstrWrapErrorTest(void); + +int BstrWrapNestTest(void); + +int CoseSign1TBSTest(void); + +int EncodeErrorTests(void); + + + +#endif /* defined(__QCBOR__qcbor_encode_tests__) */ diff --git a/middleware/qcbor/test/run_tests.c b/middleware/qcbor/test/run_tests.c new file mode 100644 index 000000000..80402dacd --- /dev/null +++ b/middleware/qcbor/test/run_tests.c @@ -0,0 +1,285 @@ +/*============================================================================== + run_tests.c -- test aggregator and results reporting + + Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved. + + SPDX-License-Identifier: BSD-3-Clause + + See BSD-3-Clause license in README.md + + Created on 9/30/18 + ==============================================================================*/ + +#include "run_tests.h" +#include "UsefulBuf.h" +#include + +#include "float_tests.h" +#include "qcbor_decode_tests.h" +#include "qcbor_encode_tests.h" +#include "UsefulBuf_Tests.h" + + + +// Used to test RunTests +int fail_test() +{ + return -44; +} + + + + +/* + Convert a number up to 999999999 to a string. This is so sprintf doesn't + have to be linked in so as to minimized dependencies even in test code. + */ +const char *NumToString(int32_t nNum, UsefulBuf StringMem) +{ + const int32_t nMax = 1000000000; + + UsefulOutBuf OutBuf; + UsefulOutBuf_Init(&OutBuf, StringMem); + + if(nNum < 0) { + UsefulOutBuf_AppendByte(&OutBuf, '-'); + nNum = -nNum; + } + if(nNum > nMax-1) { + return "XXX"; + } + + bool bDidSomeOutput = false; + for(int n = nMax; n > 0; n/=10) { + int x = nNum/n; + if(x || bDidSomeOutput){ + bDidSomeOutput = true; + UsefulOutBuf_AppendByte(&OutBuf, '0' + x); + nNum -= x * n; + } + } + if(!bDidSomeOutput){ + UsefulOutBuf_AppendByte(&OutBuf, '0'); + } + UsefulOutBuf_AppendByte(&OutBuf, '\0'); + + return UsefulOutBuf_GetError(&OutBuf) ? "" : StringMem.ptr; +} + + + + +typedef int (test_fun_t)(void); +typedef const char * (test_fun2_t)(void); + + +#define TEST_ENTRY(test_name) {#test_name, test_name, true} +#define TEST_ENTRY_DISABLED(test_name) {#test_name, test_name, false} + +typedef struct { + const char *szTestName; + test_fun_t *test_fun; + bool bEnabled; +} test_entry; + +typedef struct { + const char *szTestName; + test_fun2_t *test_fun; + bool bEnabled; +} test_entry2; + +test_entry2 s_tests2[] = { + TEST_ENTRY(UBUTest_CopyUtil), + TEST_ENTRY(UOBTest_NonAdversarial), + TEST_ENTRY(TestBasicSanity), + TEST_ENTRY(UOBTest_BoundaryConditionsTest), + TEST_ENTRY(UBMacroConversionsTest), + TEST_ENTRY(UBUtilTests), + TEST_ENTRY(UIBTest_IntegerFormat) +}; + + +test_entry s_tests[] = { + TEST_ENTRY(ParseMapAsArrayTest), + TEST_ENTRY(AllocAllStringsTest), + TEST_ENTRY(IndefiniteLengthNestTest), + TEST_ENTRY(NestedMapTestIndefLen), + TEST_ENTRY(ParseSimpleTest), + TEST_ENTRY(EncodeRawTest), + TEST_ENTRY(RTICResultsTest), + TEST_ENTRY(MapEncodeTest), + TEST_ENTRY(ArrayNestingTest1), + TEST_ENTRY(ArrayNestingTest2), + TEST_ENTRY(ArrayNestingTest3), + TEST_ENTRY(EncodeDateTest), + TEST_ENTRY(SimpleValuesTest1), + TEST_ENTRY(IntegerValuesTest1), + TEST_ENTRY(AllAddMethodsTest), + TEST_ENTRY(ParseTooDeepArrayTest), + TEST_ENTRY(ComprehensiveInputTest), + TEST_ENTRY(ParseMapTest), + TEST_ENTRY(IndefiniteLengthArrayMapTest), + TEST_ENTRY(BasicEncodeTest), + TEST_ENTRY(NestedMapTest), + TEST_ENTRY(BignumParseTest), + TEST_ENTRY(OptTagParseTest), + TEST_ENTRY(DateParseTest), + TEST_ENTRY(ShortBufferParseTest2), + TEST_ENTRY(ShortBufferParseTest), + TEST_ENTRY(ParseDeepArrayTest), + TEST_ENTRY(SimpleArrayTest), + TEST_ENTRY(IntegerValuesParseTest), + TEST_ENTRY(MemPoolTest), + TEST_ENTRY(IndefiniteLengthStringTest), + TEST_ENTRY(HalfPrecisionDecodeBasicTests), + TEST_ENTRY(DoubleAsSmallestTest), + TEST_ENTRY(HalfPrecisionAgainstRFCCodeTest), + TEST_ENTRY(BstrWrapTest), + TEST_ENTRY(BstrWrapErrorTest), + TEST_ENTRY(BstrWrapNestTest), + TEST_ENTRY(CoseSign1TBSTest), + TEST_ENTRY(StringDecoderModeFailTest), + TEST_ENTRY_DISABLED(BigComprehensiveInputTest), + TEST_ENTRY(EncodeErrorTests), + //TEST_ENTRY(fail_test), +}; + + +int RunTests(const char *szTestNames[], OutputStringCB pfOutput, void *poutCtx, int *pNumTestsRun) +{ + int nTestsFailed = 0; + int nTestsRun = 0; + UsefulBuf_MAKE_STACK_UB(StringStorage, 5); + + test_entry2 *t2; + const test_entry2 *s_tests2_end = s_tests2 + sizeof(s_tests2)/sizeof(test_entry2); + + for(t2 = s_tests2; t2 < s_tests2_end; t2++) { + if(szTestNames[0]) { + // Some tests have been named + const char **szRequestedNames; + for(szRequestedNames = szTestNames; *szRequestedNames; szRequestedNames++) { + if(!strcmp(t2->szTestName, *szRequestedNames)) { + break; // Name matched + } + } + if(*szRequestedNames == NULL) { + // Didn't match this test + continue; + } + } else { + // no tests named, but don't run "disabled" tests + if(!t2->bEnabled) { + // Don't run disabled tests when all tests are being run + // as indicated by no specific test names being given + continue; + } + } + + const char * szTestResult = (t2->test_fun)(); + nTestsRun++; + if(pfOutput) { + (*pfOutput)(t2->szTestName, poutCtx, 0); + } + + if(szTestResult) { + if(pfOutput) { + (*pfOutput)(" FAILED (returned ", poutCtx, 0); + (*pfOutput)(szTestResult, poutCtx, 0); + (*pfOutput)(")", poutCtx, 1); + } + nTestsFailed++; + } else { + if(pfOutput) { + (*pfOutput)( " PASSED", poutCtx, 1); + } + } + } + + + test_entry *t; + const test_entry *s_tests_end = s_tests + sizeof(s_tests)/sizeof(test_entry); + + for(t = s_tests; t < s_tests_end; t++) { + if(szTestNames[0]) { + // Some tests have been named + const char **szRequestedNames; + for(szRequestedNames = szTestNames; *szRequestedNames; szRequestedNames++) { + if(!strcmp(t->szTestName, *szRequestedNames)) { + break; // Name matched + } + } + if(*szRequestedNames == NULL) { + // Didn't match this test + continue; + } + } else { + // no tests named, but don't run "disabled" tests + if(!t->bEnabled) { + // Don't run disabled tests when all tests are being run + // as indicated by no specific test names being given + continue; + } + } + + int nTestResult = (t->test_fun)(); + nTestsRun++; + if(pfOutput) { + (*pfOutput)(t->szTestName, poutCtx, 0); + } + + if(nTestResult) { + if(pfOutput) { + (*pfOutput)(" FAILED (returned ", poutCtx, 0); + (*pfOutput)(NumToString(nTestResult, StringStorage), poutCtx, 0); + (*pfOutput)(")", poutCtx, 1); + } + nTestsFailed++; + } else { + if(pfOutput) { + (*pfOutput)( " PASSED", poutCtx, 1); + } + } + } + + if(pNumTestsRun) { + *pNumTestsRun = nTestsRun; + } + + if(pfOutput) { + (*pfOutput)( "SUMMARY: ", poutCtx, 0); + (*pfOutput)( NumToString(nTestsRun, StringStorage), poutCtx, 0); + (*pfOutput)( " tests run; ", poutCtx, 0); + (*pfOutput)( NumToString(nTestsFailed, StringStorage), poutCtx, 0); + (*pfOutput)( " tests failed", poutCtx, 1); + } + + return nTestsFailed; +} + + + + +static void PrintSize(const char *szWhat, uint32_t uSize, OutputStringCB pfOutput, void *pOutCtx) +{ + UsefulBuf_MAKE_STACK_UB(buffer, 20); + + (*pfOutput)(szWhat, pOutCtx, 0); + (*pfOutput)(" ", pOutCtx, 0); + (*pfOutput)(NumToString(uSize, buffer), pOutCtx, 0); + (*pfOutput)("", pOutCtx, 1); +} + +void PrintSizes(OutputStringCB pfOutput, void *pOutCtx) +{ + // Type and size of return from sizeof() varies. These will never be large so cast is safe + PrintSize("sizeof(QCBORTrackNesting)", (uint32_t)sizeof(QCBORTrackNesting), pfOutput, pOutCtx); + PrintSize("sizeof(QCBOREncodeContext)", (uint32_t)sizeof(QCBOREncodeContext), pfOutput, pOutCtx); + PrintSize("sizeof(QCBORDecodeNesting)", (uint32_t)sizeof(QCBORDecodeNesting), pfOutput, pOutCtx); + PrintSize("sizeof(QCBORDecodeContext)", (uint32_t)sizeof(QCBORDecodeContext), pfOutput, pOutCtx); + PrintSize("sizeof(QCBORItem)", (uint32_t)sizeof(QCBORItem), pfOutput, pOutCtx); + PrintSize("sizeof(QCBORStringAllocator)",(uint32_t)sizeof(QCBORStringAllocator), pfOutput, pOutCtx); + PrintSize("sizeof(QCBORTagListIn)", (uint32_t)sizeof(QCBORTagListIn), pfOutput, pOutCtx); + PrintSize("sizeof(QCBORTagListOut)", (uint32_t)sizeof(QCBORTagListOut), pfOutput, pOutCtx); + (*pfOutput)("", pOutCtx, 1); +} diff --git a/middleware/qcbor/test/run_tests.h b/middleware/qcbor/test/run_tests.h new file mode 100644 index 000000000..734d4f830 --- /dev/null +++ b/middleware/qcbor/test/run_tests.h @@ -0,0 +1,66 @@ +/*============================================================================== + run_tests.h -- test aggregator and results reporting + + Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved. + + SPDX-License-Identifier: BSD-3-Clause + + See BSD-3-Clause license in README.md + + Created 9/30/18 + ==============================================================================*/ + +/** + @file run_tests.h +*/ + +/** + @brief Type for function to output a text string + + @param[in] szString The string to output + @param[in] pOutCtx A context pointer; NULL if not needed + @param[in] bNewline If non-zero, output a newline after the string + + This is a prototype of a function to be passed to RunTests() to + output text strings. + + This can be implemented with stdio (if available) using a straight + call to fputs() where the FILE * is passed as the pOutCtx as shown in + the example code below. This code is for Linux where the newline is + a \\n. Windows usually prefers \\r\\n. + + @code + static void fputs_wrapper(const char *szString, void *pOutCtx, int bNewLine) + { + fputs(szString, (FILE *)pOutCtx); + if(bNewLine) { + fputs("\n", pOutCtx); + } + } + @endcode +*/ +typedef void (*OutputStringCB)(const char *szString, void *pOutCtx, int bNewline); + + +/** + @brief Runs the QCBOR tests. + + @param[in] szTestNames An argv-style list of test names to run. If + empty, all are run. + @param[in] pfOutput Function that is called to output text strings. + @param[in] pOutCtx Context pointer passed to output function. + @param[out] pNumTestsRun Returns the number of tests run. May be NULL. + + @return The number of tests that failed. Zero means overall success. + */ +int RunTests(const char *szTestNames[], OutputStringCB pfOutput, void *pOutCtx, int *pNumTestsRun); + + +/** + @brief Print sizes of encoder / decoder contexts. + + @param[in] pfOutput Function that is called to output text strings. + @param[in] pOutCtx Context pointer passed to output function. + */ +void PrintSizes(OutputStringCB pfOutput, void *pOutCtx); +