summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Friesel <daniel.friesel@uos.de>2020-09-07 12:57:22 +0200
committerDaniel Friesel <daniel.friesel@uos.de>2020-09-07 12:57:22 +0200
commitefa55eb5b3d3a4942789bdf397c3a6d6226475d4 (patch)
treea3a3f3079bbff203ca6c70544b4258742f5f152d
parent0558244645611f314f47e0fa427f7323ce253eaf (diff)
Revert "remove external libraries from main branch"protocol-modeling
This reverts commit 0558244645611f314f47e0fa427f7323ce253eaf.
-rw-r--r--include/lib/ArduinoJson.h19
-rw-r--r--include/lib/ArduinoJson/Configuration.hpp154
-rw-r--r--include/lib/ArduinoJson/Data/Encoding.hpp37
-rw-r--r--include/lib/ArduinoJson/Data/JsonBufferAllocated.hpp22
-rw-r--r--include/lib/ArduinoJson/Data/JsonFloat.hpp18
-rw-r--r--include/lib/ArduinoJson/Data/JsonInteger.hpp23
-rw-r--r--include/lib/ArduinoJson/Data/JsonVariantAs.hpp42
-rw-r--r--include/lib/ArduinoJson/Data/JsonVariantContent.hpp27
-rw-r--r--include/lib/ArduinoJson/Data/JsonVariantDefault.hpp23
-rw-r--r--include/lib/ArduinoJson/Data/JsonVariantType.hpp27
-rw-r--r--include/lib/ArduinoJson/Data/List.hpp94
-rw-r--r--include/lib/ArduinoJson/Data/ListConstIterator.hpp50
-rw-r--r--include/lib/ArduinoJson/Data/ListIterator.hpp60
-rw-r--r--include/lib/ArduinoJson/Data/ListNode.hpp24
-rw-r--r--include/lib/ArduinoJson/Data/NonCopyable.hpp23
-rw-r--r--include/lib/ArduinoJson/Data/ReferenceType.hpp24
-rw-r--r--include/lib/ArduinoJson/Data/ValueSaver.hpp52
-rw-r--r--include/lib/ArduinoJson/Deserialization/Comments.hpp61
-rw-r--r--include/lib/ArduinoJson/Deserialization/JsonParser.hpp102
-rw-r--r--include/lib/ArduinoJson/Deserialization/JsonParserImpl.hpp189
-rw-r--r--include/lib/ArduinoJson/Deserialization/StringWriter.hpp41
-rw-r--r--include/lib/ArduinoJson/DynamicJsonBuffer.hpp170
-rw-r--r--include/lib/ArduinoJson/JsonArray.hpp227
-rw-r--r--include/lib/ArduinoJson/JsonArrayImpl.hpp26
-rw-r--r--include/lib/ArduinoJson/JsonArraySubscript.hpp122
-rw-r--r--include/lib/ArduinoJson/JsonBuffer.hpp78
-rw-r--r--include/lib/ArduinoJson/JsonBufferBase.hpp127
-rw-r--r--include/lib/ArduinoJson/JsonBufferImpl.hpp17
-rw-r--r--include/lib/ArduinoJson/JsonObject.hpp328
-rw-r--r--include/lib/ArduinoJson/JsonObjectImpl.hpp28
-rw-r--r--include/lib/ArduinoJson/JsonObjectSubscript.hpp110
-rw-r--r--include/lib/ArduinoJson/JsonPair.hpp16
-rw-r--r--include/lib/ArduinoJson/JsonVariant.hpp355
-rw-r--r--include/lib/ArduinoJson/JsonVariantBase.hpp24
-rw-r--r--include/lib/ArduinoJson/JsonVariantCasts.hpp59
-rw-r--r--include/lib/ArduinoJson/JsonVariantComparisons.hpp139
-rw-r--r--include/lib/ArduinoJson/JsonVariantImpl.hpp126
-rw-r--r--include/lib/ArduinoJson/JsonVariantOr.hpp52
-rw-r--r--include/lib/ArduinoJson/JsonVariantSubscripts.hpp86
-rw-r--r--include/lib/ArduinoJson/Polyfills/attributes.hpp29
-rw-r--r--include/lib/ArduinoJson/Polyfills/ctype.hpp18
-rw-r--r--include/lib/ArduinoJson/Polyfills/isFloat.hpp38
-rw-r--r--include/lib/ArduinoJson/Polyfills/isInteger.hpp19
-rw-r--r--include/lib/ArduinoJson/Polyfills/math.hpp19
-rw-r--r--include/lib/ArduinoJson/Polyfills/parseFloat.hpp90
-rw-r--r--include/lib/ArduinoJson/Polyfills/parseInteger.hpp41
-rw-r--r--include/lib/ArduinoJson/RawJson.hpp46
-rw-r--r--include/lib/ArduinoJson/Serialization/DummyPrint.hpp22
-rw-r--r--include/lib/ArduinoJson/Serialization/DynamicStringBuilder.hpp35
-rw-r--r--include/lib/ArduinoJson/Serialization/FloatParts.hpp89
-rw-r--r--include/lib/ArduinoJson/Serialization/IndentedPrint.hpp68
-rw-r--r--include/lib/ArduinoJson/Serialization/JsonPrintable.hpp117
-rw-r--r--include/lib/ArduinoJson/Serialization/JsonSerializer.hpp32
-rw-r--r--include/lib/ArduinoJson/Serialization/JsonSerializerImpl.hpp103
-rw-r--r--include/lib/ArduinoJson/Serialization/JsonWriter.hpp155
-rw-r--r--include/lib/ArduinoJson/Serialization/Prettyfier.hpp133
-rw-r--r--include/lib/ArduinoJson/Serialization/StaticStringBuilder.hpp36
-rw-r--r--include/lib/ArduinoJson/Serialization/StreamPrintAdapter.hpp39
-rw-r--r--include/lib/ArduinoJson/StaticJsonBuffer.hpp126
-rw-r--r--include/lib/ArduinoJson/StringTraits/ArduinoStream.hpp61
-rw-r--r--include/lib/ArduinoJson/StringTraits/CharPointer.hpp64
-rw-r--r--include/lib/ArduinoJson/StringTraits/FlashString.hpp61
-rw-r--r--include/lib/ArduinoJson/StringTraits/StdStream.hpp60
-rw-r--r--include/lib/ArduinoJson/StringTraits/StdString.hpp77
-rw-r--r--include/lib/ArduinoJson/StringTraits/StringTraits.hpp36
-rw-r--r--include/lib/ArduinoJson/TypeTraits/EnableIf.hpp19
-rw-r--r--include/lib/ArduinoJson/TypeTraits/FloatTraits.hpp171
-rw-r--r--include/lib/ArduinoJson/TypeTraits/IsArray.hpp24
-rw-r--r--include/lib/ArduinoJson/TypeTraits/IsBaseOf.hpp27
-rw-r--r--include/lib/ArduinoJson/TypeTraits/IsChar.hpp23
-rw-r--r--include/lib/ArduinoJson/TypeTraits/IsConst.hpp21
-rw-r--r--include/lib/ArduinoJson/TypeTraits/IsFloatingPoint.hpp18
-rw-r--r--include/lib/ArduinoJson/TypeTraits/IsIntegral.hpp26
-rw-r--r--include/lib/ArduinoJson/TypeTraits/IsSame.hpp21
-rw-r--r--include/lib/ArduinoJson/TypeTraits/IsSignedIntegral.hpp28
-rw-r--r--include/lib/ArduinoJson/TypeTraits/IsUnsignedIntegral.hpp28
-rw-r--r--include/lib/ArduinoJson/TypeTraits/IsVariant.hpp17
-rw-r--r--include/lib/ArduinoJson/TypeTraits/RemoveConst.hpp20
-rw-r--r--include/lib/ArduinoJson/TypeTraits/RemoveReference.hpp20
-rw-r--r--include/lib/ArduinoJson/version.hpp10
-rw-r--r--include/lib/binn.h923
-rw-r--r--include/lib/capnp-c/capn-list.inc162
-rw-r--r--include/lib/capnp-c/capnp_c.h446
-rw-r--r--include/lib/capnp-c/capnp_priv.h62
-rw-r--r--include/lib/modernjson/adl_serializer.hpp49
-rw-r--r--include/lib/modernjson/detail/conversions/from_json.hpp370
-rw-r--r--include/lib/modernjson/detail/conversions/to_chars.hpp1094
-rw-r--r--include/lib/modernjson/detail/conversions/to_json.hpp342
-rw-r--r--include/lib/modernjson/detail/exceptions.hpp349
-rw-r--r--include/lib/modernjson/detail/input/binary_reader.hpp1984
-rw-r--r--include/lib/modernjson/detail/input/input_adapters.hpp398
-rw-r--r--include/lib/modernjson/detail/input/json_sax.hpp701
-rw-r--r--include/lib/modernjson/detail/input/lexer.hpp1506
-rw-r--r--include/lib/modernjson/detail/input/parser.hpp504
-rw-r--r--include/lib/modernjson/detail/input/position_t.hpp27
-rw-r--r--include/lib/modernjson/detail/iterators/internal_iterator.hpp25
-rw-r--r--include/lib/modernjson/detail/iterators/iter_impl.hpp614
-rw-r--r--include/lib/modernjson/detail/iterators/iteration_proxy.hpp125
-rw-r--r--include/lib/modernjson/detail/iterators/json_reverse_iterator.hpp119
-rw-r--r--include/lib/modernjson/detail/iterators/primitive_iterator.hpp120
-rw-r--r--include/lib/modernjson/detail/json_pointer.hpp696
-rw-r--r--include/lib/modernjson/detail/json_ref.hpp69
-rw-r--r--include/lib/modernjson/detail/macro_scope.hpp135
-rw-r--r--include/lib/modernjson/detail/macro_unscope.hpp22
-rw-r--r--include/lib/modernjson/detail/meta/cpp_future.hpp63
-rw-r--r--include/lib/modernjson/detail/meta/detected.hpp56
-rw-r--r--include/lib/modernjson/detail/meta/is_sax.hpp141
-rw-r--r--include/lib/modernjson/detail/meta/type_traits.hpp347
-rw-r--r--include/lib/modernjson/detail/meta/void_t.hpp13
-rw-r--r--include/lib/modernjson/detail/output/binary_writer.hpp1339
-rw-r--r--include/lib/modernjson/detail/output/output_adapters.hpp119
-rw-r--r--include/lib/modernjson/detail/output/serializer.hpp741
-rw-r--r--include/lib/modernjson/detail/value_t.hpp76
-rw-r--r--include/lib/modernjson/json.h7992
-rw-r--r--include/lib/modernjson/json_fwd.h64
-rw-r--r--include/lib/mpack/mpack.h7172
-rw-r--r--include/lib/mpmalloc.h17
-rw-r--r--include/lib/nanopb/pb.h594
-rw-r--r--include/lib/nanopb/pb_common.h42
-rw-r--r--include/lib/nanopb/pb_decode.h175
-rw-r--r--include/lib/nanopb/pb_encode.h170
-rw-r--r--include/lib/ubjson/ubj.h231
-rw-r--r--include/lib/ubjson/ubj_internal.h163
-rw-r--r--include/lib/xdr.h66
-rw-r--r--include/lib/xdr16.h68
-rw-r--r--src/app/prototest/Makefile.inc96
-rw-r--r--src/app/prototest/main.cc79
-rw-r--r--src/lib/binn.cc3372
-rw-r--r--src/lib/capnp-c/capn-malloc.cc424
-rw-r--r--src/lib/capnp-c/capn-stream.cc217
-rw-r--r--src/lib/capnp-c/capn.cc1117
-rw-r--r--src/lib/mpack/mpack.cc6440
-rw-r--r--src/lib/mpmalloc.cc42
-rw-r--r--src/lib/nanopb/pb_common.cc97
-rw-r--r--src/lib/nanopb/pb_decode.cc1528
-rw-r--r--src/lib/nanopb/pb_encode.cc893
-rw-r--r--src/lib/ubjson/ubjr.c525
-rw-r--r--src/lib/ubjson/ubjrw.c169
-rw-r--r--src/lib/ubjson/ubjw.c626
-rw-r--r--src/lib/xdr.cc188
-rw-r--r--src/lib/xdr16.cc216
141 files changed, 51809 insertions, 0 deletions
diff --git a/include/lib/ArduinoJson.h b/include/lib/ArduinoJson.h
new file mode 100644
index 0000000..c493c06
--- /dev/null
+++ b/include/lib/ArduinoJson.h
@@ -0,0 +1,19 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "ArduinoJson/version.hpp"
+
+#include "ArduinoJson/DynamicJsonBuffer.hpp"
+#include "ArduinoJson/JsonArray.hpp"
+#include "ArduinoJson/JsonObject.hpp"
+#include "ArduinoJson/StaticJsonBuffer.hpp"
+
+#include "ArduinoJson/Deserialization/JsonParserImpl.hpp"
+#include "ArduinoJson/JsonArrayImpl.hpp"
+#include "ArduinoJson/JsonBufferImpl.hpp"
+#include "ArduinoJson/JsonObjectImpl.hpp"
+#include "ArduinoJson/JsonVariantImpl.hpp"
+#include "ArduinoJson/Serialization/JsonSerializerImpl.hpp"
diff --git a/include/lib/ArduinoJson/Configuration.hpp b/include/lib/ArduinoJson/Configuration.hpp
new file mode 100644
index 0000000..0e1a4de
--- /dev/null
+++ b/include/lib/ArduinoJson/Configuration.hpp
@@ -0,0 +1,154 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+// Small or big machine?
+#ifndef ARDUINOJSON_EMBEDDED_MODE
+#if defined(ARDUINO) || defined(__IAR_SYSTEMS_ICC__) || defined(__XC) || \
+ defined(__ARMCC_VERSION) || defined(MULTIPASS_ARCH_msp430fr5969lp) || \
+ defined(MULTIPASS_ARCH_msp430fr5994lp) || \
+ defined(MULTIPASS_ARCH_arduino_nano) || defined(MULTIPASS_ARCH_blinkenrocket) || \
+ defined(MULTIPASS_ARCH_esp8266)
+#define ARDUINOJSON_EMBEDDED_MODE 1
+#else
+#define ARDUINOJSON_EMBEDDED_MODE 0
+#endif
+#endif
+
+#if ARDUINOJSON_EMBEDDED_MODE
+
+// Store floats by default to reduce the memory usage (issue #134)
+#ifndef ARDUINOJSON_USE_DOUBLE
+#define ARDUINOJSON_USE_DOUBLE 0
+#endif
+
+// Store longs by default, because they usually match the size of a float.
+#ifndef ARDUINOJSON_USE_LONG_LONG
+#define ARDUINOJSON_USE_LONG_LONG 0
+#endif
+#ifndef ARDUINOJSON_USE_INT64
+#define ARDUINOJSON_USE_INT64 0
+#endif
+
+// Embedded systems usually don't have std::string
+#ifndef ARDUINOJSON_ENABLE_STD_STRING
+#define ARDUINOJSON_ENABLE_STD_STRING 0
+#endif
+
+// Embedded systems usually don't have std::stream
+#ifndef ARDUINOJSON_ENABLE_STD_STREAM
+#define ARDUINOJSON_ENABLE_STD_STREAM 0
+#endif
+
+// Limit nesting as the stack is likely to be small
+#ifndef ARDUINOJSON_DEFAULT_NESTING_LIMIT
+#define ARDUINOJSON_DEFAULT_NESTING_LIMIT 10
+#endif
+
+#else // ARDUINOJSON_EMBEDDED_MODE
+
+// On a computer we have plenty of memory so we can use doubles
+#ifndef ARDUINOJSON_USE_DOUBLE
+#define ARDUINOJSON_USE_DOUBLE 1
+#endif
+
+// Use long long when available
+#ifndef ARDUINOJSON_USE_LONG_LONG
+#if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1800)
+#define ARDUINOJSON_USE_LONG_LONG 1
+#else
+#define ARDUINOJSON_USE_LONG_LONG 0
+#endif
+#endif
+
+// Use _int64 on old versions of Visual Studio
+#ifndef ARDUINOJSON_USE_INT64
+#if defined(_MSC_VER) && _MSC_VER <= 1700
+#define ARDUINOJSON_USE_INT64 1
+#else
+#define ARDUINOJSON_USE_INT64 0
+#endif
+#endif
+
+// On a computer, we can use std::string
+#ifndef ARDUINOJSON_ENABLE_STD_STRING
+#define ARDUINOJSON_ENABLE_STD_STRING 1
+#endif
+
+// On a computer, we can assume std::stream
+#ifndef ARDUINOJSON_ENABLE_STD_STREAM
+#define ARDUINOJSON_ENABLE_STD_STREAM 1
+#endif
+
+// On a computer, the stack is large so we can increase nesting limit
+#ifndef ARDUINOJSON_DEFAULT_NESTING_LIMIT
+#define ARDUINOJSON_DEFAULT_NESTING_LIMIT 50
+#endif
+
+#endif // ARDUINOJSON_EMBEDDED_MODE
+
+#ifdef ARDUINO
+
+// Enable support for Arduino String
+#ifndef ARDUINOJSON_ENABLE_ARDUINO_STRING
+#define ARDUINOJSON_ENABLE_ARDUINO_STRING 1
+#endif
+
+// Enable support for Arduino Stream
+#ifndef ARDUINOJSON_ENABLE_ARDUINO_STREAM
+#define ARDUINOJSON_ENABLE_ARDUINO_STREAM 1
+#endif
+
+#else // ARDUINO
+
+// Disable support for Arduino String
+#ifndef ARDUINOJSON_ENABLE_ARDUINO_STRING
+#define ARDUINOJSON_ENABLE_ARDUINO_STRING 0
+#endif
+
+// Disable support for Arduino Stream
+#ifndef ARDUINOJSON_ENABLE_ARDUINO_STREAM
+#define ARDUINOJSON_ENABLE_ARDUINO_STREAM 0
+#endif
+
+#endif // ARDUINO
+
+#ifndef ARDUINOJSON_ENABLE_PROGMEM
+#ifdef PROGMEM
+#define ARDUINOJSON_ENABLE_PROGMEM 1
+#else
+#define ARDUINOJSON_ENABLE_PROGMEM 0
+#endif
+#endif
+
+#ifndef ARDUINOJSON_ENABLE_ALIGNMENT
+#ifdef ARDUINO_ARCH_AVR
+// alignment isn't needed for 8-bit AVR
+#define ARDUINOJSON_ENABLE_ALIGNMENT 0
+#else
+// but most processors need pointers to be align on word size
+#define ARDUINOJSON_ENABLE_ALIGNMENT 1
+#endif
+#endif
+
+// Enable deprecated functions by default
+#ifndef ARDUINOJSON_ENABLE_DEPRECATED
+#define ARDUINOJSON_ENABLE_DEPRECATED 1
+#endif
+
+// Control the exponentiation threshold for big numbers
+// CAUTION: cannot be more that 1e9 !!!!
+#ifndef ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD
+#define ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD 1e7
+#endif
+
+// Control the exponentiation threshold for small numbers
+#ifndef ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD
+#define ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD 1e-5
+#endif
+
+#if ARDUINOJSON_USE_LONG_LONG && ARDUINOJSON_USE_INT64
+#error ARDUINOJSON_USE_LONG_LONG and ARDUINOJSON_USE_INT64 cannot be set together
+#endif
diff --git a/include/lib/ArduinoJson/Data/Encoding.hpp b/include/lib/ArduinoJson/Data/Encoding.hpp
new file mode 100644
index 0000000..a0efa2c
--- /dev/null
+++ b/include/lib/ArduinoJson/Data/Encoding.hpp
@@ -0,0 +1,37 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+namespace Internals {
+
+class Encoding {
+ public:
+ // Optimized for code size on a 8-bit AVR
+ static char escapeChar(char c) {
+ const char *p = escapeTable(false);
+ while (p[0] && p[1] != c) {
+ p += 2;
+ }
+ return p[0];
+ }
+
+ // Optimized for code size on a 8-bit AVR
+ static char unescapeChar(char c) {
+ const char *p = escapeTable(true);
+ for (;;) {
+ if (p[0] == '\0') return c;
+ if (p[0] == c) return p[1];
+ p += 2;
+ }
+ }
+
+ private:
+ static const char *escapeTable(bool excludeIdenticals) {
+ return &"\"\"\\\\b\bf\fn\nr\rt\t"[excludeIdenticals ? 4 : 0];
+ }
+};
+}
+}
diff --git a/include/lib/ArduinoJson/Data/JsonBufferAllocated.hpp b/include/lib/ArduinoJson/Data/JsonBufferAllocated.hpp
new file mode 100644
index 0000000..443aae4
--- /dev/null
+++ b/include/lib/ArduinoJson/Data/JsonBufferAllocated.hpp
@@ -0,0 +1,22 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "../JsonBuffer.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+class JsonBufferAllocated {
+ public:
+ void *operator new(size_t n, JsonBuffer *jsonBuffer) throw() {
+ if (!jsonBuffer) return NULL;
+ return jsonBuffer->alloc(n);
+ }
+
+ void operator delete(void *, JsonBuffer *)throw();
+};
+}
+}
diff --git a/include/lib/ArduinoJson/Data/JsonFloat.hpp b/include/lib/ArduinoJson/Data/JsonFloat.hpp
new file mode 100644
index 0000000..0ed4214
--- /dev/null
+++ b/include/lib/ArduinoJson/Data/JsonFloat.hpp
@@ -0,0 +1,18 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "../Configuration.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+#if ARDUINOJSON_USE_DOUBLE
+typedef double JsonFloat;
+#else
+typedef float JsonFloat;
+#endif
+}
+}
diff --git a/include/lib/ArduinoJson/Data/JsonInteger.hpp b/include/lib/ArduinoJson/Data/JsonInteger.hpp
new file mode 100644
index 0000000..c8ddd00
--- /dev/null
+++ b/include/lib/ArduinoJson/Data/JsonInteger.hpp
@@ -0,0 +1,23 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "../Configuration.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+#if ARDUINOJSON_USE_LONG_LONG
+typedef long long JsonInteger;
+typedef unsigned long long JsonUInt;
+#elif ARDUINOJSON_USE_INT64
+typedef __int64 JsonInteger;
+typedef unsigned _int64 JsonUInt;
+#else
+typedef long JsonInteger;
+typedef unsigned long JsonUInt;
+#endif
+}
+}
diff --git a/include/lib/ArduinoJson/Data/JsonVariantAs.hpp b/include/lib/ArduinoJson/Data/JsonVariantAs.hpp
new file mode 100644
index 0000000..8f202c5
--- /dev/null
+++ b/include/lib/ArduinoJson/Data/JsonVariantAs.hpp
@@ -0,0 +1,42 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+namespace Internals {
+
+// A metafunction that returns the type of the value returned by
+// JsonVariant::as<T>()
+template <typename T>
+struct JsonVariantAs {
+ typedef T type;
+};
+
+template <>
+struct JsonVariantAs<char*> {
+ typedef const char* type;
+};
+
+template <>
+struct JsonVariantAs<JsonArray> {
+ typedef JsonArray& type;
+};
+
+template <>
+struct JsonVariantAs<const JsonArray> {
+ typedef const JsonArray& type;
+};
+
+template <>
+struct JsonVariantAs<JsonObject> {
+ typedef JsonObject& type;
+};
+
+template <>
+struct JsonVariantAs<const JsonObject> {
+ typedef const JsonObject& type;
+};
+}
+}
diff --git a/include/lib/ArduinoJson/Data/JsonVariantContent.hpp b/include/lib/ArduinoJson/Data/JsonVariantContent.hpp
new file mode 100644
index 0000000..c525a60
--- /dev/null
+++ b/include/lib/ArduinoJson/Data/JsonVariantContent.hpp
@@ -0,0 +1,27 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "JsonFloat.hpp"
+#include "JsonInteger.hpp"
+
+namespace ArduinoJson {
+
+// Forward declarations
+class JsonArray;
+class JsonObject;
+
+namespace Internals {
+// A union that defines the actual content of a JsonVariant.
+// The enum JsonVariantType determines which member is in use.
+union JsonVariantContent {
+ JsonFloat asFloat; // used for double and float
+ JsonUInt asInteger; // used for bool, char, short, int and longs
+ const char* asString; // asString can be null
+ JsonArray* asArray; // asArray cannot be null
+ JsonObject* asObject; // asObject cannot be null
+};
+}
+}
diff --git a/include/lib/ArduinoJson/Data/JsonVariantDefault.hpp b/include/lib/ArduinoJson/Data/JsonVariantDefault.hpp
new file mode 100644
index 0000000..57ecc83
--- /dev/null
+++ b/include/lib/ArduinoJson/Data/JsonVariantDefault.hpp
@@ -0,0 +1,23 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+namespace Internals {
+
+template <typename T>
+struct JsonVariantDefault {
+ static T get() {
+ return T();
+ }
+};
+
+template <typename T>
+struct JsonVariantDefault<const T> : JsonVariantDefault<T> {};
+
+template <typename T>
+struct JsonVariantDefault<T&> : JsonVariantDefault<T> {};
+}
+}
diff --git a/include/lib/ArduinoJson/Data/JsonVariantType.hpp b/include/lib/ArduinoJson/Data/JsonVariantType.hpp
new file mode 100644
index 0000000..21f890e
--- /dev/null
+++ b/include/lib/ArduinoJson/Data/JsonVariantType.hpp
@@ -0,0 +1,27 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+class JsonArray;
+class JsonObject;
+
+namespace Internals {
+
+// Enumerated type to know the current type of a JsonVariant.
+// The value determines which member of JsonVariantContent is used.
+enum JsonVariantType {
+ JSON_UNDEFINED, // JsonVariant has not been initialized
+ JSON_UNPARSED, // JsonVariant contains an unparsed string
+ JSON_STRING, // JsonVariant stores a const char*
+ JSON_BOOLEAN, // JsonVariant stores a bool
+ JSON_POSITIVE_INTEGER, // JsonVariant stores an JsonUInt
+ JSON_NEGATIVE_INTEGER, // JsonVariant stores an JsonUInt that must be negated
+ JSON_ARRAY, // JsonVariant stores a pointer to a JsonArray
+ JSON_OBJECT, // JsonVariant stores a pointer to a JsonObject
+ JSON_FLOAT // JsonVariant stores a JsonFloat
+};
+}
+}
diff --git a/include/lib/ArduinoJson/Data/List.hpp b/include/lib/ArduinoJson/Data/List.hpp
new file mode 100644
index 0000000..506308c
--- /dev/null
+++ b/include/lib/ArduinoJson/Data/List.hpp
@@ -0,0 +1,94 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "../JsonBuffer.hpp"
+#include "ListConstIterator.hpp"
+#include "ListIterator.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+// A singly linked list of T.
+// The linked list is composed of ListNode<T>.
+// It is derived by JsonArray and JsonObject
+template <typename T>
+class List {
+ public:
+ typedef T value_type;
+ typedef ListNode<T> node_type;
+ typedef ListIterator<T> iterator;
+ typedef ListConstIterator<T> const_iterator;
+
+ // Creates an empty List<T> attached to a JsonBuffer.
+ // The JsonBuffer allows to allocate new nodes.
+ // When buffer is NULL, the List is not able to grow and success() returns
+ // false. This is used to identify bad memory allocations and parsing
+ // failures.
+ explicit List(JsonBuffer *buffer) : _buffer(buffer), _firstNode(NULL) {}
+
+ // Returns true if the object is valid
+ // Would return false in the following situation:
+ // - the memory allocation failed (StaticJsonBuffer was too small)
+ // - the JSON parsing failed
+ bool success() const {
+ return _buffer != NULL;
+ }
+
+ // Returns the numbers of elements in the list.
+ // For a JsonObject, it would return the number of key-value pairs
+ size_t size() const {
+ size_t nodeCount = 0;
+ for (node_type *node = _firstNode; node; node = node->next) nodeCount++;
+ return nodeCount;
+ }
+
+ iterator add() {
+ node_type *newNode = new (_buffer) node_type();
+
+ if (_firstNode) {
+ node_type *lastNode = _firstNode;
+ while (lastNode->next) lastNode = lastNode->next;
+ lastNode->next = newNode;
+ } else {
+ _firstNode = newNode;
+ }
+
+ return iterator(newNode);
+ }
+
+ iterator begin() {
+ return iterator(_firstNode);
+ }
+ iterator end() {
+ return iterator(NULL);
+ }
+
+ const_iterator begin() const {
+ return const_iterator(_firstNode);
+ }
+ const_iterator end() const {
+ return const_iterator(NULL);
+ }
+
+ void remove(iterator it) {
+ node_type *nodeToRemove = it._node;
+ if (!nodeToRemove) return;
+ if (nodeToRemove == _firstNode) {
+ _firstNode = nodeToRemove->next;
+ } else {
+ for (node_type *node = _firstNode; node; node = node->next)
+ if (node->next == nodeToRemove) node->next = nodeToRemove->next;
+ }
+ }
+
+ protected:
+ JsonBuffer *_buffer;
+
+ private:
+ node_type *_firstNode;
+};
+}
+}
diff --git a/include/lib/ArduinoJson/Data/ListConstIterator.hpp b/include/lib/ArduinoJson/Data/ListConstIterator.hpp
new file mode 100644
index 0000000..a6af685
--- /dev/null
+++ b/include/lib/ArduinoJson/Data/ListConstIterator.hpp
@@ -0,0 +1,50 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "ListNode.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+// A read-only forward itertor for List<T>
+template <typename T>
+class ListConstIterator {
+ public:
+ explicit ListConstIterator(const ListNode<T> *node = NULL) : _node(node) {}
+
+ const T &operator*() const {
+ return _node->content;
+ }
+ const T *operator->() {
+ return &_node->content;
+ }
+
+ bool operator==(const ListConstIterator<T> &other) const {
+ return _node == other._node;
+ }
+
+ bool operator!=(const ListConstIterator<T> &other) const {
+ return _node != other._node;
+ }
+
+ ListConstIterator<T> &operator++() {
+ if (_node) _node = _node->next;
+ return *this;
+ }
+
+ ListConstIterator<T> &operator+=(size_t distance) {
+ while (_node && distance) {
+ _node = _node->next;
+ --distance;
+ }
+ return *this;
+ }
+
+ private:
+ const ListNode<T> *_node;
+};
+}
+}
diff --git a/include/lib/ArduinoJson/Data/ListIterator.hpp b/include/lib/ArduinoJson/Data/ListIterator.hpp
new file mode 100644
index 0000000..01fa287
--- /dev/null
+++ b/include/lib/ArduinoJson/Data/ListIterator.hpp
@@ -0,0 +1,60 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "ListConstIterator.hpp"
+#include "ListNode.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+template <typename T>
+class List;
+
+// A read-write forward iterator for List<T>
+template <typename T>
+class ListIterator {
+ friend class List<T>;
+
+ public:
+ explicit ListIterator(ListNode<T> *node = NULL) : _node(node) {}
+
+ T &operator*() const {
+ return _node->content;
+ }
+ T *operator->() {
+ return &_node->content;
+ }
+
+ bool operator==(const ListIterator<T> &other) const {
+ return _node == other._node;
+ }
+
+ bool operator!=(const ListIterator<T> &other) const {
+ return _node != other._node;
+ }
+
+ ListIterator<T> &operator++() {
+ if (_node) _node = _node->next;
+ return *this;
+ }
+
+ ListIterator<T> &operator+=(size_t distance) {
+ while (_node && distance) {
+ _node = _node->next;
+ --distance;
+ }
+ return *this;
+ }
+
+ operator ListConstIterator<T>() const {
+ return ListConstIterator<T>(_node);
+ }
+
+ private:
+ ListNode<T> *_node;
+};
+}
+}
diff --git a/include/lib/ArduinoJson/Data/ListNode.hpp b/include/lib/ArduinoJson/Data/ListNode.hpp
new file mode 100644
index 0000000..c090712
--- /dev/null
+++ b/include/lib/ArduinoJson/Data/ListNode.hpp
@@ -0,0 +1,24 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include <stddef.h> // for NULL
+
+#include "JsonBufferAllocated.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+// A node for a singly-linked list.
+// Used by List<T> and its iterators.
+template <typename T>
+struct ListNode : public Internals::JsonBufferAllocated {
+ ListNode() throw() : next(NULL) {}
+
+ ListNode<T> *next;
+ T content;
+};
+}
+}
diff --git a/include/lib/ArduinoJson/Data/NonCopyable.hpp b/include/lib/ArduinoJson/Data/NonCopyable.hpp
new file mode 100644
index 0000000..73f3d8e
--- /dev/null
+++ b/include/lib/ArduinoJson/Data/NonCopyable.hpp
@@ -0,0 +1,23 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+namespace Internals {
+
+// A type that cannot be copied
+class NonCopyable {
+ protected:
+ NonCopyable() {}
+
+ private:
+ // copy constructor is private
+ NonCopyable(const NonCopyable&);
+
+ // copy operator is private
+ NonCopyable& operator=(const NonCopyable&);
+};
+}
+}
diff --git a/include/lib/ArduinoJson/Data/ReferenceType.hpp b/include/lib/ArduinoJson/Data/ReferenceType.hpp
new file mode 100644
index 0000000..1e49117
--- /dev/null
+++ b/include/lib/ArduinoJson/Data/ReferenceType.hpp
@@ -0,0 +1,24 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+namespace Internals {
+
+// A type that is meant to be used by reference only (JsonArray and JsonObject)
+class ReferenceType {
+ public:
+ bool operator==(const ReferenceType& other) const {
+ // two JsonArray are equal if they are the same instance
+ // (we don't compare the content)
+ return this == &other;
+ }
+
+ bool operator!=(const ReferenceType& other) const {
+ return this != &other;
+ }
+};
+}
+}
diff --git a/include/lib/ArduinoJson/Data/ValueSaver.hpp b/include/lib/ArduinoJson/Data/ValueSaver.hpp
new file mode 100644
index 0000000..9750f1a
--- /dev/null
+++ b/include/lib/ArduinoJson/Data/ValueSaver.hpp
@@ -0,0 +1,52 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "../JsonBuffer.hpp"
+#include "../JsonVariant.hpp"
+#include "../StringTraits/StringTraits.hpp"
+#include "../TypeTraits/EnableIf.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+template <typename Source, typename Enable = void>
+struct ValueSaver {
+ template <typename Destination>
+ static bool save(JsonBuffer*, Destination& destination, Source source) {
+ destination = source;
+ return true;
+ }
+};
+
+template <typename Source>
+struct ValueSaver<
+ Source, typename EnableIf<StringTraits<Source>::should_duplicate>::type> {
+ template <typename Destination>
+ static bool save(JsonBuffer* buffer, Destination& dest, Source source) {
+ if (!StringTraits<Source>::is_null(source)) {
+ typename StringTraits<Source>::duplicate_t dup =
+ StringTraits<Source>::duplicate(source, buffer);
+ if (!dup) return false;
+ dest = dup;
+ } else {
+ dest = reinterpret_cast<const char*>(0);
+ }
+ return true;
+ }
+};
+
+// const char*, const signed char*, const unsigned char*
+template <typename Char>
+struct ValueSaver<
+ Char*, typename EnableIf<!StringTraits<Char*>::should_duplicate>::type> {
+ template <typename Destination>
+ static bool save(JsonBuffer*, Destination& dest, Char* source) {
+ dest = reinterpret_cast<const char*>(source);
+ return true;
+ }
+};
+}
+}
diff --git a/include/lib/ArduinoJson/Deserialization/Comments.hpp b/include/lib/ArduinoJson/Deserialization/Comments.hpp
new file mode 100644
index 0000000..c2c48eb
--- /dev/null
+++ b/include/lib/ArduinoJson/Deserialization/Comments.hpp
@@ -0,0 +1,61 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+namespace Internals {
+template <typename TInput>
+void skipSpacesAndComments(TInput& input) {
+ for (;;) {
+ switch (input.current()) {
+ // spaces
+ case ' ':
+ case '\t':
+ case '\r':
+ case '\n':
+ input.move();
+ continue;
+
+ // comments
+ case '/':
+ switch (input.next()) {
+ // C-style block comment
+ case '*':
+ input.move(); // skip '/'
+ // no need to skip '*'
+ for (;;) {
+ input.move();
+ if (input.current() == '\0') return;
+ if (input.current() == '*' && input.next() == '/') {
+ input.move(); // skip '*'
+ input.move(); // skip '/'
+ break;
+ }
+ }
+ break;
+
+ // C++-style line comment
+ case '/':
+ // not need to skip "//"
+ for (;;) {
+ input.move();
+ if (input.current() == '\0') return;
+ if (input.current() == '\n') break;
+ }
+ break;
+
+ // not a comment, just a '/'
+ default:
+ return;
+ }
+ break;
+
+ default:
+ return;
+ }
+ }
+}
+}
+}
diff --git a/include/lib/ArduinoJson/Deserialization/JsonParser.hpp b/include/lib/ArduinoJson/Deserialization/JsonParser.hpp
new file mode 100644
index 0000000..4cbaf45
--- /dev/null
+++ b/include/lib/ArduinoJson/Deserialization/JsonParser.hpp
@@ -0,0 +1,102 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "../JsonBuffer.hpp"
+#include "../JsonVariant.hpp"
+#include "../TypeTraits/IsConst.hpp"
+#include "StringWriter.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+// Parse JSON string to create JsonArrays and JsonObjects
+// This internal class is not indended to be used directly.
+// Instead, use JsonBuffer.parseArray() or .parseObject()
+template <typename TReader, typename TWriter>
+class JsonParser {
+ public:
+ JsonParser(JsonBuffer *buffer, TReader reader, TWriter writer,
+ uint8_t nestingLimit)
+ : _buffer(buffer),
+ _reader(reader),
+ _writer(writer),
+ _nestingLimit(nestingLimit) {}
+
+ JsonArray &parseArray();
+ JsonObject &parseObject();
+
+ JsonVariant parseVariant() {
+ JsonVariant result;
+ parseAnythingTo(&result);
+ return result;
+ }
+
+ private:
+ JsonParser &operator=(const JsonParser &); // non-copiable
+
+ static bool eat(TReader &, char charToSkip);
+ FORCE_INLINE bool eat(char charToSkip) {
+ return eat(_reader, charToSkip);
+ }
+
+ const char *parseString();
+ bool parseAnythingTo(JsonVariant *destination);
+
+ inline bool parseArrayTo(JsonVariant *destination);
+ inline bool parseObjectTo(JsonVariant *destination);
+ inline bool parseStringTo(JsonVariant *destination);
+
+ static inline bool isBetween(char c, char min, char max) {
+ return min <= c && c <= max;
+ }
+
+ static inline bool canBeInNonQuotedString(char c) {
+ return isBetween(c, '0', '9') || isBetween(c, '_', 'z') ||
+ isBetween(c, 'A', 'Z') || c == '+' || c == '-' || c == '.';
+ }
+
+ static inline bool isQuote(char c) {
+ return c == '\'' || c == '\"';
+ }
+
+ JsonBuffer *_buffer;
+ TReader _reader;
+ TWriter _writer;
+ uint8_t _nestingLimit;
+};
+
+template <typename TJsonBuffer, typename TString, typename Enable = void>
+struct JsonParserBuilder {
+ typedef typename StringTraits<TString>::Reader InputReader;
+ typedef JsonParser<InputReader, TJsonBuffer &> TParser;
+
+ static TParser makeParser(TJsonBuffer *buffer, TString &json,
+ uint8_t nestingLimit) {
+ return TParser(buffer, InputReader(json), *buffer, nestingLimit);
+ }
+};
+
+template <typename TJsonBuffer, typename TChar>
+struct JsonParserBuilder<TJsonBuffer, TChar *,
+ typename EnableIf<!IsConst<TChar>::value>::type> {
+ typedef typename StringTraits<TChar *>::Reader TReader;
+ typedef StringWriter<TChar> TWriter;
+ typedef JsonParser<TReader, TWriter> TParser;
+
+ static TParser makeParser(TJsonBuffer *buffer, TChar *json,
+ uint8_t nestingLimit) {
+ return TParser(buffer, TReader(json), TWriter(json), nestingLimit);
+ }
+};
+
+template <typename TJsonBuffer, typename TString>
+inline typename JsonParserBuilder<TJsonBuffer, TString>::TParser makeParser(
+ TJsonBuffer *buffer, TString &json, uint8_t nestingLimit) {
+ return JsonParserBuilder<TJsonBuffer, TString>::makeParser(buffer, json,
+ nestingLimit);
+}
+} // namespace Internals
+} // namespace ArduinoJson
diff --git a/include/lib/ArduinoJson/Deserialization/JsonParserImpl.hpp b/include/lib/ArduinoJson/Deserialization/JsonParserImpl.hpp
new file mode 100644
index 0000000..5042673
--- /dev/null
+++ b/include/lib/ArduinoJson/Deserialization/JsonParserImpl.hpp
@@ -0,0 +1,189 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "Comments.hpp"
+#include "JsonParser.hpp"
+
+template <typename TReader, typename TWriter>
+inline bool ArduinoJson::Internals::JsonParser<TReader, TWriter>::eat(
+ TReader &reader, char charToSkip) {
+ skipSpacesAndComments(reader);
+ if (reader.current() != charToSkip) return false;
+ reader.move();
+ return true;
+}
+
+template <typename TReader, typename TWriter>
+inline bool
+ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseAnythingTo(
+ JsonVariant *destination) {
+ skipSpacesAndComments(_reader);
+
+ switch (_reader.current()) {
+ case '[':
+ return parseArrayTo(destination);
+
+ case '{':
+ return parseObjectTo(destination);
+
+ default:
+ return parseStringTo(destination);
+ }
+}
+
+template <typename TReader, typename TWriter>
+inline ArduinoJson::JsonArray &
+ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseArray() {
+ if (_nestingLimit == 0) return JsonArray::invalid();
+ _nestingLimit--;
+
+ // Create an empty array
+ JsonArray &array = _buffer->createArray();
+
+ // Check opening braket
+ if (!eat('[')) goto ERROR_MISSING_BRACKET;
+ if (eat(']')) goto SUCCESS_EMPTY_ARRAY;
+
+ // Read each value
+ for (;;) {
+ // 1 - Parse value
+ JsonVariant value;
+ if (!parseAnythingTo(&value)) goto ERROR_INVALID_VALUE;
+ if (!array.add(value)) goto ERROR_NO_MEMORY;
+
+ // 2 - More values?
+ if (eat(']')) goto SUCCES_NON_EMPTY_ARRAY;
+ if (!eat(',')) goto ERROR_MISSING_COMMA;
+ }
+
+SUCCESS_EMPTY_ARRAY:
+SUCCES_NON_EMPTY_ARRAY:
+ _nestingLimit++;
+ return array;
+
+ERROR_INVALID_VALUE:
+ERROR_MISSING_BRACKET:
+ERROR_MISSING_COMMA:
+ERROR_NO_MEMORY:
+ return JsonArray::invalid();
+}
+
+template <typename TReader, typename TWriter>
+inline bool ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseArrayTo(
+ JsonVariant *destination) {
+ JsonArray &array = parseArray();
+ if (!array.success()) return false;
+
+ *destination = array;
+ return true;
+}
+
+template <typename TReader, typename TWriter>
+inline ArduinoJson::JsonObject &
+ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseObject() {
+ if (_nestingLimit == 0) return JsonObject::invalid();
+ _nestingLimit--;
+
+ // Create an empty object
+ JsonObject &object = _buffer->createObject();
+
+ // Check opening brace
+ if (!eat('{')) goto ERROR_MISSING_BRACE;
+ if (eat('}')) goto SUCCESS_EMPTY_OBJECT;
+
+ // Read each key value pair
+ for (;;) {
+ // 1 - Parse key
+ const char *key = parseString();
+ if (!key) goto ERROR_INVALID_KEY;
+ if (!eat(':')) goto ERROR_MISSING_COLON;
+
+ // 2 - Parse value
+ JsonVariant value;
+ if (!parseAnythingTo(&value)) goto ERROR_INVALID_VALUE;
+ if (!object.set(key, value)) goto ERROR_NO_MEMORY;
+
+ // 3 - More keys/values?
+ if (eat('}')) goto SUCCESS_NON_EMPTY_OBJECT;
+ if (!eat(',')) goto ERROR_MISSING_COMMA;
+ }
+
+SUCCESS_EMPTY_OBJECT:
+SUCCESS_NON_EMPTY_OBJECT:
+ _nestingLimit++;
+ return object;
+
+ERROR_INVALID_KEY:
+ERROR_INVALID_VALUE:
+ERROR_MISSING_BRACE:
+ERROR_MISSING_COLON:
+ERROR_MISSING_COMMA:
+ERROR_NO_MEMORY:
+ return JsonObject::invalid();
+}
+
+template <typename TReader, typename TWriter>
+inline bool ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseObjectTo(
+ JsonVariant *destination) {
+ JsonObject &object = parseObject();
+ if (!object.success()) return false;
+
+ *destination = object;
+ return true;
+}
+
+template <typename TReader, typename TWriter>
+inline const char *
+ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseString() {
+ typename RemoveReference<TWriter>::type::String str = _writer.startString();
+
+ skipSpacesAndComments(_reader);
+ char c = _reader.current();
+
+ if (isQuote(c)) { // quotes
+ _reader.move();
+ char stopChar = c;
+ for (;;) {
+ c = _reader.current();
+ if (c == '\0') break;
+ _reader.move();
+
+ if (c == stopChar) break;
+
+ if (c == '\\') {
+ // replace char
+ c = Encoding::unescapeChar(_reader.current());
+ if (c == '\0') break;
+ _reader.move();
+ }
+
+ str.append(c);
+ }
+ } else { // no quotes
+ for (;;) {
+ if (!canBeInNonQuotedString(c)) break;
+ _reader.move();
+ str.append(c);
+ c = _reader.current();
+ }
+ }
+
+ return str.c_str();
+}
+
+template <typename TReader, typename TWriter>
+inline bool ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseStringTo(
+ JsonVariant *destination) {
+ bool hasQuotes = isQuote(_reader.current());
+ const char *value = parseString();
+ if (value == NULL) return false;
+ if (hasQuotes) {
+ *destination = value;
+ } else {
+ *destination = RawJson(value);
+ }
+ return true;
+}
diff --git a/include/lib/ArduinoJson/Deserialization/StringWriter.hpp b/include/lib/ArduinoJson/Deserialization/StringWriter.hpp
new file mode 100644
index 0000000..fd5507e
--- /dev/null
+++ b/include/lib/ArduinoJson/Deserialization/StringWriter.hpp
@@ -0,0 +1,41 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+namespace Internals {
+
+template <typename TChar>
+class StringWriter {
+ public:
+ class String {
+ public:
+ String(TChar** ptr) : _writePtr(ptr), _startPtr(*ptr) {}
+
+ void append(char c) {
+ *(*_writePtr)++ = TChar(c);
+ }
+
+ const char* c_str() const {
+ *(*_writePtr)++ = 0;
+ return reinterpret_cast<const char*>(_startPtr);
+ }
+
+ private:
+ TChar** _writePtr;
+ TChar* _startPtr;
+ };
+
+ StringWriter(TChar* buffer) : _ptr(buffer) {}
+
+ String startString() {
+ return String(&_ptr);
+ }
+
+ private:
+ TChar* _ptr;
+};
+}
+}
diff --git a/include/lib/ArduinoJson/DynamicJsonBuffer.hpp b/include/lib/ArduinoJson/DynamicJsonBuffer.hpp
new file mode 100644
index 0000000..bdbd5dd
--- /dev/null
+++ b/include/lib/ArduinoJson/DynamicJsonBuffer.hpp
@@ -0,0 +1,170 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "JsonBufferBase.hpp"
+
+#include <stdlib.h>
+
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnon-virtual-dtor"
+#elif defined(__GNUC__)
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+#pragma GCC diagnostic push
+#endif
+#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
+#endif
+
+namespace ArduinoJson {
+namespace Internals {
+class DefaultAllocator {
+ public:
+ void* allocate(size_t size) {
+ return malloc(size);
+ }
+ void deallocate(void* pointer) {
+ free(pointer);
+ }
+};
+
+template <typename TAllocator>
+class DynamicJsonBufferBase
+ : public JsonBufferBase<DynamicJsonBufferBase<TAllocator> > {
+ struct Block;
+ struct EmptyBlock {
+ Block* next;
+ size_t capacity;
+ size_t size;
+ };
+ struct Block : EmptyBlock {
+ uint8_t data[1];
+ };
+
+ public:
+ enum { EmptyBlockSize = sizeof(EmptyBlock) };
+
+ DynamicJsonBufferBase(size_t initialSize = 256)
+ : _head(NULL), _nextBlockCapacity(initialSize) {}
+
+ ~DynamicJsonBufferBase() {
+ clear();
+ }
+
+ // Gets the number of bytes occupied in the buffer
+ size_t size() const {
+ size_t total = 0;
+ for (const Block* b = _head; b; b = b->next) total += b->size;
+ return total;
+ }
+
+ // Allocates the specified amount of bytes in the buffer
+ virtual void* alloc(size_t bytes) {
+ alignNextAlloc();
+ return canAllocInHead(bytes) ? allocInHead(bytes) : allocInNewBlock(bytes);
+ }
+
+ // Resets the buffer.
+ // USE WITH CAUTION: this invalidates all previously allocated data
+ void clear() {
+ Block* currentBlock = _head;
+ while (currentBlock != NULL) {
+ _nextBlockCapacity = currentBlock->capacity;
+ Block* nextBlock = currentBlock->next;
+ _allocator.deallocate(currentBlock);
+ currentBlock = nextBlock;
+ }
+ _head = 0;
+ }
+
+ class String {
+ public:
+ String(DynamicJsonBufferBase* parent)
+ : _parent(parent), _start(NULL), _length(0) {}
+
+ void append(char c) {
+ if (_parent->canAllocInHead(1)) {
+ char* end = static_cast<char*>(_parent->allocInHead(1));
+ *end = c;
+ if (_length == 0) _start = end;
+ } else {
+ char* newStart =
+ static_cast<char*>(_parent->allocInNewBlock(_length + 1));
+ if (_start && newStart) memcpy(newStart, _start, _length);
+ if (newStart) newStart[_length] = c;
+ _start = newStart;
+ }
+ _length++;
+ }
+
+ const char* c_str() {
+ append(0);
+ return _start;
+ }
+
+ private:
+ DynamicJsonBufferBase* _parent;
+ char* _start;
+ size_t _length;
+ };
+
+ String startString() {
+ return String(this);
+ }
+
+ private:
+ void alignNextAlloc() {
+ if (_head) _head->size = this->round_size_up(_head->size);
+ }
+
+ bool canAllocInHead(size_t bytes) const {
+ return _head != NULL && _head->size + bytes <= _head->capacity;
+ }
+
+ void* allocInHead(size_t bytes) {
+ void* p = _head->data + _head->size;
+ _head->size += bytes;
+ return p;
+ }
+
+ void* allocInNewBlock(size_t bytes) {
+ size_t capacity = _nextBlockCapacity;
+ if (bytes > capacity) capacity = bytes;
+ if (!addNewBlock(capacity)) return NULL;
+ _nextBlockCapacity *= 2;
+ return allocInHead(bytes);
+ }
+
+ bool addNewBlock(size_t capacity) {
+ size_t bytes = EmptyBlockSize + capacity;
+ Block* block = static_cast<Block*>(_allocator.allocate(bytes));
+ if (block == NULL) return false;
+ block->capacity = capacity;
+ block->size = 0;
+ block->next = _head;
+ _head = block;
+ return true;
+ }
+
+ TAllocator _allocator;
+ Block* _head;
+ size_t _nextBlockCapacity;
+};
+}
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#elif defined(__GNUC__)
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+#pragma GCC diagnostic pop
+#endif
+#endif
+
+// Implements a JsonBuffer with dynamic memory allocation.
+// You are strongly encouraged to consider using StaticJsonBuffer which is much
+// more suitable for embedded systems.
+typedef Internals::DynamicJsonBufferBase<Internals::DefaultAllocator>
+ DynamicJsonBuffer;
+}
diff --git a/include/lib/ArduinoJson/JsonArray.hpp b/include/lib/ArduinoJson/JsonArray.hpp
new file mode 100644
index 0000000..4431e7a
--- /dev/null
+++ b/include/lib/ArduinoJson/JsonArray.hpp
@@ -0,0 +1,227 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "Data/JsonBufferAllocated.hpp"
+#include "Data/List.hpp"
+#include "Data/ReferenceType.hpp"
+#include "Data/ValueSaver.hpp"
+#include "JsonVariant.hpp"
+#include "Serialization/JsonPrintable.hpp"
+#include "StringTraits/StringTraits.hpp"
+#include "TypeTraits/EnableIf.hpp"
+#include "TypeTraits/IsArray.hpp"
+#include "TypeTraits/IsFloatingPoint.hpp"
+#include "TypeTraits/IsSame.hpp"
+
+// Returns the size (in bytes) of an array with n elements.
+// Can be very handy to determine the size of a StaticJsonBuffer.
+#define JSON_ARRAY_SIZE(NUMBER_OF_ELEMENTS) \
+ (sizeof(JsonArray) + (NUMBER_OF_ELEMENTS) * sizeof(JsonArray::node_type))
+
+namespace ArduinoJson {
+
+// Forward declarations
+class JsonObject;
+class JsonBuffer;
+namespace Internals {
+class JsonArraySubscript;
+}
+
+// An array of JsonVariant.
+//
+// The constructor is private, instances must be created via
+// JsonBuffer::createArray() or JsonBuffer::parseArray().
+// A JsonArray can be serialized to a JSON string via JsonArray::printTo().
+// It can also be deserialized from a JSON string via JsonBuffer::parseArray().
+class JsonArray : public Internals::JsonPrintable<JsonArray>,
+ public Internals::ReferenceType,
+ public Internals::NonCopyable,
+ public Internals::List<JsonVariant>,
+ public Internals::JsonBufferAllocated {
+ public:
+ // Create an empty JsonArray attached to the specified JsonBuffer.
+ // You should not call this constructor directly.
+ // Instead, use JsonBuffer::createArray() or JsonBuffer::parseArray().
+ explicit JsonArray(JsonBuffer *buffer) throw()
+ : Internals::List<JsonVariant>(buffer) {}
+
+ // Gets the value at the specified index
+ const Internals::JsonArraySubscript operator[](size_t index) const;
+
+ // Gets or sets the value at specified index
+ Internals::JsonArraySubscript operator[](size_t index);
+
+ // Adds the specified value at the end of the array.
+ //
+ // bool add(TValue);
+ // TValue = bool, long, int, short, float, double, RawJson, JsonVariant,
+ // std::string, String, JsonArray, JsonObject
+ template <typename T>
+ bool add(const T &value) {
+ return add_impl<const T &>(value);
+ }
+ //
+ // bool add(TValue);
+ // TValue = char*, const char*, const FlashStringHelper*
+ template <typename T>
+ bool add(T *value) {
+ return add_impl<T *>(value);
+ }
+ //
+ // bool add(TValue value, uint8_t decimals);
+ // TValue = float, double
+ template <typename T>
+ DEPRECATED("Second argument is not supported anymore")
+ bool add(T value, uint8_t) {
+ return add_impl<const JsonVariant &>(JsonVariant(value));
+ }
+
+ // Sets the value at specified index.
+ //
+ // bool add(size_t index, const TValue&);
+ // TValue = bool, long, int, short, float, double, RawJson, JsonVariant,
+ // std::string, String, JsonArray, JsonObject
+ template <typename T>
+ bool set(size_t index, const T &value) {
+ return set_impl<const T &>(index, value);
+ }
+ //
+ // bool add(size_t index, TValue);
+ // TValue = char*, const char*, const FlashStringHelper*
+ template <typename T>
+ bool set(size_t index, T *value) {
+ return set_impl<T *>(index, value);
+ }
+ //
+ // bool set(size_t index, TValue value, uint8_t decimals);
+ // TValue = float, double
+ template <typename T>
+ typename Internals::EnableIf<Internals::IsFloatingPoint<T>::value, bool>::type
+ set(size_t index, T value, uint8_t decimals) {
+ return set_impl<const JsonVariant &>(index, JsonVariant(value, decimals));
+ }
+
+ // Gets the value at the specified index.
+ template <typename T>
+ typename Internals::JsonVariantAs<T>::type get(size_t index) const {
+ const_iterator it = begin() += index;
+ return it != end() ? it->as<T>() : Internals::JsonVariantDefault<T>::get();
+ }
+
+ // Check the type of the value at specified index.
+ template <typename T>
+ bool is(size_t index) const {
+ const_iterator it = begin() += index;
+ return it != end() ? it->is<T>() : false;
+ }
+
+ // Creates a JsonArray and adds a reference at the end of the array.
+ // It's a shortcut for JsonBuffer::createArray() and JsonArray::add()
+ JsonArray &createNestedArray();
+
+ // Creates a JsonObject and adds a reference at the end of the array.
+ // It's a shortcut for JsonBuffer::createObject() and JsonArray::add()
+ JsonObject &createNestedObject();
+
+ // Removes element at specified index.
+ void remove(size_t index) {
+ remove(begin() += index);
+ }
+ using Internals::List<JsonVariant>::remove;
+
+ // Returns a reference an invalid JsonArray.
+ // This object is meant to replace a NULL pointer.
+ // This is used when memory allocation or JSON parsing fail.
+ static JsonArray &invalid() {
+ static JsonArray instance(NULL);
+ return instance;
+ }
+
+ // Imports a 1D array
+ template <typename T, size_t TN>
+ bool copyFrom(T (&array)[TN]) {
+ return copyFrom(array, TN);
+ }
+
+ // Imports a 1D array
+ template <typename T>
+ bool copyFrom(T *array, size_t len) {
+ bool ok = true;
+ for (size_t i = 0; i < len; i++) {
+ ok &= add(array[i]);
+ }
+ return ok;
+ }
+
+ // Imports a 2D array
+ template <typename T, size_t N1, size_t N2>
+ bool copyFrom(T (&array)[N1][N2]) {
+ bool ok = true;
+ for (size_t i = 0; i < N1; i++) {
+ JsonArray &nestedArray = createNestedArray();
+ for (size_t j = 0; j < N2; j++) {
+ ok &= nestedArray.add(array[i][j]);
+ }
+ }
+ return ok;
+ }
+
+ // Exports a 1D array
+ template <typename T, size_t TN>
+ size_t copyTo(T (&array)[TN]) const {
+ return copyTo(array, TN);
+ }
+
+ // Exports a 1D array
+ template <typename T>
+ size_t copyTo(T *array, size_t len) const {
+ size_t i = 0;
+ for (const_iterator it = begin(); it != end() && i < len; ++it)
+ array[i++] = *it;
+ return i;
+ }
+
+ // Exports a 2D array
+ template <typename T, size_t N1, size_t N2>
+ void copyTo(T (&array)[N1][N2]) const {
+ size_t i = 0;
+ for (const_iterator it = begin(); it != end() && i < N1; ++it) {
+ it->as<JsonArray>().copyTo(array[i++]);
+ }
+ }
+
+#if ARDUINOJSON_ENABLE_DEPRECATED
+ DEPRECATED("use remove() instead")
+ FORCE_INLINE void removeAt(size_t index) {
+ return remove(index);
+ }
+#endif
+
+ private:
+ template <typename TValueRef>
+ bool set_impl(size_t index, TValueRef value) {
+ iterator it = begin() += index;
+ if (it == end()) return false;
+ return Internals::ValueSaver<TValueRef>::save(_buffer, *it, value);
+ }
+
+ template <typename TValueRef>
+ bool add_impl(TValueRef value) {
+ iterator it = Internals::List<JsonVariant>::add();
+ if (it == end()) return false;
+ return Internals::ValueSaver<TValueRef>::save(_buffer, *it, value);
+ }
+};
+
+namespace Internals {
+template <>
+struct JsonVariantDefault<JsonArray> {
+ static JsonArray &get() {
+ return JsonArray::invalid();
+ }
+};
+}
+}
diff --git a/include/lib/ArduinoJson/JsonArrayImpl.hpp b/include/lib/ArduinoJson/JsonArrayImpl.hpp
new file mode 100644
index 0000000..924b7ea
--- /dev/null
+++ b/include/lib/ArduinoJson/JsonArrayImpl.hpp
@@ -0,0 +1,26 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "JsonArray.hpp"
+#include "JsonArraySubscript.hpp"
+#include "JsonObject.hpp"
+
+namespace ArduinoJson {
+
+inline JsonArray &JsonArray::createNestedArray() {
+ if (!_buffer) return JsonArray::invalid();
+ JsonArray &array = _buffer->createArray();
+ add(array);
+ return array;
+}
+
+inline JsonObject &JsonArray::createNestedObject() {
+ if (!_buffer) return JsonObject::invalid();
+ JsonObject &object = _buffer->createObject();
+ add(object);
+ return object;
+}
+}
diff --git a/include/lib/ArduinoJson/JsonArraySubscript.hpp b/include/lib/ArduinoJson/JsonArraySubscript.hpp
new file mode 100644
index 0000000..afb4dc1
--- /dev/null
+++ b/include/lib/ArduinoJson/JsonArraySubscript.hpp
@@ -0,0 +1,122 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "Configuration.hpp"
+#include "JsonVariantBase.hpp"
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4522)
+#endif
+
+namespace ArduinoJson {
+namespace Internals {
+class JsonArraySubscript : public JsonVariantBase<JsonArraySubscript> {
+ public:
+ FORCE_INLINE JsonArraySubscript(JsonArray& array, size_t index)
+ : _array(array), _index(index) {}
+
+ FORCE_INLINE JsonArraySubscript& operator=(const JsonArraySubscript& src) {
+ _array.set(_index, src);
+ return *this;
+ }
+
+ // Replaces the value
+ //
+ // operator=(const TValue&)
+ // TValue = bool, long, int, short, float, double, RawJson, JsonVariant,
+ // std::string, String, JsonArray, JsonObject
+ template <typename T>
+ FORCE_INLINE JsonArraySubscript& operator=(const T& src) {
+ _array.set(_index, src);
+ return *this;
+ }
+ //
+ // operator=(TValue)
+ // TValue = char*, const char*, const FlashStringHelper*
+ template <typename T>
+ FORCE_INLINE JsonArraySubscript& operator=(T* src) {
+ _array.set(_index, src);
+ return *this;
+ }
+
+ FORCE_INLINE bool success() const {
+ return _index < _array.size();
+ }
+
+ template <typename T>
+ FORCE_INLINE typename JsonVariantAs<T>::type as() const {
+ return _array.get<T>(_index);
+ }
+
+ template <typename T>
+ FORCE_INLINE bool is() const {
+ return _array.is<T>(_index);
+ }
+
+ // Replaces the value
+ //
+ // bool set(const TValue&)
+ // TValue = bool, long, int, short, float, double, RawJson, JsonVariant,
+ // std::string, String, JsonArray, JsonObject
+ template <typename TValue>
+ FORCE_INLINE bool set(const TValue& value) {
+ return _array.set(_index, value);
+ }
+ //
+ // bool set(TValue)
+ // TValue = char*, const char*, const FlashStringHelper*
+ template <typename TValue>
+ FORCE_INLINE bool set(TValue* value) {
+ return _array.set(_index, value);
+ }
+ //
+ // bool set(TValue, uint8_t decimals);
+ // TValue = float, double
+ template <typename TValue>
+ DEPRECATED("Second argument is not supported anymore")
+ FORCE_INLINE bool set(const TValue& value, uint8_t) {
+ return _array.set(_index, value);
+ }
+
+ private:
+ JsonArray& _array;
+ const size_t _index;
+};
+
+template <typename TImpl>
+inline JsonArraySubscript JsonVariantSubscripts<TImpl>::operator[](
+ size_t index) {
+ return impl()->template as<JsonArray>()[index];
+}
+
+template <typename TImpl>
+inline const JsonArraySubscript JsonVariantSubscripts<TImpl>::operator[](
+ size_t index) const {
+ return impl()->template as<JsonArray>()[index];
+}
+
+#if ARDUINOJSON_ENABLE_STD_STREAM
+inline std::ostream& operator<<(std::ostream& os,
+ const JsonArraySubscript& source) {
+ return source.printTo(os);
+}
+#endif
+}
+
+inline Internals::JsonArraySubscript JsonArray::operator[](size_t index) {
+ return Internals::JsonArraySubscript(*this, index);
+}
+
+inline const Internals::JsonArraySubscript JsonArray::operator[](
+ size_t index) const {
+ return Internals::JsonArraySubscript(*const_cast<JsonArray*>(this), index);
+}
+}
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
diff --git a/include/lib/ArduinoJson/JsonBuffer.hpp b/include/lib/ArduinoJson/JsonBuffer.hpp
new file mode 100644
index 0000000..26101e0
--- /dev/null
+++ b/include/lib/ArduinoJson/JsonBuffer.hpp
@@ -0,0 +1,78 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include <stddef.h> // for size_t
+#include <stdint.h> // for uint8_t
+#include <string.h>
+
+#include "Data/NonCopyable.hpp"
+#include "JsonVariant.hpp"
+#include "TypeTraits/EnableIf.hpp"
+#include "TypeTraits/IsArray.hpp"
+
+namespace ArduinoJson {
+class JsonArray;
+class JsonObject;
+
+// Entry point for using the library.
+//
+// Handle the memory management (done in derived classes) and calls the parser.
+// This abstract class is implemented by StaticJsonBuffer which implements a
+// fixed memory allocation.
+class JsonBuffer : Internals::NonCopyable {
+ public:
+ // Allocates an empty JsonArray.
+ //
+ // Returns a reference to the new JsonArray or JsonArray::invalid() if the
+ // allocation fails.
+ JsonArray &createArray();
+
+ // Allocates an empty JsonObject.
+ //
+ // Returns a reference to the new JsonObject or JsonObject::invalid() if the
+ // allocation fails.
+ JsonObject &createObject();
+
+ // Duplicates a string
+ //
+ // const char* strdup(TValue);
+ // TValue = const std::string&, const String&,
+ template <typename TString>
+ DEPRECATED("char* are duplicated, you don't need strdup() anymore")
+ typename Internals::EnableIf<!Internals::IsArray<TString>::value,
+ const char *>::type strdup(const TString &src) {
+ return Internals::StringTraits<TString>::duplicate(src, this);
+ }
+ //
+ // const char* strdup(TValue);
+ // TValue = char*, const char*, const FlashStringHelper*
+ template <typename TString>
+ DEPRECATED("char* are duplicated, you don't need strdup() anymore")
+ const char *strdup(TString *src) {
+ return Internals::StringTraits<TString *>::duplicate(src, this);
+ }
+
+ // Allocates n bytes in the JsonBuffer.
+ // Return a pointer to the allocated memory or NULL if allocation fails.
+ virtual void *alloc(size_t size) = 0;
+
+ protected:
+ // CAUTION: NO VIRTUAL DESTRUCTOR!
+ // If we add a virtual constructor the Arduino compiler will add malloc()
+ // and free() to the binary, adding 706 useless bytes.
+ ~JsonBuffer() {}
+
+ // Preserve aligment if necessary
+ static FORCE_INLINE size_t round_size_up(size_t bytes) {
+#if ARDUINOJSON_ENABLE_ALIGNMENT
+ const size_t x = sizeof(void *) - 1;
+ return (bytes + x) & ~x;
+#else
+ return bytes;
+#endif
+ }
+};
+}
diff --git a/include/lib/ArduinoJson/JsonBufferBase.hpp b/include/lib/ArduinoJson/JsonBufferBase.hpp
new file mode 100644
index 0000000..1e771bf
--- /dev/null
+++ b/include/lib/ArduinoJson/JsonBufferBase.hpp
@@ -0,0 +1,127 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "Deserialization/JsonParser.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+template <typename TDerived>
+class JsonBufferBase : public JsonBuffer {
+ public:
+ // Allocates and populate a JsonArray from a JSON string.
+ //
+ // The First argument is a pointer to the JSON string, the memory must be
+ // writable
+ // because the parser will insert null-terminators and replace escaped chars.
+ //
+ // The second argument set the nesting limit
+ //
+ // Returns a reference to the new JsonObject or JsonObject::invalid() if the
+ // allocation fails.
+ // With this overload, the JsonBuffer will make a copy of the string
+ //
+ // JsonArray& parseArray(TString);
+ // TString = const std::string&, const String&
+ template <typename TString>
+ typename Internals::EnableIf<!Internals::IsArray<TString>::value,
+ JsonArray &>::type
+ parseArray(const TString &json,
+ uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
+ return Internals::makeParser(that(), json, nestingLimit).parseArray();
+ }
+ //
+ // JsonArray& parseArray(TString);
+ // TString = const char*, const char[N], const FlashStringHelper*
+ template <typename TString>
+ JsonArray &parseArray(
+ TString *json, uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
+ return Internals::makeParser(that(), json, nestingLimit).parseArray();
+ }
+ //
+ // JsonArray& parseArray(TString);
+ // TString = std::istream&, Stream&
+ template <typename TString>
+ JsonArray &parseArray(
+ TString &json, uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
+ return Internals::makeParser(that(), json, nestingLimit).parseArray();
+ }
+
+ // Allocates and populate a JsonObject from a JSON string.
+ //
+ // The First argument is a pointer to the JSON string, the memory must be
+ // writable
+ // because the parser will insert null-terminators and replace escaped chars.
+ //
+ // The second argument set the nesting limit
+ //
+ // Returns a reference to the new JsonObject or JsonObject::invalid() if the
+ // allocation fails.
+ //
+ // JsonObject& parseObject(TString);
+ // TString = const std::string&, const String&
+ template <typename TString>
+ typename Internals::EnableIf<!Internals::IsArray<TString>::value,
+ JsonObject &>::type
+ parseObject(const TString &json,
+ uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
+ return Internals::makeParser(that(), json, nestingLimit).parseObject();
+ }
+ //
+ // JsonObject& parseObject(TString);
+ // TString = const char*, const char[N], const FlashStringHelper*
+ template <typename TString>
+ JsonObject &parseObject(
+ TString *json, uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
+ return Internals::makeParser(that(), json, nestingLimit).parseObject();
+ }
+ //
+ // JsonObject& parseObject(TString);
+ // TString = std::istream&, Stream&
+ template <typename TString>
+ JsonObject &parseObject(
+ TString &json, uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
+ return Internals::makeParser(that(), json, nestingLimit).parseObject();
+ }
+
+ // Generalized version of parseArray() and parseObject(), also works for
+ // integral types.
+ //
+ // JsonVariant parse(TString);
+ // TString = const std::string&, const String&
+ template <typename TString>
+ typename Internals::EnableIf<!Internals::IsArray<TString>::value,
+ JsonVariant>::type
+ parse(const TString &json,
+ uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
+ return Internals::makeParser(that(), json, nestingLimit).parseVariant();
+ }
+ //
+ // JsonVariant parse(TString);
+ // TString = const char*, const char[N], const FlashStringHelper*
+ template <typename TString>
+ JsonVariant parse(TString *json,
+ uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
+ return Internals::makeParser(that(), json, nestingLimit).parseVariant();
+ }
+ //
+ // JsonVariant parse(TString);
+ // TString = std::istream&, Stream&
+ template <typename TString>
+ JsonVariant parse(TString &json,
+ uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
+ return Internals::makeParser(that(), json, nestingLimit).parseVariant();
+ }
+
+ protected:
+ ~JsonBufferBase() {}
+
+ private:
+ TDerived *that() {
+ return static_cast<TDerived *>(this);
+ }
+};
+}
+}
diff --git a/include/lib/ArduinoJson/JsonBufferImpl.hpp b/include/lib/ArduinoJson/JsonBufferImpl.hpp
new file mode 100644
index 0000000..cdea374
--- /dev/null
+++ b/include/lib/ArduinoJson/JsonBufferImpl.hpp
@@ -0,0 +1,17 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "Deserialization/JsonParser.hpp"
+
+inline ArduinoJson::JsonArray &ArduinoJson::JsonBuffer::createArray() {
+ JsonArray *ptr = new (this) JsonArray(this);
+ return ptr ? *ptr : JsonArray::invalid();
+}
+
+inline ArduinoJson::JsonObject &ArduinoJson::JsonBuffer::createObject() {
+ JsonObject *ptr = new (this) JsonObject(this);
+ return ptr ? *ptr : JsonObject::invalid();
+}
diff --git a/include/lib/ArduinoJson/JsonObject.hpp b/include/lib/ArduinoJson/JsonObject.hpp
new file mode 100644
index 0000000..caf698a
--- /dev/null
+++ b/include/lib/ArduinoJson/JsonObject.hpp
@@ -0,0 +1,328 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "Data/JsonBufferAllocated.hpp"
+#include "Data/List.hpp"
+#include "Data/ReferenceType.hpp"
+#include "Data/ValueSaver.hpp"
+#include "JsonPair.hpp"
+#include "Serialization/JsonPrintable.hpp"
+#include "StringTraits/StringTraits.hpp"
+#include "TypeTraits/EnableIf.hpp"
+#include "TypeTraits/IsArray.hpp"
+#include "TypeTraits/IsFloatingPoint.hpp"
+#include "TypeTraits/IsSame.hpp"
+
+// Returns the size (in bytes) of an object with n elements.
+// Can be very handy to determine the size of a StaticJsonBuffer.
+#define JSON_OBJECT_SIZE(NUMBER_OF_ELEMENTS) \
+ (sizeof(JsonObject) + (NUMBER_OF_ELEMENTS) * sizeof(JsonObject::node_type))
+
+namespace ArduinoJson {
+
+// Forward declarations
+class JsonArray;
+class JsonBuffer;
+namespace Internals {
+template <typename>
+class JsonObjectSubscript;
+}
+
+// A dictionary of JsonVariant indexed by string (char*)
+//
+// The constructor is private, instances must be created via
+// JsonBuffer::createObject() or JsonBuffer::parseObject().
+// A JsonObject can be serialized to a JSON string via JsonObject::printTo().
+// It can also be deserialized from a JSON string via JsonBuffer::parseObject().
+class JsonObject : public Internals::JsonPrintable<JsonObject>,
+ public Internals::ReferenceType,
+ public Internals::NonCopyable,
+ public Internals::List<JsonPair>,
+ public Internals::JsonBufferAllocated {
+ public:
+ // Create an empty JsonArray attached to the specified JsonBuffer.
+ // You should not use this constructor directly.
+ // Instead, use JsonBuffer::createObject() or JsonBuffer.parseObject().
+ explicit JsonObject(JsonBuffer* buffer) throw()
+ : Internals::List<JsonPair>(buffer) {}
+
+ // Gets or sets the value associated with the specified key.
+ //
+ // JsonObjectSubscript operator[](TKey)
+ // TKey = const std::string&, const String&
+ template <typename TString>
+ Internals::JsonObjectSubscript<const TString&> operator[](
+ const TString& key) {
+ return Internals::JsonObjectSubscript<const TString&>(*this, key);
+ }
+ //
+ // JsonObjectSubscript operator[](TKey)
+ // TKey = char*, const char*, char[], const char[N], const FlashStringHelper*
+ template <typename TString>
+ Internals::JsonObjectSubscript<TString*> operator[](TString* key) {
+ return Internals::JsonObjectSubscript<TString*>(*this, key);
+ }
+
+ // Gets the value associated with the specified key.
+ //
+ // const JsonObjectSubscript operator[](TKey) const;
+ // TKey = const std::string&, const String&
+ template <typename TString>
+ const Internals::JsonObjectSubscript<const TString&> operator[](
+ const TString& key) const {
+ return Internals::JsonObjectSubscript<const TString&>(
+ *const_cast<JsonObject*>(this), key);
+ }
+ //
+ // const JsonObjectSubscript operator[](TKey) const;
+ // TKey = const char*, const char[N], const FlashStringHelper*
+ template <typename TString>
+ const Internals::JsonObjectSubscript<TString*> operator[](
+ TString* key) const {
+ return Internals::JsonObjectSubscript<TString*>(
+ *const_cast<JsonObject*>(this), key);
+ }
+
+ // Sets the specified key with the specified value.
+ //
+ // bool set(TKey, TValue);
+ // TKey = const std::string&, const String&
+ // TValue = bool, long, int, short, float, double, RawJson, JsonVariant,
+ // std::string, String, JsonArray, JsonObject
+ template <typename TValue, typename TString>
+ bool set(const TString& key, const TValue& value) {
+ return set_impl<const TString&, const TValue&>(key, value);
+ }
+ //
+ // bool set(TKey, TValue);
+ // TKey = const std::string&, const String&
+ // TValue = char*, const char*, const FlashStringHelper*
+ template <typename TValue, typename TString>
+ bool set(const TString& key, TValue* value) {
+ return set_impl<const TString&, TValue*>(key, value);
+ }
+ //
+ // bool set(TKey, const TValue&);
+ // TKey = char*, const char*, const FlashStringHelper*
+ // TValue = bool, long, int, short, float, double, RawJson, JsonVariant,
+ // std::string, String, JsonArray, JsonObject
+ template <typename TValue, typename TString>
+ bool set(TString* key, const TValue& value) {
+ return set_impl<TString*, const TValue&>(key, value);
+ }
+ //
+ // bool set(TKey, TValue);
+ // TKey = char*, const char*, const FlashStringHelper*
+ // TValue = char*, const char*, const FlashStringHelper*
+ template <typename TValue, typename TString>
+ bool set(TString* key, TValue* value) {
+ return set_impl<TString*, TValue*>(key, value);
+ }
+ //
+ // bool set(TKey, TValue, uint8_t decimals);
+ // TKey = const std::string&, const String&
+ // TValue = float, double
+ template <typename TValue, typename TString>
+ DEPRECATED("Second argument is not supported anymore")
+ typename Internals::EnableIf<Internals::IsFloatingPoint<TValue>::value,
+ bool>::type
+ set(const TString& key, TValue value, uint8_t) {
+ return set_impl<const TString&, const JsonVariant&>(key,
+ JsonVariant(value));
+ }
+ //
+ // bool set(TKey, TValue, uint8_t decimals);
+ // TKey = char*, const char*, const FlashStringHelper*
+ // TValue = float, double
+ template <typename TValue, typename TString>
+ DEPRECATED("Second argument is not supported anymore")
+ typename Internals::EnableIf<Internals::IsFloatingPoint<TValue>::value,
+ bool>::type
+ set(TString* key, TValue value, uint8_t) {
+ return set_impl<TString*, const JsonVariant&>(key, JsonVariant(value));
+ }
+
+ // Gets the value associated with the specified key.
+ //
+ // TValue get<TValue>(TKey) const;
+ // TKey = const std::string&, const String&
+ // TValue = bool, char, long, int, short, float, double,
+ // std::string, String, JsonArray, JsonObject
+ template <typename TValue, typename TString>
+ typename Internals::JsonVariantAs<TValue>::type get(
+ const TString& key) const {
+ return get_impl<const TString&, TValue>(key);
+ }
+ //
+ // TValue get<TValue>(TKey) const;
+ // TKey = char*, const char*, const FlashStringHelper*
+ // TValue = bool, char, long, int, short, float, double,
+ // std::string, String, JsonArray, JsonObject
+ template <typename TValue, typename TString>
+ typename Internals::JsonVariantAs<TValue>::type get(TString* key) const {
+ return get_impl<TString*, TValue>(key);
+ }
+
+ // Checks the type of the value associated with the specified key.
+ //
+ //
+ // bool is<TValue>(TKey) const;
+ // TKey = const std::string&, const String&
+ // TValue = bool, char, long, int, short, float, double,
+ // std::string, String, JsonArray, JsonObject
+ template <typename TValue, typename TString>
+ bool is(const TString& key) const {
+ return is_impl<const TString&, TValue>(key);
+ }
+ //
+ // bool is<TValue>(TKey) const;
+ // TKey = char*, const char*, const FlashStringHelper*
+ // TValue = bool, char, long, int, short, float, double,
+ // std::string, String, JsonArray, JsonObject
+ template <typename TValue, typename TString>
+ bool is(TString* key) const {
+ return is_impl<TString*, TValue>(key);
+ }
+
+ // Creates and adds a JsonArray.
+ //
+ // JsonArray& createNestedArray(TKey);
+ // TKey = const std::string&, const String&
+ template <typename TString>
+ JsonArray& createNestedArray(const TString& key) {
+ return createNestedArray_impl<const TString&>(key);
+ }
+ // JsonArray& createNestedArray(TKey);
+ // TKey = char*, const char*, char[], const char[], const FlashStringHelper*
+ template <typename TString>
+ JsonArray& createNestedArray(TString* key) {
+ return createNestedArray_impl<TString*>(key);
+ }
+
+ // Creates and adds a JsonObject.
+ //
+ // JsonObject& createNestedObject(TKey);
+ // TKey = const std::string&, const String&
+ template <typename TString>
+ JsonObject& createNestedObject(const TString& key) {
+ return createNestedObject_impl<const TString&>(key);
+ }
+ //
+ // JsonObject& createNestedObject(TKey);
+ // TKey = char*, const char*, char[], const char[], const FlashStringHelper*
+ template <typename TString>
+ JsonObject& createNestedObject(TString* key) {
+ return createNestedObject_impl<TString*>(key);
+ }
+
+ // Tells weither the specified key is present and associated with a value.
+ //
+ // bool containsKey(TKey);
+ // TKey = const std::string&, const String&
+ template <typename TString>
+ bool containsKey(const TString& key) const {
+ return findKey<const TString&>(key) != end();
+ }
+ //
+ // bool containsKey(TKey);
+ // TKey = char*, const char*, char[], const char[], const FlashStringHelper*
+ template <typename TString>
+ bool containsKey(TString* key) const {
+ return findKey<TString*>(key) != end();
+ }
+
+ // Removes the specified key and the associated value.
+ //
+ // void remove(TKey);
+ // TKey = const std::string&, const String&
+ template <typename TString>
+ void remove(const TString& key) {
+ remove(findKey<const TString&>(key));
+ }
+ //
+ // void remove(TKey);
+ // TKey = char*, const char*, char[], const char[], const FlashStringHelper*
+ template <typename TString>
+ void remove(TString* key) {
+ remove(findKey<TString*>(key));
+ }
+ //
+ // void remove(iterator)
+ using Internals::List<JsonPair>::remove;
+
+ // Returns a reference an invalid JsonObject.
+ // This object is meant to replace a NULL pointer.
+ // This is used when memory allocation or JSON parsing fail.
+ static JsonObject& invalid() {
+ static JsonObject instance(NULL);
+ return instance;
+ }
+
+ private:
+ // Returns the list node that matches the specified key.
+ template <typename TStringRef>
+ iterator findKey(TStringRef key) {
+ iterator it;
+ for (it = begin(); it != end(); ++it) {
+ if (Internals::StringTraits<TStringRef>::equals(key, it->key)) break;
+ }
+ return it;
+ }
+ template <typename TStringRef>
+ const_iterator findKey(TStringRef key) const {
+ return const_cast<JsonObject*>(this)->findKey<TStringRef>(key);
+ }
+
+ template <typename TStringRef, typename TValue>
+ typename Internals::JsonVariantAs<TValue>::type get_impl(
+ TStringRef key) const {
+ const_iterator it = findKey<TStringRef>(key);
+ return it != end() ? it->value.as<TValue>()
+ : Internals::JsonVariantDefault<TValue>::get();
+ }
+
+ template <typename TStringRef, typename TValueRef>
+ bool set_impl(TStringRef key, TValueRef value) {
+ // ignore null key
+ if (Internals::StringTraits<TStringRef>::is_null(key)) return false;
+
+ // search a matching key
+ iterator it = findKey<TStringRef>(key);
+ if (it == end()) {
+ // add the key
+ it = Internals::List<JsonPair>::add();
+ if (it == end()) return false;
+ bool key_ok =
+ Internals::ValueSaver<TStringRef>::save(_buffer, it->key, key);
+ if (!key_ok) return false;
+ }
+
+ // save the value
+ return Internals::ValueSaver<TValueRef>::save(_buffer, it->value, value);
+ }
+
+ template <typename TStringRef, typename TValue>
+ bool is_impl(TStringRef key) const {
+ const_iterator it = findKey<TStringRef>(key);
+ return it != end() ? it->value.is<TValue>() : false;
+ }
+
+ template <typename TStringRef>
+ JsonArray& createNestedArray_impl(TStringRef key);
+
+ template <typename TStringRef>
+ JsonObject& createNestedObject_impl(TStringRef key);
+};
+
+namespace Internals {
+template <>
+struct JsonVariantDefault<JsonObject> {
+ static JsonObject& get() {
+ return JsonObject::invalid();
+ }
+};
+} // namespace Internals
+} // namespace ArduinoJson
diff --git a/include/lib/ArduinoJson/JsonObjectImpl.hpp b/include/lib/ArduinoJson/JsonObjectImpl.hpp
new file mode 100644
index 0000000..e7689b5
--- /dev/null
+++ b/include/lib/ArduinoJson/JsonObjectImpl.hpp
@@ -0,0 +1,28 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "JsonArray.hpp"
+#include "JsonObject.hpp"
+#include "JsonObjectSubscript.hpp"
+
+namespace ArduinoJson {
+
+template <typename TStringRef>
+inline JsonArray &JsonObject::createNestedArray_impl(TStringRef key) {
+ if (!_buffer) return JsonArray::invalid();
+ JsonArray &array = _buffer->createArray();
+ set(key, array);
+ return array;
+}
+
+template <typename TStringRef>
+inline JsonObject &JsonObject::createNestedObject_impl(TStringRef key) {
+ if (!_buffer) return JsonObject::invalid();
+ JsonObject &object = _buffer->createObject();
+ set(key, object);
+ return object;
+}
+}
diff --git a/include/lib/ArduinoJson/JsonObjectSubscript.hpp b/include/lib/ArduinoJson/JsonObjectSubscript.hpp
new file mode 100644
index 0000000..6ac4763
--- /dev/null
+++ b/include/lib/ArduinoJson/JsonObjectSubscript.hpp
@@ -0,0 +1,110 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "Configuration.hpp"
+#include "JsonVariantBase.hpp"
+#include "TypeTraits/EnableIf.hpp"
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4522)
+#endif
+
+namespace ArduinoJson {
+namespace Internals {
+
+template <typename TStringRef>
+class JsonObjectSubscript
+ : public JsonVariantBase<JsonObjectSubscript<TStringRef> > {
+ typedef JsonObjectSubscript<TStringRef> this_type;
+
+ public:
+ FORCE_INLINE JsonObjectSubscript(JsonObject& object, TStringRef key)
+ : _object(object), _key(key) {}
+
+ FORCE_INLINE this_type& operator=(const this_type& src) {
+ _object.set(_key, src);
+ return *this;
+ }
+
+ // Set the specified value
+ //
+ // operator=(const TValue&);
+ // TValue = bool, char, long, int, short, float, double,
+ // std::string, String, JsonArray, JsonObject
+ template <typename TValue>
+ FORCE_INLINE typename EnableIf<!IsArray<TValue>::value, this_type&>::type
+ operator=(const TValue& src) {
+ _object.set(_key, src);
+ return *this;
+ }
+ //
+ // operator=(TValue);
+ // TValue = char*, const char*, const FlashStringHelper*
+ template <typename TValue>
+ FORCE_INLINE this_type& operator=(TValue* src) {
+ _object.set(_key, src);
+ return *this;
+ }
+
+ FORCE_INLINE bool success() const {
+ return _object.containsKey(_key);
+ }
+
+ template <typename TValue>
+ FORCE_INLINE typename JsonVariantAs<TValue>::type as() const {
+ return _object.get<TValue>(_key);
+ }
+
+ template <typename TValue>
+ FORCE_INLINE bool is() const {
+ return _object.is<TValue>(_key);
+ }
+
+ // Sets the specified value.
+ //
+ // bool set(const TValue&);
+ // TValue = bool, char, long, int, short, float, double, RawJson, JsonVariant,
+ // std::string, String, JsonArray, JsonObject
+ template <typename TValue>
+ FORCE_INLINE typename EnableIf<!IsArray<TValue>::value, bool>::type set(
+ const TValue& value) {
+ return _object.set(_key, value);
+ }
+ //
+ // bool set(TValue);
+ // TValue = char*, const char, const FlashStringHelper*
+ template <typename TValue>
+ FORCE_INLINE bool set(const TValue* value) {
+ return _object.set(_key, value);
+ }
+ //
+ // bool set(TValue, uint8_t decimals);
+ // TValue = float, double
+ template <typename TValue>
+ DEPRECATED("Second argument is not supported anymore")
+ FORCE_INLINE bool set(const TValue& value, uint8_t) {
+ return _object.set(_key, value);
+ }
+
+ private:
+ JsonObject& _object;
+ TStringRef _key;
+};
+
+#if ARDUINOJSON_ENABLE_STD_STREAM
+template <typename TStringRef>
+inline std::ostream& operator<<(std::ostream& os,
+ const JsonObjectSubscript<TStringRef>& source) {
+ return source.printTo(os);
+}
+#endif
+}
+}
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
diff --git a/include/lib/ArduinoJson/JsonPair.hpp b/include/lib/ArduinoJson/JsonPair.hpp
new file mode 100644
index 0000000..4172430
--- /dev/null
+++ b/include/lib/ArduinoJson/JsonPair.hpp
@@ -0,0 +1,16 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "JsonVariant.hpp"
+
+namespace ArduinoJson {
+
+// A key value pair for JsonObject.
+struct JsonPair {
+ const char* key;
+ JsonVariant value;
+};
+}
diff --git a/include/lib/ArduinoJson/JsonVariant.hpp b/include/lib/ArduinoJson/JsonVariant.hpp
new file mode 100644
index 0000000..8326cbe
--- /dev/null
+++ b/include/lib/ArduinoJson/JsonVariant.hpp
@@ -0,0 +1,355 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h> // for uint8_t
+
+#include "Data/JsonVariantContent.hpp"
+#include "Data/JsonVariantDefault.hpp"
+#include "Data/JsonVariantType.hpp"
+#include "JsonVariantBase.hpp"
+#include "RawJson.hpp"
+#include "Serialization/JsonPrintable.hpp"
+#include "TypeTraits/EnableIf.hpp"
+#include "TypeTraits/IsChar.hpp"
+#include "TypeTraits/IsFloatingPoint.hpp"
+#include "TypeTraits/IsIntegral.hpp"
+#include "TypeTraits/IsSame.hpp"
+#include "TypeTraits/IsSignedIntegral.hpp"
+#include "TypeTraits/IsUnsignedIntegral.hpp"
+#include "TypeTraits/RemoveConst.hpp"
+#include "TypeTraits/RemoveReference.hpp"
+
+namespace ArduinoJson {
+
+// Forward declarations.
+class JsonArray;
+class JsonObject;
+
+// A variant that can be a any value serializable to a JSON value.
+//
+// It can be set to:
+// - a boolean
+// - a char, short, int or a long (signed or unsigned)
+// - a string (const char*)
+// - a reference to a JsonArray or JsonObject
+class JsonVariant : public Internals::JsonVariantBase<JsonVariant> {
+ template <typename Print>
+ friend class Internals::JsonSerializer;
+
+ public:
+ // Creates an uninitialized JsonVariant
+ JsonVariant() : _type(Internals::JSON_UNDEFINED) {}
+
+ // Create a JsonVariant containing a boolean value.
+ // It will be serialized as "true" or "false" in JSON.
+ JsonVariant(bool value) {
+ using namespace Internals;
+ _type = JSON_BOOLEAN;
+ _content.asInteger = static_cast<JsonUInt>(value);
+ }
+
+ // Create a JsonVariant containing a floating point value.
+ // JsonVariant(double value);
+ // JsonVariant(float value);
+ template <typename T>
+ JsonVariant(T value, typename Internals::EnableIf<
+ Internals::IsFloatingPoint<T>::value>::type * = 0) {
+ using namespace Internals;
+ _type = JSON_FLOAT;
+ _content.asFloat = static_cast<JsonFloat>(value);
+ }
+ template <typename T>
+ DEPRECATED("Second argument is not supported anymore")
+ JsonVariant(T value, uint8_t,
+ typename Internals::EnableIf<
+ Internals::IsFloatingPoint<T>::value>::type * = 0) {
+ using namespace Internals;
+ _type = JSON_FLOAT;
+ _content.asFloat = static_cast<JsonFloat>(value);
+ }
+
+ // Create a JsonVariant containing an integer value.
+ // JsonVariant(char)
+ // JsonVariant(signed short)
+ // JsonVariant(signed int)
+ // JsonVariant(signed long)
+ // JsonVariant(signed char)
+ template <typename T>
+ JsonVariant(
+ T value,
+ typename Internals::EnableIf<Internals::IsSignedIntegral<T>::value ||
+ Internals::IsSame<T, char>::value>::type * =
+ 0) {
+ using namespace Internals;
+ if (value >= 0) {
+ _type = JSON_POSITIVE_INTEGER;
+ _content.asInteger = static_cast<JsonUInt>(value);
+ } else {
+ _type = JSON_NEGATIVE_INTEGER;
+ _content.asInteger = static_cast<JsonUInt>(-value);
+ }
+ }
+ // JsonVariant(unsigned short)
+ // JsonVariant(unsigned int)
+ // JsonVariant(unsigned long)
+ template <typename T>
+ JsonVariant(T value,
+ typename Internals::EnableIf<
+ Internals::IsUnsignedIntegral<T>::value>::type * = 0) {
+ using namespace Internals;
+ _type = JSON_POSITIVE_INTEGER;
+ _content.asInteger = static_cast<JsonUInt>(value);
+ }
+
+ // Create a JsonVariant containing a string.
+ // JsonVariant(const char*);
+ // JsonVariant(const signed char*);
+ // JsonVariant(const unsigned char*);
+ template <typename TChar>
+ JsonVariant(
+ const TChar *value,
+ typename Internals::EnableIf<Internals::IsChar<TChar>::value>::type * =
+ 0) {
+ _type = Internals::JSON_STRING;
+ _content.asString = reinterpret_cast<const char *>(value);
+ }
+
+ // Create a JsonVariant containing an unparsed string
+ JsonVariant(Internals::RawJsonString<const char *> value) {
+ _type = Internals::JSON_UNPARSED;
+ _content.asString = value;
+ }
+
+ // Create a JsonVariant containing a reference to an array.
+ // CAUTION: we are lying about constness, because the array can be modified if
+ // the variant is converted back to a JsonArray&
+ JsonVariant(const JsonArray &array);
+
+ // Create a JsonVariant containing a reference to an object.
+ // CAUTION: we are lying about constness, because the object can be modified
+ // if the variant is converted back to a JsonObject&
+ JsonVariant(const JsonObject &object);
+
+ // Get the variant as the specified type.
+ //
+ // char as<char>() const;
+ // signed char as<signed char>() const;
+ // signed short as<signed short>() const;
+ // signed int as<signed int>() const;
+ // signed long as<signed long>() const;
+ // unsigned char as<unsigned char>() const;
+ // unsigned short as<unsigned short>() const;
+ // unsigned int as<unsigned int>() const;
+ // unsigned long as<unsigned long>() const;
+ template <typename T>
+ const typename Internals::EnableIf<Internals::IsIntegral<T>::value, T>::type
+ as() const {
+ return variantAsInteger<T>();
+ }
+ // bool as<bool>() const
+ template <typename T>
+ const typename Internals::EnableIf<Internals::IsSame<T, bool>::value, T>::type
+ as() const {
+ return variantAsInteger<int>() != 0;
+ }
+ //
+ // double as<double>() const;
+ // float as<float>() const;
+ template <typename T>
+ const typename Internals::EnableIf<Internals::IsFloatingPoint<T>::value,
+ T>::type
+ as() const {
+ return variantAsFloat<T>();
+ }
+ //
+ // const char* as<const char*>() const;
+ // const char* as<char*>() const;
+ template <typename T>
+ typename Internals::EnableIf<Internals::IsSame<T, const char *>::value ||
+ Internals::IsSame<T, char *>::value,
+ const char *>::type
+ as() const {
+ return variantAsString();
+ }
+ //
+ // std::string as<std::string>() const;
+ // String as<String>() const;
+ template <typename T>
+ typename Internals::EnableIf<Internals::StringTraits<T>::has_append, T>::type
+ as() const {
+ const char *cstr = variantAsString();
+ if (cstr) return T(cstr);
+ T s;
+ printTo(s);
+ return s;
+ }
+ //
+ // JsonArray& as<JsonArray> const;
+ // JsonArray& as<JsonArray&> const;
+ template <typename T>
+ typename Internals::EnableIf<
+ Internals::IsSame<typename Internals::RemoveReference<T>::type,
+ JsonArray>::value,
+ JsonArray &>::type
+ as() const {
+ return variantAsArray();
+ }
+ //
+ // const JsonArray& as<const JsonArray&> const;
+ template <typename T>
+ typename Internals::EnableIf<
+ Internals::IsSame<typename Internals::RemoveReference<T>::type,
+ const JsonArray>::value,
+ const JsonArray &>::type
+ as() const {
+ return variantAsArray();
+ }
+ //
+ // JsonObject& as<JsonObject> const;
+ // JsonObject& as<JsonObject&> const;
+ template <typename T>
+ typename Internals::EnableIf<
+ Internals::IsSame<typename Internals::RemoveReference<T>::type,
+ JsonObject>::value,
+ JsonObject &>::type
+ as() const {
+ return variantAsObject();
+ }
+ //
+ // JsonObject& as<const JsonObject> const;
+ // JsonObject& as<const JsonObject&> const;
+ template <typename T>
+ typename Internals::EnableIf<
+ Internals::IsSame<typename Internals::RemoveReference<T>::type,
+ const JsonObject>::value,
+ const JsonObject &>::type
+ as() const {
+ return variantAsObject();
+ }
+ //
+ // JsonVariant as<JsonVariant> const;
+ template <typename T>
+ typename Internals::EnableIf<Internals::IsSame<T, JsonVariant>::value,
+ T>::type
+ as() const {
+ return *this;
+ }
+
+ // Tells weither the variant has the specified type.
+ // Returns true if the variant has type type T, false otherwise.
+ //
+ // bool is<char>() const;
+ // bool is<signed char>() const;
+ // bool is<signed short>() const;
+ // bool is<signed int>() const;
+ // bool is<signed long>() const;
+ // bool is<unsigned char>() const;
+ // bool is<unsigned short>() const;
+ // bool is<unsigned int>() const;
+ // bool is<unsigned long>() const;
+ template <typename T>
+ typename Internals::EnableIf<Internals::IsIntegral<T>::value, bool>::type is()
+ const {
+ return variantIsInteger();
+ }
+ //
+ // bool is<double>() const;
+ // bool is<float>() const;
+ template <typename T>
+ typename Internals::EnableIf<Internals::IsFloatingPoint<T>::value, bool>::type
+ is() const {
+ return variantIsFloat();
+ }
+ //
+ // bool is<bool>() const
+ template <typename T>
+ typename Internals::EnableIf<Internals::IsSame<T, bool>::value, bool>::type
+ is() const {
+ return variantIsBoolean();
+ }
+ //
+ // bool is<const char*>() const;
+ // bool is<char*>() const;
+ template <typename T>
+ typename Internals::EnableIf<Internals::IsSame<T, const char *>::value ||
+ Internals::IsSame<T, char *>::value,
+ bool>::type
+ is() const {
+ return variantIsString();
+ }
+ //
+ // bool is<JsonArray> const;
+ // bool is<JsonArray&> const;
+ // bool is<const JsonArray&> const;
+ template <typename T>
+ typename Internals::EnableIf<
+ Internals::IsSame<typename Internals::RemoveConst<
+ typename Internals::RemoveReference<T>::type>::type,
+ JsonArray>::value,
+ bool>::type
+ is() const {
+ return variantIsArray();
+ }
+ //
+ // bool is<JsonObject> const;
+ // bool is<JsonObject&> const;
+ // bool is<const JsonObject&> const;
+ template <typename T>
+ typename Internals::EnableIf<
+ Internals::IsSame<typename Internals::RemoveConst<
+ typename Internals::RemoveReference<T>::type>::type,
+ JsonObject>::value,
+ bool>::type
+ is() const {
+ return variantIsObject();
+ }
+
+ // Returns true if the variant has a value
+ bool success() const {
+ return _type != Internals::JSON_UNDEFINED;
+ }
+
+ private:
+ JsonArray &variantAsArray() const;
+ JsonObject &variantAsObject() const;
+ const char *variantAsString() const;
+ template <typename T>
+ T variantAsFloat() const;
+ template <typename T>
+ T variantAsInteger() const;
+ bool variantIsBoolean() const;
+ bool variantIsFloat() const;
+ bool variantIsInteger() const;
+ bool variantIsArray() const {
+ return _type == Internals::JSON_ARRAY;
+ }
+ bool variantIsObject() const {
+ return _type == Internals::JSON_OBJECT;
+ }
+ bool variantIsString() const {
+ return _type == Internals::JSON_STRING ||
+ (_type == Internals::JSON_UNPARSED && _content.asString &&
+ !strcmp("null", _content.asString));
+ }
+
+ // The current type of the variant
+ Internals::JsonVariantType _type;
+
+ // The various alternatives for the value of the variant.
+ Internals::JsonVariantContent _content;
+};
+
+DEPRECATED("Decimal places are ignored, use the float value instead")
+inline JsonVariant float_with_n_digits(float value, uint8_t) {
+ return JsonVariant(value);
+}
+
+DEPRECATED("Decimal places are ignored, use the double value instead")
+inline JsonVariant double_with_n_digits(double value, uint8_t) {
+ return JsonVariant(value);
+}
+}
diff --git a/include/lib/ArduinoJson/JsonVariantBase.hpp b/include/lib/ArduinoJson/JsonVariantBase.hpp
new file mode 100644
index 0000000..44acf2e
--- /dev/null
+++ b/include/lib/ArduinoJson/JsonVariantBase.hpp
@@ -0,0 +1,24 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "JsonVariantCasts.hpp"
+#include "JsonVariantComparisons.hpp"
+#include "JsonVariantOr.hpp"
+#include "JsonVariantSubscripts.hpp"
+#include "Serialization/JsonPrintable.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+template <typename TImpl>
+class JsonVariantBase : public JsonPrintable<TImpl>,
+ public JsonVariantCasts<TImpl>,
+ public JsonVariantComparisons<TImpl>,
+ public JsonVariantOr<TImpl>,
+ public JsonVariantSubscripts<TImpl>,
+ public JsonVariantTag {};
+}
+}
diff --git a/include/lib/ArduinoJson/JsonVariantCasts.hpp b/include/lib/ArduinoJson/JsonVariantCasts.hpp
new file mode 100644
index 0000000..68f5bd7
--- /dev/null
+++ b/include/lib/ArduinoJson/JsonVariantCasts.hpp
@@ -0,0 +1,59 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "Data/JsonVariantAs.hpp"
+#include "Polyfills/attributes.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+template <typename TImpl>
+class JsonVariantCasts {
+ public:
+#if ARDUINOJSON_ENABLE_DEPRECATED
+ DEPRECATED("use as<JsonArray>() instead")
+ FORCE_INLINE JsonArray &asArray() const {
+ return impl()->template as<JsonArray>();
+ }
+
+ DEPRECATED("use as<JsonObject>() instead")
+ FORCE_INLINE JsonObject &asObject() const {
+ return impl()->template as<JsonObject>();
+ }
+
+ DEPRECATED("use as<char*>() instead")
+ FORCE_INLINE const char *asString() const {
+ return impl()->template as<const char *>();
+ }
+#endif
+
+ // Gets the variant as an array.
+ // Returns a reference to the JsonArray or JsonArray::invalid() if the
+ // variant
+ // is not an array.
+ FORCE_INLINE operator JsonArray &() const {
+ return impl()->template as<JsonArray &>();
+ }
+
+ // Gets the variant as an object.
+ // Returns a reference to the JsonObject or JsonObject::invalid() if the
+ // variant is not an object.
+ FORCE_INLINE operator JsonObject &() const {
+ return impl()->template as<JsonObject &>();
+ }
+
+ template <typename T>
+ FORCE_INLINE operator T() const {
+ return impl()->template as<T>();
+ }
+
+ private:
+ const TImpl *impl() const {
+ return static_cast<const TImpl *>(this);
+ }
+};
+}
+}
diff --git a/include/lib/ArduinoJson/JsonVariantComparisons.hpp b/include/lib/ArduinoJson/JsonVariantComparisons.hpp
new file mode 100644
index 0000000..47f9d63
--- /dev/null
+++ b/include/lib/ArduinoJson/JsonVariantComparisons.hpp
@@ -0,0 +1,139 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "StringTraits/StringTraits.hpp"
+#include "TypeTraits/EnableIf.hpp"
+#include "TypeTraits/IsVariant.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+template <typename TImpl>
+class JsonVariantComparisons {
+ public:
+ template <typename TComparand>
+ friend bool operator==(const JsonVariantComparisons &variant,
+ TComparand comparand) {
+ return variant.equals(comparand);
+ }
+
+ template <typename TComparand>
+ friend typename EnableIf<!IsVariant<TComparand>::value, bool>::type
+ operator==(TComparand comparand, const JsonVariantComparisons &variant) {
+ return variant.equals(comparand);
+ }
+
+ template <typename TComparand>
+ friend bool operator!=(const JsonVariantComparisons &variant,
+ TComparand comparand) {
+ return !variant.equals(comparand);
+ }
+
+ template <typename TComparand>
+ friend typename EnableIf<!IsVariant<TComparand>::value, bool>::type
+ operator!=(TComparand comparand, const JsonVariantComparisons &variant) {
+ return !variant.equals(comparand);
+ }
+
+ template <typename TComparand>
+ friend bool operator<=(const JsonVariantComparisons &left, TComparand right) {
+ return left.as<TComparand>() <= right;
+ }
+
+ template <typename TComparand>
+ friend bool operator<=(TComparand comparand,
+ const JsonVariantComparisons &variant) {
+ return comparand <= variant.as<TComparand>();
+ }
+
+ template <typename TComparand>
+ friend bool operator>=(const JsonVariantComparisons &variant,
+ TComparand comparand) {
+ return variant.as<TComparand>() >= comparand;
+ }
+
+ template <typename TComparand>
+ friend bool operator>=(TComparand comparand,
+ const JsonVariantComparisons &variant) {
+ return comparand >= variant.as<TComparand>();
+ }
+
+ template <typename TComparand>
+ friend bool operator<(const JsonVariantComparisons &varian,
+ TComparand comparand) {
+ return varian.as<TComparand>() < comparand;
+ }
+
+ template <typename TComparand>
+ friend bool operator<(TComparand comparand,
+ const JsonVariantComparisons &variant) {
+ return comparand < variant.as<TComparand>();
+ }
+
+ template <typename TComparand>
+ friend bool operator>(const JsonVariantComparisons &variant,
+ TComparand comparand) {
+ return variant.as<TComparand>() > comparand;
+ }
+
+ template <typename TComparand>
+ friend bool operator>(TComparand comparand,
+ const JsonVariantComparisons &variant) {
+ return comparand > variant.as<TComparand>();
+ }
+
+ private:
+ const TImpl *impl() const {
+ return static_cast<const TImpl *>(this);
+ }
+
+ template <typename T>
+ const typename JsonVariantAs<T>::type as() const {
+ return impl()->template as<T>();
+ }
+
+ template <typename T>
+ bool is() const {
+ return impl()->template is<T>();
+ }
+
+ template <typename TString>
+ typename EnableIf<StringTraits<TString>::has_equals, bool>::type equals(
+ const TString &comparand) const {
+ const char *value = as<const char *>();
+ return StringTraits<TString>::equals(comparand, value);
+ }
+
+ template <typename TComparand>
+ typename EnableIf<!IsVariant<TComparand>::value &&
+ !StringTraits<TComparand>::has_equals,
+ bool>::type
+ equals(const TComparand &comparand) const {
+ return as<TComparand>() == comparand;
+ }
+
+ template <typename TVariant2>
+ bool equals(const JsonVariantComparisons<TVariant2> &right) const {
+ using namespace Internals;
+ if (is<bool>() && right.template is<bool>())
+ return as<bool>() == right.template as<bool>();
+ if (is<JsonInteger>() && right.template is<JsonInteger>())
+ return as<JsonInteger>() == right.template as<JsonInteger>();
+ if (is<JsonFloat>() && right.template is<JsonFloat>())
+ return as<JsonFloat>() == right.template as<JsonFloat>();
+ if (is<JsonArray>() && right.template is<JsonArray>())
+ return as<JsonArray>() == right.template as<JsonArray>();
+ if (is<JsonObject>() && right.template is<JsonObject>())
+ return as<JsonObject>() == right.template as<JsonObject>();
+ if (is<char *>() && right.template is<char *>())
+ return StringTraits<const char *>::equals(as<char *>(),
+ right.template as<char *>());
+
+ return false;
+ }
+};
+} // namespace Internals
+} // namespace ArduinoJson
diff --git a/include/lib/ArduinoJson/JsonVariantImpl.hpp b/include/lib/ArduinoJson/JsonVariantImpl.hpp
new file mode 100644
index 0000000..31f96ce
--- /dev/null
+++ b/include/lib/ArduinoJson/JsonVariantImpl.hpp
@@ -0,0 +1,126 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "Configuration.hpp"
+#include "JsonArray.hpp"
+#include "JsonObject.hpp"
+#include "JsonVariant.hpp"
+#include "Polyfills/isFloat.hpp"
+#include "Polyfills/isInteger.hpp"
+#include "Polyfills/parseFloat.hpp"
+#include "Polyfills/parseInteger.hpp"
+
+#include <string.h> // for strcmp
+
+namespace ArduinoJson {
+
+inline JsonVariant::JsonVariant(const JsonArray &array) {
+ if (array.success()) {
+ _type = Internals::JSON_ARRAY;
+ _content.asArray = const_cast<JsonArray *>(&array);
+ } else {
+ _type = Internals::JSON_UNDEFINED;
+ }
+}
+
+inline JsonVariant::JsonVariant(const JsonObject &object) {
+ if (object.success()) {
+ _type = Internals::JSON_OBJECT;
+ _content.asObject = const_cast<JsonObject *>(&object);
+ } else {
+ _type = Internals::JSON_UNDEFINED;
+ }
+}
+
+inline JsonArray &JsonVariant::variantAsArray() const {
+ if (_type == Internals::JSON_ARRAY) return *_content.asArray;
+ return JsonArray::invalid();
+}
+
+inline JsonObject &JsonVariant::variantAsObject() const {
+ if (_type == Internals::JSON_OBJECT) return *_content.asObject;
+ return JsonObject::invalid();
+}
+
+template <typename T>
+inline T JsonVariant::variantAsInteger() const {
+ using namespace Internals;
+ switch (_type) {
+ case JSON_UNDEFINED:
+ return 0;
+ case JSON_POSITIVE_INTEGER:
+ case JSON_BOOLEAN:
+ return T(_content.asInteger);
+ case JSON_NEGATIVE_INTEGER:
+ return T(~_content.asInteger + 1);
+ case JSON_STRING:
+ case JSON_UNPARSED:
+ return parseInteger<T>(_content.asString);
+ default:
+ return T(_content.asFloat);
+ }
+}
+
+inline const char *JsonVariant::variantAsString() const {
+ using namespace Internals;
+ if (_type == JSON_UNPARSED && _content.asString &&
+ !strcmp("null", _content.asString))
+ return NULL;
+ if (_type == JSON_STRING || _type == JSON_UNPARSED) return _content.asString;
+ return NULL;
+}
+
+template <typename T>
+inline T JsonVariant::variantAsFloat() const {
+ using namespace Internals;
+ switch (_type) {
+ case JSON_UNDEFINED:
+ return 0;
+ case JSON_POSITIVE_INTEGER:
+ case JSON_BOOLEAN:
+ return static_cast<T>(_content.asInteger);
+ case JSON_NEGATIVE_INTEGER:
+ return -static_cast<T>(_content.asInteger);
+ case JSON_STRING:
+ case JSON_UNPARSED:
+ return parseFloat<T>(_content.asString);
+ default:
+ return static_cast<T>(_content.asFloat);
+ }
+}
+
+inline bool JsonVariant::variantIsBoolean() const {
+ using namespace Internals;
+ if (_type == JSON_BOOLEAN) return true;
+
+ if (_type != JSON_UNPARSED || _content.asString == NULL) return false;
+
+ return !strcmp(_content.asString, "true") ||
+ !strcmp(_content.asString, "false");
+}
+
+inline bool JsonVariant::variantIsInteger() const {
+ using namespace Internals;
+
+ return _type == JSON_POSITIVE_INTEGER || _type == JSON_NEGATIVE_INTEGER ||
+ (_type == JSON_UNPARSED && isInteger(_content.asString));
+}
+
+inline bool JsonVariant::variantIsFloat() const {
+ using namespace Internals;
+
+ return _type == JSON_FLOAT || _type == JSON_POSITIVE_INTEGER ||
+ _type == JSON_NEGATIVE_INTEGER ||
+ (_type == JSON_UNPARSED && isFloat(_content.asString));
+}
+
+#if ARDUINOJSON_ENABLE_STD_STREAM
+inline std::ostream &operator<<(std::ostream &os, const JsonVariant &source) {
+ return source.printTo(os);
+}
+#endif
+
+} // namespace ArduinoJson
diff --git a/include/lib/ArduinoJson/JsonVariantOr.hpp b/include/lib/ArduinoJson/JsonVariantOr.hpp
new file mode 100644
index 0000000..d8022fc
--- /dev/null
+++ b/include/lib/ArduinoJson/JsonVariantOr.hpp
@@ -0,0 +1,52 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "Data/JsonVariantAs.hpp"
+#include "Polyfills/attributes.hpp"
+#include "TypeTraits/EnableIf.hpp"
+#include "TypeTraits/IsIntegral.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+template <typename TImpl>
+class JsonVariantOr {
+ public:
+ // Returns the default value if the JsonVariant is undefined of incompatible
+ template <typename T>
+ typename EnableIf<!IsIntegral<T>::value, T>::type operator|(
+ const T &defaultValue) const {
+ if (impl()->template is<T>())
+ return impl()->template as<T>();
+ else
+ return defaultValue;
+ }
+
+ // Returns the default value if the JsonVariant is undefined of incompatible
+ // Special case for string: null is treated as undefined
+ const char *operator|(const char *defaultValue) const {
+ const char *value = impl()->template as<const char *>();
+ return value ? value : defaultValue;
+ }
+
+ // Returns the default value if the JsonVariant is undefined of incompatible
+ // Special case for integers: we also accept double
+ template <typename Integer>
+ typename EnableIf<IsIntegral<Integer>::value, Integer>::type operator|(
+ const Integer &defaultValue) const {
+ if (impl()->template is<double>())
+ return impl()->template as<Integer>();
+ else
+ return defaultValue;
+ }
+
+ private:
+ const TImpl *impl() const {
+ return static_cast<const TImpl *>(this);
+ }
+};
+} // namespace Internals
+} // namespace ArduinoJson
diff --git a/include/lib/ArduinoJson/JsonVariantSubscripts.hpp b/include/lib/ArduinoJson/JsonVariantSubscripts.hpp
new file mode 100644
index 0000000..279ee01
--- /dev/null
+++ b/include/lib/ArduinoJson/JsonVariantSubscripts.hpp
@@ -0,0 +1,86 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "Data/JsonVariantAs.hpp"
+#include "Polyfills/attributes.hpp"
+#include "StringTraits/StringTraits.hpp"
+#include "TypeTraits/EnableIf.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+// Forward declarations.
+class JsonArraySubscript;
+template <typename TKey>
+class JsonObjectSubscript;
+
+template <typename TImpl>
+class JsonVariantSubscripts {
+ public:
+ // Mimics an array or an object.
+ // Returns the size of the array or object if the variant has that type.
+ // Returns 0 if the variant is neither an array nor an object
+ size_t size() const {
+ return impl()->template as<JsonArray>().size() +
+ impl()->template as<JsonObject>().size();
+ }
+
+ // Mimics an array.
+ // Returns the element at specified index if the variant is an array.
+ // Returns JsonVariant::invalid() if the variant is not an array.
+ FORCE_INLINE const JsonArraySubscript operator[](size_t index) const;
+ FORCE_INLINE JsonArraySubscript operator[](size_t index);
+
+ // Mimics an object.
+ // Returns the value associated with the specified key if the variant is
+ // an object.
+ // Return JsonVariant::invalid() if the variant is not an object.
+ //
+ // const JsonObjectSubscript operator[](TKey) const;
+ // TKey = const std::string&, const String&
+ template <typename TString>
+ FORCE_INLINE
+ typename EnableIf<StringTraits<TString>::has_equals,
+ const JsonObjectSubscript<const TString &> >::type
+ operator[](const TString &key) const {
+ return impl()->template as<JsonObject>()[key];
+ }
+ //
+ // const JsonObjectSubscript operator[](TKey) const;
+ // TKey = const std::string&, const String&
+ template <typename TString>
+ FORCE_INLINE typename EnableIf<StringTraits<TString>::has_equals,
+ JsonObjectSubscript<const TString &> >::type
+ operator[](const TString &key) {
+ return impl()->template as<JsonObject>()[key];
+ }
+ //
+ // JsonObjectSubscript operator[](TKey);
+ // TKey = const char*, const char[N], const FlashStringHelper*
+ template <typename TString>
+ FORCE_INLINE typename EnableIf<StringTraits<const TString *>::has_equals,
+ JsonObjectSubscript<const TString *> >::type
+ operator[](const TString *key) {
+ return impl()->template as<JsonObject>()[key];
+ }
+ //
+ // JsonObjectSubscript operator[](TKey);
+ // TKey = const char*, const char[N], const FlashStringHelper*
+ template <typename TString>
+ FORCE_INLINE
+ typename EnableIf<StringTraits<TString *>::has_equals,
+ const JsonObjectSubscript<const TString *> >::type
+ operator[](const TString *key) const {
+ return impl()->template as<JsonObject>()[key];
+ }
+
+ private:
+ const TImpl *impl() const {
+ return static_cast<const TImpl *>(this);
+ }
+};
+}
+}
diff --git a/include/lib/ArduinoJson/Polyfills/attributes.hpp b/include/lib/ArduinoJson/Polyfills/attributes.hpp
new file mode 100644
index 0000000..b49091d
--- /dev/null
+++ b/include/lib/ArduinoJson/Polyfills/attributes.hpp
@@ -0,0 +1,29 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#ifdef _MSC_VER // Visual Studio
+
+#define FORCE_INLINE // __forceinline causes C4714 when returning std::string
+#define NO_INLINE __declspec(noinline)
+#define DEPRECATED(msg) __declspec(deprecated(msg))
+
+#elif defined(__GNUC__) // GCC or Clang
+
+#define FORCE_INLINE __attribute__((always_inline))
+#define NO_INLINE __attribute__((noinline))
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)
+#define DEPRECATED(msg) __attribute__((deprecated(msg)))
+#else
+#define DEPRECATED(msg) __attribute__((deprecated))
+#endif
+
+#else // Other compilers
+
+#define FORCE_INLINE
+#define NO_INLINE
+#define DEPRECATED(msg)
+
+#endif
diff --git a/include/lib/ArduinoJson/Polyfills/ctype.hpp b/include/lib/ArduinoJson/Polyfills/ctype.hpp
new file mode 100644
index 0000000..2d52703
--- /dev/null
+++ b/include/lib/ArduinoJson/Polyfills/ctype.hpp
@@ -0,0 +1,18 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+namespace Internals {
+
+inline bool isdigit(char c) {
+ return '0' <= c && c <= '9';
+}
+
+inline bool issign(char c) {
+ return '-' == c || c == '+';
+}
+}
+}
diff --git a/include/lib/ArduinoJson/Polyfills/isFloat.hpp b/include/lib/ArduinoJson/Polyfills/isFloat.hpp
new file mode 100644
index 0000000..973b89f
--- /dev/null
+++ b/include/lib/ArduinoJson/Polyfills/isFloat.hpp
@@ -0,0 +1,38 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include <string.h> // for strcmp
+#include "./ctype.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+inline bool isFloat(const char* s) {
+ if (!s) return false;
+
+ if (!strcmp(s, "NaN")) return true;
+ if (issign(*s)) s++;
+ if (!strcmp(s, "Infinity")) return true;
+ if (*s == '\0') return false;
+
+ while (isdigit(*s)) s++;
+
+ if (*s == '.') {
+ s++;
+ while (isdigit(*s)) s++;
+ }
+
+ if (*s == 'e' || *s == 'E') {
+ s++;
+ if (issign(*s)) s++;
+ if (!isdigit(*s)) return false;
+ while (isdigit(*s)) s++;
+ }
+
+ return *s == '\0';
+}
+}
+}
diff --git a/include/lib/ArduinoJson/Polyfills/isInteger.hpp b/include/lib/ArduinoJson/Polyfills/isInteger.hpp
new file mode 100644
index 0000000..8049079
--- /dev/null
+++ b/include/lib/ArduinoJson/Polyfills/isInteger.hpp
@@ -0,0 +1,19 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "./ctype.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+inline bool isInteger(const char* s) {
+ if (!s || !*s) return false;
+ if (issign(*s)) s++;
+ while (isdigit(*s)) s++;
+ return *s == '\0';
+}
+} // namespace Internals
+} // namespace ArduinoJson
diff --git a/include/lib/ArduinoJson/Polyfills/math.hpp b/include/lib/ArduinoJson/Polyfills/math.hpp
new file mode 100644
index 0000000..48773ed
--- /dev/null
+++ b/include/lib/ArduinoJson/Polyfills/math.hpp
@@ -0,0 +1,19 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+bool isNaN(T x) {
+ return x != x;
+}
+
+template <typename T>
+bool isInfinity(T x) {
+ return x != 0.0 && x * 2 == x;
+}
+}
+}
diff --git a/include/lib/ArduinoJson/Polyfills/parseFloat.hpp b/include/lib/ArduinoJson/Polyfills/parseFloat.hpp
new file mode 100644
index 0000000..49b0f6f
--- /dev/null
+++ b/include/lib/ArduinoJson/Polyfills/parseFloat.hpp
@@ -0,0 +1,90 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "../TypeTraits/FloatTraits.hpp"
+#include "./ctype.hpp"
+#include "./math.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+template <typename T>
+inline T parseFloat(const char* s) {
+ typedef FloatTraits<T> traits;
+ typedef typename traits::mantissa_type mantissa_t;
+ typedef typename traits::exponent_type exponent_t;
+
+ if (!s) return 0; // NULL
+
+ bool negative_result = false;
+ switch (*s) {
+ case '-':
+ negative_result = true;
+ s++;
+ break;
+ case '+':
+ s++;
+ break;
+ }
+
+ if (*s == 't') return 1; // true
+ if (*s == 'n' || *s == 'N') return traits::nan();
+ if (*s == 'i' || *s == 'I')
+ return negative_result ? -traits::inf() : traits::inf();
+
+ mantissa_t mantissa = 0;
+ exponent_t exponent_offset = 0;
+
+ while (isdigit(*s)) {
+ if (mantissa < traits::mantissa_max / 10)
+ mantissa = mantissa * 10 + (*s - '0');
+ else
+ exponent_offset++;
+ s++;
+ }
+
+ if (*s == '.') {
+ s++;
+ while (isdigit(*s)) {
+ if (mantissa < traits::mantissa_max / 10) {
+ mantissa = mantissa * 10 + (*s - '0');
+ exponent_offset--;
+ }
+ s++;
+ }
+ }
+
+ int exponent = 0;
+ if (*s == 'e' || *s == 'E') {
+ s++;
+ bool negative_exponent = false;
+ if (*s == '-') {
+ negative_exponent = true;
+ s++;
+ } else if (*s == '+') {
+ s++;
+ }
+
+ while (isdigit(*s)) {
+ exponent = exponent * 10 + (*s - '0');
+ if (exponent + exponent_offset > traits::exponent_max) {
+ if (negative_exponent)
+ return negative_result ? -0.0f : 0.0f;
+ else
+ return negative_result ? -traits::inf() : traits::inf();
+ }
+ s++;
+ }
+ if (negative_exponent) exponent = -exponent;
+ }
+ exponent += exponent_offset;
+
+ T result = traits::make_float(static_cast<T>(mantissa), exponent);
+
+ return negative_result ? -result : result;
+}
+}
+}
diff --git a/include/lib/ArduinoJson/Polyfills/parseInteger.hpp b/include/lib/ArduinoJson/Polyfills/parseInteger.hpp
new file mode 100644
index 0000000..e8f1974
--- /dev/null
+++ b/include/lib/ArduinoJson/Polyfills/parseInteger.hpp
@@ -0,0 +1,41 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include <stdlib.h>
+
+#include "../Configuration.hpp"
+#include "./ctype.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+T parseInteger(const char *s) {
+ if (!s) return 0; // NULL
+
+ if (*s == 't') return 1; // "true"
+
+ T result = 0;
+ bool negative_result = false;
+
+ switch (*s) {
+ case '-':
+ negative_result = true;
+ s++;
+ break;
+ case '+':
+ s++;
+ break;
+ }
+
+ while (isdigit(*s)) {
+ result = T(result * 10 + T(*s - '0'));
+ s++;
+ }
+
+ return negative_result ? T(~result + 1) : result;
+}
+}
+}
diff --git a/include/lib/ArduinoJson/RawJson.hpp b/include/lib/ArduinoJson/RawJson.hpp
new file mode 100644
index 0000000..4beb980
--- /dev/null
+++ b/include/lib/ArduinoJson/RawJson.hpp
@@ -0,0 +1,46 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+
+namespace Internals {
+// A special type of data that can be used to insert pregenerated JSON portions.
+template <typename T>
+class RawJsonString {
+ public:
+ explicit RawJsonString(T str) : _str(str) {}
+ operator T() const {
+ return _str;
+ }
+
+ private:
+ T _str;
+};
+
+template <typename String>
+struct StringTraits<RawJsonString<String>, void> {
+ static bool is_null(RawJsonString<String> source) {
+ return StringTraits<String>::is_null(static_cast<String>(source));
+ }
+
+ typedef RawJsonString<const char*> duplicate_t;
+
+ template <typename Buffer>
+ static duplicate_t duplicate(RawJsonString<String> source, Buffer* buffer) {
+ return duplicate_t(StringTraits<String>::duplicate(source, buffer));
+ }
+
+ static const bool has_append = false;
+ static const bool has_equals = false;
+ static const bool should_duplicate = StringTraits<String>::should_duplicate;
+};
+}
+
+template <typename T>
+inline Internals::RawJsonString<T> RawJson(T str) {
+ return Internals::RawJsonString<T>(str);
+}
+}
diff --git a/include/lib/ArduinoJson/Serialization/DummyPrint.hpp b/include/lib/ArduinoJson/Serialization/DummyPrint.hpp
new file mode 100644
index 0000000..9fdf2d6
--- /dev/null
+++ b/include/lib/ArduinoJson/Serialization/DummyPrint.hpp
@@ -0,0 +1,22 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+namespace Internals {
+
+// A dummy Print implementation used in JsonPrintable::measureLength()
+class DummyPrint {
+ public:
+ size_t print(char) {
+ return 1;
+ }
+
+ size_t print(const char* s) {
+ return strlen(s);
+ }
+};
+}
+}
diff --git a/include/lib/ArduinoJson/Serialization/DynamicStringBuilder.hpp b/include/lib/ArduinoJson/Serialization/DynamicStringBuilder.hpp
new file mode 100644
index 0000000..41be639
--- /dev/null
+++ b/include/lib/ArduinoJson/Serialization/DynamicStringBuilder.hpp
@@ -0,0 +1,35 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "../StringTraits/StringTraits.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+// A Print implementation that allows to write in a String
+template <typename TString>
+class DynamicStringBuilder {
+ public:
+ DynamicStringBuilder(TString &str) : _str(str) {}
+
+ size_t print(char c) {
+ StringTraits<TString>::append(_str, c);
+ return 1;
+ }
+
+ size_t print(const char *s) {
+ size_t initialLen = _str.length();
+ StringTraits<TString>::append(_str, s);
+ return _str.length() - initialLen;
+ }
+
+ private:
+ DynamicStringBuilder &operator=(const DynamicStringBuilder &);
+
+ TString &_str;
+};
+}
+}
diff --git a/include/lib/ArduinoJson/Serialization/FloatParts.hpp b/include/lib/ArduinoJson/Serialization/FloatParts.hpp
new file mode 100644
index 0000000..c14e3b5
--- /dev/null
+++ b/include/lib/ArduinoJson/Serialization/FloatParts.hpp
@@ -0,0 +1,89 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "../Configuration.hpp"
+#include "../Polyfills/math.hpp"
+#include "../TypeTraits/FloatTraits.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+template <typename TFloat>
+struct FloatParts {
+ uint32_t integral;
+ uint32_t decimal;
+ int16_t exponent;
+ int8_t decimalPlaces;
+
+ FloatParts(TFloat value) {
+ uint32_t maxDecimalPart = sizeof(TFloat) >= 8 ? 1000000000 : 1000000;
+ decimalPlaces = sizeof(TFloat) >= 8 ? 9 : 6;
+
+ exponent = normalize(value);
+
+ integral = uint32_t(value);
+ // reduce number of decimal places by the number of integral places
+ for (uint32_t tmp = integral; tmp >= 10; tmp /= 10) {
+ maxDecimalPart /= 10;
+ decimalPlaces--;
+ }
+
+ TFloat remainder = (value - TFloat(integral)) * TFloat(maxDecimalPart);
+
+ decimal = uint32_t(remainder);
+ remainder = remainder - TFloat(decimal);
+
+ // rounding:
+ // increment by 1 if remainder >= 0.5
+ decimal += uint32_t(remainder * 2);
+ if (decimal >= maxDecimalPart) {
+ decimal = 0;
+ integral++;
+ if (exponent && integral >= 10) {
+ exponent++;
+ integral = 1;
+ }
+ }
+
+ // remove trailing zeros
+ while (decimal % 10 == 0 && decimalPlaces > 0) {
+ decimal /= 10;
+ decimalPlaces--;
+ }
+ }
+
+ static int16_t normalize(TFloat& value) {
+ typedef FloatTraits<TFloat> traits;
+ int16_t powersOf10 = 0;
+
+ int8_t index = sizeof(TFloat) == 8 ? 8 : 5;
+ int bit = 1 << index;
+
+ if (value >= ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD) {
+ for (; index >= 0; index--) {
+ if (value >= traits::positiveBinaryPowerOfTen(index)) {
+ value *= traits::negativeBinaryPowerOfTen(index);
+ powersOf10 = int16_t(powersOf10 + bit);
+ }
+ bit >>= 1;
+ }
+ }
+
+ if (value > 0 && value <= ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD) {
+ for (; index >= 0; index--) {
+ if (value < traits::negativeBinaryPowerOfTenPlusOne(index)) {
+ value *= traits::positiveBinaryPowerOfTen(index);
+ powersOf10 = int16_t(powersOf10 - bit);
+ }
+ bit >>= 1;
+ }
+ }
+
+ return powersOf10;
+ }
+};
+}
+}
diff --git a/include/lib/ArduinoJson/Serialization/IndentedPrint.hpp b/include/lib/ArduinoJson/Serialization/IndentedPrint.hpp
new file mode 100644
index 0000000..864f9aa
--- /dev/null
+++ b/include/lib/ArduinoJson/Serialization/IndentedPrint.hpp
@@ -0,0 +1,68 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+namespace Internals {
+
+// Decorator on top of Print to allow indented output.
+// This class is used by JsonPrintable::prettyPrintTo() but can also be used
+// for your own purpose, like logging.
+template <typename Print>
+class IndentedPrint {
+ public:
+ explicit IndentedPrint(Print &p) : sink(&p) {
+ level = 0;
+ tabSize = 2;
+ isNewLine = true;
+ }
+
+ size_t print(char c) {
+ size_t n = 0;
+ if (isNewLine) n += writeTabs();
+ n += sink->print(c);
+ isNewLine = c == '\n';
+ return n;
+ }
+
+ size_t print(const char *s) {
+ // TODO: optimize
+ size_t n = 0;
+ while (*s) n += print(*s++);
+ return n;
+ }
+
+ // Adds one level of indentation
+ void indent() {
+ if (level < MAX_LEVEL) level++;
+ }
+
+ // Removes one level of indentation
+ void unindent() {
+ if (level > 0) level--;
+ }
+
+ // Set the number of space printed for each level of indentation
+ void setTabSize(uint8_t n) {
+ if (n < MAX_TAB_SIZE) tabSize = n & MAX_TAB_SIZE;
+ }
+
+ private:
+ Print *sink;
+ uint8_t level : 4;
+ uint8_t tabSize : 3;
+ bool isNewLine : 1;
+
+ size_t writeTabs() {
+ size_t n = 0;
+ for (int i = 0; i < level * tabSize; i++) n += sink->print(' ');
+ return n;
+ }
+
+ static const int MAX_LEVEL = 15; // because it's only 4 bits
+ static const int MAX_TAB_SIZE = 7; // because it's only 3 bits
+};
+}
+}
diff --git a/include/lib/ArduinoJson/Serialization/JsonPrintable.hpp b/include/lib/ArduinoJson/Serialization/JsonPrintable.hpp
new file mode 100644
index 0000000..e73f8fb
--- /dev/null
+++ b/include/lib/ArduinoJson/Serialization/JsonPrintable.hpp
@@ -0,0 +1,117 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "../Configuration.hpp"
+#include "../TypeTraits/EnableIf.hpp"
+#include "DummyPrint.hpp"
+#include "DynamicStringBuilder.hpp"
+#include "IndentedPrint.hpp"
+#include "JsonSerializer.hpp"
+#include "JsonWriter.hpp"
+#include "Prettyfier.hpp"
+#include "StaticStringBuilder.hpp"
+
+#if ARDUINOJSON_ENABLE_STD_STREAM
+#include "StreamPrintAdapter.hpp"
+#endif
+
+namespace ArduinoJson {
+namespace Internals {
+
+// Implements all the overloads of printTo() and prettyPrintTo()
+// Caution: this class use a template parameter to avoid virtual methods.
+// This is a bit curious but allows to reduce the size of JsonVariant, JsonArray
+// and JsonObject.
+template <typename T>
+class JsonPrintable {
+ public:
+ template <typename Print>
+ typename EnableIf<!StringTraits<Print>::has_append, size_t>::type printTo(
+ Print &print) const {
+ JsonWriter<Print> writer(print);
+ JsonSerializer<JsonWriter<Print> >::serialize(downcast(), writer);
+ return writer.bytesWritten();
+ }
+
+#if ARDUINOJSON_ENABLE_STD_STREAM
+ std::ostream &printTo(std::ostream &os) const {
+ StreamPrintAdapter adapter(os);
+ printTo(adapter);
+ return os;
+ }
+#endif
+
+ size_t printTo(char *buffer, size_t bufferSize) const {
+ StaticStringBuilder sb(buffer, bufferSize);
+ return printTo(sb);
+ }
+
+ template <size_t TN>
+ size_t printTo(char (&buffer)[TN]) const {
+ return printTo(buffer, TN);
+ }
+
+ template <typename TString>
+ typename EnableIf<StringTraits<TString>::has_append, size_t>::type printTo(
+ TString &str) const {
+ DynamicStringBuilder<TString> sb(str);
+ return printTo(sb);
+ }
+
+ template <typename Print>
+ size_t prettyPrintTo(IndentedPrint<Print> &print) const {
+ Prettyfier<Print> p(print);
+ return printTo(p);
+ }
+
+ size_t prettyPrintTo(char *buffer, size_t bufferSize) const {
+ StaticStringBuilder sb(buffer, bufferSize);
+ return prettyPrintTo(sb);
+ }
+
+ template <size_t TN>
+ size_t prettyPrintTo(char (&buffer)[TN]) const {
+ return prettyPrintTo(buffer, TN);
+ }
+
+ template <typename Print>
+ typename EnableIf<!StringTraits<Print>::has_append, size_t>::type
+ prettyPrintTo(Print &print) const {
+ IndentedPrint<Print> indentedPrint(print);
+ return prettyPrintTo(indentedPrint);
+ }
+
+ template <typename TString>
+ typename EnableIf<StringTraits<TString>::has_append, size_t>::type
+ prettyPrintTo(TString &str) const {
+ DynamicStringBuilder<TString> sb(str);
+ return prettyPrintTo(sb);
+ }
+
+ size_t measureLength() const {
+ DummyPrint dp;
+ return printTo(dp);
+ }
+
+ size_t measurePrettyLength() const {
+ DummyPrint dp;
+ return prettyPrintTo(dp);
+ }
+
+ private:
+ const T &downcast() const {
+ return *static_cast<const T *>(this);
+ }
+};
+
+#if ARDUINOJSON_ENABLE_STD_STREAM
+template <typename T>
+inline std::ostream &operator<<(std::ostream &os, const JsonPrintable<T> &v) {
+ return v.printTo(os);
+}
+#endif
+}
+}
diff --git a/include/lib/ArduinoJson/Serialization/JsonSerializer.hpp b/include/lib/ArduinoJson/Serialization/JsonSerializer.hpp
new file mode 100644
index 0000000..0cb537f
--- /dev/null
+++ b/include/lib/ArduinoJson/Serialization/JsonSerializer.hpp
@@ -0,0 +1,32 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "JsonWriter.hpp"
+
+namespace ArduinoJson {
+
+class JsonArray;
+class JsonObject;
+class JsonVariant;
+
+namespace Internals {
+
+class JsonArraySubscript;
+template <typename TKey>
+class JsonObjectSubscript;
+
+template <typename Writer>
+class JsonSerializer {
+ public:
+ static void serialize(const JsonArray &, Writer &);
+ static void serialize(const JsonArraySubscript &, Writer &);
+ static void serialize(const JsonObject &, Writer &);
+ template <typename TKey>
+ static void serialize(const JsonObjectSubscript<TKey> &, Writer &);
+ static void serialize(const JsonVariant &, Writer &);
+};
+}
+}
diff --git a/include/lib/ArduinoJson/Serialization/JsonSerializerImpl.hpp b/include/lib/ArduinoJson/Serialization/JsonSerializerImpl.hpp
new file mode 100644
index 0000000..0faae27
--- /dev/null
+++ b/include/lib/ArduinoJson/Serialization/JsonSerializerImpl.hpp
@@ -0,0 +1,103 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "../JsonArray.hpp"
+#include "../JsonArraySubscript.hpp"
+#include "../JsonObject.hpp"
+#include "../JsonObjectSubscript.hpp"
+#include "../JsonVariant.hpp"
+#include "JsonSerializer.hpp"
+
+template <typename Writer>
+inline void ArduinoJson::Internals::JsonSerializer<Writer>::serialize(
+ const JsonArray& array, Writer& writer) {
+ writer.beginArray();
+
+ JsonArray::const_iterator it = array.begin();
+ while (it != array.end()) {
+ serialize(*it, writer);
+
+ ++it;
+ if (it == array.end()) break;
+
+ writer.writeComma();
+ }
+
+ writer.endArray();
+}
+
+template <typename Writer>
+inline void ArduinoJson::Internals::JsonSerializer<Writer>::serialize(
+ const JsonArraySubscript& arraySubscript, Writer& writer) {
+ serialize(arraySubscript.as<JsonVariant>(), writer);
+}
+
+template <typename Writer>
+inline void ArduinoJson::Internals::JsonSerializer<Writer>::serialize(
+ const JsonObject& object, Writer& writer) {
+ writer.beginObject();
+
+ JsonObject::const_iterator it = object.begin();
+ while (it != object.end()) {
+ writer.writeString(it->key);
+ writer.writeColon();
+ serialize(it->value, writer);
+
+ ++it;
+ if (it == object.end()) break;
+
+ writer.writeComma();
+ }
+
+ writer.endObject();
+}
+
+template <typename Writer>
+template <typename TKey>
+inline void ArduinoJson::Internals::JsonSerializer<Writer>::serialize(
+ const JsonObjectSubscript<TKey>& objectSubscript, Writer& writer) {
+ serialize(objectSubscript.template as<JsonVariant>(), writer);
+}
+
+template <typename Writer>
+inline void ArduinoJson::Internals::JsonSerializer<Writer>::serialize(
+ const JsonVariant& variant, Writer& writer) {
+ switch (variant._type) {
+ case JSON_FLOAT:
+ writer.writeFloat(variant._content.asFloat);
+ return;
+
+ case JSON_ARRAY:
+ serialize(*variant._content.asArray, writer);
+ return;
+
+ case JSON_OBJECT:
+ serialize(*variant._content.asObject, writer);
+ return;
+
+ case JSON_STRING:
+ writer.writeString(variant._content.asString);
+ return;
+
+ case JSON_UNPARSED:
+ writer.writeRaw(variant._content.asString);
+ return;
+
+ case JSON_NEGATIVE_INTEGER:
+ writer.writeRaw('-'); // Falls through.
+
+ case JSON_POSITIVE_INTEGER:
+ writer.writeInteger(variant._content.asInteger);
+ return;
+
+ case JSON_BOOLEAN:
+ writer.writeBoolean(variant._content.asInteger != 0);
+ return;
+
+ default: // JSON_UNDEFINED
+ return;
+ }
+}
diff --git a/include/lib/ArduinoJson/Serialization/JsonWriter.hpp b/include/lib/ArduinoJson/Serialization/JsonWriter.hpp
new file mode 100644
index 0000000..146d51d
--- /dev/null
+++ b/include/lib/ArduinoJson/Serialization/JsonWriter.hpp
@@ -0,0 +1,155 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include <stdint.h>
+#include "../Data/Encoding.hpp"
+#include "../Data/JsonInteger.hpp"
+#include "../Polyfills/attributes.hpp"
+#include "../Serialization/FloatParts.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+// Writes the JSON tokens to a Print implementation
+// This class is used by:
+// - JsonArray::writeTo()
+// - JsonObject::writeTo()
+// - JsonVariant::writeTo()
+// Its derived by PrettyJsonWriter that overrides some members to add
+// indentation.
+template <typename Print>
+class JsonWriter {
+ public:
+ explicit JsonWriter(Print &sink) : _sink(sink), _length(0) {}
+
+ // Returns the number of bytes sent to the Print implementation.
+ // This is very handy for implementations of printTo() that must return the
+ // number of bytes written.
+ size_t bytesWritten() const {
+ return _length;
+ }
+
+ void beginArray() {
+ writeRaw('[');
+ }
+ void endArray() {
+ writeRaw(']');
+ }
+
+ void beginObject() {
+ writeRaw('{');
+ }
+ void endObject() {
+ writeRaw('}');
+ }
+
+ void writeColon() {
+ writeRaw(':');
+ }
+ void writeComma() {
+ writeRaw(',');
+ }
+
+ void writeBoolean(bool value) {
+ writeRaw(value ? "true" : "false");
+ }
+
+ void writeString(const char *value) {
+ if (!value) {
+ writeRaw("null");
+ } else {
+ writeRaw('\"');
+ while (*value) writeChar(*value++);
+ writeRaw('\"');
+ }
+ }
+
+ void writeChar(char c) {
+ char specialChar = Encoding::escapeChar(c);
+ if (specialChar) {
+ writeRaw('\\');
+ writeRaw(specialChar);
+ } else {
+ writeRaw(c);
+ }
+ }
+
+ template <typename TFloat>
+ void writeFloat(TFloat value) {
+ if (isNaN(value)) return writeRaw("NaN");
+
+ if (value < 0.0) {
+ writeRaw('-');
+ value = -value;
+ }
+
+ if (isInfinity(value)) return writeRaw("Infinity");
+
+ FloatParts<TFloat> parts(value);
+
+ writeInteger(parts.integral);
+ if (parts.decimalPlaces) writeDecimals(parts.decimal, parts.decimalPlaces);
+
+ if (parts.exponent < 0) {
+ writeRaw("e-");
+ writeInteger(-parts.exponent);
+ }
+
+ if (parts.exponent > 0) {
+ writeRaw('e');
+ writeInteger(parts.exponent);
+ }
+ }
+
+ template <typename UInt>
+ void writeInteger(UInt value) {
+ char buffer[22];
+ char *end = buffer + sizeof(buffer) - 1;
+ char *ptr = end;
+
+ *ptr = 0;
+ do {
+ *--ptr = char(value % 10 + '0');
+ value = UInt(value / 10);
+ } while (value);
+
+ writeRaw(ptr);
+ }
+
+ void writeDecimals(uint32_t value, int8_t width) {
+ // buffer should be big enough for all digits, the dot and the null
+ // terminator
+ char buffer[16];
+ char *ptr = buffer + sizeof(buffer) - 1;
+
+ // write the string in reverse order
+ *ptr = 0;
+ while (width--) {
+ *--ptr = char(value % 10 + '0');
+ value /= 10;
+ }
+ *--ptr = '.';
+
+ // and dump it in the right order
+ writeRaw(ptr);
+ }
+
+ void writeRaw(const char *s) {
+ _length += _sink.print(s);
+ }
+ void writeRaw(char c) {
+ _length += _sink.print(c);
+ }
+
+ protected:
+ Print &_sink;
+ size_t _length;
+
+ private:
+ JsonWriter &operator=(const JsonWriter &); // cannot be assigned
+};
+}
+}
diff --git a/include/lib/ArduinoJson/Serialization/Prettyfier.hpp b/include/lib/ArduinoJson/Serialization/Prettyfier.hpp
new file mode 100644
index 0000000..8b4f0d2
--- /dev/null
+++ b/include/lib/ArduinoJson/Serialization/Prettyfier.hpp
@@ -0,0 +1,133 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "IndentedPrint.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+// Converts a compact JSON string into an indented one.
+template <typename Print>
+class Prettyfier {
+ public:
+ explicit Prettyfier(IndentedPrint<Print>& p) : _sink(p) {
+ _previousChar = 0;
+ _inString = false;
+ }
+
+ size_t print(char c) {
+ size_t n = _inString ? handleStringChar(c) : handleMarkupChar(c);
+ _previousChar = c;
+ return n;
+ }
+
+ size_t print(const char* s) {
+ // TODO: optimize
+ size_t n = 0;
+ while (*s) n += print(*s++);
+ return n;
+ }
+
+ private:
+ Prettyfier& operator=(const Prettyfier&); // cannot be assigned
+
+ bool inEmptyBlock() {
+ return _previousChar == '{' || _previousChar == '[';
+ }
+
+ size_t handleStringChar(char c) {
+ bool isQuote = c == '"' && _previousChar != '\\';
+
+ if (isQuote) _inString = false;
+
+ return _sink.print(c);
+ }
+
+ size_t handleMarkupChar(char c) {
+ switch (c) {
+ case '{':
+ case '[':
+ return writeBlockOpen(c);
+
+ case '}':
+ case ']':
+ return writeBlockClose(c);
+
+ case ':':
+ return writeColon();
+
+ case ',':
+ return writeComma();
+
+ case '"':
+ return writeQuoteOpen();
+
+ default:
+ return writeNormalChar(c);
+ }
+ }
+
+ size_t writeBlockClose(char c) {
+ size_t n = 0;
+ n += unindentIfNeeded();
+ n += _sink.print(c);
+ return n;
+ }
+
+ size_t writeBlockOpen(char c) {
+ size_t n = 0;
+ n += indentIfNeeded();
+ n += _sink.print(c);
+ return n;
+ }
+
+ size_t writeColon() {
+ size_t n = 0;
+ n += _sink.print(": ");
+ return n;
+ }
+
+ size_t writeComma() {
+ size_t n = 0;
+ n += _sink.print(",\r\n");
+ return n;
+ }
+
+ size_t writeQuoteOpen() {
+ _inString = true;
+ size_t n = 0;
+ n += indentIfNeeded();
+ n += _sink.print('"');
+ return n;
+ }
+
+ size_t writeNormalChar(char c) {
+ size_t n = 0;
+ n += indentIfNeeded();
+ n += _sink.print(c);
+ return n;
+ }
+
+ size_t indentIfNeeded() {
+ if (!inEmptyBlock()) return 0;
+
+ _sink.indent();
+ return _sink.print("\r\n");
+ }
+
+ size_t unindentIfNeeded() {
+ if (inEmptyBlock()) return 0;
+
+ _sink.unindent();
+ return _sink.print("\r\n");
+ }
+
+ char _previousChar;
+ IndentedPrint<Print>& _sink;
+ bool _inString;
+};
+}
+}
diff --git a/include/lib/ArduinoJson/Serialization/StaticStringBuilder.hpp b/include/lib/ArduinoJson/Serialization/StaticStringBuilder.hpp
new file mode 100644
index 0000000..9617bbd
--- /dev/null
+++ b/include/lib/ArduinoJson/Serialization/StaticStringBuilder.hpp
@@ -0,0 +1,36 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+namespace Internals {
+
+// A Print implementation that allows to write in a char[]
+class StaticStringBuilder {
+ public:
+ StaticStringBuilder(char *buf, size_t size) : end(buf + size - 1), p(buf) {
+ *p = '\0';
+ }
+
+ size_t print(char c) {
+ if (p >= end) return 0;
+ *p++ = c;
+ *p = '\0';
+ return 1;
+ }
+
+ size_t print(const char *s) {
+ char *begin = p;
+ while (p < end && *s) *p++ = *s++;
+ *p = '\0';
+ return size_t(p - begin);
+ }
+
+ private:
+ char *end;
+ char *p;
+};
+}
+}
diff --git a/include/lib/ArduinoJson/Serialization/StreamPrintAdapter.hpp b/include/lib/ArduinoJson/Serialization/StreamPrintAdapter.hpp
new file mode 100644
index 0000000..60f0af4
--- /dev/null
+++ b/include/lib/ArduinoJson/Serialization/StreamPrintAdapter.hpp
@@ -0,0 +1,39 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "../Configuration.hpp"
+
+#if ARDUINOJSON_ENABLE_STD_STREAM
+
+#include <ostream>
+
+namespace ArduinoJson {
+namespace Internals {
+
+class StreamPrintAdapter {
+ public:
+ explicit StreamPrintAdapter(std::ostream& os) : _os(os) {}
+
+ size_t print(char c) {
+ _os << c;
+ return 1;
+ }
+
+ size_t print(const char* s) {
+ _os << s;
+ return strlen(s);
+ }
+
+ private:
+ // cannot be assigned
+ StreamPrintAdapter& operator=(const StreamPrintAdapter&);
+
+ std::ostream& _os;
+};
+}
+}
+
+#endif // ARDUINOJSON_ENABLE_STD_STREAM
diff --git a/include/lib/ArduinoJson/StaticJsonBuffer.hpp b/include/lib/ArduinoJson/StaticJsonBuffer.hpp
new file mode 100644
index 0000000..267d9d0
--- /dev/null
+++ b/include/lib/ArduinoJson/StaticJsonBuffer.hpp
@@ -0,0 +1,126 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "JsonBufferBase.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+class StaticJsonBufferBase : public JsonBufferBase<StaticJsonBufferBase> {
+ public:
+ class String {
+ public:
+ String(StaticJsonBufferBase* parent) : _parent(parent) {
+ _start = parent->_buffer + parent->_size;
+ }
+
+ void append(char c) {
+ if (_parent->canAlloc(1)) {
+ char* last = static_cast<char*>(_parent->doAlloc(1));
+ *last = c;
+ }
+ }
+
+ const char* c_str() const {
+ if (_parent->canAlloc(1)) {
+ char* last = static_cast<char*>(_parent->doAlloc(1));
+ *last = '\0';
+ return _start;
+ } else {
+ return NULL;
+ }
+ }
+
+ private:
+ StaticJsonBufferBase* _parent;
+ char* _start;
+ };
+
+ StaticJsonBufferBase(char* buffer, size_t capa)
+ : _buffer(buffer), _capacity(capa), _size(0) {}
+
+ // Gets the capacity of the buffer in bytes
+ size_t capacity() const {
+ return _capacity;
+ }
+
+ // Gets the current usage of the buffer in bytes
+ size_t size() const {
+ return _size;
+ }
+
+ // Allocates the specified amount of bytes in the buffer
+ virtual void* alloc(size_t bytes) {
+ alignNextAlloc();
+ if (!canAlloc(bytes)) return NULL;
+ return doAlloc(bytes);
+ }
+
+ // Resets the buffer.
+ // USE WITH CAUTION: this invalidates all previously allocated data
+ void clear() {
+ _size = 0;
+ }
+
+ String startString() {
+ return String(this);
+ }
+
+ protected:
+ ~StaticJsonBufferBase() {}
+
+ private:
+ void alignNextAlloc() {
+ _size = round_size_up(_size);
+ }
+
+ bool canAlloc(size_t bytes) const {
+ return _size + bytes <= _capacity;
+ }
+
+ void* doAlloc(size_t bytes) {
+ void* p = &_buffer[_size];
+ _size += bytes;
+ return p;
+ }
+
+ char* _buffer;
+ size_t _capacity;
+ size_t _size;
+};
+}
+
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnon-virtual-dtor"
+#elif defined(__GNUC__)
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+#pragma GCC diagnostic push
+#endif
+#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
+#endif
+
+// Implements a JsonBuffer with fixed memory allocation.
+// The template paramenter CAPACITY specifies the capacity of the buffer in
+// bytes.
+template <size_t CAPACITY>
+class StaticJsonBuffer : public Internals::StaticJsonBufferBase {
+ public:
+ explicit StaticJsonBuffer()
+ : Internals::StaticJsonBufferBase(_buffer, CAPACITY) {}
+
+ private:
+ char _buffer[CAPACITY];
+};
+}
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#elif defined(__GNUC__)
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+#pragma GCC diagnostic pop
+#endif
+#endif
diff --git a/include/lib/ArduinoJson/StringTraits/ArduinoStream.hpp b/include/lib/ArduinoJson/StringTraits/ArduinoStream.hpp
new file mode 100644
index 0000000..5db0852
--- /dev/null
+++ b/include/lib/ArduinoJson/StringTraits/ArduinoStream.hpp
@@ -0,0 +1,61 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#if ARDUINOJSON_ENABLE_ARDUINO_STREAM
+
+#include <Stream.h>
+
+namespace ArduinoJson {
+namespace Internals {
+
+struct ArduinoStreamTraits {
+ class Reader {
+ Stream& _stream;
+ char _current, _next;
+
+ public:
+ Reader(Stream& stream) : _stream(stream), _current(0), _next(0) {}
+
+ void move() {
+ _current = _next;
+ _next = 0;
+ }
+
+ char current() {
+ if (!_current) _current = read();
+ return _current;
+ }
+
+ char next() {
+ // assumes that current() has been called
+ if (!_next) _next = read();
+ return _next;
+ }
+
+ private:
+ char read() {
+ // don't use _stream.read() as it ignores the timeout
+ char c = 0;
+ _stream.readBytes(&c, 1);
+ return c;
+ }
+ };
+
+ static const bool has_append = false;
+ static const bool has_equals = false;
+};
+
+template <typename TStream>
+struct StringTraits<
+ TStream,
+ // match any type that is derived from Stream:
+ typename EnableIf<
+ IsBaseOf<Stream, typename RemoveReference<TStream>::type>::value>::type>
+ : ArduinoStreamTraits {};
+}
+}
+
+#endif
diff --git a/include/lib/ArduinoJson/StringTraits/CharPointer.hpp b/include/lib/ArduinoJson/StringTraits/CharPointer.hpp
new file mode 100644
index 0000000..98896cc
--- /dev/null
+++ b/include/lib/ArduinoJson/StringTraits/CharPointer.hpp
@@ -0,0 +1,64 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+namespace Internals {
+
+template <typename TChar>
+struct CharPointerTraits {
+ class Reader {
+ const TChar* _ptr;
+
+ public:
+ Reader(const TChar* ptr)
+ : _ptr(ptr ? ptr : reinterpret_cast<const TChar*>("")) {}
+
+ void move() {
+ ++_ptr;
+ }
+
+ char current() const {
+ return char(_ptr[0]);
+ }
+
+ char next() const {
+ return char(_ptr[1]);
+ }
+ };
+
+ static bool equals(const TChar* str, const char* expected) {
+ const char* actual = reinterpret_cast<const char*>(str);
+ if (!actual || !expected) return actual == expected;
+ return strcmp(actual, expected) == 0;
+ }
+
+ static bool is_null(const TChar* str) {
+ return !str;
+ }
+
+ typedef const char* duplicate_t;
+
+ template <typename Buffer>
+ static duplicate_t duplicate(const TChar* str, Buffer* buffer) {
+ if (!str) return NULL;
+ size_t size = strlen(reinterpret_cast<const char*>(str)) + 1;
+ void* dup = buffer->alloc(size);
+ if (dup != NULL) memcpy(dup, str, size);
+ return static_cast<duplicate_t>(dup);
+ }
+
+ static const bool has_append = false;
+ static const bool has_equals = true;
+ static const bool should_duplicate = !IsConst<TChar>::value;
+};
+
+// char*, unsigned char*, signed char*
+// const char*, const unsigned char*, const signed char*
+template <typename TChar>
+struct StringTraits<TChar*, typename EnableIf<IsChar<TChar>::value>::type>
+ : CharPointerTraits<TChar> {};
+} // namespace Internals
+} // namespace ArduinoJson
diff --git a/include/lib/ArduinoJson/StringTraits/FlashString.hpp b/include/lib/ArduinoJson/StringTraits/FlashString.hpp
new file mode 100644
index 0000000..0701b9b
--- /dev/null
+++ b/include/lib/ArduinoJson/StringTraits/FlashString.hpp
@@ -0,0 +1,61 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#if ARDUINOJSON_ENABLE_PROGMEM
+
+namespace ArduinoJson {
+namespace Internals {
+template <>
+struct StringTraits<const __FlashStringHelper*, void> {
+ class Reader {
+ const char* _ptr;
+
+ public:
+ Reader(const __FlashStringHelper* ptr)
+ : _ptr(reinterpret_cast<const char*>(ptr)) {}
+
+ void move() {
+ _ptr++;
+ }
+
+ char current() const {
+ return pgm_read_byte_near(_ptr);
+ }
+
+ char next() const {
+ return pgm_read_byte_near(_ptr + 1);
+ }
+ };
+
+ static bool equals(const __FlashStringHelper* str, const char* expected) {
+ const char* actual = reinterpret_cast<const char*>(str);
+ if (!actual || !expected) return actual == expected;
+ return strcmp_P(expected, actual) == 0;
+ }
+
+ static bool is_null(const __FlashStringHelper* str) {
+ return !str;
+ }
+
+ typedef const char* duplicate_t;
+
+ template <typename Buffer>
+ static duplicate_t duplicate(const __FlashStringHelper* str, Buffer* buffer) {
+ if (!str) return NULL;
+ size_t size = strlen_P((const char*)str) + 1;
+ void* dup = buffer->alloc(size);
+ if (dup != NULL) memcpy_P(dup, (const char*)str, size);
+ return static_cast<duplicate_t>(dup);
+ }
+
+ static const bool has_append = false;
+ static const bool has_equals = true;
+ static const bool should_duplicate = true;
+};
+} // namespace Internals
+} // namespace ArduinoJson
+
+#endif
diff --git a/include/lib/ArduinoJson/StringTraits/StdStream.hpp b/include/lib/ArduinoJson/StringTraits/StdStream.hpp
new file mode 100644
index 0000000..227c744
--- /dev/null
+++ b/include/lib/ArduinoJson/StringTraits/StdStream.hpp
@@ -0,0 +1,60 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#if ARDUINOJSON_ENABLE_STD_STREAM
+
+#include <istream>
+
+namespace ArduinoJson {
+namespace Internals {
+
+struct StdStreamTraits {
+ class Reader {
+ std::istream& _stream;
+ char _current, _next;
+
+ public:
+ Reader(std::istream& stream) : _stream(stream), _current(0), _next(0) {}
+
+ void move() {
+ _current = _next;
+ _next = 0;
+ }
+
+ char current() {
+ if (!_current) _current = read();
+ return _current;
+ }
+
+ char next() {
+ // assumes that current() has been called
+ if (!_next) _next = read();
+ return _next;
+ }
+
+ private:
+ Reader& operator=(const Reader&); // Visual Studio C4512
+
+ char read() {
+ return _stream.eof() ? '\0' : static_cast<char>(_stream.get());
+ }
+ };
+
+ static const bool has_append = false;
+ static const bool has_equals = false;
+};
+
+template <typename TStream>
+struct StringTraits<
+ TStream,
+ // match any type that is derived from std::istream:
+ typename EnableIf<IsBaseOf<
+ std::istream, typename RemoveReference<TStream>::type>::value>::type>
+ : StdStreamTraits {};
+}
+}
+
+#endif
diff --git a/include/lib/ArduinoJson/StringTraits/StdString.hpp b/include/lib/ArduinoJson/StringTraits/StdString.hpp
new file mode 100644
index 0000000..35f4461
--- /dev/null
+++ b/include/lib/ArduinoJson/StringTraits/StdString.hpp
@@ -0,0 +1,77 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#if ARDUINOJSON_ENABLE_STD_STRING || ARDUINOJSON_ENABLE_ARDUINO_STRING
+
+#if ARDUINOJSON_ENABLE_ARDUINO_STRING
+#include <WString.h>
+#endif
+
+#if ARDUINOJSON_ENABLE_STD_STRING
+#include <string>
+#endif
+
+namespace ArduinoJson {
+namespace Internals {
+
+template <typename TString>
+struct StdStringTraits {
+ typedef const char* duplicate_t;
+
+ template <typename Buffer>
+ static duplicate_t duplicate(const TString& str, Buffer* buffer) {
+ if (!str.c_str()) return NULL; // <- Arduino string can return NULL
+ size_t size = str.length() + 1;
+ void* dup = buffer->alloc(size);
+ if (dup != NULL) memcpy(dup, str.c_str(), size);
+ return static_cast<duplicate_t>(dup);
+ }
+
+ static bool is_null(const TString& str) {
+ // Arduino's String::c_str() can return NULL
+ return !str.c_str();
+ }
+
+ struct Reader : CharPointerTraits<char>::Reader {
+ Reader(const TString& str) : CharPointerTraits<char>::Reader(str.c_str()) {}
+ };
+
+ static bool equals(const TString& str, const char* expected) {
+ // Arduino's String::c_str() can return NULL
+ const char* actual = str.c_str();
+ if (!actual || !expected) return actual == expected;
+ return 0 == strcmp(actual, expected);
+ }
+
+ static void append(TString& str, char c) {
+ str += c;
+ }
+
+ static void append(TString& str, const char* s) {
+ str += s;
+ }
+
+ static const bool has_append = true;
+ static const bool has_equals = true;
+ static const bool should_duplicate = true;
+};
+
+#if ARDUINOJSON_ENABLE_ARDUINO_STRING
+template <>
+struct StringTraits<String, void> : StdStringTraits<String> {};
+template <>
+struct StringTraits<StringSumHelper, void> : StdStringTraits<StringSumHelper> {
+};
+#endif
+
+#if ARDUINOJSON_ENABLE_STD_STRING
+template <>
+struct StringTraits<std::string, void> : StdStringTraits<std::string> {};
+#endif
+} // namespace Internals
+} // namespace ArduinoJson
+
+#endif
diff --git a/include/lib/ArduinoJson/StringTraits/StringTraits.hpp b/include/lib/ArduinoJson/StringTraits/StringTraits.hpp
new file mode 100644
index 0000000..dd5694b
--- /dev/null
+++ b/include/lib/ArduinoJson/StringTraits/StringTraits.hpp
@@ -0,0 +1,36 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include <string.h>
+#include "../Configuration.hpp"
+#include "../TypeTraits/EnableIf.hpp"
+#include "../TypeTraits/IsBaseOf.hpp"
+#include "../TypeTraits/IsChar.hpp"
+#include "../TypeTraits/IsConst.hpp"
+#include "../TypeTraits/RemoveReference.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+template <typename TString, typename Enable = void>
+struct StringTraits {
+ static const bool has_append = false;
+ static const bool has_equals = false;
+};
+
+template <typename TString>
+struct StringTraits<const TString, void> : StringTraits<TString> {};
+
+template <typename TString>
+struct StringTraits<TString&, void> : StringTraits<TString> {};
+}
+}
+
+#include "ArduinoStream.hpp"
+#include "CharPointer.hpp"
+#include "FlashString.hpp"
+#include "StdStream.hpp"
+#include "StdString.hpp"
diff --git a/include/lib/ArduinoJson/TypeTraits/EnableIf.hpp b/include/lib/ArduinoJson/TypeTraits/EnableIf.hpp
new file mode 100644
index 0000000..83fc5e0
--- /dev/null
+++ b/include/lib/ArduinoJson/TypeTraits/EnableIf.hpp
@@ -0,0 +1,19 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+namespace Internals {
+
+// A meta-function that return the type T if Condition is true.
+template <bool Condition, typename T = void>
+struct EnableIf {};
+
+template <typename T>
+struct EnableIf<true, T> {
+ typedef T type;
+};
+}
+}
diff --git a/include/lib/ArduinoJson/TypeTraits/FloatTraits.hpp b/include/lib/ArduinoJson/TypeTraits/FloatTraits.hpp
new file mode 100644
index 0000000..648cc82
--- /dev/null
+++ b/include/lib/ArduinoJson/TypeTraits/FloatTraits.hpp
@@ -0,0 +1,171 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include <stdint.h>
+#include <stdlib.h> // for size_t
+#include "../Configuration.hpp"
+#include "../Polyfills/math.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+template <typename T, size_t = sizeof(T)>
+struct FloatTraits {};
+
+template <typename T>
+struct FloatTraits<T, 8 /*64bits*/> {
+ typedef int64_t mantissa_type;
+ static const short mantissa_bits = 52;
+ static const mantissa_type mantissa_max =
+ (static_cast<mantissa_type>(1) << mantissa_bits) - 1;
+
+ typedef int16_t exponent_type;
+ static const exponent_type exponent_max = 308;
+
+ template <typename TExponent>
+ static T make_float(T m, TExponent e) {
+ if (e > 0) {
+ for (uint8_t index = 0; e != 0; index++) {
+ if (e & 1) m *= positiveBinaryPowerOfTen(index);
+ e >>= 1;
+ }
+ } else {
+ e = TExponent(-e);
+ for (uint8_t index = 0; e != 0; index++) {
+ if (e & 1) m *= negativeBinaryPowerOfTen(index);
+ e >>= 1;
+ }
+ }
+ return m;
+ }
+
+ static T positiveBinaryPowerOfTen(int index) {
+ static T factors[] = {
+ 1e1,
+ 1e2,
+ 1e4,
+ 1e8,
+ 1e16,
+ forge(0x4693B8B5, 0xB5056E17), // 1e32
+ forge(0x4D384F03, 0xE93FF9F5), // 1e64
+ forge(0x5A827748, 0xF9301D32), // 1e128
+ forge(0x75154FDD, 0x7F73BF3C) // 1e256
+ };
+ return factors[index];
+ }
+
+ static T negativeBinaryPowerOfTen(int index) {
+ static T factors[] = {
+ forge(0x3FB99999, 0x9999999A), // 1e-1
+ forge(0x3F847AE1, 0x47AE147B), // 1e-2
+ forge(0x3F1A36E2, 0xEB1C432D), // 1e-4
+ forge(0x3E45798E, 0xE2308C3A), // 1e-8
+ forge(0x3C9CD2B2, 0x97D889BC), // 1e-16
+ forge(0x3949F623, 0xD5A8A733), // 1e-32
+ forge(0x32A50FFD, 0x44F4A73D), // 1e-64
+ forge(0x255BBA08, 0xCF8C979D), // 1e-128
+ forge(0x0AC80628, 0x64AC6F43) // 1e-256
+ };
+ return factors[index];
+ }
+
+ static T negativeBinaryPowerOfTenPlusOne(int index) {
+ static T factors[] = {
+ 1e0,
+ forge(0x3FB99999, 0x9999999A), // 1e-1
+ forge(0x3F50624D, 0xD2F1A9FC), // 1e-3
+ forge(0x3E7AD7F2, 0x9ABCAF48), // 1e-7
+ forge(0x3CD203AF, 0x9EE75616), // 1e-15
+ forge(0x398039D6, 0x65896880), // 1e-31
+ forge(0x32DA53FC, 0x9631D10D), // 1e-63
+ forge(0x25915445, 0x81B7DEC2), // 1e-127
+ forge(0x0AFE07B2, 0x7DD78B14) // 1e-255
+ };
+ return factors[index];
+ }
+
+ static T nan() {
+ return forge(0x7ff80000, 0x00000000);
+ }
+
+ static T inf() {
+ return forge(0x7ff00000, 0x00000000);
+ }
+
+ // constructs a double floating point values from its binary representation
+ // we use this function to workaround platforms with single precision literals
+ // (for example, when -fsingle-precision-constant is passed to GCC)
+ static T forge(uint32_t msb, uint32_t lsb) {
+ union {
+ uint64_t integerBits;
+ T floatBits;
+ };
+ integerBits = (uint64_t(msb) << 32) | lsb;
+ return floatBits;
+ }
+};
+
+template <typename T>
+struct FloatTraits<T, 4 /*32bits*/> {
+ typedef int32_t mantissa_type;
+ static const short mantissa_bits = 23;
+ static const mantissa_type mantissa_max =
+ (static_cast<mantissa_type>(1) << mantissa_bits) - 1;
+
+ typedef int8_t exponent_type;
+ static const exponent_type exponent_max = 38;
+
+ template <typename TExponent>
+ static T make_float(T m, TExponent e) {
+ if (e > 0) {
+ for (uint8_t index = 0; e != 0; index++) {
+ if (e & 1) m *= positiveBinaryPowerOfTen(index);
+ e >>= 1;
+ }
+ } else {
+ e = -e;
+ for (uint8_t index = 0; e != 0; index++) {
+ if (e & 1) m *= negativeBinaryPowerOfTen(index);
+ e >>= 1;
+ }
+ }
+ return m;
+ }
+
+ static T positiveBinaryPowerOfTen(int index) {
+ static T factors[] = {1e1f, 1e2f, 1e4f, 1e8f, 1e16f, 1e32f};
+ return factors[index];
+ }
+
+ static T negativeBinaryPowerOfTen(int index) {
+ static T factors[] = {1e-1f, 1e-2f, 1e-4f, 1e-8f, 1e-16f, 1e-32f};
+ return factors[index];
+ }
+
+ static T negativeBinaryPowerOfTenPlusOne(int index) {
+ static T factors[] = {1e0f, 1e-1f, 1e-3f, 1e-7f, 1e-15f, 1e-31f};
+ return factors[index];
+ }
+
+ static T forge(uint32_t bits) {
+ union {
+ uint32_t integerBits;
+ T floatBits;
+ };
+ integerBits = bits;
+ return floatBits;
+ }
+
+ static T nan() {
+ return forge(0x7fc00000);
+ }
+
+ static T inf() {
+ return forge(0x7f800000);
+ }
+};
+}
+}
diff --git a/include/lib/ArduinoJson/TypeTraits/IsArray.hpp b/include/lib/ArduinoJson/TypeTraits/IsArray.hpp
new file mode 100644
index 0000000..da42072
--- /dev/null
+++ b/include/lib/ArduinoJson/TypeTraits/IsArray.hpp
@@ -0,0 +1,24 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+namespace Internals {
+
+// A meta-function that return the type T without the const modifier
+template <typename T>
+struct IsArray {
+ static const bool value = false;
+};
+template <typename T>
+struct IsArray<T[]> {
+ static const bool value = true;
+};
+template <typename T, size_t TN>
+struct IsArray<T[TN]> {
+ static const bool value = true;
+};
+}
+}
diff --git a/include/lib/ArduinoJson/TypeTraits/IsBaseOf.hpp b/include/lib/ArduinoJson/TypeTraits/IsBaseOf.hpp
new file mode 100644
index 0000000..bf24e96
--- /dev/null
+++ b/include/lib/ArduinoJson/TypeTraits/IsBaseOf.hpp
@@ -0,0 +1,27 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+namespace Internals {
+
+// A meta-function that returns true if Derived inherits from TBase is an
+// integral type.
+template <typename TBase, typename TDerived>
+class IsBaseOf {
+ protected: // <- to avoid GCC's "all member functions in class are private"
+ typedef char Yes[1];
+ typedef char No[2];
+
+ static Yes &probe(const TBase *);
+ static No &probe(...);
+
+ public:
+ enum {
+ value = sizeof(probe(reinterpret_cast<TDerived *>(0))) == sizeof(Yes)
+ };
+};
+}
+}
diff --git a/include/lib/ArduinoJson/TypeTraits/IsChar.hpp b/include/lib/ArduinoJson/TypeTraits/IsChar.hpp
new file mode 100644
index 0000000..d97cec2
--- /dev/null
+++ b/include/lib/ArduinoJson/TypeTraits/IsChar.hpp
@@ -0,0 +1,23 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "IsSame.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+// A meta-function that returns true if T is a charater
+template <typename T>
+struct IsChar {
+ static const bool value = IsSame<T, char>::value ||
+ IsSame<T, signed char>::value ||
+ IsSame<T, unsigned char>::value;
+};
+
+template <typename T>
+struct IsChar<const T> : IsChar<T> {};
+}
+}
diff --git a/include/lib/ArduinoJson/TypeTraits/IsConst.hpp b/include/lib/ArduinoJson/TypeTraits/IsConst.hpp
new file mode 100644
index 0000000..512ee5c
--- /dev/null
+++ b/include/lib/ArduinoJson/TypeTraits/IsConst.hpp
@@ -0,0 +1,21 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+namespace Internals {
+
+// A meta-function that return the type T without the const modifier
+template <typename T>
+struct IsConst {
+ static const bool value = false;
+};
+
+template <typename T>
+struct IsConst<const T> {
+ static const bool value = true;
+};
+}
+}
diff --git a/include/lib/ArduinoJson/TypeTraits/IsFloatingPoint.hpp b/include/lib/ArduinoJson/TypeTraits/IsFloatingPoint.hpp
new file mode 100644
index 0000000..e41a682
--- /dev/null
+++ b/include/lib/ArduinoJson/TypeTraits/IsFloatingPoint.hpp
@@ -0,0 +1,18 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "IsSame.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+// A meta-function that returns true if T is a floating point type
+template <typename T>
+struct IsFloatingPoint {
+ static const bool value = IsSame<T, float>::value || IsSame<T, double>::value;
+};
+}
+}
diff --git a/include/lib/ArduinoJson/TypeTraits/IsIntegral.hpp b/include/lib/ArduinoJson/TypeTraits/IsIntegral.hpp
new file mode 100644
index 0000000..17ae5f2
--- /dev/null
+++ b/include/lib/ArduinoJson/TypeTraits/IsIntegral.hpp
@@ -0,0 +1,26 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "IsSame.hpp"
+#include "IsSignedIntegral.hpp"
+#include "IsUnsignedIntegral.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+// A meta-function that returns true if T is an integral type.
+template <typename T>
+struct IsIntegral {
+ static const bool value = IsSignedIntegral<T>::value ||
+ IsUnsignedIntegral<T>::value ||
+ IsSame<T, char>::value;
+ // CAUTION: differs from std::is_integral as it doesn't include bool
+};
+
+template <typename T>
+struct IsIntegral<const T> : IsIntegral<T> {};
+}
+}
diff --git a/include/lib/ArduinoJson/TypeTraits/IsSame.hpp b/include/lib/ArduinoJson/TypeTraits/IsSame.hpp
new file mode 100644
index 0000000..06567c9
--- /dev/null
+++ b/include/lib/ArduinoJson/TypeTraits/IsSame.hpp
@@ -0,0 +1,21 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+namespace Internals {
+
+// A meta-function that returns true if types T and U are the same.
+template <typename T, typename U>
+struct IsSame {
+ static const bool value = false;
+};
+
+template <typename T>
+struct IsSame<T, T> {
+ static const bool value = true;
+};
+}
+}
diff --git a/include/lib/ArduinoJson/TypeTraits/IsSignedIntegral.hpp b/include/lib/ArduinoJson/TypeTraits/IsSignedIntegral.hpp
new file mode 100644
index 0000000..7334eb9
--- /dev/null
+++ b/include/lib/ArduinoJson/TypeTraits/IsSignedIntegral.hpp
@@ -0,0 +1,28 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "../Configuration.hpp"
+#include "IsSame.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+// A meta-function that returns true if T is an integral type.
+template <typename T>
+struct IsSignedIntegral {
+ static const bool value =
+ IsSame<T, signed char>::value || IsSame<T, signed short>::value ||
+ IsSame<T, signed int>::value || IsSame<T, signed long>::value ||
+#if ARDUINOJSON_USE_LONG_LONG
+ IsSame<T, signed long long>::value ||
+#endif
+#if ARDUINOJSON_USE_INT64
+ IsSame<T, signed __int64>::value ||
+#endif
+ false;
+};
+}
+}
diff --git a/include/lib/ArduinoJson/TypeTraits/IsUnsignedIntegral.hpp b/include/lib/ArduinoJson/TypeTraits/IsUnsignedIntegral.hpp
new file mode 100644
index 0000000..938423f
--- /dev/null
+++ b/include/lib/ArduinoJson/TypeTraits/IsUnsignedIntegral.hpp
@@ -0,0 +1,28 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "../Configuration.hpp"
+#include "IsSame.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+// A meta-function that returns true if T is an integral type.
+template <typename T>
+struct IsUnsignedIntegral {
+ static const bool value =
+ IsSame<T, unsigned char>::value || IsSame<T, unsigned short>::value ||
+ IsSame<T, unsigned int>::value || IsSame<T, unsigned long>::value ||
+#if ARDUINOJSON_USE_LONG_LONG
+ IsSame<T, unsigned long long>::value ||
+#endif
+#if ARDUINOJSON_USE_INT64
+ IsSame<T, unsigned __int64>::value ||
+#endif
+ false;
+};
+}
+}
diff --git a/include/lib/ArduinoJson/TypeTraits/IsVariant.hpp b/include/lib/ArduinoJson/TypeTraits/IsVariant.hpp
new file mode 100644
index 0000000..f8b299f
--- /dev/null
+++ b/include/lib/ArduinoJson/TypeTraits/IsVariant.hpp
@@ -0,0 +1,17 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#include "IsBaseOf.hpp"
+
+namespace ArduinoJson {
+namespace Internals {
+
+class JsonVariantTag {};
+
+template <typename T>
+struct IsVariant : IsBaseOf<JsonVariantTag, T> {};
+}
+}
diff --git a/include/lib/ArduinoJson/TypeTraits/RemoveConst.hpp b/include/lib/ArduinoJson/TypeTraits/RemoveConst.hpp
new file mode 100644
index 0000000..39d4cb5
--- /dev/null
+++ b/include/lib/ArduinoJson/TypeTraits/RemoveConst.hpp
@@ -0,0 +1,20 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+namespace Internals {
+
+// A meta-function that return the type T without the const modifier
+template <typename T>
+struct RemoveConst {
+ typedef T type;
+};
+template <typename T>
+struct RemoveConst<const T> {
+ typedef T type;
+};
+}
+}
diff --git a/include/lib/ArduinoJson/TypeTraits/RemoveReference.hpp b/include/lib/ArduinoJson/TypeTraits/RemoveReference.hpp
new file mode 100644
index 0000000..395a128
--- /dev/null
+++ b/include/lib/ArduinoJson/TypeTraits/RemoveReference.hpp
@@ -0,0 +1,20 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+namespace ArduinoJson {
+namespace Internals {
+
+// A meta-function that return the type T without the reference modifier.
+template <typename T>
+struct RemoveReference {
+ typedef T type;
+};
+template <typename T>
+struct RemoveReference<T&> {
+ typedef T type;
+};
+}
+}
diff --git a/include/lib/ArduinoJson/version.hpp b/include/lib/ArduinoJson/version.hpp
new file mode 100644
index 0000000..a71c3ab
--- /dev/null
+++ b/include/lib/ArduinoJson/version.hpp
@@ -0,0 +1,10 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2018
+// MIT License
+
+#pragma once
+
+#define ARDUINOJSON_VERSION "5.13.2"
+#define ARDUINOJSON_VERSION_MAJOR 5
+#define ARDUINOJSON_VERSION_MINOR 13
+#define ARDUINOJSON_VERSION_REVISION 2
diff --git a/include/lib/binn.h b/include/lib/binn.h
new file mode 100644
index 0000000..d8d1733
--- /dev/null
+++ b/include/lib/binn.h
@@ -0,0 +1,923 @@
+
+// TO ENABLE INLINE FUNCTIONS:
+// ON MSVC: enable the 'Inline Function Expansion' (/Ob2) compiler option, and maybe the
+// 'Whole Program Optimitazion' (/GL), that requires the
+// 'Link Time Code Generation' (/LTCG) linker option to be enabled too
+
+#ifndef BINN_H
+#define BINN_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#ifndef NULL
+#ifdef __cplusplus
+#define NULL 0
+#else
+#define NULL ((void *)0)
+#endif
+#endif
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#ifndef BOOL
+typedef int BOOL;
+#endif
+
+#ifndef APIENTRY
+ #ifdef _WIN32
+ #define APIENTRY __stdcall
+ #else
+ //#define APIENTRY __attribute__((stdcall))
+ #define APIENTRY
+ #endif
+#endif
+
+#ifndef BINN_PRIVATE
+ #ifdef DEBUG
+ #define BINN_PRIVATE
+ #else
+ #define BINN_PRIVATE static
+ #endif
+#endif
+
+#ifdef _MSC_VER
+ #define INLINE __inline
+ #define ALWAYS_INLINE __forceinline
+#else
+ // you can change to 'extern inline' if using the gcc option -flto
+ #define INLINE static inline
+ #define ALWAYS_INLINE static inline __attribute__((always_inline))
+#endif
+
+#ifndef int64
+#if defined(_MSC_VER) || defined(__BORLANDC__)
+ typedef __int64 int64;
+ typedef unsigned __int64 uint64;
+#else
+ typedef long long int int64;
+ typedef unsigned long long int uint64;
+#endif
+#endif
+
+#ifdef _WIN32
+#define INT64_FORMAT "I64i"
+#define UINT64_FORMAT "I64u"
+#define INT64_HEX_FORMAT "I64x"
+#else
+#define INT64_FORMAT "lli"
+#define UINT64_FORMAT "llu"
+#define INT64_HEX_FORMAT "llx"
+#endif
+
+
+// BINN CONSTANTS ----------------------------------------
+
+#define INVALID_BINN 0
+
+// Storage Data Types ------------------------------------
+
+#define BINN_STORAGE_NOBYTES 0x00
+#define BINN_STORAGE_BYTE 0x20 // 8 bits
+#define BINN_STORAGE_WORD 0x40 // 16 bits -- the endianess (byte order) is automatically corrected
+#define BINN_STORAGE_DWORD 0x60 // 32 bits -- the endianess (byte order) is automatically corrected
+#define BINN_STORAGE_QWORD 0x80 // 64 bits -- the endianess (byte order) is automatically corrected
+#define BINN_STORAGE_STRING 0xA0 // Are stored with null termination
+#define BINN_STORAGE_BLOB 0xC0
+#define BINN_STORAGE_CONTAINER 0xE0
+#define BINN_STORAGE_VIRTUAL 0x80000
+//--
+#define BINN_STORAGE_MIN BINN_STORAGE_NOBYTES
+#define BINN_STORAGE_MAX BINN_STORAGE_CONTAINER
+
+#define BINN_STORAGE_MASK 0xE0
+#define BINN_STORAGE_MASK16 0xE000
+#define BINN_STORAGE_HAS_MORE 0x10
+#define BINN_TYPE_MASK 0x0F
+#define BINN_TYPE_MASK16 0x0FFF
+
+#define BINN_MAX_VALUE_MASK 0xFFFFF
+//++
+
+// Data Formats ------------------------------------------
+
+#define BINN_LIST 0xE0
+#define BINN_MAP 0xE1
+#define BINN_OBJECT 0xE2
+
+#define BINN_NULL 0x00
+#define BINN_TRUE 0x01
+#define BINN_FALSE 0x02
+
+#define BINN_UINT8 0x20 // (BYTE) (unsigned byte) Is the default format for the BYTE type
+#define BINN_INT8 0x21 // (BYTE) (signed byte, from -128 to +127. The 0x80 is the sign bit, so the range in hex is from 0x80 [-128] to 0x7F [127], being 0x00 = 0 and 0xFF = -1)
+#define BINN_UINT16 0x40 // (WORD) (unsigned integer) Is the default format for the WORD type
+#define BINN_INT16 0x41 // (WORD) (signed integer)
+#define BINN_UINT32 0x60 // (DWORD) (unsigned integer) Is the default format for the DWORD type
+#define BINN_INT32 0x61 // (DWORD) (signed integer)
+#define BINN_UINT64 0x80 // (QWORD) (unsigned integer) Is the default format for the QWORD type
+#define BINN_INT64 0x81 // (QWORD) (signed integer)
+
+#define BINN_SCHAR BINN_INT8
+#define BINN_UCHAR BINN_UINT8
+
+#define BINN_STRING 0xA0 // (STRING) Raw String
+#define BINN_DATETIME 0xA1 // (STRING) iso8601 format -- YYYY-MM-DD HH:MM:SS
+#define BINN_DATE 0xA2 // (STRING) iso8601 format -- YYYY-MM-DD
+#define BINN_TIME 0xA3 // (STRING) iso8601 format -- HH:MM:SS
+#define BINN_DECIMAL 0xA4 // (STRING) High precision number - used for generic decimal values and for those ones that cannot be represented in the float64 format.
+#define BINN_CURRENCYSTR 0xA5 // (STRING) With currency unit/symbol - check for some iso standard format
+#define BINN_SINGLE_STR 0xA6 // (STRING) Can be restored to float32
+#define BINN_DOUBLE_STR 0xA7 // (STRING) May be restored to float64
+
+#define BINN_FLOAT32 0x62 // (DWORD)
+#define BINN_FLOAT64 0x82 // (QWORD)
+#define BINN_FLOAT BINN_FLOAT32
+#define BINN_SINGLE BINN_FLOAT32
+#define BINN_DOUBLE BINN_FLOAT64
+
+#define BINN_CURRENCY 0x83 // (QWORD)
+
+#define BINN_BLOB 0xC0 // (BLOB) Raw Blob
+
+
+// virtual types:
+
+#define BINN_BOOL 0x80061 // (DWORD) The value may be 0 or 1
+
+#ifdef BINN_EXTENDED
+//#define BINN_SINGLE 0x800A1 // (STRING) Can be restored to float32
+//#define BINN_DOUBLE 0x800A2 // (STRING) May be restored to float64
+#endif
+
+//#define BINN_BINN 0x800E1 // (CONTAINER)
+//#define BINN_BINN_BUFFER 0x800C1 // (BLOB) user binn. it's not open by the parser
+
+
+// extended content types:
+
+// strings:
+
+#define BINN_HTML 0xB001
+#define BINN_XML 0xB002
+#define BINN_JSON 0xB003
+#define BINN_JAVASCRIPT 0xB004
+#define BINN_CSS 0xB005
+
+// blobs:
+
+#define BINN_JPEG 0xD001
+#define BINN_GIF 0xD002
+#define BINN_PNG 0xD003
+#define BINN_BMP 0xD004
+
+
+
+//--
+
+// type families
+#define BINN_FAMILY_NONE 0x00
+#define BINN_FAMILY_NULL 0xf1
+#define BINN_FAMILY_INT 0xf2
+#define BINN_FAMILY_FLOAT 0xf3
+#define BINN_FAMILY_STRING 0xf4
+#define BINN_FAMILY_BLOB 0xf5
+#define BINN_FAMILY_BOOL 0xf6
+#define BINN_FAMILY_BINN 0xf7
+
+// integer types related to signal
+#define BINN_SIGNED_INT 11
+#define BINN_UNSIGNED_INT 22
+
+//++
+
+
+typedef void (*binn_mem_free)(void*);
+#define BINN_STATIC ((binn_mem_free)0)
+#define BINN_TRANSIENT ((binn_mem_free)-1)
+
+
+// --- BINN STRUCTURE --------------------------------------------------------------
+
+
+struct binn_struct {
+ int header; // this struct header holds the magic number (BINN_MAGIC) that identifies this memory block as a binn structure
+ BOOL allocated; // the struct can be allocated using malloc_fn() or can be on the stack
+ BOOL writable; // did it was create for writing? it can use the pbuf if not unified with ptr
+ BOOL dirty; // the container header is not written to the buffer
+ //
+ void *pbuf; // use *ptr below?
+ BOOL pre_allocated;
+ int alloc_size;
+ int used_size;
+ //
+ int type;
+ void *ptr;
+ int size;
+ int count;
+ //
+ binn_mem_free freefn; // used only when type == BINN_STRING or BINN_BLOB
+ //
+ union {
+ signed char vint8;
+ signed short vint16;
+ signed int vint32;
+ int64 vint64;
+ unsigned char vuint8;
+ unsigned short vuint16;
+ unsigned int vuint32;
+ uint64 vuint64;
+ //
+ signed char vchar;
+ unsigned char vuchar;
+ signed short vshort;
+ unsigned short vushort;
+ signed int vint;
+ unsigned int vuint;
+ //
+ float vfloat;
+ double vdouble;
+ //
+ BOOL vbool;
+ };
+ //
+ BOOL disable_int_compression;
+};
+
+typedef struct binn_struct binn;
+
+
+
+// --- GENERAL FUNCTIONS ----------------------------------------------------------
+
+
+void APIENTRY binn_set_alloc_functions(void* (*new_malloc)(size_t), void* (*new_realloc)(void*,size_t), void (*new_free)(void*));
+
+int APIENTRY binn_create_type(int storage_type, int data_type_index);
+BOOL APIENTRY binn_get_type_info(int long_type, int *pstorage_type, int *pextra_type);
+
+int APIENTRY binn_get_write_storage(int type);
+int APIENTRY binn_get_read_storage(int type);
+
+BOOL APIENTRY binn_is_container(binn *item);
+
+
+// --- WRITE FUNCTIONS ------------------------------------------------------------
+
+// create a new binn allocating memory for the structure
+binn * APIENTRY binn_new(int type, int size, void *buffer);
+binn * APIENTRY binn_list();
+binn * APIENTRY binn_map();
+binn * APIENTRY binn_object();
+
+// create a new binn storing the structure on the stack
+BOOL APIENTRY binn_create(binn *item, int type, int size, void *buffer);
+BOOL APIENTRY binn_create_list(binn *list);
+BOOL APIENTRY binn_create_map(binn *map);
+BOOL APIENTRY binn_create_object(binn *object);
+
+
+BOOL APIENTRY binn_list_add_new(binn *list, binn *value);
+BOOL APIENTRY binn_map_set_new(binn *map, int id, binn *value);
+BOOL APIENTRY binn_object_set_new(binn *obj, char *key, binn *value);
+
+
+// extended interface
+
+BOOL APIENTRY binn_list_add(binn *list, int type, void *pvalue, int size);
+BOOL APIENTRY binn_map_set(binn *map, int id, int type, void *pvalue, int size);
+BOOL APIENTRY binn_object_set(binn *obj, char *key, int type, void *pvalue, int size);
+
+
+// release memory
+
+void APIENTRY binn_free(binn *item);
+void * APIENTRY binn_release(binn *item); // free the binn structure but keeps the binn buffer allocated, returning a pointer to it. use the free function to release the buffer later
+
+
+// --- CREATING VALUES ---------------------------------------------------
+
+binn * APIENTRY binn_value(int type, void *pvalue, int size, binn_mem_free freefn);
+
+ALWAYS_INLINE binn * binn_int8(signed char value) {
+ return binn_value(BINN_INT8, &value, 0, NULL);
+}
+ALWAYS_INLINE binn * binn_int16(short value) {
+ return binn_value(BINN_INT16, &value, 0, NULL);
+}
+ALWAYS_INLINE binn * binn_int32(int value) {
+ return binn_value(BINN_INT32, &value, 0, NULL);
+}
+ALWAYS_INLINE binn * binn_int64(int64 value) {
+ return binn_value(BINN_INT64, &value, 0, NULL);
+}
+ALWAYS_INLINE binn * binn_uint8(unsigned char value) {
+ return binn_value(BINN_UINT8, &value, 0, NULL);
+}
+ALWAYS_INLINE binn * binn_uint16(unsigned short value) {
+ return binn_value(BINN_UINT16, &value, 0, NULL);
+}
+ALWAYS_INLINE binn * binn_uint32(unsigned int value) {
+ return binn_value(BINN_UINT32, &value, 0, NULL);
+}
+ALWAYS_INLINE binn * binn_uint64(uint64 value) {
+ return binn_value(BINN_UINT64, &value, 0, NULL);
+}
+ALWAYS_INLINE binn * binn_float(float value) {
+ return binn_value(BINN_FLOAT, &value, 0, NULL);
+}
+ALWAYS_INLINE binn * binn_double(double value) {
+ return binn_value(BINN_DOUBLE, &value, 0, NULL);
+}
+ALWAYS_INLINE binn * binn_bool(BOOL value) {
+ return binn_value(BINN_BOOL, &value, 0, NULL);
+}
+ALWAYS_INLINE binn * binn_null() {
+ return binn_value(BINN_NULL, NULL, 0, NULL);
+}
+ALWAYS_INLINE binn * binn_string(char *str, binn_mem_free freefn) {
+ return binn_value(BINN_STRING, str, 0, freefn);
+}
+ALWAYS_INLINE binn * binn_blob(void *ptr, int size, binn_mem_free freefn) {
+ return binn_value(BINN_BLOB, ptr, size, freefn);
+}
+
+
+// --- READ FUNCTIONS -------------------------------------------------------------
+
+// these functions accept pointer to the binn structure and pointer to the binn buffer
+void * APIENTRY binn_ptr(void *ptr);
+int APIENTRY binn_size(void *ptr);
+int APIENTRY binn_type(void *ptr);
+int APIENTRY binn_count(void *ptr);
+
+BOOL APIENTRY binn_is_valid(void *ptr, int *ptype, int *pcount, int *psize);
+/* the function returns the values (type, count and size) and they don't need to be
+ initialized. these values are read from the buffer. example:
+
+ int type, count, size;
+ result = binn_is_valid(ptr, &type, &count, &size);
+*/
+BOOL APIENTRY binn_is_valid_ex(void *ptr, int *ptype, int *pcount, int *psize);
+/* if some value is informed (type, count or size) then the function will check if
+ the value returned from the serialized data matches the informed value. otherwise
+ the values must be initialized to zero. example:
+
+ int type=0, count=0, size = known_size;
+ result = binn_is_valid_ex(ptr, &type, &count, &size);
+*/
+
+BOOL APIENTRY binn_is_struct(void *ptr);
+
+
+// Loading a binn buffer into a binn value - this is optional
+
+BOOL APIENTRY binn_load(void *data, binn *item); // on stack
+binn * APIENTRY binn_open(void *data); // allocated
+
+
+// easiest interface to use, but don't check if the value is there
+
+signed char APIENTRY binn_list_int8(void *list, int pos);
+short APIENTRY binn_list_int16(void *list, int pos);
+int APIENTRY binn_list_int32(void *list, int pos);
+int64 APIENTRY binn_list_int64(void *list, int pos);
+unsigned char APIENTRY binn_list_uint8(void *list, int pos);
+unsigned short APIENTRY binn_list_uint16(void *list, int pos);
+unsigned int APIENTRY binn_list_uint32(void *list, int pos);
+uint64 APIENTRY binn_list_uint64(void *list, int pos);
+float APIENTRY binn_list_float(void *list, int pos);
+double APIENTRY binn_list_double(void *list, int pos);
+BOOL APIENTRY binn_list_bool(void *list, int pos);
+BOOL APIENTRY binn_list_null(void *list, int pos);
+char * APIENTRY binn_list_str(void *list, int pos);
+void * APIENTRY binn_list_blob(void *list, int pos, int *psize);
+void * APIENTRY binn_list_list(void *list, int pos);
+void * APIENTRY binn_list_map(void *list, int pos);
+void * APIENTRY binn_list_object(void *list, int pos);
+
+signed char APIENTRY binn_map_int8(void *map, int id);
+short APIENTRY binn_map_int16(void *map, int id);
+int APIENTRY binn_map_int32(void *map, int id);
+int64 APIENTRY binn_map_int64(void *map, int id);
+unsigned char APIENTRY binn_map_uint8(void *map, int id);
+unsigned short APIENTRY binn_map_uint16(void *map, int id);
+unsigned int APIENTRY binn_map_uint32(void *map, int id);
+uint64 APIENTRY binn_map_uint64(void *map, int id);
+float APIENTRY binn_map_float(void *map, int id);
+double APIENTRY binn_map_double(void *map, int id);
+BOOL APIENTRY binn_map_bool(void *map, int id);
+BOOL APIENTRY binn_map_null(void *map, int id);
+char * APIENTRY binn_map_str(void *map, int id);
+void * APIENTRY binn_map_blob(void *map, int id, int *psize);
+void * APIENTRY binn_map_list(void *map, int id);
+void * APIENTRY binn_map_map(void *map, int id);
+void * APIENTRY binn_map_object(void *map, int id);
+
+signed char APIENTRY binn_object_int8(void *obj, char *key);
+short APIENTRY binn_object_int16(void *obj, char *key);
+int APIENTRY binn_object_int32(void *obj, char *key);
+int64 APIENTRY binn_object_int64(void *obj, char *key);
+unsigned char APIENTRY binn_object_uint8(void *obj, char *key);
+unsigned short APIENTRY binn_object_uint16(void *obj, char *key);
+unsigned int APIENTRY binn_object_uint32(void *obj, char *key);
+uint64 APIENTRY binn_object_uint64(void *obj, char *key);
+float APIENTRY binn_object_float(void *obj, char *key);
+double APIENTRY binn_object_double(void *obj, char *key);
+BOOL APIENTRY binn_object_bool(void *obj, char *key);
+BOOL APIENTRY binn_object_null(void *obj, char *key);
+char * APIENTRY binn_object_str(void *obj, char *key);
+void * APIENTRY binn_object_blob(void *obj, char *key, int *psize);
+void * APIENTRY binn_object_list(void *obj, char *key);
+void * APIENTRY binn_object_map(void *obj, char *key);
+void * APIENTRY binn_object_object(void *obj, char *key);
+
+
+// return a pointer to an allocated binn structure - must be released with the free() function or equivalent set in binn_set_alloc_functions()
+binn * APIENTRY binn_list_value(void *list, int pos);
+binn * APIENTRY binn_map_value(void *map, int id);
+binn * APIENTRY binn_object_value(void *obj, char *key);
+
+// read the value to a binn structure on the stack
+BOOL APIENTRY binn_list_get_value(void* list, int pos, binn *value);
+BOOL APIENTRY binn_map_get_value(void* map, int id, binn *value);
+BOOL APIENTRY binn_object_get_value(void *obj, char *key, binn *value);
+
+// single interface - these functions check the data type
+BOOL APIENTRY binn_list_get(void *list, int pos, int type, void *pvalue, int *psize);
+BOOL APIENTRY binn_map_get(void *map, int id, int type, void *pvalue, int *psize);
+BOOL APIENTRY binn_object_get(void *obj, char *key, int type, void *pvalue, int *psize);
+
+// these 3 functions return a pointer to the value and the data type
+// they are thread-safe on big-endian devices
+// on little-endian devices they are thread-safe only to return pointers to list, map, object, blob and strings
+// the returned pointer to 16, 32 and 64 bits values must be used only by single-threaded applications
+void * APIENTRY binn_list_read(void *list, int pos, int *ptype, int *psize);
+void * APIENTRY binn_map_read(void *map, int id, int *ptype, int *psize);
+void * APIENTRY binn_object_read(void *obj, char *key, int *ptype, int *psize);
+
+
+// READ PAIR FUNCTIONS
+
+// these functions use base 1 in the 'pos' argument
+
+// on stack
+BOOL APIENTRY binn_map_get_pair(void *map, int pos, int *pid, binn *value);
+BOOL APIENTRY binn_object_get_pair(void *obj, int pos, char *pkey, binn *value); // must free the memory returned in the pkey
+
+// allocated
+binn * APIENTRY binn_map_pair(void *map, int pos, int *pid);
+binn * APIENTRY binn_object_pair(void *obj, int pos, char *pkey); // must free the memory returned in the pkey
+
+// these 2 functions return a pointer to the value and the data type
+// they are thread-safe on big-endian devices
+// on little-endian devices they are thread-safe only to return pointers to list, map, object, blob and strings
+// the returned pointer to 16, 32 and 64 bits values must be used only by single-threaded applications
+void * APIENTRY binn_map_read_pair(void *ptr, int pos, int *pid, int *ptype, int *psize);
+void * APIENTRY binn_object_read_pair(void *ptr, int pos, char *pkey, int *ptype, int *psize);
+
+
+// SEQUENTIAL READ FUNCTIONS
+
+typedef struct binn_iter_struct {
+ unsigned char *pnext;
+ unsigned char *plimit;
+ int type;
+ int count;
+ int current;
+} binn_iter;
+
+BOOL APIENTRY binn_iter_init(binn_iter *iter, void *pbuf, int type);
+
+// allocated
+binn * APIENTRY binn_list_next_value(binn_iter *iter);
+binn * APIENTRY binn_map_next_value(binn_iter *iter, int *pid);
+binn * APIENTRY binn_object_next_value(binn_iter *iter, char *pkey); // the key must be declared as: char key[256];
+
+// on stack
+BOOL APIENTRY binn_list_next(binn_iter *iter, binn *value);
+BOOL APIENTRY binn_map_next(binn_iter *iter, int *pid, binn *value);
+BOOL APIENTRY binn_object_next(binn_iter *iter, char *pkey, binn *value); // the key must be declared as: char key[256];
+
+// these 3 functions return a pointer to the value and the data type
+// they are thread-safe on big-endian devices
+// on little-endian devices they are thread-safe only to return pointers to list, map, object, blob and strings
+// the returned pointer to 16, 32 and 64 bits values must be used only by single-threaded applications
+void * APIENTRY binn_list_read_next(binn_iter *iter, int *ptype, int *psize);
+void * APIENTRY binn_map_read_next(binn_iter *iter, int *pid, int *ptype, int *psize);
+void * APIENTRY binn_object_read_next(binn_iter *iter, char *pkey, int *ptype, int *psize); // the key must be declared as: char key[256];
+
+
+// --- MACROS ------------------------------------------------------------
+
+
+#define binn_is_writable(item) (item)->writable;
+
+
+// set values on stack allocated binn structures
+
+#define binn_set_null(item) do { (item)->type = BINN_NULL; } while (0)
+
+#define binn_set_bool(item,value) do { (item)->type = BINN_BOOL; (item)->vbool = value; (item)->ptr = &((item)->vbool); } while (0)
+
+#define binn_set_int(item,value) do { (item)->type = BINN_INT32; (item)->vint32 = value; (item)->ptr = &((item)->vint32); } while (0)
+#define binn_set_int64(item,value) do { (item)->type = BINN_INT64; (item)->vint64 = value; (item)->ptr = &((item)->vint64); } while (0)
+
+#define binn_set_uint(item,value) do { (item)->type = BINN_UINT32; (item)->vuint32 = value; (item)->ptr = &((item)->vuint32); } while (0)
+#define binn_set_uint64(item,value) do { (item)->type = BINN_UINT64; (item)->vuint64 = value; (item)->ptr = &((item)->vuint64); } while (0)
+
+#define binn_set_float(item,value) do { (item)->type = BINN_FLOAT; (item)->vfloat = value; (item)->ptr = &((item)->vfloat); } while (0)
+#define binn_set_double(item,value) do { (item)->type = BINN_DOUBLE; (item)->vdouble = value; (item)->ptr = &((item)->vdouble); } while (0)
+
+//#define binn_set_string(item,str,pfree) do { (item)->type = BINN_STRING; (item)->ptr = str; (item)->freefn = pfree; } while (0)
+//#define binn_set_blob(item,ptr,size,pfree) do { (item)->type = BINN_BLOB; (item)->ptr = ptr; (item)->freefn = pfree; (item)->size = size; } while (0)
+BOOL APIENTRY binn_set_string(binn *item, char *str, binn_mem_free pfree);
+BOOL APIENTRY binn_set_blob(binn *item, void *ptr, int size, binn_mem_free pfree);
+
+
+//#define binn_double(value) { (item)->type = BINN_DOUBLE; (item)->vdouble = value; (item)->ptr = &((item)->vdouble) }
+
+
+
+// FOREACH MACROS
+// must use these declarations in the function that will use them:
+// binn_iter iter;
+// char key[256]; // only for the object
+// int id; // only for the map
+// binn value;
+
+#define binn_object_foreach(object, key, value) \
+ binn_iter_init(&iter, object, BINN_OBJECT); \
+ while (binn_object_next(&iter, key, &value))
+
+#define binn_map_foreach(map, id, value) \
+ binn_iter_init(&iter, map, BINN_MAP); \
+ while (binn_map_next(&iter, &id, &value))
+
+#define binn_list_foreach(list, value) \
+ binn_iter_init(&iter, list, BINN_LIST); \
+ while (binn_list_next(&iter, &value))
+
+
+
+/*************************************************************************************/
+/*** SET FUNCTIONS *******************************************************************/
+/*************************************************************************************/
+
+ALWAYS_INLINE BOOL binn_list_add_int8(binn *list, signed char value) {
+ return binn_list_add(list, BINN_INT8, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_list_add_int16(binn *list, short value) {
+ return binn_list_add(list, BINN_INT16, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_list_add_int32(binn *list, int value) {
+ return binn_list_add(list, BINN_INT32, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_list_add_int64(binn *list, int64 value) {
+ return binn_list_add(list, BINN_INT64, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_list_add_uint8(binn *list, unsigned char value) {
+ return binn_list_add(list, BINN_UINT8, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_list_add_uint16(binn *list, unsigned short value) {
+ return binn_list_add(list, BINN_UINT16, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_list_add_uint32(binn *list, unsigned int value) {
+ return binn_list_add(list, BINN_UINT32, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_list_add_uint64(binn *list, uint64 value) {
+ return binn_list_add(list, BINN_UINT64, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_list_add_float(binn *list, float value) {
+ return binn_list_add(list, BINN_FLOAT32, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_list_add_double(binn *list, double value) {
+ return binn_list_add(list, BINN_FLOAT64, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_list_add_bool(binn *list, BOOL value) {
+ return binn_list_add(list, BINN_BOOL, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_list_add_null(binn *list) {
+ return binn_list_add(list, BINN_NULL, NULL, 0);
+}
+ALWAYS_INLINE BOOL binn_list_add_str(binn *list, char *str) {
+ return binn_list_add(list, BINN_STRING, str, 0);
+}
+ALWAYS_INLINE BOOL binn_list_add_blob(binn *list, void *ptr, int size) {
+ return binn_list_add(list, BINN_BLOB, ptr, size);
+}
+ALWAYS_INLINE BOOL binn_list_add_list(binn *list, void *list2) {
+ return binn_list_add(list, BINN_LIST, binn_ptr(list2), binn_size(list2));
+}
+ALWAYS_INLINE BOOL binn_list_add_map(binn *list, void *map) {
+ return binn_list_add(list, BINN_MAP, binn_ptr(map), binn_size(map));
+}
+ALWAYS_INLINE BOOL binn_list_add_object(binn *list, void *obj) {
+ return binn_list_add(list, BINN_OBJECT, binn_ptr(obj), binn_size(obj));
+}
+ALWAYS_INLINE BOOL binn_list_add_value(binn *list, binn *value) {
+ return binn_list_add(list, value->type, binn_ptr(value), binn_size(value));
+}
+
+/*************************************************************************************/
+
+ALWAYS_INLINE BOOL binn_map_set_int8(binn *map, int id, signed char value) {
+ return binn_map_set(map, id, BINN_INT8, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_map_set_int16(binn *map, int id, short value) {
+ return binn_map_set(map, id, BINN_INT16, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_map_set_int32(binn *map, int id, int value) {
+ return binn_map_set(map, id, BINN_INT32, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_map_set_int64(binn *map, int id, int64 value) {
+ return binn_map_set(map, id, BINN_INT64, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_map_set_uint8(binn *map, int id, unsigned char value) {
+ return binn_map_set(map, id, BINN_UINT8, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_map_set_uint16(binn *map, int id, unsigned short value) {
+ return binn_map_set(map, id, BINN_UINT16, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_map_set_uint32(binn *map, int id, unsigned int value) {
+ return binn_map_set(map, id, BINN_UINT32, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_map_set_uint64(binn *map, int id, uint64 value) {
+ return binn_map_set(map, id, BINN_UINT64, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_map_set_float(binn *map, int id, float value) {
+ return binn_map_set(map, id, BINN_FLOAT32, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_map_set_double(binn *map, int id, double value) {
+ return binn_map_set(map, id, BINN_FLOAT64, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_map_set_bool(binn *map, int id, BOOL value) {
+ return binn_map_set(map, id, BINN_BOOL, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_map_set_null(binn *map, int id) {
+ return binn_map_set(map, id, BINN_NULL, NULL, 0);
+}
+ALWAYS_INLINE BOOL binn_map_set_str(binn *map, int id, char *str) {
+ return binn_map_set(map, id, BINN_STRING, str, 0);
+}
+ALWAYS_INLINE BOOL binn_map_set_blob(binn *map, int id, void *ptr, int size) {
+ return binn_map_set(map, id, BINN_BLOB, ptr, size);
+}
+ALWAYS_INLINE BOOL binn_map_set_list(binn *map, int id, void *list) {
+ return binn_map_set(map, id, BINN_LIST, binn_ptr(list), binn_size(list));
+}
+ALWAYS_INLINE BOOL binn_map_set_map(binn *map, int id, void *map2) {
+ return binn_map_set(map, id, BINN_MAP, binn_ptr(map2), binn_size(map2));
+}
+ALWAYS_INLINE BOOL binn_map_set_object(binn *map, int id, void *obj) {
+ return binn_map_set(map, id, BINN_OBJECT, binn_ptr(obj), binn_size(obj));
+}
+ALWAYS_INLINE BOOL binn_map_set_value(binn *map, int id, binn *value) {
+ return binn_map_set(map, id, value->type, binn_ptr(value), binn_size(value));
+}
+
+/*************************************************************************************/
+
+ALWAYS_INLINE BOOL binn_object_set_int8(binn *obj, char *key, signed char value) {
+ return binn_object_set(obj, key, BINN_INT8, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_object_set_int16(binn *obj, char *key, short value) {
+ return binn_object_set(obj, key, BINN_INT16, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_object_set_int32(binn *obj, char *key, int value) {
+ return binn_object_set(obj, key, BINN_INT32, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_object_set_int64(binn *obj, char *key, int64 value) {
+ return binn_object_set(obj, key, BINN_INT64, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_object_set_uint8(binn *obj, char *key, unsigned char value) {
+ return binn_object_set(obj, key, BINN_UINT8, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_object_set_uint16(binn *obj, char *key, unsigned short value) {
+ return binn_object_set(obj, key, BINN_UINT16, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_object_set_uint32(binn *obj, char *key, unsigned int value) {
+ return binn_object_set(obj, key, BINN_UINT32, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_object_set_uint64(binn *obj, char *key, uint64 value) {
+ return binn_object_set(obj, key, BINN_UINT64, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_object_set_float(binn *obj, char *key, float value) {
+ return binn_object_set(obj, key, BINN_FLOAT32, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_object_set_double(binn *obj, char *key, double value) {
+ return binn_object_set(obj, key, BINN_FLOAT64, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_object_set_bool(binn *obj, char *key, BOOL value) {
+ return binn_object_set(obj, key, BINN_BOOL, &value, 0);
+}
+ALWAYS_INLINE BOOL binn_object_set_null(binn *obj, char *key) {
+ return binn_object_set(obj, key, BINN_NULL, NULL, 0);
+}
+ALWAYS_INLINE BOOL binn_object_set_str(binn *obj, char *key, char *str) {
+ return binn_object_set(obj, key, BINN_STRING, str, 0);
+}
+ALWAYS_INLINE BOOL binn_object_set_blob(binn *obj, char *key, void *ptr, int size) {
+ return binn_object_set(obj, key, BINN_BLOB, ptr, size);
+}
+ALWAYS_INLINE BOOL binn_object_set_list(binn *obj, char *key, void *list) {
+ return binn_object_set(obj, key, BINN_LIST, binn_ptr(list), binn_size(list));
+}
+ALWAYS_INLINE BOOL binn_object_set_map(binn *obj, char *key, void *map) {
+ return binn_object_set(obj, key, BINN_MAP, binn_ptr(map), binn_size(map));
+}
+ALWAYS_INLINE BOOL binn_object_set_object(binn *obj, char *key, void *obj2) {
+ return binn_object_set(obj, key, BINN_OBJECT, binn_ptr(obj2), binn_size(obj2));
+}
+ALWAYS_INLINE BOOL binn_object_set_value(binn *obj, char *key, binn *value) {
+ return binn_object_set(obj, key, value->type, binn_ptr(value), binn_size(value));
+}
+
+/*************************************************************************************/
+/*** GET FUNCTIONS *******************************************************************/
+/*************************************************************************************/
+
+ALWAYS_INLINE BOOL binn_list_get_int8(void *list, int pos, signed char *pvalue) {
+ return binn_list_get(list, pos, BINN_INT8, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_list_get_int16(void *list, int pos, short *pvalue) {
+ return binn_list_get(list, pos, BINN_INT16, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_list_get_int32(void *list, int pos, int *pvalue) {
+ return binn_list_get(list, pos, BINN_INT32, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_list_get_int64(void *list, int pos, int64 *pvalue) {
+ return binn_list_get(list, pos, BINN_INT64, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_list_get_uint8(void *list, int pos, unsigned char *pvalue) {
+ return binn_list_get(list, pos, BINN_UINT8, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_list_get_uint16(void *list, int pos, unsigned short *pvalue) {
+ return binn_list_get(list, pos, BINN_UINT16, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_list_get_uint32(void *list, int pos, unsigned int *pvalue) {
+ return binn_list_get(list, pos, BINN_UINT32, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_list_get_uint64(void *list, int pos, uint64 *pvalue) {
+ return binn_list_get(list, pos, BINN_UINT64, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_list_get_float(void *list, int pos, float *pvalue) {
+ return binn_list_get(list, pos, BINN_FLOAT32, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_list_get_double(void *list, int pos, double *pvalue) {
+ return binn_list_get(list, pos, BINN_FLOAT64, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_list_get_bool(void *list, int pos, BOOL *pvalue) {
+ return binn_list_get(list, pos, BINN_BOOL, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_list_get_str(void *list, int pos, char **pvalue) {
+ return binn_list_get(list, pos, BINN_STRING, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_list_get_blob(void *list, int pos, void **pvalue, int *psize) {
+ return binn_list_get(list, pos, BINN_BLOB, pvalue, psize);
+}
+ALWAYS_INLINE BOOL binn_list_get_list(void *list, int pos, void **pvalue) {
+ return binn_list_get(list, pos, BINN_LIST, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_list_get_map(void *list, int pos, void **pvalue) {
+ return binn_list_get(list, pos, BINN_MAP, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_list_get_object(void *list, int pos, void **pvalue) {
+ return binn_list_get(list, pos, BINN_OBJECT, pvalue, NULL);
+}
+
+/***************************************************************************/
+
+ALWAYS_INLINE BOOL binn_map_get_int8(void *map, int id, signed char *pvalue) {
+ return binn_map_get(map, id, BINN_INT8, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_map_get_int16(void *map, int id, short *pvalue) {
+ return binn_map_get(map, id, BINN_INT16, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_map_get_int32(void *map, int id, int *pvalue) {
+ return binn_map_get(map, id, BINN_INT32, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_map_get_int64(void *map, int id, int64 *pvalue) {
+ return binn_map_get(map, id, BINN_INT64, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_map_get_uint8(void *map, int id, unsigned char *pvalue) {
+ return binn_map_get(map, id, BINN_UINT8, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_map_get_uint16(void *map, int id, unsigned short *pvalue) {
+ return binn_map_get(map, id, BINN_UINT16, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_map_get_uint32(void *map, int id, unsigned int *pvalue) {
+ return binn_map_get(map, id, BINN_UINT32, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_map_get_uint64(void *map, int id, uint64 *pvalue) {
+ return binn_map_get(map, id, BINN_UINT64, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_map_get_float(void *map, int id, float *pvalue) {
+ return binn_map_get(map, id, BINN_FLOAT32, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_map_get_double(void *map, int id, double *pvalue) {
+ return binn_map_get(map, id, BINN_FLOAT64, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_map_get_bool(void *map, int id, BOOL *pvalue) {
+ return binn_map_get(map, id, BINN_BOOL, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_map_get_str(void *map, int id, char **pvalue) {
+ return binn_map_get(map, id, BINN_STRING, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_map_get_blob(void *map, int id, void **pvalue, int *psize) {
+ return binn_map_get(map, id, BINN_BLOB, pvalue, psize);
+}
+ALWAYS_INLINE BOOL binn_map_get_list(void *map, int id, void **pvalue) {
+ return binn_map_get(map, id, BINN_LIST, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_map_get_map(void *map, int id, void **pvalue) {
+ return binn_map_get(map, id, BINN_MAP, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_map_get_object(void *map, int id, void **pvalue) {
+ return binn_map_get(map, id, BINN_OBJECT, pvalue, NULL);
+}
+
+/***************************************************************************/
+
+// usage:
+// if (binn_object_get_int32(obj, "key", &value) == FALSE) xxx;
+
+ALWAYS_INLINE BOOL binn_object_get_int8(void *obj, char *key, signed char *pvalue) {
+ return binn_object_get(obj, key, BINN_INT8, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_object_get_int16(void *obj, char *key, short *pvalue) {
+ return binn_object_get(obj, key, BINN_INT16, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_object_get_int32(void *obj, char *key, int *pvalue) {
+ return binn_object_get(obj, key, BINN_INT32, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_object_get_int64(void *obj, char *key, int64 *pvalue) {
+ return binn_object_get(obj, key, BINN_INT64, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_object_get_uint8(void *obj, char *key, unsigned char *pvalue) {
+ return binn_object_get(obj, key, BINN_UINT8, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_object_get_uint16(void *obj, char *key, unsigned short *pvalue) {
+ return binn_object_get(obj, key, BINN_UINT16, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_object_get_uint32(void *obj, char *key, unsigned int *pvalue) {
+ return binn_object_get(obj, key, BINN_UINT32, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_object_get_uint64(void *obj, char *key, uint64 *pvalue) {
+ return binn_object_get(obj, key, BINN_UINT64, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_object_get_float(void *obj, char *key, float *pvalue) {
+ return binn_object_get(obj, key, BINN_FLOAT32, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_object_get_double(void *obj, char *key, double *pvalue) {
+ return binn_object_get(obj, key, BINN_FLOAT64, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_object_get_bool(void *obj, char *key, BOOL *pvalue) {
+ return binn_object_get(obj, key, BINN_BOOL, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_object_get_str(void *obj, char *key, char **pvalue) {
+ return binn_object_get(obj, key, BINN_STRING, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_object_get_blob(void *obj, char *key, void **pvalue, int *psize) {
+ return binn_object_get(obj, key, BINN_BLOB, pvalue, psize);
+}
+ALWAYS_INLINE BOOL binn_object_get_list(void *obj, char *key, void **pvalue) {
+ return binn_object_get(obj, key, BINN_LIST, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_object_get_map(void *obj, char *key, void **pvalue) {
+ return binn_object_get(obj, key, BINN_MAP, pvalue, NULL);
+}
+ALWAYS_INLINE BOOL binn_object_get_object(void *obj, char *key, void **pvalue) {
+ return binn_object_get(obj, key, BINN_OBJECT, pvalue, NULL);
+}
+
+/***************************************************************************/
+
+BOOL APIENTRY binn_get_int32(binn *value, int *pint);
+BOOL APIENTRY binn_get_int64(binn *value, int64 *pint);
+BOOL APIENTRY binn_get_double(binn *value, double *pfloat);
+BOOL APIENTRY binn_get_bool(binn *value, BOOL *pbool);
+char * APIENTRY binn_get_str(binn *value);
+
+// boolean string values:
+// 1, true, yes, on
+// 0, false, no, off
+
+// boolean number values:
+// !=0 [true]
+// ==0 [false]
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //BINN_H
diff --git a/include/lib/capnp-c/capn-list.inc b/include/lib/capnp-c/capn-list.inc
new file mode 100644
index 0000000..ad94ec8
--- /dev/null
+++ b/include/lib/capnp-c/capn-list.inc
@@ -0,0 +1,162 @@
+/* capn-list.inc
+ *
+ * Copyright (C) 2013 James McKaskill
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+#define CAT2(A,B) A ## B
+#define CAT(A,B) CAT2(A, B)
+#define UINT_T CAT(CAT(uint, SZ), _t)
+#define LIST_T CAT(capn_list, SZ)
+#define FLIP CAT(capn_flip, SZ)
+
+UINT_T CAT(capn_get,SZ) (LIST_T l, int off) {
+ char *d;
+ capn_ptr p = l.p;
+ if (off >= p.len) {
+ return 0;
+ }
+
+ switch (p.type) {
+ case CAPN_LIST:
+ if (p.datasz < SZ/8)
+ return 0;
+ d = p.data + off * (p.datasz + 8*p.ptrs);
+ return FLIP(*(UINT_T*)d);
+
+ case CAPN_PTR_LIST:
+ d = struct_ptr(p.seg, p.data + 8*off, SZ/8);
+ if (d) {
+ return FLIP(*(UINT_T*)d);
+ } else {
+ return 0;
+ }
+
+ default:
+ return 0;
+ }
+}
+
+int CAT(capn_getv,SZ) (LIST_T l, int off, UINT_T *to, int sz) {
+ int i;
+ capn_ptr p;
+ capn_resolve(&l.p);
+ p = l.p;
+ if (off + sz > p.len) {
+ sz = p.len - off;
+ }
+
+ switch (p.type) {
+ case CAPN_LIST:
+ if (p.datasz == SZ/8 && !p.ptrs && (SZ == 8 || CAPN_LITTLE)) {
+ memcpy(to, p.data + off, sz * (SZ/8));
+ return sz;
+ } else if (p.datasz < SZ/8) {
+ return -1;
+ }
+
+ for (i = 0; i < sz; i++) {
+ char *d = p.data + (i + off) * (p.datasz + 8*p.ptrs);
+ to[i] = FLIP(*(UINT_T*)d);
+ }
+ return sz;
+
+ case CAPN_PTR_LIST:
+ for (i = 0; i < sz; i++) {
+ char *d = struct_ptr(p.seg, p.data + 8*(i+off), SZ/8);
+ if (d) {
+ to[i] = FLIP(*(UINT_T*)d);
+ } else {
+ return -1;
+ }
+ }
+ return sz;
+
+ default:
+ return -1;
+ }
+}
+
+int CAT(capn_set,SZ) (LIST_T l, int off, UINT_T v) {
+ char *d;
+ capn_ptr p = l.p;
+ if (off >= p.len) {
+ return -1;
+ }
+
+ switch (p.type) {
+ case CAPN_LIST:
+ if (p.datasz < SZ/8)
+ return -1;
+ d = p.data + off * (p.datasz + 8*p.ptrs);
+ *(UINT_T*) d = FLIP(v);
+ return 0;
+
+ case CAPN_PTR_LIST:
+ d = struct_ptr(p.seg, p.data + 8*off, SZ/8);
+ if (!d) {
+ return -1;
+ }
+ *(UINT_T*) d = FLIP(v);
+ return 0;
+
+ default:
+ return -1;
+ }
+}
+
+int CAT(capn_setv,SZ) (LIST_T l, int off, const UINT_T *from, int sz) {
+ int i;
+ capn_ptr p = l.p;
+ if (off + sz > p.len) {
+ sz = p.len - off;
+ }
+
+ switch (p.type) {
+ case CAPN_LIST:
+ if (p.datasz == SZ/8 && !p.ptrs && (SZ == 8 || CAPN_LITTLE)) {
+ memcpy(p.data + off, from, sz * (SZ/8));
+ return sz;
+ } else if (p.datasz < SZ/8) {
+ return -1;
+ }
+
+ for (i = 0; i < sz; i++) {
+ char *d = p.data + (i + off) * (p.datasz + 8*p.ptrs);
+ *(UINT_T*) d = FLIP(from[i]);
+ }
+ return sz;
+
+ case CAPN_PTR_LIST:
+ for (i = 0; i < sz; i++) {
+ char *d = struct_ptr(p.seg, p.data + 8*(i+off), SZ/8);
+ if (d) {
+ *(UINT_T*) d = FLIP(from[i]);
+ } else {
+ return -1;
+ }
+ }
+ return sz;
+
+ default:
+ return -1;
+ }
+}
+
+LIST_T CAT(capn_new_list,SZ) (struct capn_segment *seg, int sz) {
+ LIST_T l = {{CAPN_LIST}};
+ l.p.seg = seg;
+ l.p.len = sz;
+ l.p.datasz = SZ/8;
+ new_object(&l.p, sz*(SZ/8));
+ return l;
+}
+
+#undef CAT2
+#undef CAT
+#undef UINT_T
+#undef LIST_T
+#undef FLIP
+
diff --git a/include/lib/capnp-c/capnp_c.h b/include/lib/capnp-c/capnp_c.h
new file mode 100644
index 0000000..28f171d
--- /dev/null
+++ b/include/lib/capnp-c/capnp_c.h
@@ -0,0 +1,446 @@
+/* vim: set sw=8 ts=8 sts=8 noet: */
+/* capnp_c.h
+ *
+ * Copyright (C) 2013 James McKaskill
+ * Copyright (C) 2014 Steve Dee
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+#ifndef CAPNP_C_H
+#define CAPNP_C_H
+
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#if defined(unix) && !defined(__APPLE__)
+#include <endian.h>
+#endif
+
+/* ssize_t is a POSIX type, not an ISO C one...
+ * Windows seems to only have SSIZE_T in BaseTsd.h
+ */
+#ifdef _MSC_VER
+typedef intmax_t ssize_t;
+#else
+#include <stddef.h>
+#endif
+
+// AVR does not have ssize_T either
+#ifdef MULTIPASS_ARCH_arduino_nano
+typedef intmax_t ssize_t;
+#endif
+
+// Cross-platform macro ALIGNED_(x) aligns a struct by `x` bytes.
+#ifdef _MSC_VER
+#define ALIGNED_(x) __declspec(align(x))
+#endif
+#ifdef __GNUC__
+#define ALIGNED_(x) __attribute__ ((aligned(x)))
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(__cplusplus) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L)
+#define CAPN_INLINE static inline
+#else
+#define CAPN_INLINE static
+#endif
+
+#define CAPN_VERSION 1
+
+/* struct capn is a common structure shared between segments in the same
+ * session/context so that far pointers between segments will be created.
+ *
+ * lookup is used to lookup segments by id when derefencing a far pointer
+ *
+ * create is used to create or lookup an alternate segment that has at least
+ * sz available (ie returned seg->len + sz <= seg->cap)
+ *
+ * create_local is used to create a segment for the copy tree and should be
+ * allocated in the local memory space.
+ *
+ * Allocated segments must be zero initialized.
+ *
+ * create and lookup can be NULL if you don't need multiple segments and don't
+ * want to support copying
+ *
+ * seglist and copylist are linked lists which can be used to free up segments
+ * on cleanup, but should not be modified by the user.
+ *
+ * lookup, create, create_local, and user can be set by the user. Other values
+ * should be zero initialized.
+ */
+struct capn {
+ /* user settable */
+ struct capn_segment *(*lookup)(void* /*user*/, uint32_t /*id */);
+ struct capn_segment *(*create)(void* /*user*/, uint32_t /*id */, int /*sz*/);
+ struct capn_segment *(*create_local)(void* /*user*/, int /*sz*/);
+ void *user;
+ /* zero initialized, user should not modify */
+ uint32_t segnum;
+ struct capn_tree *copy;
+ struct capn_tree *segtree;
+ struct capn_segment *seglist, *lastseg;
+ struct capn_segment *copylist;
+};
+
+/* struct capn_tree is a rb tree header used internally for the segment id
+ * lookup and copy tree */
+struct capn_tree {
+ struct capn_tree *parent, *link[2];
+ unsigned int red : 1;
+};
+
+struct capn_tree *capn_tree_insert(struct capn_tree *root, struct capn_tree *n);
+
+/* struct capn_segment contains the information about a single segment.
+ *
+ * capn points to a struct capn that is shared between segments in the
+ * same session
+ *
+ * id specifies the segment id, used for far pointers
+ *
+ * data specifies the segment data. This should not move after creation.
+ *
+ * len specifies the current segment length. This is 0 for a blank
+ * segment.
+ *
+ * cap specifies the segment capacity.
+ *
+ * When creating new structures len will be incremented until it reaches cap,
+ * at which point a new segment will be requested via capn->create. The
+ * create callback can either create a new segment or expand an existing
+ * one by incrementing cap and returning the expanded segment.
+ *
+ * data, len, and cap must all be 8 byte aligned, hence the ALIGNED_(8) macro
+ * on the struct definition.
+ *
+ * data, len, cap, and user should all be set by the user. Other values
+ * should be zero initialized.
+ */
+
+struct ALIGNED_(8) capn_segment {
+ struct capn_tree hdr;
+ struct capn_segment *next;
+ struct capn *capn;
+ uint32_t id;
+ /* user settable */
+ char *data;
+ size_t len, cap;
+ void *user;
+};
+
+enum CAPN_TYPE {
+ CAPN_NULL = 0,
+ CAPN_STRUCT = 1,
+ CAPN_LIST = 2,
+ CAPN_PTR_LIST = 3,
+ CAPN_BIT_LIST = 4,
+ CAPN_FAR_POINTER = 5,
+};
+
+struct capn_ptr {
+ unsigned int type : 4;
+ unsigned int has_ptr_tag : 1;
+ unsigned int is_list_member : 1;
+ unsigned int is_composite_list : 1;
+ unsigned int datasz : 19;
+ unsigned int ptrs : 16;
+ int len;
+ char *data;
+ struct capn_segment *seg;
+};
+
+struct capn_text {
+ int len;
+ const char *str;
+ struct capn_segment *seg;
+};
+
+typedef struct capn_ptr capn_ptr;
+typedef struct capn_text capn_text;
+typedef struct {capn_ptr p;} capn_data;
+typedef struct {capn_ptr p;} capn_list1;
+typedef struct {capn_ptr p;} capn_list8;
+typedef struct {capn_ptr p;} capn_list16;
+typedef struct {capn_ptr p;} capn_list32;
+typedef struct {capn_ptr p;} capn_list64;
+
+struct capn_msg {
+ struct capn_segment *seg;
+ uint64_t iface;
+ uint16_t method;
+ capn_ptr args;
+};
+
+/* capn_append_segment appends a segment to a session */
+void capn_append_segment(struct capn*, struct capn_segment*);
+
+capn_ptr capn_root(struct capn *c);
+void capn_resolve(capn_ptr *p);
+
+#define capn_len(list) ((list).p.type == CAPN_FAR_POINTER ? (capn_resolve(&(list).p), (list).p.len) : (list).p.len)
+
+/* capn_getp|setp functions get/set ptrs in list/structs
+ * off is the list index or pointer index in a struct
+ * capn_setp will copy the data, create far pointers, etc if the target
+ * is in a different segment/context.
+ * Both of these will use/return inner pointers for composite lists.
+ */
+capn_ptr capn_getp(capn_ptr p, int off, int resolve);
+int capn_setp(capn_ptr p, int off, capn_ptr tgt);
+
+capn_text capn_get_text(capn_ptr p, int off, capn_text def);
+capn_data capn_get_data(capn_ptr p, int off);
+int capn_set_text(capn_ptr p, int off, capn_text tgt);
+/* there is no set_data -- use capn_new_list8 + capn_setv8 instead
+ * and set data.p = list.p */
+
+/* capn_get* functions get data from a list
+ * The length of the list is given by p->size
+ * off specifies how far into the list to start
+ * sz indicates the number of elements to get
+ * The function returns the number of elements read or -1 on an error.
+ * off must be byte aligned for capn_getv1
+ */
+int capn_get1(capn_list1 p, int off);
+uint8_t capn_get8(capn_list8 p, int off);
+uint16_t capn_get16(capn_list16 p, int off);
+uint32_t capn_get32(capn_list32 p, int off);
+uint64_t capn_get64(capn_list64 p, int off);
+int capn_getv1(capn_list1 p, int off, uint8_t *data, int sz);
+int capn_getv8(capn_list8 p, int off, uint8_t *data, int sz);
+int capn_getv16(capn_list16 p, int off, uint16_t *data, int sz);
+int capn_getv32(capn_list32 p, int off, uint32_t *data, int sz);
+int capn_getv64(capn_list64 p, int off, uint64_t *data, int sz);
+
+/* capn_set* functions set data in a list
+ * off specifies how far into the list to start
+ * sz indicates the number of elements to write
+ * The function returns the number of elemnts written or -1 on an error.
+ * off must be byte aligned for capn_setv1
+ */
+int capn_set1(capn_list1 p, int off, int v);
+int capn_set8(capn_list8 p, int off, uint8_t v);
+int capn_set16(capn_list16 p, int off, uint16_t v);
+int capn_set32(capn_list32 p, int off, uint32_t v);
+int capn_set64(capn_list64 p, int off, uint64_t v);
+int capn_setv1(capn_list1 p, int off, const uint8_t *data, int sz);
+int capn_setv8(capn_list8 p, int off, const uint8_t *data, int sz);
+int capn_setv16(capn_list16 p, int off, const uint16_t *data, int sz);
+int capn_setv32(capn_list32 p, int off, const uint32_t *data, int sz);
+int capn_setv64(capn_list64 p, int off, const uint64_t *data, int sz);
+
+/* capn_new_* functions create a new object
+ * datasz is in bytes, ptrs is # of pointers, sz is # of elements in the list
+ * On an error a CAPN_NULL pointer is returned
+ */
+capn_ptr capn_new_string(struct capn_segment *seg, const char *str, ssize_t sz);
+capn_ptr capn_new_struct(struct capn_segment *seg, int datasz, int ptrs);
+capn_ptr capn_new_interface(struct capn_segment *seg, int datasz, int ptrs);
+capn_ptr capn_new_ptr_list(struct capn_segment *seg, int sz);
+capn_ptr capn_new_list(struct capn_segment *seg, int sz, int datasz, int ptrs);
+capn_list1 capn_new_list1(struct capn_segment *seg, int sz);
+capn_list8 capn_new_list8(struct capn_segment *seg, int sz);
+capn_list16 capn_new_list16(struct capn_segment *seg, int sz);
+capn_list32 capn_new_list32(struct capn_segment *seg, int sz);
+capn_list64 capn_new_list64(struct capn_segment *seg, int sz);
+
+/* capn_read|write* functions read/write struct values
+ * off is the offset into the structure in bytes
+ * Rarely should these be called directly, instead use the generated code.
+ * Data must be xored with the default value
+ * These are inlined
+ */
+CAPN_INLINE uint8_t capn_read8(capn_ptr p, int off);
+CAPN_INLINE uint16_t capn_read16(capn_ptr p, int off);
+CAPN_INLINE uint32_t capn_read32(capn_ptr p, int off);
+CAPN_INLINE uint64_t capn_read64(capn_ptr p, int off);
+CAPN_INLINE int capn_write1(capn_ptr p, int off, int val);
+CAPN_INLINE int capn_write8(capn_ptr p, int off, uint8_t val);
+CAPN_INLINE int capn_write16(capn_ptr p, int off, uint16_t val);
+CAPN_INLINE int capn_write32(capn_ptr p, int off, uint32_t val);
+CAPN_INLINE int capn_write64(capn_ptr p, int off, uint64_t val);
+
+/* capn_init_malloc inits the capn struct with a create function which
+ * allocates segments on the heap using malloc
+ *
+ * capn_init_(fp|mem) inits by reading segments in from the file/memory buffer
+ * in serialized form (optionally packed). It will then setup the create
+ * function ala capn_init_malloc so that further segments can be created.
+ *
+ * capn_free frees all the segment headers and data created by the create
+ * function setup by capn_init_*
+ */
+void capn_init_malloc(struct capn *c);
+int capn_init_fp(struct capn *c, FILE *f, int packed);
+int capn_init_mem(struct capn *c, const uint8_t *p, size_t sz, int packed);
+
+/* capn_write_(fp|mem) writes segments to the file/memory buffer in
+ * serialized form and returns the number of bytes written.
+ */
+/* TODO */
+/*int capn_write_fp(struct capn *c, FILE *f, int packed);*/
+int capn_write_fd(struct capn *c, ssize_t (*write_fd)(int fd, const void *p, size_t count), int fd, int packed);
+int capn_write_mem(struct capn *c, uint8_t *p, size_t sz, int packed);
+
+void capn_free(struct capn *c);
+void capn_reset_copy(struct capn *c);
+
+/* Inline functions */
+
+
+CAPN_INLINE uint8_t capn_flip8(uint8_t v) {
+ return v;
+}
+CAPN_INLINE uint16_t capn_flip16(uint16_t v) {
+#if defined(__BYTE_ORDER) && (__BYTE_ORDER == __LITTLE_ENDIAN)
+ return v;
+#elif defined(__BYTE_ORDER) && (__BYTE_ORDER == __BIG_ENDIAN) && \
+ defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 8
+ return __builtin_bswap16(v);
+#else
+ union { uint16_t u; uint8_t v[2]; } s;
+ s.v[0] = (uint8_t)v;
+ s.v[1] = (uint8_t)(v>>8);
+ return s.u;
+#endif
+}
+CAPN_INLINE uint32_t capn_flip32(uint32_t v) {
+#if defined(__BYTE_ORDER) && (__BYTE_ORDER == __LITTLE_ENDIAN)
+ return v;
+#elif defined(__BYTE_ORDER) && (__BYTE_ORDER == __BIG_ENDIAN) && \
+ defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 8
+ return __builtin_bswap32(v);
+#else
+ union { uint32_t u; uint8_t v[4]; } s;
+ s.v[0] = (uint8_t)v;
+ s.v[1] = (uint8_t)(v>>8);
+ s.v[2] = (uint8_t)(v>>16);
+ s.v[3] = (uint8_t)(v>>24);
+ return s.u;
+#endif
+}
+CAPN_INLINE uint64_t capn_flip64(uint64_t v) {
+#if defined(__BYTE_ORDER) && (__BYTE_ORDER == __LITTLE_ENDIAN)
+ return v;
+#elif defined(__BYTE_ORDER) && (__BYTE_ORDER == __BIG_ENDIAN) && \
+ defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 8
+ return __builtin_bswap64(v);
+#else
+ union { uint64_t u; uint8_t v[8]; } s;
+ s.v[0] = (uint8_t)v;
+ s.v[1] = (uint8_t)(v>>8);
+ s.v[2] = (uint8_t)(v>>16);
+ s.v[3] = (uint8_t)(v>>24);
+ s.v[4] = (uint8_t)(v>>32);
+ s.v[5] = (uint8_t)(v>>40);
+ s.v[6] = (uint8_t)(v>>48);
+ s.v[7] = (uint8_t)(v>>56);
+ return s.u;
+#endif
+}
+
+CAPN_INLINE int capn_write1(capn_ptr p, int off, int val) {
+ if (off >= p.datasz*8) {
+ return -1;
+ } else if (val) {
+ uint8_t tmp = (uint8_t)(1 << (off & 7));
+ ((uint8_t*) p.data)[off >> 3] |= tmp;
+ return 0;
+ } else {
+ uint8_t tmp = (uint8_t)(~(1 << (off & 7)));
+ ((uint8_t*) p.data)[off >> 3] &= tmp;
+ return 0;
+ }
+}
+
+CAPN_INLINE uint8_t capn_read8(capn_ptr p, int off) {
+ return off+1 <= p.datasz ? capn_flip8(*(uint8_t*) (p.data+off)) : 0;
+}
+CAPN_INLINE int capn_write8(capn_ptr p, int off, uint8_t val) {
+ if (off+1 <= p.datasz) {
+ *(uint8_t*) (p.data+off) = capn_flip8(val);
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+CAPN_INLINE uint16_t capn_read16(capn_ptr p, int off) {
+ return off+2 <= p.datasz ? capn_flip16(*(uint16_t*) (p.data+off)) : 0;
+}
+CAPN_INLINE int capn_write16(capn_ptr p, int off, uint16_t val) {
+ if (off+2 <= p.datasz) {
+ *(uint16_t*) (p.data+off) = capn_flip16(val);
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+CAPN_INLINE uint32_t capn_read32(capn_ptr p, int off) {
+ return off+4 <= p.datasz ? capn_flip32(*(uint32_t*) (p.data+off)) : 0;
+}
+CAPN_INLINE int capn_write32(capn_ptr p, int off, uint32_t val) {
+ if (off+4 <= p.datasz) {
+ *(uint32_t*) (p.data+off) = capn_flip32(val);
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+CAPN_INLINE uint64_t capn_read64(capn_ptr p, int off) {
+ return off+8 <= p.datasz ? capn_flip64(*(uint64_t*) (p.data+off)) : 0;
+}
+CAPN_INLINE int capn_write64(capn_ptr p, int off, uint64_t val) {
+ if (off+8 <= p.datasz) {
+ *(uint64_t*) (p.data+off) = capn_flip64(val);
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+union capn_conv_f32 {
+ uint32_t u;
+ float f;
+};
+
+union capn_conv_f64 {
+ uint64_t u;
+ double f;
+};
+
+CAPN_INLINE float capn_to_f32(uint32_t v) {
+ union capn_conv_f32 u;
+ u.u = v;
+ return u.f;
+}
+CAPN_INLINE double capn_to_f64(uint64_t v) {
+ union capn_conv_f64 u;
+ u.u = v;
+ return u.f;
+}
+CAPN_INLINE uint32_t capn_from_f32(float v) {
+ union capn_conv_f32 u;
+ u.f = v;
+ return u.u;
+}
+CAPN_INLINE uint64_t capn_from_f64(double v) {
+ union capn_conv_f64 u;
+ u.f = v;
+ return u.u;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/lib/capnp-c/capnp_priv.h b/include/lib/capnp-c/capnp_priv.h
new file mode 100644
index 0000000..6c03763
--- /dev/null
+++ b/include/lib/capnp-c/capnp_priv.h
@@ -0,0 +1,62 @@
+/* vim: set sw=8 ts=8 sts=8 noet: */
+/* capnp_c.h
+ *
+ * Copyright (C) 2013 James McKaskill
+ * Copyright (C) 2014 Steve Dee
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+/*
+ * functions / structures in this header are private to the capnproto-c
+ * library; applications should not call or use them.
+ */
+
+#ifndef CAPNP_PRIV_H
+#define CAPNP_PRIV_H
+
+#include "capnp_c.h"
+
+#if defined(__GNUC__) && __GNUC__ >= 4
+# define intern __attribute__((visibility ("internal")))
+#else
+# define intern /**/
+#endif
+
+/* capn_stream encapsulates the needed fields for capn_(deflate|inflate) in a
+ * similar manner to z_stream from zlib
+ *
+ * The user should set next_in, avail_in, next_out, avail_out to the
+ * available in/out buffers before calling capn_(deflate|inflate).
+ *
+ * Other fields should be zero initialized.
+ */
+struct capn_stream {
+ const uint8_t *next_in;
+ size_t avail_in;
+ uint8_t *next_out;
+ size_t avail_out;
+ unsigned zeros, raw;
+
+ uint8_t inflate_buf[8];
+ size_t avail_buf;
+};
+
+#define CAPN_MISALIGNED -1
+#define CAPN_NEED_MORE -2
+
+/* capn_deflate deflates a stream to the packed format
+ * capn_inflate inflates a stream from the packed format
+ *
+ * Returns:
+ * CAPN_MISALIGNED - if the unpacked data is not 8 byte aligned
+ * CAPN_NEED_MORE - more packed data/room is required (out for inflate, in for
+ * deflate)
+ * 0 - success, all output for inflate, all input for deflate processed
+ */
+intern int capn_deflate(struct capn_stream*);
+intern int capn_inflate(struct capn_stream*);
+
+
+#endif /* CAPNP_PRIV_H */
diff --git a/include/lib/modernjson/adl_serializer.hpp b/include/lib/modernjson/adl_serializer.hpp
new file mode 100644
index 0000000..45f2452
--- /dev/null
+++ b/include/lib/modernjson/adl_serializer.hpp
@@ -0,0 +1,49 @@
+#pragma once
+
+#include <utility>
+
+#include <lib/modernjson/detail/conversions/from_json.hpp>
+#include <lib/modernjson/detail/conversions/to_json.hpp>
+
+namespace nlohmann
+{
+
+template<typename, typename>
+struct adl_serializer
+{
+ /*!
+ @brief convert a JSON value to any value type
+
+ This function is usually called by the `get()` function of the
+ @ref basic_json class (either explicit or via conversion operators).
+
+ @param[in] j JSON value to read from
+ @param[in,out] val value to write to
+ */
+ template<typename BasicJsonType, typename ValueType>
+ static auto from_json(BasicJsonType&& j, ValueType& val) noexcept(
+ noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), val)))
+ -> decltype(::nlohmann::from_json(std::forward<BasicJsonType>(j), val), void())
+ {
+ ::nlohmann::from_json(std::forward<BasicJsonType>(j), val);
+ }
+
+ /*!
+ @brief convert any value type to a JSON value
+
+ This function is usually called by the constructors of the @ref basic_json
+ class.
+
+ @param[in,out] j JSON value to write to
+ @param[in] val value to read from
+ */
+ template <typename BasicJsonType, typename ValueType>
+ static auto to_json(BasicJsonType& j, ValueType&& val) noexcept(
+ noexcept(::nlohmann::to_json(j, std::forward<ValueType>(val))))
+ -> decltype(::nlohmann::to_json(j, std::forward<ValueType>(val)), void())
+ {
+ ::nlohmann::to_json(j, std::forward<ValueType>(val));
+ }
+};
+
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/conversions/from_json.hpp b/include/lib/modernjson/detail/conversions/from_json.hpp
new file mode 100644
index 0000000..817d4b7
--- /dev/null
+++ b/include/lib/modernjson/detail/conversions/from_json.hpp
@@ -0,0 +1,370 @@
+#pragma once
+
+#include <algorithm> // transform
+#include <array> // array
+#include <ciso646> // and, not
+#include <forward_list> // forward_list
+#include <iterator> // inserter, front_inserter, end
+#include <map> // map
+#include <string> // string
+#include <tuple> // tuple, make_tuple
+#include <type_traits> // is_arithmetic, is_same, is_enum, underlying_type, is_convertible
+#include <unordered_map> // unordered_map
+#include <utility> // pair, declval
+#include <valarray> // valarray
+
+#include <lib/modernjson/detail/exceptions.hpp>
+#include <lib/modernjson/detail/macro_scope.hpp>
+#include <lib/modernjson/detail/meta/cpp_future.hpp>
+#include <lib/modernjson/detail/meta/type_traits.hpp>
+#include <lib/modernjson/detail/value_t.hpp>
+
+namespace nlohmann
+{
+namespace detail
+{
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename std::nullptr_t& n)
+{
+ if (JSON_UNLIKELY(not j.is_null()))
+ {
+ JSON_THROW(type_error::create(302, "type must be null, but is " + std::string(j.type_name())));
+ }
+ n = nullptr;
+}
+
+// overloads for basic_json template parameters
+template<typename BasicJsonType, typename ArithmeticType,
+ enable_if_t<std::is_arithmetic<ArithmeticType>::value and
+ not std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
+ int> = 0>
+void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
+{
+ switch (static_cast<value_t>(j))
+ {
+ case value_t::number_unsigned:
+ {
+ val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
+ break;
+ }
+ case value_t::number_integer:
+ {
+ val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
+ break;
+ }
+ case value_t::number_float:
+ {
+ val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
+ break;
+ }
+
+ default:
+ JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name())));
+ }
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b)
+{
+ if (JSON_UNLIKELY(not j.is_boolean()))
+ {
+ JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name())));
+ }
+ b = *j.template get_ptr<const typename BasicJsonType::boolean_t*>();
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s)
+{
+ if (JSON_UNLIKELY(not j.is_string()))
+ {
+ JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name())));
+ }
+ s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
+}
+
+template <
+ typename BasicJsonType, typename ConstructibleStringType,
+ enable_if_t <
+ is_constructible_string_type<BasicJsonType, ConstructibleStringType>::value and
+ not std::is_same<typename BasicJsonType::string_t,
+ ConstructibleStringType>::value,
+ int > = 0 >
+void from_json(const BasicJsonType& j, ConstructibleStringType& s)
+{
+ if (JSON_UNLIKELY(not j.is_string()))
+ {
+ JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name())));
+ }
+
+ s = *j.template get_ptr<const typename BasicJsonType::string_t*>();
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val)
+{
+ get_arithmetic_value(j, val);
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val)
+{
+ get_arithmetic_value(j, val);
+}
+
+template<typename BasicJsonType>
+void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val)
+{
+ get_arithmetic_value(j, val);
+}
+
+template<typename BasicJsonType, typename EnumType,
+ enable_if_t<std::is_enum<EnumType>::value, int> = 0>
+void from_json(const BasicJsonType& j, EnumType& e)
+{
+ typename std::underlying_type<EnumType>::type val;
+ get_arithmetic_value(j, val);
+ e = static_cast<EnumType>(val);
+}
+
+// forward_list doesn't have an insert method
+template<typename BasicJsonType, typename T, typename Allocator,
+ enable_if_t<std::is_convertible<BasicJsonType, T>::value, int> = 0>
+void from_json(const BasicJsonType& j, std::forward_list<T, Allocator>& l)
+{
+ if (JSON_UNLIKELY(not j.is_array()))
+ {
+ JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name())));
+ }
+ std::transform(j.rbegin(), j.rend(),
+ std::front_inserter(l), [](const BasicJsonType & i)
+ {
+ return i.template get<T>();
+ });
+}
+
+// valarray doesn't have an insert method
+template<typename BasicJsonType, typename T,
+ enable_if_t<std::is_convertible<BasicJsonType, T>::value, int> = 0>
+void from_json(const BasicJsonType& j, std::valarray<T>& l)
+{
+ if (JSON_UNLIKELY(not j.is_array()))
+ {
+ JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name())));
+ }
+ l.resize(j.size());
+ std::copy(j.m_value.array->begin(), j.m_value.array->end(), std::begin(l));
+}
+
+template<typename BasicJsonType>
+void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/)
+{
+ arr = *j.template get_ptr<const typename BasicJsonType::array_t*>();
+}
+
+template <typename BasicJsonType, typename T, std::size_t N>
+auto from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr,
+ priority_tag<2> /*unused*/)
+-> decltype(j.template get<T>(), void())
+{
+ for (std::size_t i = 0; i < N; ++i)
+ {
+ arr[i] = j.at(i).template get<T>();
+ }
+}
+
+template<typename BasicJsonType, typename ConstructibleArrayType>
+auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<1> /*unused*/)
+-> decltype(
+ arr.reserve(std::declval<typename ConstructibleArrayType::size_type>()),
+ j.template get<typename ConstructibleArrayType::value_type>(),
+ void())
+{
+ using std::end;
+
+ arr.reserve(j.size());
+ std::transform(j.begin(), j.end(),
+ std::inserter(arr, end(arr)), [](const BasicJsonType & i)
+ {
+ // get<BasicJsonType>() returns *this, this won't call a from_json
+ // method when value_type is BasicJsonType
+ return i.template get<typename ConstructibleArrayType::value_type>();
+ });
+}
+
+template <typename BasicJsonType, typename ConstructibleArrayType>
+void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr,
+ priority_tag<0> /*unused*/)
+{
+ using std::end;
+
+ std::transform(
+ j.begin(), j.end(), std::inserter(arr, end(arr)),
+ [](const BasicJsonType & i)
+ {
+ // get<BasicJsonType>() returns *this, this won't call a from_json
+ // method when value_type is BasicJsonType
+ return i.template get<typename ConstructibleArrayType::value_type>();
+ });
+}
+
+template <typename BasicJsonType, typename ConstructibleArrayType,
+ enable_if_t <
+ is_constructible_array_type<BasicJsonType, ConstructibleArrayType>::value and
+ not is_constructible_object_type<BasicJsonType, ConstructibleArrayType>::value and
+ not is_constructible_string_type<BasicJsonType, ConstructibleArrayType>::value and
+ not is_basic_json<ConstructibleArrayType>::value,
+ int > = 0 >
+
+auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr)
+-> decltype(from_json_array_impl(j, arr, priority_tag<3> {}),
+j.template get<typename ConstructibleArrayType::value_type>(),
+void())
+{
+ if (JSON_UNLIKELY(not j.is_array()))
+ {
+ JSON_THROW(type_error::create(302, "type must be array, but is " +
+ std::string(j.type_name())));
+ }
+
+ from_json_array_impl(j, arr, priority_tag<3> {});
+}
+
+template<typename BasicJsonType, typename ConstructibleObjectType,
+ enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0>
+void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
+{
+ if (JSON_UNLIKELY(not j.is_object()))
+ {
+ JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name())));
+ }
+
+ auto inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
+ using value_type = typename ConstructibleObjectType::value_type;
+ std::transform(
+ inner_object->begin(), inner_object->end(),
+ std::inserter(obj, obj.begin()),
+ [](typename BasicJsonType::object_t::value_type const & p)
+ {
+ return value_type(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
+ });
+}
+
+// overload for arithmetic types, not chosen for basic_json template arguments
+// (BooleanType, etc..); note: Is it really necessary to provide explicit
+// overloads for boolean_t etc. in case of a custom BooleanType which is not
+// an arithmetic type?
+template<typename BasicJsonType, typename ArithmeticType,
+ enable_if_t <
+ std::is_arithmetic<ArithmeticType>::value and
+ not std::is_same<ArithmeticType, typename BasicJsonType::number_unsigned_t>::value and
+ not std::is_same<ArithmeticType, typename BasicJsonType::number_integer_t>::value and
+ not std::is_same<ArithmeticType, typename BasicJsonType::number_float_t>::value and
+ not std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
+ int> = 0>
+void from_json(const BasicJsonType& j, ArithmeticType& val)
+{
+ switch (static_cast<value_t>(j))
+ {
+ case value_t::number_unsigned:
+ {
+ val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
+ break;
+ }
+ case value_t::number_integer:
+ {
+ val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
+ break;
+ }
+ case value_t::number_float:
+ {
+ val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
+ break;
+ }
+ case value_t::boolean:
+ {
+ val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t*>());
+ break;
+ }
+
+ default:
+ JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name())));
+ }
+}
+
+template<typename BasicJsonType, typename A1, typename A2>
+void from_json(const BasicJsonType& j, std::pair<A1, A2>& p)
+{
+ p = {j.at(0).template get<A1>(), j.at(1).template get<A2>()};
+}
+
+template<typename BasicJsonType, typename Tuple, std::size_t... Idx>
+void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence<Idx...> /*unused*/)
+{
+ t = std::make_tuple(j.at(Idx).template get<typename std::tuple_element<Idx, Tuple>::type>()...);
+}
+
+template<typename BasicJsonType, typename... Args>
+void from_json(const BasicJsonType& j, std::tuple<Args...>& t)
+{
+ from_json_tuple_impl(j, t, index_sequence_for<Args...> {});
+}
+
+template <typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator,
+ typename = enable_if_t<not std::is_constructible<
+ typename BasicJsonType::string_t, Key>::value>>
+void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allocator>& m)
+{
+ if (JSON_UNLIKELY(not j.is_array()))
+ {
+ JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name())));
+ }
+ for (const auto& p : j)
+ {
+ if (JSON_UNLIKELY(not p.is_array()))
+ {
+ JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name())));
+ }
+ m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());
+ }
+}
+
+template <typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator,
+ typename = enable_if_t<not std::is_constructible<
+ typename BasicJsonType::string_t, Key>::value>>
+void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyEqual, Allocator>& m)
+{
+ if (JSON_UNLIKELY(not j.is_array()))
+ {
+ JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name())));
+ }
+ for (const auto& p : j)
+ {
+ if (JSON_UNLIKELY(not p.is_array()))
+ {
+ JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(p.type_name())));
+ }
+ m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());
+ }
+}
+
+struct from_json_fn
+{
+ template<typename BasicJsonType, typename T>
+ auto operator()(const BasicJsonType& j, T& val) const
+ noexcept(noexcept(from_json(j, val)))
+ -> decltype(from_json(j, val), void())
+ {
+ return from_json(j, val);
+ }
+};
+} // namespace detail
+
+/// namespace to hold default `from_json` function
+/// to see why this is required:
+/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html
+namespace
+{
+constexpr const auto& from_json = detail::static_const<detail::from_json_fn>::value;
+} // namespace
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/conversions/to_chars.hpp b/include/lib/modernjson/detail/conversions/to_chars.hpp
new file mode 100644
index 0000000..b32e176
--- /dev/null
+++ b/include/lib/modernjson/detail/conversions/to_chars.hpp
@@ -0,0 +1,1094 @@
+#pragma once
+
+#include <cassert> // assert
+#include <ciso646> // or, and, not
+#include <cmath> // signbit, isfinite
+#include <cstdint> // intN_t, uintN_t
+#include <cstring> // memcpy, memmove
+
+namespace nlohmann
+{
+namespace detail
+{
+
+/*!
+@brief implements the Grisu2 algorithm for binary to decimal floating-point
+conversion.
+
+This implementation is a slightly modified version of the reference
+implementation which may be obtained from
+http://florian.loitsch.com/publications (bench.tar.gz).
+
+The code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch.
+
+For a detailed description of the algorithm see:
+
+[1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with
+ Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming
+ Language Design and Implementation, PLDI 2010
+[2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately",
+ Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language
+ Design and Implementation, PLDI 1996
+*/
+namespace dtoa_impl
+{
+
+template <typename Target, typename Source>
+Target reinterpret_bits(const Source source)
+{
+ static_assert(sizeof(Target) == sizeof(Source), "size mismatch");
+
+ Target target;
+ std::memcpy(&target, &source, sizeof(Source));
+ return target;
+}
+
+struct diyfp // f * 2^e
+{
+ static constexpr int kPrecision = 64; // = q
+
+ uint64_t f = 0;
+ int e = 0;
+
+ constexpr diyfp(uint64_t f_, int e_) noexcept : f(f_), e(e_) {}
+
+ /*!
+ @brief returns x - y
+ @pre x.e == y.e and x.f >= y.f
+ */
+ static diyfp sub(const diyfp& x, const diyfp& y) noexcept
+ {
+ assert(x.e == y.e);
+ assert(x.f >= y.f);
+
+ return {x.f - y.f, x.e};
+ }
+
+ /*!
+ @brief returns x * y
+ @note The result is rounded. (Only the upper q bits are returned.)
+ */
+ static diyfp mul(const diyfp& x, const diyfp& y) noexcept
+ {
+ static_assert(kPrecision == 64, "internal error");
+
+ // Computes:
+ // f = round((x.f * y.f) / 2^q)
+ // e = x.e + y.e + q
+
+ // Emulate the 64-bit * 64-bit multiplication:
+ //
+ // p = u * v
+ // = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi)
+ // = (u_lo v_lo ) + 2^32 ((u_lo v_hi ) + (u_hi v_lo )) + 2^64 (u_hi v_hi )
+ // = (p0 ) + 2^32 ((p1 ) + (p2 )) + 2^64 (p3 )
+ // = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3 )
+ // = (p0_lo ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi + p2_hi + p3)
+ // = (p0_lo ) + 2^32 (Q ) + 2^64 (H )
+ // = (p0_lo ) + 2^32 (Q_lo + 2^32 Q_hi ) + 2^64 (H )
+ //
+ // (Since Q might be larger than 2^32 - 1)
+ //
+ // = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H)
+ //
+ // (Q_hi + H does not overflow a 64-bit int)
+ //
+ // = p_lo + 2^64 p_hi
+
+ const uint64_t u_lo = x.f & 0xFFFFFFFF;
+ const uint64_t u_hi = x.f >> 32;
+ const uint64_t v_lo = y.f & 0xFFFFFFFF;
+ const uint64_t v_hi = y.f >> 32;
+
+ const uint64_t p0 = u_lo * v_lo;
+ const uint64_t p1 = u_lo * v_hi;
+ const uint64_t p2 = u_hi * v_lo;
+ const uint64_t p3 = u_hi * v_hi;
+
+ const uint64_t p0_hi = p0 >> 32;
+ const uint64_t p1_lo = p1 & 0xFFFFFFFF;
+ const uint64_t p1_hi = p1 >> 32;
+ const uint64_t p2_lo = p2 & 0xFFFFFFFF;
+ const uint64_t p2_hi = p2 >> 32;
+
+ uint64_t Q = p0_hi + p1_lo + p2_lo;
+
+ // The full product might now be computed as
+ //
+ // p_hi = p3 + p2_hi + p1_hi + (Q >> 32)
+ // p_lo = p0_lo + (Q << 32)
+ //
+ // But in this particular case here, the full p_lo is not required.
+ // Effectively we only need to add the highest bit in p_lo to p_hi (and
+ // Q_hi + 1 does not overflow).
+
+ Q += uint64_t{1} << (64 - 32 - 1); // round, ties up
+
+ const uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32);
+
+ return {h, x.e + y.e + 64};
+ }
+
+ /*!
+ @brief normalize x such that the significand is >= 2^(q-1)
+ @pre x.f != 0
+ */
+ static diyfp normalize(diyfp x) noexcept
+ {
+ assert(x.f != 0);
+
+ while ((x.f >> 63) == 0)
+ {
+ x.f <<= 1;
+ x.e--;
+ }
+
+ return x;
+ }
+
+ /*!
+ @brief normalize x such that the result has the exponent E
+ @pre e >= x.e and the upper e - x.e bits of x.f must be zero.
+ */
+ static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept
+ {
+ const int delta = x.e - target_exponent;
+
+ assert(delta >= 0);
+ assert(((x.f << delta) >> delta) == x.f);
+
+ return {x.f << delta, target_exponent};
+ }
+};
+
+struct boundaries
+{
+ diyfp w;
+ diyfp minus;
+ diyfp plus;
+};
+
+/*!
+Compute the (normalized) diyfp representing the input number 'value' and its
+boundaries.
+
+@pre value must be finite and positive
+*/
+template <typename FloatType>
+boundaries compute_boundaries(FloatType value)
+{
+ assert(std::isfinite(value));
+ assert(value > 0);
+
+ // Convert the IEEE representation into a diyfp.
+ //
+ // If v is denormal:
+ // value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1))
+ // If v is normalized:
+ // value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1))
+
+ static_assert(std::numeric_limits<FloatType>::is_iec559,
+ "internal error: dtoa_short requires an IEEE-754 floating-point implementation");
+
+ constexpr int kPrecision = std::numeric_limits<FloatType>::digits; // = p (includes the hidden bit)
+ constexpr int kBias = std::numeric_limits<FloatType>::max_exponent - 1 + (kPrecision - 1);
+ constexpr int kMinExp = 1 - kBias;
+ constexpr uint64_t kHiddenBit = uint64_t{1} << (kPrecision - 1); // = 2^(p-1)
+
+ using bits_type = typename std::conditional< kPrecision == 24, uint32_t, uint64_t >::type;
+
+ const uint64_t bits = reinterpret_bits<bits_type>(value);
+ const uint64_t E = bits >> (kPrecision - 1);
+ const uint64_t F = bits & (kHiddenBit - 1);
+
+ const bool is_denormal = (E == 0);
+ const diyfp v = is_denormal
+ ? diyfp(F, kMinExp)
+ : diyfp(F + kHiddenBit, static_cast<int>(E) - kBias);
+
+ // Compute the boundaries m- and m+ of the floating-point value
+ // v = f * 2^e.
+ //
+ // Determine v- and v+, the floating-point predecessor and successor if v,
+ // respectively.
+ //
+ // v- = v - 2^e if f != 2^(p-1) or e == e_min (A)
+ // = v - 2^(e-1) if f == 2^(p-1) and e > e_min (B)
+ //
+ // v+ = v + 2^e
+ //
+ // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_
+ // between m- and m+ round to v, regardless of how the input rounding
+ // algorithm breaks ties.
+ //
+ // ---+-------------+-------------+-------------+-------------+--- (A)
+ // v- m- v m+ v+
+ //
+ // -----------------+------+------+-------------+-------------+--- (B)
+ // v- m- v m+ v+
+
+ const bool lower_boundary_is_closer = (F == 0 and E > 1);
+ const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1);
+ const diyfp m_minus = lower_boundary_is_closer
+ ? diyfp(4 * v.f - 1, v.e - 2) // (B)
+ : diyfp(2 * v.f - 1, v.e - 1); // (A)
+
+ // Determine the normalized w+ = m+.
+ const diyfp w_plus = diyfp::normalize(m_plus);
+
+ // Determine w- = m- such that e_(w-) = e_(w+).
+ const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e);
+
+ return {diyfp::normalize(v), w_minus, w_plus};
+}
+
+// Given normalized diyfp w, Grisu needs to find a (normalized) cached
+// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies
+// within a certain range [alpha, gamma] (Definition 3.2 from [1])
+//
+// alpha <= e = e_c + e_w + q <= gamma
+//
+// or
+//
+// f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q
+// <= f_c * f_w * 2^gamma
+//
+// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies
+//
+// 2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma
+//
+// or
+//
+// 2^(q - 2 + alpha) <= c * w < 2^(q + gamma)
+//
+// The choice of (alpha,gamma) determines the size of the table and the form of
+// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well
+// in practice:
+//
+// The idea is to cut the number c * w = f * 2^e into two parts, which can be
+// processed independently: An integral part p1, and a fractional part p2:
+//
+// f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e
+// = (f div 2^-e) + (f mod 2^-e) * 2^e
+// = p1 + p2 * 2^e
+//
+// The conversion of p1 into decimal form requires a series of divisions and
+// modulos by (a power of) 10. These operations are faster for 32-bit than for
+// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be
+// achieved by choosing
+//
+// -e >= 32 or e <= -32 := gamma
+//
+// In order to convert the fractional part
+//
+// p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ...
+//
+// into decimal form, the fraction is repeatedly multiplied by 10 and the digits
+// d[-i] are extracted in order:
+//
+// (10 * p2) div 2^-e = d[-1]
+// (10 * p2) mod 2^-e = d[-2] / 10^1 + ...
+//
+// The multiplication by 10 must not overflow. It is sufficient to choose
+//
+// 10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64.
+//
+// Since p2 = f mod 2^-e < 2^-e,
+//
+// -e <= 60 or e >= -60 := alpha
+
+constexpr int kAlpha = -60;
+constexpr int kGamma = -32;
+
+struct cached_power // c = f * 2^e ~= 10^k
+{
+ uint64_t f;
+ int e;
+ int k;
+};
+
+/*!
+For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached
+power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c
+satisfies (Definition 3.2 from [1])
+
+ alpha <= e_c + e + q <= gamma.
+*/
+inline cached_power get_cached_power_for_binary_exponent(int e)
+{
+ // Now
+ //
+ // alpha <= e_c + e + q <= gamma (1)
+ // ==> f_c * 2^alpha <= c * 2^e * 2^q
+ //
+ // and since the c's are normalized, 2^(q-1) <= f_c,
+ //
+ // ==> 2^(q - 1 + alpha) <= c * 2^(e + q)
+ // ==> 2^(alpha - e - 1) <= c
+ //
+ // If c were an exakt power of ten, i.e. c = 10^k, one may determine k as
+ //
+ // k = ceil( log_10( 2^(alpha - e - 1) ) )
+ // = ceil( (alpha - e - 1) * log_10(2) )
+ //
+ // From the paper:
+ // "In theory the result of the procedure could be wrong since c is rounded,
+ // and the computation itself is approximated [...]. In practice, however,
+ // this simple function is sufficient."
+ //
+ // For IEEE double precision floating-point numbers converted into
+ // normalized diyfp's w = f * 2^e, with q = 64,
+ //
+ // e >= -1022 (min IEEE exponent)
+ // -52 (p - 1)
+ // -52 (p - 1, possibly normalize denormal IEEE numbers)
+ // -11 (normalize the diyfp)
+ // = -1137
+ //
+ // and
+ //
+ // e <= +1023 (max IEEE exponent)
+ // -52 (p - 1)
+ // -11 (normalize the diyfp)
+ // = 960
+ //
+ // This binary exponent range [-1137,960] results in a decimal exponent
+ // range [-307,324]. One does not need to store a cached power for each
+ // k in this range. For each such k it suffices to find a cached power
+ // such that the exponent of the product lies in [alpha,gamma].
+ // This implies that the difference of the decimal exponents of adjacent
+ // table entries must be less than or equal to
+ //
+ // floor( (gamma - alpha) * log_10(2) ) = 8.
+ //
+ // (A smaller distance gamma-alpha would require a larger table.)
+
+ // NB:
+ // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34.
+
+ constexpr int kCachedPowersSize = 79;
+ constexpr int kCachedPowersMinDecExp = -300;
+ constexpr int kCachedPowersDecStep = 8;
+
+ static constexpr cached_power kCachedPowers[] =
+ {
+ { 0xAB70FE17C79AC6CA, -1060, -300 },
+ { 0xFF77B1FCBEBCDC4F, -1034, -292 },
+ { 0xBE5691EF416BD60C, -1007, -284 },
+ { 0x8DD01FAD907FFC3C, -980, -276 },
+ { 0xD3515C2831559A83, -954, -268 },
+ { 0x9D71AC8FADA6C9B5, -927, -260 },
+ { 0xEA9C227723EE8BCB, -901, -252 },
+ { 0xAECC49914078536D, -874, -244 },
+ { 0x823C12795DB6CE57, -847, -236 },
+ { 0xC21094364DFB5637, -821, -228 },
+ { 0x9096EA6F3848984F, -794, -220 },
+ { 0xD77485CB25823AC7, -768, -212 },
+ { 0xA086CFCD97BF97F4, -741, -204 },
+ { 0xEF340A98172AACE5, -715, -196 },
+ { 0xB23867FB2A35B28E, -688, -188 },
+ { 0x84C8D4DFD2C63F3B, -661, -180 },
+ { 0xC5DD44271AD3CDBA, -635, -172 },
+ { 0x936B9FCEBB25C996, -608, -164 },
+ { 0xDBAC6C247D62A584, -582, -156 },
+ { 0xA3AB66580D5FDAF6, -555, -148 },
+ { 0xF3E2F893DEC3F126, -529, -140 },
+ { 0xB5B5ADA8AAFF80B8, -502, -132 },
+ { 0x87625F056C7C4A8B, -475, -124 },
+ { 0xC9BCFF6034C13053, -449, -116 },
+ { 0x964E858C91BA2655, -422, -108 },
+ { 0xDFF9772470297EBD, -396, -100 },
+ { 0xA6DFBD9FB8E5B88F, -369, -92 },
+ { 0xF8A95FCF88747D94, -343, -84 },
+ { 0xB94470938FA89BCF, -316, -76 },
+ { 0x8A08F0F8BF0F156B, -289, -68 },
+ { 0xCDB02555653131B6, -263, -60 },
+ { 0x993FE2C6D07B7FAC, -236, -52 },
+ { 0xE45C10C42A2B3B06, -210, -44 },
+ { 0xAA242499697392D3, -183, -36 },
+ { 0xFD87B5F28300CA0E, -157, -28 },
+ { 0xBCE5086492111AEB, -130, -20 },
+ { 0x8CBCCC096F5088CC, -103, -12 },
+ { 0xD1B71758E219652C, -77, -4 },
+ { 0x9C40000000000000, -50, 4 },
+ { 0xE8D4A51000000000, -24, 12 },
+ { 0xAD78EBC5AC620000, 3, 20 },
+ { 0x813F3978F8940984, 30, 28 },
+ { 0xC097CE7BC90715B3, 56, 36 },
+ { 0x8F7E32CE7BEA5C70, 83, 44 },
+ { 0xD5D238A4ABE98068, 109, 52 },
+ { 0x9F4F2726179A2245, 136, 60 },
+ { 0xED63A231D4C4FB27, 162, 68 },
+ { 0xB0DE65388CC8ADA8, 189, 76 },
+ { 0x83C7088E1AAB65DB, 216, 84 },
+ { 0xC45D1DF942711D9A, 242, 92 },
+ { 0x924D692CA61BE758, 269, 100 },
+ { 0xDA01EE641A708DEA, 295, 108 },
+ { 0xA26DA3999AEF774A, 322, 116 },
+ { 0xF209787BB47D6B85, 348, 124 },
+ { 0xB454E4A179DD1877, 375, 132 },
+ { 0x865B86925B9BC5C2, 402, 140 },
+ { 0xC83553C5C8965D3D, 428, 148 },
+ { 0x952AB45CFA97A0B3, 455, 156 },
+ { 0xDE469FBD99A05FE3, 481, 164 },
+ { 0xA59BC234DB398C25, 508, 172 },
+ { 0xF6C69A72A3989F5C, 534, 180 },
+ { 0xB7DCBF5354E9BECE, 561, 188 },
+ { 0x88FCF317F22241E2, 588, 196 },
+ { 0xCC20CE9BD35C78A5, 614, 204 },
+ { 0x98165AF37B2153DF, 641, 212 },
+ { 0xE2A0B5DC971F303A, 667, 220 },
+ { 0xA8D9D1535CE3B396, 694, 228 },
+ { 0xFB9B7CD9A4A7443C, 720, 236 },
+ { 0xBB764C4CA7A44410, 747, 244 },
+ { 0x8BAB8EEFB6409C1A, 774, 252 },
+ { 0xD01FEF10A657842C, 800, 260 },
+ { 0x9B10A4E5E9913129, 827, 268 },
+ { 0xE7109BFBA19C0C9D, 853, 276 },
+ { 0xAC2820D9623BF429, 880, 284 },
+ { 0x80444B5E7AA7CF85, 907, 292 },
+ { 0xBF21E44003ACDD2D, 933, 300 },
+ { 0x8E679C2F5E44FF8F, 960, 308 },
+ { 0xD433179D9C8CB841, 986, 316 },
+ { 0x9E19DB92B4E31BA9, 1013, 324 },
+ };
+
+ // This computation gives exactly the same results for k as
+ // k = ceil((kAlpha - e - 1) * 0.30102999566398114)
+ // for |e| <= 1500, but doesn't require floating-point operations.
+ // NB: log_10(2) ~= 78913 / 2^18
+ assert(e >= -1500);
+ assert(e <= 1500);
+ const int f = kAlpha - e - 1;
+ const int k = (f * 78913) / (1 << 18) + static_cast<int>(f > 0);
+
+ const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep;
+ assert(index >= 0);
+ assert(index < kCachedPowersSize);
+ static_cast<void>(kCachedPowersSize); // Fix warning.
+
+ const cached_power cached = kCachedPowers[index];
+ assert(kAlpha <= cached.e + e + 64);
+ assert(kGamma >= cached.e + e + 64);
+
+ return cached;
+}
+
+/*!
+For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k.
+For n == 0, returns 1 and sets pow10 := 1.
+*/
+inline int find_largest_pow10(const uint32_t n, uint32_t& pow10)
+{
+ // LCOV_EXCL_START
+ if (n >= 1000000000)
+ {
+ pow10 = 1000000000;
+ return 10;
+ }
+ // LCOV_EXCL_STOP
+ else if (n >= 100000000)
+ {
+ pow10 = 100000000;
+ return 9;
+ }
+ else if (n >= 10000000)
+ {
+ pow10 = 10000000;
+ return 8;
+ }
+ else if (n >= 1000000)
+ {
+ pow10 = 1000000;
+ return 7;
+ }
+ else if (n >= 100000)
+ {
+ pow10 = 100000;
+ return 6;
+ }
+ else if (n >= 10000)
+ {
+ pow10 = 10000;
+ return 5;
+ }
+ else if (n >= 1000)
+ {
+ pow10 = 1000;
+ return 4;
+ }
+ else if (n >= 100)
+ {
+ pow10 = 100;
+ return 3;
+ }
+ else if (n >= 10)
+ {
+ pow10 = 10;
+ return 2;
+ }
+ else
+ {
+ pow10 = 1;
+ return 1;
+ }
+}
+
+inline void grisu2_round(char* buf, int len, uint64_t dist, uint64_t delta,
+ uint64_t rest, uint64_t ten_k)
+{
+ assert(len >= 1);
+ assert(dist <= delta);
+ assert(rest <= delta);
+ assert(ten_k > 0);
+
+ // <--------------------------- delta ---->
+ // <---- dist --------->
+ // --------------[------------------+-------------------]--------------
+ // M- w M+
+ //
+ // ten_k
+ // <------>
+ // <---- rest ---->
+ // --------------[------------------+----+--------------]--------------
+ // w V
+ // = buf * 10^k
+ //
+ // ten_k represents a unit-in-the-last-place in the decimal representation
+ // stored in buf.
+ // Decrement buf by ten_k while this takes buf closer to w.
+
+ // The tests are written in this order to avoid overflow in unsigned
+ // integer arithmetic.
+
+ while (rest < dist
+ and delta - rest >= ten_k
+ and (rest + ten_k < dist or dist - rest > rest + ten_k - dist))
+ {
+ assert(buf[len - 1] != '0');
+ buf[len - 1]--;
+ rest += ten_k;
+ }
+}
+
+/*!
+Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+.
+M- and M+ must be normalized and share the same exponent -60 <= e <= -32.
+*/
+inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent,
+ diyfp M_minus, diyfp w, diyfp M_plus)
+{
+ static_assert(kAlpha >= -60, "internal error");
+ static_assert(kGamma <= -32, "internal error");
+
+ // Generates the digits (and the exponent) of a decimal floating-point
+ // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's
+ // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma.
+ //
+ // <--------------------------- delta ---->
+ // <---- dist --------->
+ // --------------[------------------+-------------------]--------------
+ // M- w M+
+ //
+ // Grisu2 generates the digits of M+ from left to right and stops as soon as
+ // V is in [M-,M+].
+
+ assert(M_plus.e >= kAlpha);
+ assert(M_plus.e <= kGamma);
+
+ uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e)
+ uint64_t dist = diyfp::sub(M_plus, w ).f; // (significand of (M+ - w ), implicit exponent is e)
+
+ // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0):
+ //
+ // M+ = f * 2^e
+ // = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e
+ // = ((p1 ) * 2^-e + (p2 )) * 2^e
+ // = p1 + p2 * 2^e
+
+ const diyfp one(uint64_t{1} << -M_plus.e, M_plus.e);
+
+ auto p1 = static_cast<uint32_t>(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.)
+ uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e
+
+ // 1)
+ //
+ // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0]
+
+ assert(p1 > 0);
+
+ uint32_t pow10;
+ const int k = find_largest_pow10(p1, pow10);
+
+ // 10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1)
+ //
+ // p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1))
+ // = (d[k-1] ) * 10^(k-1) + (p1 mod 10^(k-1))
+ //
+ // M+ = p1 + p2 * 2^e
+ // = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1)) + p2 * 2^e
+ // = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e
+ // = d[k-1] * 10^(k-1) + ( rest) * 2^e
+ //
+ // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0)
+ //
+ // p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0]
+ //
+ // but stop as soon as
+ //
+ // rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e
+
+ int n = k;
+ while (n > 0)
+ {
+ // Invariants:
+ // M+ = buffer * 10^n + (p1 + p2 * 2^e) (buffer = 0 for n = k)
+ // pow10 = 10^(n-1) <= p1 < 10^n
+ //
+ const uint32_t d = p1 / pow10; // d = p1 div 10^(n-1)
+ const uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1)
+ //
+ // M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e
+ // = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e)
+ //
+ assert(d <= 9);
+ buffer[length++] = static_cast<char>('0' + d); // buffer := buffer * 10 + d
+ //
+ // M+ = buffer * 10^(n-1) + (r + p2 * 2^e)
+ //
+ p1 = r;
+ n--;
+ //
+ // M+ = buffer * 10^n + (p1 + p2 * 2^e)
+ // pow10 = 10^n
+ //
+
+ // Now check if enough digits have been generated.
+ // Compute
+ //
+ // p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e
+ //
+ // Note:
+ // Since rest and delta share the same exponent e, it suffices to
+ // compare the significands.
+ const uint64_t rest = (uint64_t{p1} << -one.e) + p2;
+ if (rest <= delta)
+ {
+ // V = buffer * 10^n, with M- <= V <= M+.
+
+ decimal_exponent += n;
+
+ // We may now just stop. But instead look if the buffer could be
+ // decremented to bring V closer to w.
+ //
+ // pow10 = 10^n is now 1 ulp in the decimal representation V.
+ // The rounding procedure works with diyfp's with an implicit
+ // exponent of e.
+ //
+ // 10^n = (10^n * 2^-e) * 2^e = ulp * 2^e
+ //
+ const uint64_t ten_n = uint64_t{pow10} << -one.e;
+ grisu2_round(buffer, length, dist, delta, rest, ten_n);
+
+ return;
+ }
+
+ pow10 /= 10;
+ //
+ // pow10 = 10^(n-1) <= p1 < 10^n
+ // Invariants restored.
+ }
+
+ // 2)
+ //
+ // The digits of the integral part have been generated:
+ //
+ // M+ = d[k-1]...d[1]d[0] + p2 * 2^e
+ // = buffer + p2 * 2^e
+ //
+ // Now generate the digits of the fractional part p2 * 2^e.
+ //
+ // Note:
+ // No decimal point is generated: the exponent is adjusted instead.
+ //
+ // p2 actually represents the fraction
+ //
+ // p2 * 2^e
+ // = p2 / 2^-e
+ // = d[-1] / 10^1 + d[-2] / 10^2 + ...
+ //
+ // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...)
+ //
+ // p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m
+ // + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...)
+ //
+ // using
+ //
+ // 10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e)
+ // = ( d) * 2^-e + ( r)
+ //
+ // or
+ // 10^m * p2 * 2^e = d + r * 2^e
+ //
+ // i.e.
+ //
+ // M+ = buffer + p2 * 2^e
+ // = buffer + 10^-m * (d + r * 2^e)
+ // = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e
+ //
+ // and stop as soon as 10^-m * r * 2^e <= delta * 2^e
+
+ assert(p2 > delta);
+
+ int m = 0;
+ for (;;)
+ {
+ // Invariant:
+ // M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e
+ // = buffer * 10^-m + 10^-m * (p2 ) * 2^e
+ // = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e
+ // = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e
+ //
+ assert(p2 <= UINT64_MAX / 10);
+ p2 *= 10;
+ const uint64_t d = p2 >> -one.e; // d = (10 * p2) div 2^-e
+ const uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e
+ //
+ // M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e
+ // = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e))
+ // = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e
+ //
+ assert(d <= 9);
+ buffer[length++] = static_cast<char>('0' + d); // buffer := buffer * 10 + d
+ //
+ // M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e
+ //
+ p2 = r;
+ m++;
+ //
+ // M+ = buffer * 10^-m + 10^-m * p2 * 2^e
+ // Invariant restored.
+
+ // Check if enough digits have been generated.
+ //
+ // 10^-m * p2 * 2^e <= delta * 2^e
+ // p2 * 2^e <= 10^m * delta * 2^e
+ // p2 <= 10^m * delta
+ delta *= 10;
+ dist *= 10;
+ if (p2 <= delta)
+ {
+ break;
+ }
+ }
+
+ // V = buffer * 10^-m, with M- <= V <= M+.
+
+ decimal_exponent -= m;
+
+ // 1 ulp in the decimal representation is now 10^-m.
+ // Since delta and dist are now scaled by 10^m, we need to do the
+ // same with ulp in order to keep the units in sync.
+ //
+ // 10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e
+ //
+ const uint64_t ten_m = one.f;
+ grisu2_round(buffer, length, dist, delta, p2, ten_m);
+
+ // By construction this algorithm generates the shortest possible decimal
+ // number (Loitsch, Theorem 6.2) which rounds back to w.
+ // For an input number of precision p, at least
+ //
+ // N = 1 + ceil(p * log_10(2))
+ //
+ // decimal digits are sufficient to identify all binary floating-point
+ // numbers (Matula, "In-and-Out conversions").
+ // This implies that the algorithm does not produce more than N decimal
+ // digits.
+ //
+ // N = 17 for p = 53 (IEEE double precision)
+ // N = 9 for p = 24 (IEEE single precision)
+}
+
+/*!
+v = buf * 10^decimal_exponent
+len is the length of the buffer (number of decimal digits)
+The buffer must be large enough, i.e. >= max_digits10.
+*/
+inline void grisu2(char* buf, int& len, int& decimal_exponent,
+ diyfp m_minus, diyfp v, diyfp m_plus)
+{
+ assert(m_plus.e == m_minus.e);
+ assert(m_plus.e == v.e);
+
+ // --------(-----------------------+-----------------------)-------- (A)
+ // m- v m+
+ //
+ // --------------------(-----------+-----------------------)-------- (B)
+ // m- v m+
+ //
+ // First scale v (and m- and m+) such that the exponent is in the range
+ // [alpha, gamma].
+
+ const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e);
+
+ const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k
+
+ // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma]
+ const diyfp w = diyfp::mul(v, c_minus_k);
+ const diyfp w_minus = diyfp::mul(m_minus, c_minus_k);
+ const diyfp w_plus = diyfp::mul(m_plus, c_minus_k);
+
+ // ----(---+---)---------------(---+---)---------------(---+---)----
+ // w- w w+
+ // = c*m- = c*v = c*m+
+ //
+ // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and
+ // w+ are now off by a small amount.
+ // In fact:
+ //
+ // w - v * 10^k < 1 ulp
+ //
+ // To account for this inaccuracy, add resp. subtract 1 ulp.
+ //
+ // --------+---[---------------(---+---)---------------]---+--------
+ // w- M- w M+ w+
+ //
+ // Now any number in [M-, M+] (bounds included) will round to w when input,
+ // regardless of how the input rounding algorithm breaks ties.
+ //
+ // And digit_gen generates the shortest possible such number in [M-, M+].
+ // Note that this does not mean that Grisu2 always generates the shortest
+ // possible number in the interval (m-, m+).
+ const diyfp M_minus(w_minus.f + 1, w_minus.e);
+ const diyfp M_plus (w_plus.f - 1, w_plus.e );
+
+ decimal_exponent = -cached.k; // = -(-k) = k
+
+ grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus);
+}
+
+/*!
+v = buf * 10^decimal_exponent
+len is the length of the buffer (number of decimal digits)
+The buffer must be large enough, i.e. >= max_digits10.
+*/
+template <typename FloatType>
+void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value)
+{
+ static_assert(diyfp::kPrecision >= std::numeric_limits<FloatType>::digits + 3,
+ "internal error: not enough precision");
+
+ assert(std::isfinite(value));
+ assert(value > 0);
+
+ // If the neighbors (and boundaries) of 'value' are always computed for double-precision
+ // numbers, all float's can be recovered using strtod (and strtof). However, the resulting
+ // decimal representations are not exactly "short".
+ //
+ // The documentation for 'std::to_chars' (https://en.cppreference.com/w/cpp/utility/to_chars)
+ // says "value is converted to a string as if by std::sprintf in the default ("C") locale"
+ // and since sprintf promotes float's to double's, I think this is exactly what 'std::to_chars'
+ // does.
+ // On the other hand, the documentation for 'std::to_chars' requires that "parsing the
+ // representation using the corresponding std::from_chars function recovers value exactly". That
+ // indicates that single precision floating-point numbers should be recovered using
+ // 'std::strtof'.
+ //
+ // NB: If the neighbors are computed for single-precision numbers, there is a single float
+ // (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision
+ // value is off by 1 ulp.
+#if 0
+ const boundaries w = compute_boundaries(static_cast<double>(value));
+#else
+ const boundaries w = compute_boundaries(value);
+#endif
+
+ grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus);
+}
+
+/*!
+@brief appends a decimal representation of e to buf
+@return a pointer to the element following the exponent.
+@pre -1000 < e < 1000
+*/
+inline char* append_exponent(char* buf, int e)
+{
+ assert(e > -1000);
+ assert(e < 1000);
+
+ if (e < 0)
+ {
+ e = -e;
+ *buf++ = '-';
+ }
+ else
+ {
+ *buf++ = '+';
+ }
+
+ auto k = static_cast<uint32_t>(e);
+ if (k < 10)
+ {
+ // Always print at least two digits in the exponent.
+ // This is for compatibility with printf("%g").
+ *buf++ = '0';
+ *buf++ = static_cast<char>('0' + k);
+ }
+ else if (k < 100)
+ {
+ *buf++ = static_cast<char>('0' + k / 10);
+ k %= 10;
+ *buf++ = static_cast<char>('0' + k);
+ }
+ else
+ {
+ *buf++ = static_cast<char>('0' + k / 100);
+ k %= 100;
+ *buf++ = static_cast<char>('0' + k / 10);
+ k %= 10;
+ *buf++ = static_cast<char>('0' + k);
+ }
+
+ return buf;
+}
+
+/*!
+@brief prettify v = buf * 10^decimal_exponent
+
+If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point
+notation. Otherwise it will be printed in exponential notation.
+
+@pre min_exp < 0
+@pre max_exp > 0
+*/
+inline char* format_buffer(char* buf, int len, int decimal_exponent,
+ int min_exp, int max_exp)
+{
+ assert(min_exp < 0);
+ assert(max_exp > 0);
+
+ const int k = len;
+ const int n = len + decimal_exponent;
+
+ // v = buf * 10^(n-k)
+ // k is the length of the buffer (number of decimal digits)
+ // n is the position of the decimal point relative to the start of the buffer.
+
+ if (k <= n and n <= max_exp)
+ {
+ // digits[000]
+ // len <= max_exp + 2
+
+ std::memset(buf + k, '0', static_cast<size_t>(n - k));
+ // Make it look like a floating-point number (#362, #378)
+ buf[n + 0] = '.';
+ buf[n + 1] = '0';
+ return buf + (n + 2);
+ }
+
+ if (0 < n and n <= max_exp)
+ {
+ // dig.its
+ // len <= max_digits10 + 1
+
+ assert(k > n);
+
+ std::memmove(buf + (n + 1), buf + n, static_cast<size_t>(k - n));
+ buf[n] = '.';
+ return buf + (k + 1);
+ }
+
+ if (min_exp < n and n <= 0)
+ {
+ // 0.[000]digits
+ // len <= 2 + (-min_exp - 1) + max_digits10
+
+ std::memmove(buf + (2 + -n), buf, static_cast<size_t>(k));
+ buf[0] = '0';
+ buf[1] = '.';
+ std::memset(buf + 2, '0', static_cast<size_t>(-n));
+ return buf + (2 + (-n) + k);
+ }
+
+ if (k == 1)
+ {
+ // dE+123
+ // len <= 1 + 5
+
+ buf += 1;
+ }
+ else
+ {
+ // d.igitsE+123
+ // len <= max_digits10 + 1 + 5
+
+ std::memmove(buf + 2, buf + 1, static_cast<size_t>(k - 1));
+ buf[1] = '.';
+ buf += 1 + k;
+ }
+
+ *buf++ = 'e';
+ return append_exponent(buf, n - 1);
+}
+
+} // namespace dtoa_impl
+
+/*!
+@brief generates a decimal representation of the floating-point number value in [first, last).
+
+The format of the resulting decimal representation is similar to printf's %g
+format. Returns an iterator pointing past-the-end of the decimal representation.
+
+@note The input number must be finite, i.e. NaN's and Inf's are not supported.
+@note The buffer must be large enough.
+@note The result is NOT null-terminated.
+*/
+template <typename FloatType>
+char* to_chars(char* first, const char* last, FloatType value)
+{
+ static_cast<void>(last); // maybe unused - fix warning
+ assert(std::isfinite(value));
+
+ // Use signbit(value) instead of (value < 0) since signbit works for -0.
+ if (std::signbit(value))
+ {
+ value = -value;
+ *first++ = '-';
+ }
+
+ if (value == 0) // +-0
+ {
+ *first++ = '0';
+ // Make it look like a floating-point number (#362, #378)
+ *first++ = '.';
+ *first++ = '0';
+ return first;
+ }
+
+ assert(last - first >= std::numeric_limits<FloatType>::max_digits10);
+
+ // Compute v = buffer * 10^decimal_exponent.
+ // The decimal digits are stored in the buffer, which needs to be interpreted
+ // as an unsigned decimal integer.
+ // len is the length of the buffer, i.e. the number of decimal digits.
+ int len = 0;
+ int decimal_exponent = 0;
+ dtoa_impl::grisu2(first, len, decimal_exponent, value);
+
+ assert(len <= std::numeric_limits<FloatType>::max_digits10);
+
+ // Format the buffer like printf("%.*g", prec, value)
+ constexpr int kMinExp = -4;
+ // Use digits10 here to increase compatibility with version 2.
+ constexpr int kMaxExp = std::numeric_limits<FloatType>::digits10;
+
+ assert(last - first >= kMaxExp + 2);
+ assert(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits<FloatType>::max_digits10);
+ assert(last - first >= std::numeric_limits<FloatType>::max_digits10 + 6);
+
+ return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp);
+}
+
+} // namespace detail
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/conversions/to_json.hpp b/include/lib/modernjson/detail/conversions/to_json.hpp
new file mode 100644
index 0000000..5c3669c
--- /dev/null
+++ b/include/lib/modernjson/detail/conversions/to_json.hpp
@@ -0,0 +1,342 @@
+#pragma once
+
+#include <ciso646> // or, and, not
+#include <iterator> // begin, end
+#include <tuple> // tuple, get
+#include <type_traits> // is_same, is_constructible, is_floating_point, is_enum, underlying_type
+#include <utility> // move, forward, declval, pair
+#include <valarray> // valarray
+#include <vector> // vector
+
+#include <lib/modernjson/detail/meta/cpp_future.hpp>
+#include <lib/modernjson/detail/meta/type_traits.hpp>
+#include <lib/modernjson/detail/value_t.hpp>
+#include <lib/modernjson/detail/iterators/iteration_proxy.hpp>
+
+namespace nlohmann
+{
+namespace detail
+{
+//////////////////
+// constructors //
+//////////////////
+
+template<value_t> struct external_constructor;
+
+template<>
+struct external_constructor<value_t::boolean>
+{
+ template<typename BasicJsonType>
+ static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept
+ {
+ j.m_type = value_t::boolean;
+ j.m_value = b;
+ j.assert_invariant();
+ }
+};
+
+template<>
+struct external_constructor<value_t::string>
+{
+ template<typename BasicJsonType>
+ static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s)
+ {
+ j.m_type = value_t::string;
+ j.m_value = s;
+ j.assert_invariant();
+ }
+
+ template<typename BasicJsonType>
+ static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s)
+ {
+ j.m_type = value_t::string;
+ j.m_value = std::move(s);
+ j.assert_invariant();
+ }
+
+ template<typename BasicJsonType, typename CompatibleStringType,
+ enable_if_t<not std::is_same<CompatibleStringType, typename BasicJsonType::string_t>::value,
+ int> = 0>
+ static void construct(BasicJsonType& j, const CompatibleStringType& str)
+ {
+ j.m_type = value_t::string;
+ j.m_value.string = j.template create<typename BasicJsonType::string_t>(str);
+ j.assert_invariant();
+ }
+};
+
+template<>
+struct external_constructor<value_t::number_float>
+{
+ template<typename BasicJsonType>
+ static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept
+ {
+ j.m_type = value_t::number_float;
+ j.m_value = val;
+ j.assert_invariant();
+ }
+};
+
+template<>
+struct external_constructor<value_t::number_unsigned>
+{
+ template<typename BasicJsonType>
+ static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept
+ {
+ j.m_type = value_t::number_unsigned;
+ j.m_value = val;
+ j.assert_invariant();
+ }
+};
+
+template<>
+struct external_constructor<value_t::number_integer>
+{
+ template<typename BasicJsonType>
+ static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept
+ {
+ j.m_type = value_t::number_integer;
+ j.m_value = val;
+ j.assert_invariant();
+ }
+};
+
+template<>
+struct external_constructor<value_t::array>
+{
+ template<typename BasicJsonType>
+ static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr)
+ {
+ j.m_type = value_t::array;
+ j.m_value = arr;
+ j.assert_invariant();
+ }
+
+ template<typename BasicJsonType>
+ static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr)
+ {
+ j.m_type = value_t::array;
+ j.m_value = std::move(arr);
+ j.assert_invariant();
+ }
+
+ template<typename BasicJsonType, typename CompatibleArrayType,
+ enable_if_t<not std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value,
+ int> = 0>
+ static void construct(BasicJsonType& j, const CompatibleArrayType& arr)
+ {
+ using std::begin;
+ using std::end;
+ j.m_type = value_t::array;
+ j.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));
+ j.assert_invariant();
+ }
+
+ template<typename BasicJsonType>
+ static void construct(BasicJsonType& j, const std::vector<bool>& arr)
+ {
+ j.m_type = value_t::array;
+ j.m_value = value_t::array;
+ j.m_value.array->reserve(arr.size());
+ for (const bool x : arr)
+ {
+ j.m_value.array->push_back(x);
+ }
+ j.assert_invariant();
+ }
+
+ template<typename BasicJsonType, typename T,
+ enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
+ static void construct(BasicJsonType& j, const std::valarray<T>& arr)
+ {
+ j.m_type = value_t::array;
+ j.m_value = value_t::array;
+ j.m_value.array->resize(arr.size());
+ std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin());
+ j.assert_invariant();
+ }
+};
+
+template<>
+struct external_constructor<value_t::object>
+{
+ template<typename BasicJsonType>
+ static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj)
+ {
+ j.m_type = value_t::object;
+ j.m_value = obj;
+ j.assert_invariant();
+ }
+
+ template<typename BasicJsonType>
+ static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
+ {
+ j.m_type = value_t::object;
+ j.m_value = std::move(obj);
+ j.assert_invariant();
+ }
+
+ template<typename BasicJsonType, typename CompatibleObjectType,
+ enable_if_t<not std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value, int> = 0>
+ static void construct(BasicJsonType& j, const CompatibleObjectType& obj)
+ {
+ using std::begin;
+ using std::end;
+
+ j.m_type = value_t::object;
+ j.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));
+ j.assert_invariant();
+ }
+};
+
+/////////////
+// to_json //
+/////////////
+
+template<typename BasicJsonType, typename T,
+ enable_if_t<std::is_same<T, typename BasicJsonType::boolean_t>::value, int> = 0>
+void to_json(BasicJsonType& j, T b) noexcept
+{
+ external_constructor<value_t::boolean>::construct(j, b);
+}
+
+template<typename BasicJsonType, typename CompatibleString,
+ enable_if_t<std::is_constructible<typename BasicJsonType::string_t, CompatibleString>::value, int> = 0>
+void to_json(BasicJsonType& j, const CompatibleString& s)
+{
+ external_constructor<value_t::string>::construct(j, s);
+}
+
+template<typename BasicJsonType>
+void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s)
+{
+ external_constructor<value_t::string>::construct(j, std::move(s));
+}
+
+template<typename BasicJsonType, typename FloatType,
+ enable_if_t<std::is_floating_point<FloatType>::value, int> = 0>
+void to_json(BasicJsonType& j, FloatType val) noexcept
+{
+ external_constructor<value_t::number_float>::construct(j, static_cast<typename BasicJsonType::number_float_t>(val));
+}
+
+template<typename BasicJsonType, typename CompatibleNumberUnsignedType,
+ enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_unsigned_t, CompatibleNumberUnsignedType>::value, int> = 0>
+void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept
+{
+ external_constructor<value_t::number_unsigned>::construct(j, static_cast<typename BasicJsonType::number_unsigned_t>(val));
+}
+
+template<typename BasicJsonType, typename CompatibleNumberIntegerType,
+ enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType>::value, int> = 0>
+void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept
+{
+ external_constructor<value_t::number_integer>::construct(j, static_cast<typename BasicJsonType::number_integer_t>(val));
+}
+
+template<typename BasicJsonType, typename EnumType,
+ enable_if_t<std::is_enum<EnumType>::value, int> = 0>
+void to_json(BasicJsonType& j, EnumType e) noexcept
+{
+ using underlying_type = typename std::underlying_type<EnumType>::type;
+ external_constructor<value_t::number_integer>::construct(j, static_cast<underlying_type>(e));
+}
+
+template<typename BasicJsonType>
+void to_json(BasicJsonType& j, const std::vector<bool>& e)
+{
+ external_constructor<value_t::array>::construct(j, e);
+}
+
+template <typename BasicJsonType, typename CompatibleArrayType,
+ enable_if_t<is_compatible_array_type<BasicJsonType,
+ CompatibleArrayType>::value and
+ not is_compatible_object_type<
+ BasicJsonType, CompatibleArrayType>::value and
+ not is_compatible_string_type<BasicJsonType, CompatibleArrayType>::value and
+ not is_basic_json<CompatibleArrayType>::value,
+ int> = 0>
+void to_json(BasicJsonType& j, const CompatibleArrayType& arr)
+{
+ external_constructor<value_t::array>::construct(j, arr);
+}
+
+template<typename BasicJsonType, typename T,
+ enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
+void to_json(BasicJsonType& j, const std::valarray<T>& arr)
+{
+ external_constructor<value_t::array>::construct(j, std::move(arr));
+}
+
+template<typename BasicJsonType>
+void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr)
+{
+ external_constructor<value_t::array>::construct(j, std::move(arr));
+}
+
+template<typename BasicJsonType, typename CompatibleObjectType,
+ enable_if_t<is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value and not is_basic_json<CompatibleObjectType>::value, int> = 0>
+void to_json(BasicJsonType& j, const CompatibleObjectType& obj)
+{
+ external_constructor<value_t::object>::construct(j, obj);
+}
+
+template<typename BasicJsonType>
+void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
+{
+ external_constructor<value_t::object>::construct(j, std::move(obj));
+}
+
+template <
+ typename BasicJsonType, typename T, std::size_t N,
+ enable_if_t<not std::is_constructible<typename BasicJsonType::string_t,
+ const T (&)[N]>::value,
+ int> = 0 >
+void to_json(BasicJsonType& j, const T (&arr)[N])
+{
+ external_constructor<value_t::array>::construct(j, arr);
+}
+
+template<typename BasicJsonType, typename... Args>
+void to_json(BasicJsonType& j, const std::pair<Args...>& p)
+{
+ j = {p.first, p.second};
+}
+
+// for https://github.com/nlohmann/json/pull/1134
+template<typename BasicJsonType, typename T,
+ enable_if_t<std::is_same<T, typename iteration_proxy<typename BasicJsonType::iterator>::iteration_proxy_internal>::value, int> = 0>
+void to_json(BasicJsonType& j, const T& b)
+{
+ j = {{b.key(), b.value()}};
+}
+
+template<typename BasicJsonType, typename Tuple, std::size_t... Idx>
+void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<Idx...> /*unused*/)
+{
+ j = {std::get<Idx>(t)...};
+}
+
+template<typename BasicJsonType, typename... Args>
+void to_json(BasicJsonType& j, const std::tuple<Args...>& t)
+{
+ to_json_tuple_impl(j, t, index_sequence_for<Args...> {});
+}
+
+struct to_json_fn
+{
+ template<typename BasicJsonType, typename T>
+ auto operator()(BasicJsonType& j, T&& val) const noexcept(noexcept(to_json(j, std::forward<T>(val))))
+ -> decltype(to_json(j, std::forward<T>(val)), void())
+ {
+ return to_json(j, std::forward<T>(val));
+ }
+};
+} // namespace detail
+
+/// namespace to hold default `to_json` function
+namespace
+{
+constexpr const auto& to_json = detail::static_const<detail::to_json_fn>::value;
+} // namespace
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/exceptions.hpp b/include/lib/modernjson/detail/exceptions.hpp
new file mode 100644
index 0000000..bcb1f2e
--- /dev/null
+++ b/include/lib/modernjson/detail/exceptions.hpp
@@ -0,0 +1,349 @@
+#pragma once
+
+#include <exception> // exception
+#include <stdexcept> // runtime_error
+#include <string> // to_string
+
+#include <lib/modernjson/detail/input/position_t.hpp>
+
+namespace nlohmann
+{
+namespace detail
+{
+////////////////
+// exceptions //
+////////////////
+
+/*!
+@brief general exception of the @ref basic_json class
+
+This class is an extension of `std::exception` objects with a member @a id for
+exception ids. It is used as the base class for all exceptions thrown by the
+@ref basic_json class. This class can hence be used as "wildcard" to catch
+exceptions.
+
+Subclasses:
+- @ref parse_error for exceptions indicating a parse error
+- @ref invalid_iterator for exceptions indicating errors with iterators
+- @ref type_error for exceptions indicating executing a member function with
+ a wrong type
+- @ref out_of_range for exceptions indicating access out of the defined range
+- @ref other_error for exceptions indicating other library errors
+
+@internal
+@note To have nothrow-copy-constructible exceptions, we internally use
+ `std::runtime_error` which can cope with arbitrary-length error messages.
+ Intermediate strings are built with static functions and then passed to
+ the actual constructor.
+@endinternal
+
+@liveexample{The following code shows how arbitrary library exceptions can be
+caught.,exception}
+
+@since version 3.0.0
+*/
+class exception : public std::exception
+{
+ public:
+ /// returns the explanatory string
+ const char* what() const noexcept override
+ {
+ return m.what();
+ }
+
+ /// the id of the exception
+ const int id;
+
+ protected:
+ exception(int id_, const char* what_arg) : id(id_), m(what_arg) {}
+
+ static std::string name(const std::string& ename, int id_)
+ {
+ return "[json.exception." + ename + "." + std::to_string(id_) + "] ";
+ }
+
+ private:
+ /// an exception object as storage for error messages
+ std::runtime_error m;
+};
+
+/*!
+@brief exception indicating a parse error
+
+This exception is thrown by the library when a parse error occurs. Parse errors
+can occur during the deserialization of JSON text, CBOR, MessagePack, as well
+as when using JSON Patch.
+
+Member @a byte holds the byte index of the last read character in the input
+file.
+
+Exceptions have ids 1xx.
+
+name / id | example message | description
+------------------------------ | --------------- | -------------------------
+json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position.
+json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point.
+json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid.
+json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects.
+json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors.
+json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number without a leading `0`.
+json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character.
+json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences.
+json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number.
+json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read.
+json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xF8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read.
+json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read.
+json.exception.parse_error.114 | parse error: Unsupported BSON record type 0x0F | The parsing of the corresponding BSON record type is not implemented (yet).
+
+@note For an input with n bytes, 1 is the index of the first character and n+1
+ is the index of the terminating null byte or the end of file. This also
+ holds true when reading a byte vector (CBOR or MessagePack).
+
+@liveexample{The following code shows how a `parse_error` exception can be
+caught.,parse_error}
+
+@sa @ref exception for the base class of the library exceptions
+@sa @ref invalid_iterator for exceptions indicating errors with iterators
+@sa @ref type_error for exceptions indicating executing a member function with
+ a wrong type
+@sa @ref out_of_range for exceptions indicating access out of the defined range
+@sa @ref other_error for exceptions indicating other library errors
+
+@since version 3.0.0
+*/
+class parse_error : public exception
+{
+ public:
+ /*!
+ @brief create a parse error exception
+ @param[in] id_ the id of the exception
+ @param[in] position the position where the error occurred (or with
+ chars_read_total=0 if the position cannot be
+ determined)
+ @param[in] what_arg the explanatory string
+ @return parse_error object
+ */
+ static parse_error create(int id_, const position_t& pos, const std::string& what_arg)
+ {
+ std::string w = exception::name("parse_error", id_) + "parse error" +
+ position_string(pos) + ": " + what_arg;
+ return parse_error(id_, pos.chars_read_total, w.c_str());
+ }
+
+ static parse_error create(int id_, std::size_t byte_, const std::string& what_arg)
+ {
+ std::string w = exception::name("parse_error", id_) + "parse error" +
+ (byte_ != 0 ? (" at byte " + std::to_string(byte_)) : "") +
+ ": " + what_arg;
+ return parse_error(id_, byte_, w.c_str());
+ }
+
+ /*!
+ @brief byte index of the parse error
+
+ The byte index of the last read character in the input file.
+
+ @note For an input with n bytes, 1 is the index of the first character and
+ n+1 is the index of the terminating null byte or the end of file.
+ This also holds true when reading a byte vector (CBOR or MessagePack).
+ */
+ const std::size_t byte;
+
+ private:
+ parse_error(int id_, std::size_t byte_, const char* what_arg)
+ : exception(id_, what_arg), byte(byte_) {}
+
+ static std::string position_string(const position_t& pos)
+ {
+ return " at line " + std::to_string(pos.lines_read + 1) +
+ ", column " + std::to_string(pos.chars_read_current_line);
+ }
+};
+
+/*!
+@brief exception indicating errors with iterators
+
+This exception is thrown if iterators passed to a library function do not match
+the expected semantics.
+
+Exceptions have ids 2xx.
+
+name / id | example message | description
+----------------------------------- | --------------- | -------------------------
+json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid.
+json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion.
+json.exception.invalid_iterator.203 | iterators do not fit current value | Either iterator passed to function @ref erase(IteratorType first, IteratorType last) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from.
+json.exception.invalid_iterator.204 | iterators out of range | When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an erase function, this range has to be exactly (@ref begin(), @ref end()), because this is the only way the single stored value is expressed. All other ranges are invalid.
+json.exception.invalid_iterator.205 | iterator out of range | When an iterator for a primitive type (number, boolean, or string) is passed to an erase function, the iterator has to be the @ref begin() iterator, because it is the only way to address the stored value. All other iterators are invalid.
+json.exception.invalid_iterator.206 | cannot construct with iterators from null | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) belong to a JSON null value and hence to not define a valid range.
+json.exception.invalid_iterator.207 | cannot use key() for non-object iterators | The key() member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key.
+json.exception.invalid_iterator.208 | cannot use operator[] for object iterators | The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered.
+json.exception.invalid_iterator.209 | cannot use offsets with object iterators | The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered.
+json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid.
+json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to.
+json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container.
+json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered.
+json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin().
+
+@liveexample{The following code shows how an `invalid_iterator` exception can be
+caught.,invalid_iterator}
+
+@sa @ref exception for the base class of the library exceptions
+@sa @ref parse_error for exceptions indicating a parse error
+@sa @ref type_error for exceptions indicating executing a member function with
+ a wrong type
+@sa @ref out_of_range for exceptions indicating access out of the defined range
+@sa @ref other_error for exceptions indicating other library errors
+
+@since version 3.0.0
+*/
+class invalid_iterator : public exception
+{
+ public:
+ static invalid_iterator create(int id_, const std::string& what_arg)
+ {
+ std::string w = exception::name("invalid_iterator", id_) + what_arg;
+ return invalid_iterator(id_, w.c_str());
+ }
+
+ private:
+ invalid_iterator(int id_, const char* what_arg)
+ : exception(id_, what_arg) {}
+};
+
+/*!
+@brief exception indicating executing a member function with a wrong type
+
+This exception is thrown in case of a type error; that is, a library function is
+executed on a JSON value whose type does not match the expected semantics.
+
+Exceptions have ids 3xx.
+
+name / id | example message | description
+----------------------------- | --------------- | -------------------------
+json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead.
+json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types.
+json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual type is object | To retrieve a reference to a value stored in a @ref basic_json object with @ref get_ref, the type of the reference must match the value type. For instance, for a JSON array, the @a ReferenceType must be @ref array_t&.
+json.exception.type_error.304 | cannot use at() with string | The @ref at() member functions can only be executed for certain JSON types.
+json.exception.type_error.305 | cannot use operator[] with string | The @ref operator[] member functions can only be executed for certain JSON types.
+json.exception.type_error.306 | cannot use value() with string | The @ref value() member functions can only be executed for certain JSON types.
+json.exception.type_error.307 | cannot use erase() with string | The @ref erase() member functions can only be executed for certain JSON types.
+json.exception.type_error.308 | cannot use push_back() with string | The @ref push_back() and @ref operator+= member functions can only be executed for certain JSON types.
+json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types.
+json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types.
+json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types.
+json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types.
+json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined.
+json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers.
+json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive.
+json.exception.type_error.316 | invalid UTF-8 byte at index 10: 0x7E | The @ref dump function only works with UTF-8 encoded strings; that is, if you assign a `std::string` to a JSON value, make sure it is UTF-8 encoded. |
+json.exception.type_error.317 | JSON value cannot be serialized to requested format | The dynamic type of the object cannot be represented in the requested serialization format (e.g. a raw `true` or `null` JSON object cannot be serialized to BSON) |
+
+@liveexample{The following code shows how a `type_error` exception can be
+caught.,type_error}
+
+@sa @ref exception for the base class of the library exceptions
+@sa @ref parse_error for exceptions indicating a parse error
+@sa @ref invalid_iterator for exceptions indicating errors with iterators
+@sa @ref out_of_range for exceptions indicating access out of the defined range
+@sa @ref other_error for exceptions indicating other library errors
+
+@since version 3.0.0
+*/
+class type_error : public exception
+{
+ public:
+ static type_error create(int id_, const std::string& what_arg)
+ {
+ std::string w = exception::name("type_error", id_) + what_arg;
+ return type_error(id_, w.c_str());
+ }
+
+ private:
+ type_error(int id_, const char* what_arg) : exception(id_, what_arg) {}
+};
+
+/*!
+@brief exception indicating access out of the defined range
+
+This exception is thrown in case a library function is called on an input
+parameter that exceeds the expected range, for instance in case of array
+indices or nonexisting object keys.
+
+Exceptions have ids 4xx.
+
+name / id | example message | description
+------------------------------- | --------------- | -------------------------
+json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1.
+json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it.
+json.exception.out_of_range.403 | key 'foo' not found | The provided key was not found in the JSON object.
+json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved.
+json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value.
+json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF.
+json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON and BSON only support integer numbers up to 9223372036854775807. |
+json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. |
+json.exception.out_of_range.409 | BSON key cannot contain code point U+0000 (at byte 2) | Key identifiers to be serialized to BSON cannot contain code point U+0000, since the key is stored as zero-terminated c-string |
+
+@liveexample{The following code shows how an `out_of_range` exception can be
+caught.,out_of_range}
+
+@sa @ref exception for the base class of the library exceptions
+@sa @ref parse_error for exceptions indicating a parse error
+@sa @ref invalid_iterator for exceptions indicating errors with iterators
+@sa @ref type_error for exceptions indicating executing a member function with
+ a wrong type
+@sa @ref other_error for exceptions indicating other library errors
+
+@since version 3.0.0
+*/
+class out_of_range : public exception
+{
+ public:
+ static out_of_range create(int id_, const std::string& what_arg)
+ {
+ std::string w = exception::name("out_of_range", id_) + what_arg;
+ return out_of_range(id_, w.c_str());
+ }
+
+ private:
+ out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {}
+};
+
+/*!
+@brief exception indicating other library errors
+
+This exception is thrown in case of errors that cannot be classified with the
+other exception types.
+
+Exceptions have ids 5xx.
+
+name / id | example message | description
+------------------------------ | --------------- | -------------------------
+json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed.
+
+@sa @ref exception for the base class of the library exceptions
+@sa @ref parse_error for exceptions indicating a parse error
+@sa @ref invalid_iterator for exceptions indicating errors with iterators
+@sa @ref type_error for exceptions indicating executing a member function with
+ a wrong type
+@sa @ref out_of_range for exceptions indicating access out of the defined range
+
+@liveexample{The following code shows how an `other_error` exception can be
+caught.,other_error}
+
+@since version 3.0.0
+*/
+class other_error : public exception
+{
+ public:
+ static other_error create(int id_, const std::string& what_arg)
+ {
+ std::string w = exception::name("other_error", id_) + what_arg;
+ return other_error(id_, w.c_str());
+ }
+
+ private:
+ other_error(int id_, const char* what_arg) : exception(id_, what_arg) {}
+};
+} // namespace detail
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/input/binary_reader.hpp b/include/lib/modernjson/detail/input/binary_reader.hpp
new file mode 100644
index 0000000..637569a
--- /dev/null
+++ b/include/lib/modernjson/detail/input/binary_reader.hpp
@@ -0,0 +1,1984 @@
+#pragma once
+
+#include <algorithm> // generate_n
+#include <array> // array
+#include <cassert> // assert
+#include <cmath> // ldexp
+#include <cstddef> // size_t
+#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
+#include <cstdio> // snprintf
+#include <cstring> // memcpy
+#include <iterator> // back_inserter
+#include <limits> // numeric_limits
+#include <string> // char_traits, string
+#include <utility> // make_pair, move
+
+#include <lib/modernjson/detail/input/input_adapters.hpp>
+#include <lib/modernjson/detail/input/json_sax.hpp>
+#include <lib/modernjson/detail/exceptions.hpp>
+#include <lib/modernjson/detail/macro_scope.hpp>
+#include <lib/modernjson/detail/meta/is_sax.hpp>
+#include <lib/modernjson/detail/value_t.hpp>
+
+namespace nlohmann
+{
+namespace detail
+{
+///////////////////
+// binary reader //
+///////////////////
+
+/*!
+@brief deserialization of CBOR, MessagePack, and UBJSON values
+*/
+template<typename BasicJsonType, typename SAX = json_sax_dom_parser<BasicJsonType>>
+class binary_reader
+{
+ using number_integer_t = typename BasicJsonType::number_integer_t;
+ using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+ using number_float_t = typename BasicJsonType::number_float_t;
+ using string_t = typename BasicJsonType::string_t;
+ using json_sax_t = SAX;
+
+ public:
+ /*!
+ @brief create a binary reader
+
+ @param[in] adapter input adapter to read from
+ */
+ explicit binary_reader(input_adapter_t adapter) : ia(std::move(adapter))
+ {
+ (void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
+ assert(ia);
+ }
+
+ /*!
+ @param[in] format the binary format to parse
+ @param[in] sax_ a SAX event processor
+ @param[in] strict whether to expect the input to be consumed completed
+
+ @return
+ */
+ bool sax_parse(const input_format_t format,
+ json_sax_t* sax_,
+ const bool strict = true)
+ {
+ sax = sax_;
+ bool result = false;
+
+ switch (format)
+ {
+ case input_format_t::bson:
+ result = parse_bson_internal();
+ break;
+
+ case input_format_t::cbor:
+ result = parse_cbor_internal();
+ break;
+
+ case input_format_t::msgpack:
+ result = parse_msgpack_internal();
+ break;
+
+ case input_format_t::ubjson:
+ result = parse_ubjson_internal();
+ break;
+
+ // LCOV_EXCL_START
+ default:
+ assert(false);
+ // LCOV_EXCL_STOP
+ }
+
+ // strict mode: next byte must be EOF
+ if (result and strict)
+ {
+ if (format == input_format_t::ubjson)
+ {
+ get_ignore_noop();
+ }
+ else
+ {
+ get();
+ }
+
+ if (JSON_UNLIKELY(current != std::char_traits<char>::eof()))
+ {
+ return sax->parse_error(chars_read, get_token_string(),
+ parse_error::create(110, chars_read, exception_message(format, "expected end of input; last byte: 0x" + get_token_string(), "value")));
+ }
+ }
+
+ return result;
+ }
+
+ /*!
+ @brief determine system byte order
+
+ @return true if and only if system's byte order is little endian
+
+ @note from http://stackoverflow.com/a/1001328/266378
+ */
+ static constexpr bool little_endianess(int num = 1) noexcept
+ {
+ return (*reinterpret_cast<char*>(&num) == 1);
+ }
+
+ private:
+ //////////
+ // BSON //
+ //////////
+
+ /*!
+ @brief Reads in a BSON-object and passes it to the SAX-parser.
+ @return whether a valid BSON-value was passed to the SAX parser
+ */
+ bool parse_bson_internal()
+ {
+ std::int32_t document_size;
+ get_number<std::int32_t, true>(input_format_t::bson, document_size);
+
+ if (JSON_UNLIKELY(not sax->start_object(std::size_t(-1))))
+ {
+ return false;
+ }
+
+ if (JSON_UNLIKELY(not parse_bson_element_list(/*is_array*/false)))
+ {
+ return false;
+ }
+
+ return sax->end_object();
+ }
+
+ /*!
+ @brief Parses a C-style string from the BSON input.
+ @param[in, out] result A reference to the string variable where the read
+ string is to be stored.
+ @return `true` if the \x00-byte indicating the end of the string was
+ encountered before the EOF; false` indicates an unexpected EOF.
+ */
+ bool get_bson_cstr(string_t& result)
+ {
+ auto out = std::back_inserter(result);
+ while (true)
+ {
+ get();
+ if (JSON_UNLIKELY(not unexpect_eof(input_format_t::bson, "cstring")))
+ {
+ return false;
+ }
+ if (current == 0x00)
+ {
+ return true;
+ }
+ *out++ = static_cast<char>(current);
+ }
+
+ return true;
+ }
+
+ /*!
+ @brief Parses a zero-terminated string of length @a len from the BSON
+ input.
+ @param[in] len The length (including the zero-byte at the end) of the
+ string to be read.
+ @param[in, out] result A reference to the string variable where the read
+ string is to be stored.
+ @tparam NumberType The type of the length @a len
+ @pre len >= 1
+ @return `true` if the string was successfully parsed
+ */
+ template<typename NumberType>
+ bool get_bson_string(const NumberType len, string_t& result)
+ {
+ if (JSON_UNLIKELY(len < 1))
+ {
+ auto last_token = get_token_string();
+ return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::bson, "string length must be at least 1, is " + std::to_string(len), "string")));
+ }
+
+ return get_string(input_format_t::bson, len - static_cast<NumberType>(1), result) and get() != std::char_traits<char>::eof();
+ }
+
+ /*!
+ @brief Read a BSON document element of the given @a element_type.
+ @param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html
+ @param[in] element_type_parse_position The position in the input stream,
+ where the `element_type` was read.
+ @warning Not all BSON element types are supported yet. An unsupported
+ @a element_type will give rise to a parse_error.114:
+ Unsupported BSON record type 0x...
+ @return whether a valid BSON-object/array was passed to the SAX parser
+ */
+ bool parse_bson_element_internal(const int element_type,
+ const std::size_t element_type_parse_position)
+ {
+ switch (element_type)
+ {
+ case 0x01: // double
+ {
+ double number;
+ return get_number<double, true>(input_format_t::bson, number) and sax->number_float(static_cast<number_float_t>(number), "");
+ }
+
+ case 0x02: // string
+ {
+ std::int32_t len;
+ string_t value;
+ return get_number<std::int32_t, true>(input_format_t::bson, len) and get_bson_string(len, value) and sax->string(value);
+ }
+
+ case 0x03: // object
+ {
+ return parse_bson_internal();
+ }
+
+ case 0x04: // array
+ {
+ return parse_bson_array();
+ }
+
+ case 0x08: // boolean
+ {
+ return sax->boolean(get() != 0);
+ }
+
+ case 0x0A: // null
+ {
+ return sax->null();
+ }
+
+ case 0x10: // int32
+ {
+ std::int32_t value;
+ return get_number<std::int32_t, true>(input_format_t::bson, value) and sax->number_integer(value);
+ }
+
+ case 0x12: // int64
+ {
+ std::int64_t value;
+ return get_number<std::int64_t, true>(input_format_t::bson, value) and sax->number_integer(value);
+ }
+
+ default: // anything else not supported (yet)
+ {
+ char cr[3];
+ (std::snprintf)(cr, sizeof(cr), "%.2hhX", static_cast<unsigned char>(element_type));
+ return sax->parse_error(element_type_parse_position, std::string(cr), parse_error::create(114, element_type_parse_position, "Unsupported BSON record type 0x" + std::string(cr)));
+ }
+ }
+ }
+
+ /*!
+ @brief Read a BSON element list (as specified in the BSON-spec)
+
+ The same binary layout is used for objects and arrays, hence it must be
+ indicated with the argument @a is_array which one is expected
+ (true --> array, false --> object).
+
+ @param[in] is_array Determines if the element list being read is to be
+ treated as an object (@a is_array == false), or as an
+ array (@a is_array == true).
+ @return whether a valid BSON-object/array was passed to the SAX parser
+ */
+ bool parse_bson_element_list(const bool is_array)
+ {
+ string_t key;
+ while (int element_type = get())
+ {
+ if (JSON_UNLIKELY(not unexpect_eof(input_format_t::bson, "element list")))
+ {
+ return false;
+ }
+
+ const std::size_t element_type_parse_position = chars_read;
+ if (JSON_UNLIKELY(not get_bson_cstr(key)))
+ {
+ return false;
+ }
+
+ if (not is_array)
+ {
+ if (not sax->key(key))
+ {
+ return false;
+ }
+ }
+
+ if (JSON_UNLIKELY(not parse_bson_element_internal(element_type, element_type_parse_position)))
+ {
+ return false;
+ }
+
+ // get_bson_cstr only appends
+ key.clear();
+ }
+
+ return true;
+ }
+
+ /*!
+ @brief Reads an array from the BSON input and passes it to the SAX-parser.
+ @return whether a valid BSON-array was passed to the SAX parser
+ */
+ bool parse_bson_array()
+ {
+ std::int32_t document_size;
+ get_number<std::int32_t, true>(input_format_t::bson, document_size);
+
+ if (JSON_UNLIKELY(not sax->start_array(std::size_t(-1))))
+ {
+ return false;
+ }
+
+ if (JSON_UNLIKELY(not parse_bson_element_list(/*is_array*/true)))
+ {
+ return false;
+ }
+
+ return sax->end_array();
+ }
+
+ //////////
+ // CBOR //
+ //////////
+
+ /*!
+ @param[in] get_char whether a new character should be retrieved from the
+ input (true, default) or whether the last read
+ character should be considered instead
+
+ @return whether a valid CBOR value was passed to the SAX parser
+ */
+ bool parse_cbor_internal(const bool get_char = true)
+ {
+ switch (get_char ? get() : current)
+ {
+ // EOF
+ case std::char_traits<char>::eof():
+ return unexpect_eof(input_format_t::cbor, "value");
+
+ // Integer 0x00..0x17 (0..23)
+ case 0x00:
+ case 0x01:
+ case 0x02:
+ case 0x03:
+ case 0x04:
+ case 0x05:
+ case 0x06:
+ case 0x07:
+ case 0x08:
+ case 0x09:
+ case 0x0A:
+ case 0x0B:
+ case 0x0C:
+ case 0x0D:
+ case 0x0E:
+ case 0x0F:
+ case 0x10:
+ case 0x11:
+ case 0x12:
+ case 0x13:
+ case 0x14:
+ case 0x15:
+ case 0x16:
+ case 0x17:
+ return sax->number_unsigned(static_cast<number_unsigned_t>(current));
+
+ case 0x18: // Unsigned integer (one-byte uint8_t follows)
+ {
+ uint8_t number;
+ return get_number(input_format_t::cbor, number) and sax->number_unsigned(number);
+ }
+
+ case 0x19: // Unsigned integer (two-byte uint16_t follows)
+ {
+ uint16_t number;
+ return get_number(input_format_t::cbor, number) and sax->number_unsigned(number);
+ }
+
+ case 0x1A: // Unsigned integer (four-byte uint32_t follows)
+ {
+ uint32_t number;
+ return get_number(input_format_t::cbor, number) and sax->number_unsigned(number);
+ }
+
+ case 0x1B: // Unsigned integer (eight-byte uint64_t follows)
+ {
+ uint64_t number;
+ return get_number(input_format_t::cbor, number) and sax->number_unsigned(number);
+ }
+
+ // Negative integer -1-0x00..-1-0x17 (-1..-24)
+ case 0x20:
+ case 0x21:
+ case 0x22:
+ case 0x23:
+ case 0x24:
+ case 0x25:
+ case 0x26:
+ case 0x27:
+ case 0x28:
+ case 0x29:
+ case 0x2A:
+ case 0x2B:
+ case 0x2C:
+ case 0x2D:
+ case 0x2E:
+ case 0x2F:
+ case 0x30:
+ case 0x31:
+ case 0x32:
+ case 0x33:
+ case 0x34:
+ case 0x35:
+ case 0x36:
+ case 0x37:
+ return sax->number_integer(static_cast<int8_t>(0x20 - 1 - current));
+
+ case 0x38: // Negative integer (one-byte uint8_t follows)
+ {
+ uint8_t number;
+ return get_number(input_format_t::cbor, number) and sax->number_integer(static_cast<number_integer_t>(-1) - number);
+ }
+
+ case 0x39: // Negative integer -1-n (two-byte uint16_t follows)
+ {
+ uint16_t number;
+ return get_number(input_format_t::cbor, number) and sax->number_integer(static_cast<number_integer_t>(-1) - number);
+ }
+
+ case 0x3A: // Negative integer -1-n (four-byte uint32_t follows)
+ {
+ uint32_t number;
+ return get_number(input_format_t::cbor, number) and sax->number_integer(static_cast<number_integer_t>(-1) - number);
+ }
+
+ case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows)
+ {
+ uint64_t number;
+ return get_number(input_format_t::cbor, number) and sax->number_integer(static_cast<number_integer_t>(-1)
+ - static_cast<number_integer_t>(number));
+ }
+
+ // UTF-8 string (0x00..0x17 bytes follow)
+ case 0x60:
+ case 0x61:
+ case 0x62:
+ case 0x63:
+ case 0x64:
+ case 0x65:
+ case 0x66:
+ case 0x67:
+ case 0x68:
+ case 0x69:
+ case 0x6A:
+ case 0x6B:
+ case 0x6C:
+ case 0x6D:
+ case 0x6E:
+ case 0x6F:
+ case 0x70:
+ case 0x71:
+ case 0x72:
+ case 0x73:
+ case 0x74:
+ case 0x75:
+ case 0x76:
+ case 0x77:
+ case 0x78: // UTF-8 string (one-byte uint8_t for n follows)
+ case 0x79: // UTF-8 string (two-byte uint16_t for n follow)
+ case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)
+ case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)
+ case 0x7F: // UTF-8 string (indefinite length)
+ {
+ string_t s;
+ return get_cbor_string(s) and sax->string(s);
+ }
+
+ // array (0x00..0x17 data items follow)
+ case 0x80:
+ case 0x81:
+ case 0x82:
+ case 0x83:
+ case 0x84:
+ case 0x85:
+ case 0x86:
+ case 0x87:
+ case 0x88:
+ case 0x89:
+ case 0x8A:
+ case 0x8B:
+ case 0x8C:
+ case 0x8D:
+ case 0x8E:
+ case 0x8F:
+ case 0x90:
+ case 0x91:
+ case 0x92:
+ case 0x93:
+ case 0x94:
+ case 0x95:
+ case 0x96:
+ case 0x97:
+ return get_cbor_array(static_cast<std::size_t>(current & 0x1F));
+
+ case 0x98: // array (one-byte uint8_t for n follows)
+ {
+ uint8_t len;
+ return get_number(input_format_t::cbor, len) and get_cbor_array(static_cast<std::size_t>(len));
+ }
+
+ case 0x99: // array (two-byte uint16_t for n follow)
+ {
+ uint16_t len;
+ return get_number(input_format_t::cbor, len) and get_cbor_array(static_cast<std::size_t>(len));
+ }
+
+ case 0x9A: // array (four-byte uint32_t for n follow)
+ {
+ uint32_t len;
+ return get_number(input_format_t::cbor, len) and get_cbor_array(static_cast<std::size_t>(len));
+ }
+
+ case 0x9B: // array (eight-byte uint64_t for n follow)
+ {
+ uint64_t len;
+ return get_number(input_format_t::cbor, len) and get_cbor_array(static_cast<std::size_t>(len));
+ }
+
+ case 0x9F: // array (indefinite length)
+ return get_cbor_array(std::size_t(-1));
+
+ // map (0x00..0x17 pairs of data items follow)
+ case 0xA0:
+ case 0xA1:
+ case 0xA2:
+ case 0xA3:
+ case 0xA4:
+ case 0xA5:
+ case 0xA6:
+ case 0xA7:
+ case 0xA8:
+ case 0xA9:
+ case 0xAA:
+ case 0xAB:
+ case 0xAC:
+ case 0xAD:
+ case 0xAE:
+ case 0xAF:
+ case 0xB0:
+ case 0xB1:
+ case 0xB2:
+ case 0xB3:
+ case 0xB4:
+ case 0xB5:
+ case 0xB6:
+ case 0xB7:
+ return get_cbor_object(static_cast<std::size_t>(current & 0x1F));
+
+ case 0xB8: // map (one-byte uint8_t for n follows)
+ {
+ uint8_t len;
+ return get_number(input_format_t::cbor, len) and get_cbor_object(static_cast<std::size_t>(len));
+ }
+
+ case 0xB9: // map (two-byte uint16_t for n follow)
+ {
+ uint16_t len;
+ return get_number(input_format_t::cbor, len) and get_cbor_object(static_cast<std::size_t>(len));
+ }
+
+ case 0xBA: // map (four-byte uint32_t for n follow)
+ {
+ uint32_t len;
+ return get_number(input_format_t::cbor, len) and get_cbor_object(static_cast<std::size_t>(len));
+ }
+
+ case 0xBB: // map (eight-byte uint64_t for n follow)
+ {
+ uint64_t len;
+ return get_number(input_format_t::cbor, len) and get_cbor_object(static_cast<std::size_t>(len));
+ }
+
+ case 0xBF: // map (indefinite length)
+ return get_cbor_object(std::size_t(-1));
+
+ case 0xF4: // false
+ return sax->boolean(false);
+
+ case 0xF5: // true
+ return sax->boolean(true);
+
+ case 0xF6: // null
+ return sax->null();
+
+ case 0xF9: // Half-Precision Float (two-byte IEEE 754)
+ {
+ const int byte1_raw = get();
+ if (JSON_UNLIKELY(not unexpect_eof(input_format_t::cbor, "number")))
+ {
+ return false;
+ }
+ const int byte2_raw = get();
+ if (JSON_UNLIKELY(not unexpect_eof(input_format_t::cbor, "number")))
+ {
+ return false;
+ }
+
+ const auto byte1 = static_cast<unsigned char>(byte1_raw);
+ const auto byte2 = static_cast<unsigned char>(byte2_raw);
+
+ // code from RFC 7049, Appendix D, Figure 3:
+ // As half-precision floating-point numbers were only added
+ // to IEEE 754 in 2008, today's programming platforms often
+ // still only have limited support for them. It is very
+ // easy to include at least decoding support for them even
+ // without such support. An example of a small decoder for
+ // half-precision floating-point numbers in the C language
+ // is shown in Fig. 3.
+ const int half = (byte1 << 8) + byte2;
+ const double val = [&half]
+ {
+ const int exp = (half >> 10) & 0x1F;
+ const int mant = half & 0x3FF;
+ assert(0 <= exp and exp <= 32);
+ assert(0 <= mant and mant <= 1024);
+ switch (exp)
+ {
+ case 0:
+ return std::ldexp(mant, -24);
+ case 31:
+ return (mant == 0)
+ ? std::numeric_limits<double>::infinity()
+ : std::numeric_limits<double>::quiet_NaN();
+ default:
+ return std::ldexp(mant + 1024, exp - 25);
+ }
+ }();
+ return sax->number_float((half & 0x8000) != 0
+ ? static_cast<number_float_t>(-val)
+ : static_cast<number_float_t>(val), "");
+ }
+
+ case 0xFA: // Single-Precision Float (four-byte IEEE 754)
+ {
+ float number;
+ return get_number(input_format_t::cbor, number) and sax->number_float(static_cast<number_float_t>(number), "");
+ }
+
+ case 0xFB: // Double-Precision Float (eight-byte IEEE 754)
+ {
+ double number;
+ return get_number(input_format_t::cbor, number) and sax->number_float(static_cast<number_float_t>(number), "");
+ }
+
+ default: // anything else (0xFF is handled inside the other types)
+ {
+ auto last_token = get_token_string();
+ return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::cbor, "invalid byte: 0x" + last_token, "value")));
+ }
+ }
+ }
+
+ /*!
+ @brief reads a CBOR string
+
+ This function first reads starting bytes to determine the expected
+ string length and then copies this number of bytes into a string.
+ Additionally, CBOR's strings with indefinite lengths are supported.
+
+ @param[out] result created string
+
+ @return whether string creation completed
+ */
+ bool get_cbor_string(string_t& result)
+ {
+ if (JSON_UNLIKELY(not unexpect_eof(input_format_t::cbor, "string")))
+ {
+ return false;
+ }
+
+ switch (current)
+ {
+ // UTF-8 string (0x00..0x17 bytes follow)
+ case 0x60:
+ case 0x61:
+ case 0x62:
+ case 0x63:
+ case 0x64:
+ case 0x65:
+ case 0x66:
+ case 0x67:
+ case 0x68:
+ case 0x69:
+ case 0x6A:
+ case 0x6B:
+ case 0x6C:
+ case 0x6D:
+ case 0x6E:
+ case 0x6F:
+ case 0x70:
+ case 0x71:
+ case 0x72:
+ case 0x73:
+ case 0x74:
+ case 0x75:
+ case 0x76:
+ case 0x77:
+ {
+ return get_string(input_format_t::cbor, current & 0x1F, result);
+ }
+
+ case 0x78: // UTF-8 string (one-byte uint8_t for n follows)
+ {
+ uint8_t len;
+ return get_number(input_format_t::cbor, len) and get_string(input_format_t::cbor, len, result);
+ }
+
+ case 0x79: // UTF-8 string (two-byte uint16_t for n follow)
+ {
+ uint16_t len;
+ return get_number(input_format_t::cbor, len) and get_string(input_format_t::cbor, len, result);
+ }
+
+ case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)
+ {
+ uint32_t len;
+ return get_number(input_format_t::cbor, len) and get_string(input_format_t::cbor, len, result);
+ }
+
+ case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)
+ {
+ uint64_t len;
+ return get_number(input_format_t::cbor, len) and get_string(input_format_t::cbor, len, result);
+ }
+
+ case 0x7F: // UTF-8 string (indefinite length)
+ {
+ while (get() != 0xFF)
+ {
+ string_t chunk;
+ if (not get_cbor_string(chunk))
+ {
+ return false;
+ }
+ result.append(chunk);
+ }
+ return true;
+ }
+
+ default:
+ {
+ auto last_token = get_token_string();
+ return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::cbor, "expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x" + last_token, "string")));
+ }
+ }
+ }
+
+ /*!
+ @param[in] len the length of the array or std::size_t(-1) for an
+ array of indefinite size
+ @return whether array creation completed
+ */
+ bool get_cbor_array(const std::size_t len)
+ {
+ if (JSON_UNLIKELY(not sax->start_array(len)))
+ {
+ return false;
+ }
+
+ if (len != std::size_t(-1))
+ {
+ for (std::size_t i = 0; i < len; ++i)
+ {
+ if (JSON_UNLIKELY(not parse_cbor_internal()))
+ {
+ return false;
+ }
+ }
+ }
+ else
+ {
+ while (get() != 0xFF)
+ {
+ if (JSON_UNLIKELY(not parse_cbor_internal(false)))
+ {
+ return false;
+ }
+ }
+ }
+
+ return sax->end_array();
+ }
+
+ /*!
+ @param[in] len the length of the object or std::size_t(-1) for an
+ object of indefinite size
+ @return whether object creation completed
+ */
+ bool get_cbor_object(const std::size_t len)
+ {
+ if (not JSON_UNLIKELY(sax->start_object(len)))
+ {
+ return false;
+ }
+
+ string_t key;
+ if (len != std::size_t(-1))
+ {
+ for (std::size_t i = 0; i < len; ++i)
+ {
+ get();
+ if (JSON_UNLIKELY(not get_cbor_string(key) or not sax->key(key)))
+ {
+ return false;
+ }
+
+ if (JSON_UNLIKELY(not parse_cbor_internal()))
+ {
+ return false;
+ }
+ key.clear();
+ }
+ }
+ else
+ {
+ while (get() != 0xFF)
+ {
+ if (JSON_UNLIKELY(not get_cbor_string(key) or not sax->key(key)))
+ {
+ return false;
+ }
+
+ if (JSON_UNLIKELY(not parse_cbor_internal()))
+ {
+ return false;
+ }
+ key.clear();
+ }
+ }
+
+ return sax->end_object();
+ }
+
+ /////////////
+ // MsgPack //
+ /////////////
+
+ /*!
+ @return whether a valid MessagePack value was passed to the SAX parser
+ */
+ bool parse_msgpack_internal()
+ {
+ switch (get())
+ {
+ // EOF
+ case std::char_traits<char>::eof():
+ return unexpect_eof(input_format_t::msgpack, "value");
+
+ // positive fixint
+ case 0x00:
+ case 0x01:
+ case 0x02:
+ case 0x03:
+ case 0x04:
+ case 0x05:
+ case 0x06:
+ case 0x07:
+ case 0x08:
+ case 0x09:
+ case 0x0A:
+ case 0x0B:
+ case 0x0C:
+ case 0x0D:
+ case 0x0E:
+ case 0x0F:
+ case 0x10:
+ case 0x11:
+ case 0x12:
+ case 0x13:
+ case 0x14:
+ case 0x15:
+ case 0x16:
+ case 0x17:
+ case 0x18:
+ case 0x19:
+ case 0x1A:
+ case 0x1B:
+ case 0x1C:
+ case 0x1D:
+ case 0x1E:
+ case 0x1F:
+ case 0x20:
+ case 0x21:
+ case 0x22:
+ case 0x23:
+ case 0x24:
+ case 0x25:
+ case 0x26:
+ case 0x27:
+ case 0x28:
+ case 0x29:
+ case 0x2A:
+ case 0x2B:
+ case 0x2C:
+ case 0x2D:
+ case 0x2E:
+ case 0x2F:
+ case 0x30:
+ case 0x31:
+ case 0x32:
+ case 0x33:
+ case 0x34:
+ case 0x35:
+ case 0x36:
+ case 0x37:
+ case 0x38:
+ case 0x39:
+ case 0x3A:
+ case 0x3B:
+ case 0x3C:
+ case 0x3D:
+ case 0x3E:
+ case 0x3F:
+ case 0x40:
+ case 0x41:
+ case 0x42:
+ case 0x43:
+ case 0x44:
+ case 0x45:
+ case 0x46:
+ case 0x47:
+ case 0x48:
+ case 0x49:
+ case 0x4A:
+ case 0x4B:
+ case 0x4C:
+ case 0x4D:
+ case 0x4E:
+ case 0x4F:
+ case 0x50:
+ case 0x51:
+ case 0x52:
+ case 0x53:
+ case 0x54:
+ case 0x55:
+ case 0x56:
+ case 0x57:
+ case 0x58:
+ case 0x59:
+ case 0x5A:
+ case 0x5B:
+ case 0x5C:
+ case 0x5D:
+ case 0x5E:
+ case 0x5F:
+ case 0x60:
+ case 0x61:
+ case 0x62:
+ case 0x63:
+ case 0x64:
+ case 0x65:
+ case 0x66:
+ case 0x67:
+ case 0x68:
+ case 0x69:
+ case 0x6A:
+ case 0x6B:
+ case 0x6C:
+ case 0x6D:
+ case 0x6E:
+ case 0x6F:
+ case 0x70:
+ case 0x71:
+ case 0x72:
+ case 0x73:
+ case 0x74:
+ case 0x75:
+ case 0x76:
+ case 0x77:
+ case 0x78:
+ case 0x79:
+ case 0x7A:
+ case 0x7B:
+ case 0x7C:
+ case 0x7D:
+ case 0x7E:
+ case 0x7F:
+ return sax->number_unsigned(static_cast<number_unsigned_t>(current));
+
+ // fixmap
+ case 0x80:
+ case 0x81:
+ case 0x82:
+ case 0x83:
+ case 0x84:
+ case 0x85:
+ case 0x86:
+ case 0x87:
+ case 0x88:
+ case 0x89:
+ case 0x8A:
+ case 0x8B:
+ case 0x8C:
+ case 0x8D:
+ case 0x8E:
+ case 0x8F:
+ return get_msgpack_object(static_cast<std::size_t>(current & 0x0F));
+
+ // fixarray
+ case 0x90:
+ case 0x91:
+ case 0x92:
+ case 0x93:
+ case 0x94:
+ case 0x95:
+ case 0x96:
+ case 0x97:
+ case 0x98:
+ case 0x99:
+ case 0x9A:
+ case 0x9B:
+ case 0x9C:
+ case 0x9D:
+ case 0x9E:
+ case 0x9F:
+ return get_msgpack_array(static_cast<std::size_t>(current & 0x0F));
+
+ // fixstr
+ case 0xA0:
+ case 0xA1:
+ case 0xA2:
+ case 0xA3:
+ case 0xA4:
+ case 0xA5:
+ case 0xA6:
+ case 0xA7:
+ case 0xA8:
+ case 0xA9:
+ case 0xAA:
+ case 0xAB:
+ case 0xAC:
+ case 0xAD:
+ case 0xAE:
+ case 0xAF:
+ case 0xB0:
+ case 0xB1:
+ case 0xB2:
+ case 0xB3:
+ case 0xB4:
+ case 0xB5:
+ case 0xB6:
+ case 0xB7:
+ case 0xB8:
+ case 0xB9:
+ case 0xBA:
+ case 0xBB:
+ case 0xBC:
+ case 0xBD:
+ case 0xBE:
+ case 0xBF:
+ {
+ string_t s;
+ return get_msgpack_string(s) and sax->string(s);
+ }
+
+ case 0xC0: // nil
+ return sax->null();
+
+ case 0xC2: // false
+ return sax->boolean(false);
+
+ case 0xC3: // true
+ return sax->boolean(true);
+
+ case 0xCA: // float 32
+ {
+ float number;
+ return get_number(input_format_t::msgpack, number) and sax->number_float(static_cast<number_float_t>(number), "");
+ }
+
+ case 0xCB: // float 64
+ {
+ double number;
+ return get_number(input_format_t::msgpack, number) and sax->number_float(static_cast<number_float_t>(number), "");
+ }
+
+ case 0xCC: // uint 8
+ {
+ uint8_t number;
+ return get_number(input_format_t::msgpack, number) and sax->number_unsigned(number);
+ }
+
+ case 0xCD: // uint 16
+ {
+ uint16_t number;
+ return get_number(input_format_t::msgpack, number) and sax->number_unsigned(number);
+ }
+
+ case 0xCE: // uint 32
+ {
+ uint32_t number;
+ return get_number(input_format_t::msgpack, number) and sax->number_unsigned(number);
+ }
+
+ case 0xCF: // uint 64
+ {
+ uint64_t number;
+ return get_number(input_format_t::msgpack, number) and sax->number_unsigned(number);
+ }
+
+ case 0xD0: // int 8
+ {
+ int8_t number;
+ return get_number(input_format_t::msgpack, number) and sax->number_integer(number);
+ }
+
+ case 0xD1: // int 16
+ {
+ int16_t number;
+ return get_number(input_format_t::msgpack, number) and sax->number_integer(number);
+ }
+
+ case 0xD2: // int 32
+ {
+ int32_t number;
+ return get_number(input_format_t::msgpack, number) and sax->number_integer(number);
+ }
+
+ case 0xD3: // int 64
+ {
+ int64_t number;
+ return get_number(input_format_t::msgpack, number) and sax->number_integer(number);
+ }
+
+ case 0xD9: // str 8
+ case 0xDA: // str 16
+ case 0xDB: // str 32
+ {
+ string_t s;
+ return get_msgpack_string(s) and sax->string(s);
+ }
+
+ case 0xDC: // array 16
+ {
+ uint16_t len;
+ return get_number(input_format_t::msgpack, len) and get_msgpack_array(static_cast<std::size_t>(len));
+ }
+
+ case 0xDD: // array 32
+ {
+ uint32_t len;
+ return get_number(input_format_t::msgpack, len) and get_msgpack_array(static_cast<std::size_t>(len));
+ }
+
+ case 0xDE: // map 16
+ {
+ uint16_t len;
+ return get_number(input_format_t::msgpack, len) and get_msgpack_object(static_cast<std::size_t>(len));
+ }
+
+ case 0xDF: // map 32
+ {
+ uint32_t len;
+ return get_number(input_format_t::msgpack, len) and get_msgpack_object(static_cast<std::size_t>(len));
+ }
+
+ // negative fixint
+ case 0xE0:
+ case 0xE1:
+ case 0xE2:
+ case 0xE3:
+ case 0xE4:
+ case 0xE5:
+ case 0xE6:
+ case 0xE7:
+ case 0xE8:
+ case 0xE9:
+ case 0xEA:
+ case 0xEB:
+ case 0xEC:
+ case 0xED:
+ case 0xEE:
+ case 0xEF:
+ case 0xF0:
+ case 0xF1:
+ case 0xF2:
+ case 0xF3:
+ case 0xF4:
+ case 0xF5:
+ case 0xF6:
+ case 0xF7:
+ case 0xF8:
+ case 0xF9:
+ case 0xFA:
+ case 0xFB:
+ case 0xFC:
+ case 0xFD:
+ case 0xFE:
+ case 0xFF:
+ return sax->number_integer(static_cast<int8_t>(current));
+
+ default: // anything else
+ {
+ auto last_token = get_token_string();
+ return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::msgpack, "invalid byte: 0x" + last_token, "value")));
+ }
+ }
+ }
+
+ /*!
+ @brief reads a MessagePack string
+
+ This function first reads starting bytes to determine the expected
+ string length and then copies this number of bytes into a string.
+
+ @param[out] result created string
+
+ @return whether string creation completed
+ */
+ bool get_msgpack_string(string_t& result)
+ {
+ if (JSON_UNLIKELY(not unexpect_eof(input_format_t::msgpack, "string")))
+ {
+ return false;
+ }
+
+ switch (current)
+ {
+ // fixstr
+ case 0xA0:
+ case 0xA1:
+ case 0xA2:
+ case 0xA3:
+ case 0xA4:
+ case 0xA5:
+ case 0xA6:
+ case 0xA7:
+ case 0xA8:
+ case 0xA9:
+ case 0xAA:
+ case 0xAB:
+ case 0xAC:
+ case 0xAD:
+ case 0xAE:
+ case 0xAF:
+ case 0xB0:
+ case 0xB1:
+ case 0xB2:
+ case 0xB3:
+ case 0xB4:
+ case 0xB5:
+ case 0xB6:
+ case 0xB7:
+ case 0xB8:
+ case 0xB9:
+ case 0xBA:
+ case 0xBB:
+ case 0xBC:
+ case 0xBD:
+ case 0xBE:
+ case 0xBF:
+ {
+ return get_string(input_format_t::msgpack, current & 0x1F, result);
+ }
+
+ case 0xD9: // str 8
+ {
+ uint8_t len;
+ return get_number(input_format_t::msgpack, len) and get_string(input_format_t::msgpack, len, result);
+ }
+
+ case 0xDA: // str 16
+ {
+ uint16_t len;
+ return get_number(input_format_t::msgpack, len) and get_string(input_format_t::msgpack, len, result);
+ }
+
+ case 0xDB: // str 32
+ {
+ uint32_t len;
+ return get_number(input_format_t::msgpack, len) and get_string(input_format_t::msgpack, len, result);
+ }
+
+ default:
+ {
+ auto last_token = get_token_string();
+ return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::msgpack, "expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x" + last_token, "string")));
+ }
+ }
+ }
+
+ /*!
+ @param[in] len the length of the array
+ @return whether array creation completed
+ */
+ bool get_msgpack_array(const std::size_t len)
+ {
+ if (JSON_UNLIKELY(not sax->start_array(len)))
+ {
+ return false;
+ }
+
+ for (std::size_t i = 0; i < len; ++i)
+ {
+ if (JSON_UNLIKELY(not parse_msgpack_internal()))
+ {
+ return false;
+ }
+ }
+
+ return sax->end_array();
+ }
+
+ /*!
+ @param[in] len the length of the object
+ @return whether object creation completed
+ */
+ bool get_msgpack_object(const std::size_t len)
+ {
+ if (JSON_UNLIKELY(not sax->start_object(len)))
+ {
+ return false;
+ }
+
+ string_t key;
+ for (std::size_t i = 0; i < len; ++i)
+ {
+ get();
+ if (JSON_UNLIKELY(not get_msgpack_string(key) or not sax->key(key)))
+ {
+ return false;
+ }
+
+ if (JSON_UNLIKELY(not parse_msgpack_internal()))
+ {
+ return false;
+ }
+ key.clear();
+ }
+
+ return sax->end_object();
+ }
+
+ ////////////
+ // UBJSON //
+ ////////////
+
+ /*!
+ @param[in] get_char whether a new character should be retrieved from the
+ input (true, default) or whether the last read
+ character should be considered instead
+
+ @return whether a valid UBJSON value was passed to the SAX parser
+ */
+ bool parse_ubjson_internal(const bool get_char = true)
+ {
+ return get_ubjson_value(get_char ? get_ignore_noop() : current);
+ }
+
+ /*!
+ @brief reads a UBJSON string
+
+ This function is either called after reading the 'S' byte explicitly
+ indicating a string, or in case of an object key where the 'S' byte can be
+ left out.
+
+ @param[out] result created string
+ @param[in] get_char whether a new character should be retrieved from the
+ input (true, default) or whether the last read
+ character should be considered instead
+
+ @return whether string creation completed
+ */
+ bool get_ubjson_string(string_t& result, const bool get_char = true)
+ {
+ if (get_char)
+ {
+ get(); // TODO: may we ignore N here?
+ }
+
+ if (JSON_UNLIKELY(not unexpect_eof(input_format_t::ubjson, "value")))
+ {
+ return false;
+ }
+
+ switch (current)
+ {
+ case 'U':
+ {
+ uint8_t len;
+ return get_number(input_format_t::ubjson, len) and get_string(input_format_t::ubjson, len, result);
+ }
+
+ case 'i':
+ {
+ int8_t len;
+ return get_number(input_format_t::ubjson, len) and get_string(input_format_t::ubjson, len, result);
+ }
+
+ case 'I':
+ {
+ int16_t len;
+ return get_number(input_format_t::ubjson, len) and get_string(input_format_t::ubjson, len, result);
+ }
+
+ case 'l':
+ {
+ int32_t len;
+ return get_number(input_format_t::ubjson, len) and get_string(input_format_t::ubjson, len, result);
+ }
+
+ case 'L':
+ {
+ int64_t len;
+ return get_number(input_format_t::ubjson, len) and get_string(input_format_t::ubjson, len, result);
+ }
+
+ default:
+ auto last_token = get_token_string();
+ return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "expected length type specification (U, i, I, l, L); last byte: 0x" + last_token, "string")));
+ }
+ }
+
+ /*!
+ @param[out] result determined size
+ @return whether size determination completed
+ */
+ bool get_ubjson_size_value(std::size_t& result)
+ {
+ switch (get_ignore_noop())
+ {
+ case 'U':
+ {
+ uint8_t number;
+ if (JSON_UNLIKELY(not get_number(input_format_t::ubjson, number)))
+ {
+ return false;
+ }
+ result = static_cast<std::size_t>(number);
+ return true;
+ }
+
+ case 'i':
+ {
+ int8_t number;
+ if (JSON_UNLIKELY(not get_number(input_format_t::ubjson, number)))
+ {
+ return false;
+ }
+ result = static_cast<std::size_t>(number);
+ return true;
+ }
+
+ case 'I':
+ {
+ int16_t number;
+ if (JSON_UNLIKELY(not get_number(input_format_t::ubjson, number)))
+ {
+ return false;
+ }
+ result = static_cast<std::size_t>(number);
+ return true;
+ }
+
+ case 'l':
+ {
+ int32_t number;
+ if (JSON_UNLIKELY(not get_number(input_format_t::ubjson, number)))
+ {
+ return false;
+ }
+ result = static_cast<std::size_t>(number);
+ return true;
+ }
+
+ case 'L':
+ {
+ int64_t number;
+ if (JSON_UNLIKELY(not get_number(input_format_t::ubjson, number)))
+ {
+ return false;
+ }
+ result = static_cast<std::size_t>(number);
+ return true;
+ }
+
+ default:
+ {
+ auto last_token = get_token_string();
+ return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "expected length type specification (U, i, I, l, L) after '#'; last byte: 0x" + last_token, "size")));
+ }
+ }
+ }
+
+ /*!
+ @brief determine the type and size for a container
+
+ In the optimized UBJSON format, a type and a size can be provided to allow
+ for a more compact representation.
+
+ @param[out] result pair of the size and the type
+
+ @return whether pair creation completed
+ */
+ bool get_ubjson_size_type(std::pair<std::size_t, int>& result)
+ {
+ result.first = string_t::npos; // size
+ result.second = 0; // type
+
+ get_ignore_noop();
+
+ if (current == '$')
+ {
+ result.second = get(); // must not ignore 'N', because 'N' maybe the type
+ if (JSON_UNLIKELY(not unexpect_eof(input_format_t::ubjson, "type")))
+ {
+ return false;
+ }
+
+ get_ignore_noop();
+ if (JSON_UNLIKELY(current != '#'))
+ {
+ if (JSON_UNLIKELY(not unexpect_eof(input_format_t::ubjson, "value")))
+ {
+ return false;
+ }
+ auto last_token = get_token_string();
+ return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::ubjson, "expected '#' after type information; last byte: 0x" + last_token, "size")));
+ }
+
+ return get_ubjson_size_value(result.first);
+ }
+ else if (current == '#')
+ {
+ return get_ubjson_size_value(result.first);
+ }
+ return true;
+ }
+
+ /*!
+ @param prefix the previously read or set type prefix
+ @return whether value creation completed
+ */
+ bool get_ubjson_value(const int prefix)
+ {
+ switch (prefix)
+ {
+ case std::char_traits<char>::eof(): // EOF
+ return unexpect_eof(input_format_t::ubjson, "value");
+
+ case 'T': // true
+ return sax->boolean(true);
+ case 'F': // false
+ return sax->boolean(false);
+
+ case 'Z': // null
+ return sax->null();
+
+ case 'U':
+ {
+ uint8_t number;
+ return get_number(input_format_t::ubjson, number) and sax->number_unsigned(number);
+ }
+
+ case 'i':
+ {
+ int8_t number;
+ return get_number(input_format_t::ubjson, number) and sax->number_integer(number);
+ }
+
+ case 'I':
+ {
+ int16_t number;
+ return get_number(input_format_t::ubjson, number) and sax->number_integer(number);
+ }
+
+ case 'l':
+ {
+ int32_t number;
+ return get_number(input_format_t::ubjson, number) and sax->number_integer(number);
+ }
+
+ case 'L':
+ {
+ int64_t number;
+ return get_number(input_format_t::ubjson, number) and sax->number_integer(number);
+ }
+
+ case 'd':
+ {
+ float number;
+ return get_number(input_format_t::ubjson, number) and sax->number_float(static_cast<number_float_t>(number), "");
+ }
+
+ case 'D':
+ {
+ double number;
+ return get_number(input_format_t::ubjson, number) and sax->number_float(static_cast<number_float_t>(number), "");
+ }
+
+ case 'C': // char
+ {
+ get();
+ if (JSON_UNLIKELY(not unexpect_eof(input_format_t::ubjson, "char")))
+ {
+ return false;
+ }
+ if (JSON_UNLIKELY(current > 127))
+ {
+ auto last_token = get_token_string();
+ return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format_t::ubjson, "byte after 'C' must be in range 0x00..0x7F; last byte: 0x" + last_token, "char")));
+ }
+ string_t s(1, static_cast<char>(current));
+ return sax->string(s);
+ }
+
+ case 'S': // string
+ {
+ string_t s;
+ return get_ubjson_string(s) and sax->string(s);
+ }
+
+ case '[': // array
+ return get_ubjson_array();
+
+ case '{': // object
+ return get_ubjson_object();
+
+ default: // anything else
+ {
+ auto last_token = get_token_string();
+ return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format_t::ubjson, "invalid byte: 0x" + last_token, "value")));
+ }
+ }
+ }
+
+ /*!
+ @return whether array creation completed
+ */
+ bool get_ubjson_array()
+ {
+ std::pair<std::size_t, int> size_and_type;
+ if (JSON_UNLIKELY(not get_ubjson_size_type(size_and_type)))
+ {
+ return false;
+ }
+
+ if (size_and_type.first != string_t::npos)
+ {
+ if (JSON_UNLIKELY(not sax->start_array(size_and_type.first)))
+ {
+ return false;
+ }
+
+ if (size_and_type.second != 0)
+ {
+ if (size_and_type.second != 'N')
+ {
+ for (std::size_t i = 0; i < size_and_type.first; ++i)
+ {
+ if (JSON_UNLIKELY(not get_ubjson_value(size_and_type.second)))
+ {
+ return false;
+ }
+ }
+ }
+ }
+ else
+ {
+ for (std::size_t i = 0; i < size_and_type.first; ++i)
+ {
+ if (JSON_UNLIKELY(not parse_ubjson_internal()))
+ {
+ return false;
+ }
+ }
+ }
+ }
+ else
+ {
+ if (JSON_UNLIKELY(not sax->start_array(std::size_t(-1))))
+ {
+ return false;
+ }
+
+ while (current != ']')
+ {
+ if (JSON_UNLIKELY(not parse_ubjson_internal(false)))
+ {
+ return false;
+ }
+ get_ignore_noop();
+ }
+ }
+
+ return sax->end_array();
+ }
+
+ /*!
+ @return whether object creation completed
+ */
+ bool get_ubjson_object()
+ {
+ std::pair<std::size_t, int> size_and_type;
+ if (JSON_UNLIKELY(not get_ubjson_size_type(size_and_type)))
+ {
+ return false;
+ }
+
+ string_t key;
+ if (size_and_type.first != string_t::npos)
+ {
+ if (JSON_UNLIKELY(not sax->start_object(size_and_type.first)))
+ {
+ return false;
+ }
+
+ if (size_and_type.second != 0)
+ {
+ for (std::size_t i = 0; i < size_and_type.first; ++i)
+ {
+ if (JSON_UNLIKELY(not get_ubjson_string(key) or not sax->key(key)))
+ {
+ return false;
+ }
+ if (JSON_UNLIKELY(not get_ubjson_value(size_and_type.second)))
+ {
+ return false;
+ }
+ key.clear();
+ }
+ }
+ else
+ {
+ for (std::size_t i = 0; i < size_and_type.first; ++i)
+ {
+ if (JSON_UNLIKELY(not get_ubjson_string(key) or not sax->key(key)))
+ {
+ return false;
+ }
+ if (JSON_UNLIKELY(not parse_ubjson_internal()))
+ {
+ return false;
+ }
+ key.clear();
+ }
+ }
+ }
+ else
+ {
+ if (JSON_UNLIKELY(not sax->start_object(std::size_t(-1))))
+ {
+ return false;
+ }
+
+ while (current != '}')
+ {
+ if (JSON_UNLIKELY(not get_ubjson_string(key, false) or not sax->key(key)))
+ {
+ return false;
+ }
+ if (JSON_UNLIKELY(not parse_ubjson_internal()))
+ {
+ return false;
+ }
+ get_ignore_noop();
+ key.clear();
+ }
+ }
+
+ return sax->end_object();
+ }
+
+ ///////////////////////
+ // Utility functions //
+ ///////////////////////
+
+ /*!
+ @brief get next character from the input
+
+ This function provides the interface to the used input adapter. It does
+ not throw in case the input reached EOF, but returns a -'ve valued
+ `std::char_traits<char>::eof()` in that case.
+
+ @return character read from the input
+ */
+ int get()
+ {
+ ++chars_read;
+ return (current = ia->get_character());
+ }
+
+ /*!
+ @return character read from the input after ignoring all 'N' entries
+ */
+ int get_ignore_noop()
+ {
+ do
+ {
+ get();
+ }
+ while (current == 'N');
+
+ return current;
+ }
+
+ /*
+ @brief read a number from the input
+
+ @tparam NumberType the type of the number
+ @param[in] format the current format (for diagnostics)
+ @param[out] result number of type @a NumberType
+
+ @return whether conversion completed
+
+ @note This function needs to respect the system's endianess, because
+ bytes in CBOR, MessagePack, and UBJSON are stored in network order
+ (big endian) and therefore need reordering on little endian systems.
+ */
+ template<typename NumberType, bool InputIsLittleEndian = false>
+ bool get_number(const input_format_t format, NumberType& result)
+ {
+ // step 1: read input into array with system's byte order
+ std::array<uint8_t, sizeof(NumberType)> vec;
+ for (std::size_t i = 0; i < sizeof(NumberType); ++i)
+ {
+ get();
+ if (JSON_UNLIKELY(not unexpect_eof(format, "number")))
+ {
+ return false;
+ }
+
+ // reverse byte order prior to conversion if necessary
+ if (is_little_endian && !InputIsLittleEndian)
+ {
+ vec[sizeof(NumberType) - i - 1] = static_cast<uint8_t>(current);
+ }
+ else
+ {
+ vec[i] = static_cast<uint8_t>(current); // LCOV_EXCL_LINE
+ }
+ }
+
+ // step 2: convert array into number of type T and return
+ std::memcpy(&result, vec.data(), sizeof(NumberType));
+ return true;
+ }
+
+ /*!
+ @brief create a string by reading characters from the input
+
+ @tparam NumberType the type of the number
+ @param[in] format the current format (for diagnostics)
+ @param[in] len number of characters to read
+ @param[out] result string created by reading @a len bytes
+
+ @return whether string creation completed
+
+ @note We can not reserve @a len bytes for the result, because @a len
+ may be too large. Usually, @ref unexpect_eof() detects the end of
+ the input before we run out of string memory.
+ */
+ template<typename NumberType>
+ bool get_string(const input_format_t format,
+ const NumberType len,
+ string_t& result)
+ {
+ bool success = true;
+ std::generate_n(std::back_inserter(result), len, [this, &success, &format]()
+ {
+ get();
+ if (JSON_UNLIKELY(not unexpect_eof(format, "string")))
+ {
+ success = false;
+ }
+ return static_cast<char>(current);
+ });
+ return success;
+ }
+
+ /*!
+ @param[in] format the current format (for diagnostics)
+ @param[in] context further context information (for diagnostics)
+ @return whether the last read character is not EOF
+ */
+ bool unexpect_eof(const input_format_t format, const char* context) const
+ {
+ if (JSON_UNLIKELY(current == std::char_traits<char>::eof()))
+ {
+ return sax->parse_error(chars_read, "<end of file>",
+ parse_error::create(110, chars_read, exception_message(format, "unexpected end of input", context)));
+ }
+ return true;
+ }
+
+ /*!
+ @return a string representation of the last read byte
+ */
+ std::string get_token_string() const
+ {
+ char cr[3];
+ (std::snprintf)(cr, 3, "%.2hhX", static_cast<unsigned char>(current));
+ return std::string{cr};
+ }
+
+ /*!
+ @param[in] format the current format
+ @param[in] detail a detailed error message
+ @param[in] context further contect information
+ @return a message string to use in the parse_error exceptions
+ */
+ std::string exception_message(const input_format_t format,
+ const std::string& detail,
+ const std::string& context) const
+ {
+ std::string error_msg = "syntax error while parsing ";
+
+ switch (format)
+ {
+ case input_format_t::cbor:
+ error_msg += "CBOR";
+ break;
+
+ case input_format_t::msgpack:
+ error_msg += "MessagePack";
+ break;
+
+ case input_format_t::ubjson:
+ error_msg += "UBJSON";
+ break;
+
+ case input_format_t::bson:
+ error_msg += "BSON";
+ break;
+
+ // LCOV_EXCL_START
+ default:
+ assert(false);
+ // LCOV_EXCL_STOP
+ }
+
+ return error_msg + " " + context + ": " + detail;
+ }
+
+ private:
+ /// input adapter
+ input_adapter_t ia = nullptr;
+
+ /// the current character
+ int current = std::char_traits<char>::eof();
+
+ /// the number of characters read
+ std::size_t chars_read = 0;
+
+ /// whether we can assume little endianess
+ const bool is_little_endian = little_endianess();
+
+ /// the SAX parser
+ json_sax_t* sax = nullptr;
+};
+} // namespace detail
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/input/input_adapters.hpp b/include/lib/modernjson/detail/input/input_adapters.hpp
new file mode 100644
index 0000000..dfb8caf
--- /dev/null
+++ b/include/lib/modernjson/detail/input/input_adapters.hpp
@@ -0,0 +1,398 @@
+#pragma once
+
+#include <cassert> // assert
+#include <cstddef> // size_t
+#include <cstring> // strlen
+#include <istream> // istream
+#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next
+#include <memory> // shared_ptr, make_shared, addressof
+#include <numeric> // accumulate
+#include <string> // string, char_traits
+#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
+#include <utility> // pair, declval
+
+#include <lib/modernjson/detail/macro_scope.hpp>
+
+namespace nlohmann
+{
+namespace detail
+{
+/// the supported input formats
+enum class input_format_t { json, cbor, msgpack, ubjson, bson };
+
+////////////////////
+// input adapters //
+////////////////////
+
+/*!
+@brief abstract input adapter interface
+
+Produces a stream of std::char_traits<char>::int_type characters from a
+std::istream, a buffer, or some other input type. Accepts the return of
+exactly one non-EOF character for future input. The int_type characters
+returned consist of all valid char values as positive values (typically
+unsigned char), plus an EOF value outside that range, specified by the value
+of the function std::char_traits<char>::eof(). This value is typically -1, but
+could be any arbitrary value which is not a valid char value.
+*/
+struct input_adapter_protocol
+{
+ /// get a character [0,255] or std::char_traits<char>::eof().
+ virtual std::char_traits<char>::int_type get_character() = 0;
+ virtual ~input_adapter_protocol() = default;
+};
+
+/// a type to simplify interfaces
+using input_adapter_t = std::shared_ptr<input_adapter_protocol>;
+
+/*!
+Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at
+beginning of input. Does not support changing the underlying std::streambuf
+in mid-input. Maintains underlying std::istream and std::streambuf to support
+subsequent use of standard std::istream operations to process any input
+characters following those used in parsing the JSON input. Clears the
+std::istream flags; any input errors (e.g., EOF) will be detected by the first
+subsequent call for input from the std::istream.
+*/
+class input_stream_adapter : public input_adapter_protocol
+{
+ public:
+ ~input_stream_adapter() override
+ {
+ // clear stream flags; we use underlying streambuf I/O, do not
+ // maintain ifstream flags, except eof
+ is.clear(is.rdstate() & std::ios::eofbit);
+ }
+
+ explicit input_stream_adapter(std::istream& i)
+ : is(i), sb(*i.rdbuf())
+ {}
+
+ // delete because of pointer members
+ input_stream_adapter(const input_stream_adapter&) = delete;
+ input_stream_adapter& operator=(input_stream_adapter&) = delete;
+ input_stream_adapter(input_stream_adapter&&) = delete;
+ input_stream_adapter& operator=(input_stream_adapter&&) = delete;
+
+ // std::istream/std::streambuf use std::char_traits<char>::to_int_type, to
+ // ensure that std::char_traits<char>::eof() and the character 0xFF do not
+ // end up as the same value, eg. 0xFFFFFFFF.
+ std::char_traits<char>::int_type get_character() override
+ {
+ auto res = sb.sbumpc();
+ // set eof manually, as we don't use the istream interface.
+ if (res == EOF)
+ {
+ is.clear(is.rdstate() | std::ios::eofbit);
+ }
+ return res;
+ }
+
+ private:
+ /// the associated input stream
+ std::istream& is;
+ std::streambuf& sb;
+};
+
+/// input adapter for buffer input
+class input_buffer_adapter : public input_adapter_protocol
+{
+ public:
+ input_buffer_adapter(const char* b, const std::size_t l) noexcept
+ : cursor(b), limit(b + l)
+ {}
+
+ // delete because of pointer members
+ input_buffer_adapter(const input_buffer_adapter&) = delete;
+ input_buffer_adapter& operator=(input_buffer_adapter&) = delete;
+ input_buffer_adapter(input_buffer_adapter&&) = delete;
+ input_buffer_adapter& operator=(input_buffer_adapter&&) = delete;
+ ~input_buffer_adapter() override = default;
+
+ std::char_traits<char>::int_type get_character() noexcept override
+ {
+ if (JSON_LIKELY(cursor < limit))
+ {
+ return std::char_traits<char>::to_int_type(*(cursor++));
+ }
+
+ return std::char_traits<char>::eof();
+ }
+
+ private:
+ /// pointer to the current character
+ const char* cursor;
+ /// pointer past the last character
+ const char* const limit;
+};
+
+template<typename WideStringType, size_t T>
+struct wide_string_input_helper
+{
+ // UTF-32
+ static void fill_buffer(const WideStringType& str, size_t& current_wchar, std::array<std::char_traits<char>::int_type, 4>& utf8_bytes, size_t& utf8_bytes_index, size_t& utf8_bytes_filled)
+ {
+ utf8_bytes_index = 0;
+
+ if (current_wchar == str.size())
+ {
+ utf8_bytes[0] = std::char_traits<char>::eof();
+ utf8_bytes_filled = 1;
+ }
+ else
+ {
+ // get the current character
+ const auto wc = static_cast<int>(str[current_wchar++]);
+
+ // UTF-32 to UTF-8 encoding
+ if (wc < 0x80)
+ {
+ utf8_bytes[0] = wc;
+ utf8_bytes_filled = 1;
+ }
+ else if (wc <= 0x7FF)
+ {
+ utf8_bytes[0] = 0xC0 | ((wc >> 6) & 0x1F);
+ utf8_bytes[1] = 0x80 | (wc & 0x3F);
+ utf8_bytes_filled = 2;
+ }
+ else if (wc <= 0xFFFF)
+ {
+ utf8_bytes[0] = 0xE0 | ((wc >> 12) & 0x0F);
+ utf8_bytes[1] = 0x80 | ((wc >> 6) & 0x3F);
+ utf8_bytes[2] = 0x80 | (wc & 0x3F);
+ utf8_bytes_filled = 3;
+ }
+ else if (wc <= 0x10FFFF)
+ {
+ utf8_bytes[0] = 0xF0 | ((wc >> 18) & 0x07);
+ utf8_bytes[1] = 0x80 | ((wc >> 12) & 0x3F);
+ utf8_bytes[2] = 0x80 | ((wc >> 6) & 0x3F);
+ utf8_bytes[3] = 0x80 | (wc & 0x3F);
+ utf8_bytes_filled = 4;
+ }
+ else
+ {
+ // unknown character
+ utf8_bytes[0] = wc;
+ utf8_bytes_filled = 1;
+ }
+ }
+ }
+};
+
+template<typename WideStringType>
+struct wide_string_input_helper<WideStringType, 2>
+{
+ // UTF-16
+ static void fill_buffer(const WideStringType& str, size_t& current_wchar, std::array<std::char_traits<char>::int_type, 4>& utf8_bytes, size_t& utf8_bytes_index, size_t& utf8_bytes_filled)
+ {
+ utf8_bytes_index = 0;
+
+ if (current_wchar == str.size())
+ {
+ utf8_bytes[0] = std::char_traits<char>::eof();
+ utf8_bytes_filled = 1;
+ }
+ else
+ {
+ // get the current character
+ const auto wc = static_cast<int>(str[current_wchar++]);
+
+ // UTF-16 to UTF-8 encoding
+ if (wc < 0x80)
+ {
+ utf8_bytes[0] = wc;
+ utf8_bytes_filled = 1;
+ }
+ else if (wc <= 0x7FF)
+ {
+ utf8_bytes[0] = 0xC0 | ((wc >> 6));
+ utf8_bytes[1] = 0x80 | (wc & 0x3F);
+ utf8_bytes_filled = 2;
+ }
+ else if (0xD800 > wc or wc >= 0xE000)
+ {
+ utf8_bytes[0] = 0xE0 | ((wc >> 12));
+ utf8_bytes[1] = 0x80 | ((wc >> 6) & 0x3F);
+ utf8_bytes[2] = 0x80 | (wc & 0x3F);
+ utf8_bytes_filled = 3;
+ }
+ else
+ {
+ if (current_wchar < str.size())
+ {
+ const auto wc2 = static_cast<int>(str[current_wchar++]);
+ const int charcode = 0x10000 + (((wc & 0x3FF) << 10) | (wc2 & 0x3FF));
+ utf8_bytes[0] = 0xf0 | (charcode >> 18);
+ utf8_bytes[1] = 0x80 | ((charcode >> 12) & 0x3F);
+ utf8_bytes[2] = 0x80 | ((charcode >> 6) & 0x3F);
+ utf8_bytes[3] = 0x80 | (charcode & 0x3F);
+ utf8_bytes_filled = 4;
+ }
+ else
+ {
+ // unknown character
+ ++current_wchar;
+ utf8_bytes[0] = wc;
+ utf8_bytes_filled = 1;
+ }
+ }
+ }
+ }
+};
+
+template<typename WideStringType>
+class wide_string_input_adapter : public input_adapter_protocol
+{
+ public:
+ explicit wide_string_input_adapter(const WideStringType& w) noexcept
+ : str(w)
+ {}
+
+ std::char_traits<char>::int_type get_character() noexcept override
+ {
+ // check if buffer needs to be filled
+ if (utf8_bytes_index == utf8_bytes_filled)
+ {
+ fill_buffer<sizeof(typename WideStringType::value_type)>();
+
+ assert(utf8_bytes_filled > 0);
+ assert(utf8_bytes_index == 0);
+ }
+
+ // use buffer
+ assert(utf8_bytes_filled > 0);
+ assert(utf8_bytes_index < utf8_bytes_filled);
+ return utf8_bytes[utf8_bytes_index++];
+ }
+
+ private:
+ template<size_t T>
+ void fill_buffer()
+ {
+ wide_string_input_helper<WideStringType, T>::fill_buffer(str, current_wchar, utf8_bytes, utf8_bytes_index, utf8_bytes_filled);
+ }
+
+ /// the wstring to process
+ const WideStringType& str;
+
+ /// index of the current wchar in str
+ std::size_t current_wchar = 0;
+
+ /// a buffer for UTF-8 bytes
+ std::array<std::char_traits<char>::int_type, 4> utf8_bytes = {{0, 0, 0, 0}};
+
+ /// index to the utf8_codes array for the next valid byte
+ std::size_t utf8_bytes_index = 0;
+ /// number of valid bytes in the utf8_codes array
+ std::size_t utf8_bytes_filled = 0;
+};
+
+class input_adapter
+{
+ public:
+ // native support
+
+ /// input adapter for input stream
+ input_adapter(std::istream& i)
+ : ia(std::make_shared<input_stream_adapter>(i)) {}
+
+ /// input adapter for input stream
+ input_adapter(std::istream&& i)
+ : ia(std::make_shared<input_stream_adapter>(i)) {}
+
+ input_adapter(const std::wstring& ws)
+ : ia(std::make_shared<wide_string_input_adapter<std::wstring>>(ws)) {}
+
+ input_adapter(const std::u16string& ws)
+ : ia(std::make_shared<wide_string_input_adapter<std::u16string>>(ws)) {}
+
+ input_adapter(const std::u32string& ws)
+ : ia(std::make_shared<wide_string_input_adapter<std::u32string>>(ws)) {}
+
+ /// input adapter for buffer
+ template<typename CharT,
+ typename std::enable_if<
+ std::is_pointer<CharT>::value and
+ std::is_integral<typename std::remove_pointer<CharT>::type>::value and
+ sizeof(typename std::remove_pointer<CharT>::type) == 1,
+ int>::type = 0>
+ input_adapter(CharT b, std::size_t l)
+ : ia(std::make_shared<input_buffer_adapter>(reinterpret_cast<const char*>(b), l)) {}
+
+ // derived support
+
+ /// input adapter for string literal
+ template<typename CharT,
+ typename std::enable_if<
+ std::is_pointer<CharT>::value and
+ std::is_integral<typename std::remove_pointer<CharT>::type>::value and
+ sizeof(typename std::remove_pointer<CharT>::type) == 1,
+ int>::type = 0>
+ input_adapter(CharT b)
+ : input_adapter(reinterpret_cast<const char*>(b),
+ std::strlen(reinterpret_cast<const char*>(b))) {}
+
+ /// input adapter for iterator range with contiguous storage
+ template<class IteratorType,
+ typename std::enable_if<
+ std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value,
+ int>::type = 0>
+ input_adapter(IteratorType first, IteratorType last)
+ {
+#ifndef NDEBUG
+ // assertion to check that the iterator range is indeed contiguous,
+ // see http://stackoverflow.com/a/35008842/266378 for more discussion
+ const auto is_contiguous = std::accumulate(
+ first, last, std::pair<bool, int>(true, 0),
+ [&first](std::pair<bool, int> res, decltype(*first) val)
+ {
+ res.first &= (val == *(std::next(std::addressof(*first), res.second++)));
+ return res;
+ }).first;
+ assert(is_contiguous);
+#endif
+
+ // assertion to check that each element is 1 byte long
+ static_assert(
+ sizeof(typename std::iterator_traits<IteratorType>::value_type) == 1,
+ "each element in the iterator range must have the size of 1 byte");
+
+ const auto len = static_cast<size_t>(std::distance(first, last));
+ if (JSON_LIKELY(len > 0))
+ {
+ // there is at least one element: use the address of first
+ ia = std::make_shared<input_buffer_adapter>(reinterpret_cast<const char*>(&(*first)), len);
+ }
+ else
+ {
+ // the address of first cannot be used: use nullptr
+ ia = std::make_shared<input_buffer_adapter>(nullptr, len);
+ }
+ }
+
+ /// input adapter for array
+ template<class T, std::size_t N>
+ input_adapter(T (&array)[N])
+ : input_adapter(std::begin(array), std::end(array)) {}
+
+ /// input adapter for contiguous container
+ template<class ContiguousContainer, typename
+ std::enable_if<not std::is_pointer<ContiguousContainer>::value and
+ std::is_base_of<std::random_access_iterator_tag, typename std::iterator_traits<decltype(std::begin(std::declval<ContiguousContainer const>()))>::iterator_category>::value,
+ int>::type = 0>
+ input_adapter(const ContiguousContainer& c)
+ : input_adapter(std::begin(c), std::end(c)) {}
+
+ operator input_adapter_t()
+ {
+ return ia;
+ }
+
+ private:
+ /// the actual adapter
+ input_adapter_t ia = nullptr;
+};
+} // namespace detail
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/input/json_sax.hpp b/include/lib/modernjson/detail/input/json_sax.hpp
new file mode 100644
index 0000000..e1d48a2
--- /dev/null
+++ b/include/lib/modernjson/detail/input/json_sax.hpp
@@ -0,0 +1,701 @@
+#pragma once
+
+#include <cstddef>
+#include <string>
+#include <vector>
+
+#include <lib/modernjson/detail/input/parser.hpp>
+#include <lib/modernjson/detail/exceptions.hpp>
+
+namespace nlohmann
+{
+
+/*!
+@brief SAX interface
+
+This class describes the SAX interface used by @ref nlohmann::json::sax_parse.
+Each function is called in different situations while the input is parsed. The
+boolean return value informs the parser whether to continue processing the
+input.
+*/
+template<typename BasicJsonType>
+struct json_sax
+{
+ /// type for (signed) integers
+ using number_integer_t = typename BasicJsonType::number_integer_t;
+ /// type for unsigned integers
+ using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+ /// type for floating-point numbers
+ using number_float_t = typename BasicJsonType::number_float_t;
+ /// type for strings
+ using string_t = typename BasicJsonType::string_t;
+
+ /*!
+ @brief a null value was read
+ @return whether parsing should proceed
+ */
+ virtual bool null() = 0;
+
+ /*!
+ @brief a boolean value was read
+ @param[in] val boolean value
+ @return whether parsing should proceed
+ */
+ virtual bool boolean(bool val) = 0;
+
+ /*!
+ @brief an integer number was read
+ @param[in] val integer value
+ @return whether parsing should proceed
+ */
+ virtual bool number_integer(number_integer_t val) = 0;
+
+ /*!
+ @brief an unsigned integer number was read
+ @param[in] val unsigned integer value
+ @return whether parsing should proceed
+ */
+ virtual bool number_unsigned(number_unsigned_t val) = 0;
+
+ /*!
+ @brief an floating-point number was read
+ @param[in] val floating-point value
+ @param[in] s raw token value
+ @return whether parsing should proceed
+ */
+ virtual bool number_float(number_float_t val, const string_t& s) = 0;
+
+ /*!
+ @brief a string was read
+ @param[in] val string value
+ @return whether parsing should proceed
+ @note It is safe to move the passed string.
+ */
+ virtual bool string(string_t& val) = 0;
+
+ /*!
+ @brief the beginning of an object was read
+ @param[in] elements number of object elements or -1 if unknown
+ @return whether parsing should proceed
+ @note binary formats may report the number of elements
+ */
+ virtual bool start_object(std::size_t elements) = 0;
+
+ /*!
+ @brief an object key was read
+ @param[in] val object key
+ @return whether parsing should proceed
+ @note It is safe to move the passed string.
+ */
+ virtual bool key(string_t& val) = 0;
+
+ /*!
+ @brief the end of an object was read
+ @return whether parsing should proceed
+ */
+ virtual bool end_object() = 0;
+
+ /*!
+ @brief the beginning of an array was read
+ @param[in] elements number of array elements or -1 if unknown
+ @return whether parsing should proceed
+ @note binary formats may report the number of elements
+ */
+ virtual bool start_array(std::size_t elements) = 0;
+
+ /*!
+ @brief the end of an array was read
+ @return whether parsing should proceed
+ */
+ virtual bool end_array() = 0;
+
+ /*!
+ @brief a parse error occurred
+ @param[in] position the position in the input where the error occurs
+ @param[in] last_token the last read token
+ @param[in] ex an exception object describing the error
+ @return whether parsing should proceed (must return false)
+ */
+ virtual bool parse_error(std::size_t position,
+ const std::string& last_token,
+ const detail::exception& ex) = 0;
+
+ virtual ~json_sax() = default;
+};
+
+
+namespace detail
+{
+/*!
+@brief SAX implementation to create a JSON value from SAX events
+
+This class implements the @ref json_sax interface and processes the SAX events
+to create a JSON value which makes it basically a DOM parser. The structure or
+hierarchy of the JSON value is managed by the stack `ref_stack` which contains
+a pointer to the respective array or object for each recursion depth.
+
+After successful parsing, the value that is passed by reference to the
+constructor contains the parsed value.
+
+@tparam BasicJsonType the JSON type
+*/
+template<typename BasicJsonType>
+class json_sax_dom_parser
+{
+ public:
+ using number_integer_t = typename BasicJsonType::number_integer_t;
+ using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+ using number_float_t = typename BasicJsonType::number_float_t;
+ using string_t = typename BasicJsonType::string_t;
+
+ /*!
+ @param[in, out] r reference to a JSON value that is manipulated while
+ parsing
+ @param[in] allow_exceptions_ whether parse errors yield exceptions
+ */
+ explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true)
+ : root(r), allow_exceptions(allow_exceptions_)
+ {}
+
+ bool null()
+ {
+ handle_value(nullptr);
+ return true;
+ }
+
+ bool boolean(bool val)
+ {
+ handle_value(val);
+ return true;
+ }
+
+ bool number_integer(number_integer_t val)
+ {
+ handle_value(val);
+ return true;
+ }
+
+ bool number_unsigned(number_unsigned_t val)
+ {
+ handle_value(val);
+ return true;
+ }
+
+ bool number_float(number_float_t val, const string_t& /*unused*/)
+ {
+ handle_value(val);
+ return true;
+ }
+
+ bool string(string_t& val)
+ {
+ handle_value(val);
+ return true;
+ }
+
+ bool start_object(std::size_t len)
+ {
+ ref_stack.push_back(handle_value(BasicJsonType::value_t::object));
+
+ if (JSON_UNLIKELY(len != std::size_t(-1) and len > ref_stack.back()->max_size()))
+ {
+ JSON_THROW(out_of_range::create(408,
+ "excessive object size: " + std::to_string(len)));
+ }
+
+ return true;
+ }
+
+ bool key(string_t& val)
+ {
+ // add null at given key and store the reference for later
+ object_element = &(ref_stack.back()->m_value.object->operator[](val));
+ return true;
+ }
+
+ bool end_object()
+ {
+ ref_stack.pop_back();
+ return true;
+ }
+
+ bool start_array(std::size_t len)
+ {
+ ref_stack.push_back(handle_value(BasicJsonType::value_t::array));
+
+ if (JSON_UNLIKELY(len != std::size_t(-1) and len > ref_stack.back()->max_size()))
+ {
+ JSON_THROW(out_of_range::create(408,
+ "excessive array size: " + std::to_string(len)));
+ }
+
+ return true;
+ }
+
+ bool end_array()
+ {
+ ref_stack.pop_back();
+ return true;
+ }
+
+ bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,
+ const detail::exception& ex)
+ {
+ errored = true;
+ if (allow_exceptions)
+ {
+ // determine the proper exception type from the id
+ switch ((ex.id / 100) % 100)
+ {
+ case 1:
+ JSON_THROW(*reinterpret_cast<const detail::parse_error*>(&ex));
+ case 4:
+ JSON_THROW(*reinterpret_cast<const detail::out_of_range*>(&ex));
+ // LCOV_EXCL_START
+ case 2:
+ JSON_THROW(*reinterpret_cast<const detail::invalid_iterator*>(&ex));
+ case 3:
+ JSON_THROW(*reinterpret_cast<const detail::type_error*>(&ex));
+ case 5:
+ JSON_THROW(*reinterpret_cast<const detail::other_error*>(&ex));
+ default:
+ assert(false);
+ // LCOV_EXCL_STOP
+ }
+ }
+ return false;
+ }
+
+ constexpr bool is_errored() const
+ {
+ return errored;
+ }
+
+ private:
+ /*!
+ @invariant If the ref stack is empty, then the passed value will be the new
+ root.
+ @invariant If the ref stack contains a value, then it is an array or an
+ object to which we can add elements
+ */
+ template<typename Value>
+ BasicJsonType* handle_value(Value&& v)
+ {
+ if (ref_stack.empty())
+ {
+ root = BasicJsonType(std::forward<Value>(v));
+ return &root;
+ }
+
+ assert(ref_stack.back()->is_array() or ref_stack.back()->is_object());
+
+ if (ref_stack.back()->is_array())
+ {
+ ref_stack.back()->m_value.array->emplace_back(std::forward<Value>(v));
+ return &(ref_stack.back()->m_value.array->back());
+ }
+ else
+ {
+ assert(object_element);
+ *object_element = BasicJsonType(std::forward<Value>(v));
+ return object_element;
+ }
+ }
+
+ /// the parsed JSON value
+ BasicJsonType& root;
+ /// stack to model hierarchy of values
+ std::vector<BasicJsonType*> ref_stack;
+ /// helper to hold the reference for the next object element
+ BasicJsonType* object_element = nullptr;
+ /// whether a syntax error occurred
+ bool errored = false;
+ /// whether to throw exceptions in case of errors
+ const bool allow_exceptions = true;
+};
+
+template<typename BasicJsonType>
+class json_sax_dom_callback_parser
+{
+ public:
+ using number_integer_t = typename BasicJsonType::number_integer_t;
+ using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+ using number_float_t = typename BasicJsonType::number_float_t;
+ using string_t = typename BasicJsonType::string_t;
+ using parser_callback_t = typename BasicJsonType::parser_callback_t;
+ using parse_event_t = typename BasicJsonType::parse_event_t;
+
+ json_sax_dom_callback_parser(BasicJsonType& r,
+ const parser_callback_t cb,
+ const bool allow_exceptions_ = true)
+ : root(r), callback(cb), allow_exceptions(allow_exceptions_)
+ {
+ keep_stack.push_back(true);
+ }
+
+ bool null()
+ {
+ handle_value(nullptr);
+ return true;
+ }
+
+ bool boolean(bool val)
+ {
+ handle_value(val);
+ return true;
+ }
+
+ bool number_integer(number_integer_t val)
+ {
+ handle_value(val);
+ return true;
+ }
+
+ bool number_unsigned(number_unsigned_t val)
+ {
+ handle_value(val);
+ return true;
+ }
+
+ bool number_float(number_float_t val, const string_t& /*unused*/)
+ {
+ handle_value(val);
+ return true;
+ }
+
+ bool string(string_t& val)
+ {
+ handle_value(val);
+ return true;
+ }
+
+ bool start_object(std::size_t len)
+ {
+ // check callback for object start
+ const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::object_start, discarded);
+ keep_stack.push_back(keep);
+
+ auto val = handle_value(BasicJsonType::value_t::object, true);
+ ref_stack.push_back(val.second);
+
+ // check object limit
+ if (ref_stack.back())
+ {
+ if (JSON_UNLIKELY(len != std::size_t(-1) and len > ref_stack.back()->max_size()))
+ {
+ JSON_THROW(out_of_range::create(408,
+ "excessive object size: " + std::to_string(len)));
+ }
+ }
+
+ return true;
+ }
+
+ bool key(string_t& val)
+ {
+ BasicJsonType k = BasicJsonType(val);
+
+ // check callback for key
+ const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::key, k);
+ key_keep_stack.push_back(keep);
+
+ // add discarded value at given key and store the reference for later
+ if (keep and ref_stack.back())
+ {
+ object_element = &(ref_stack.back()->m_value.object->operator[](val) = discarded);
+ }
+
+ return true;
+ }
+
+ bool end_object()
+ {
+ if (ref_stack.back())
+ {
+ if (not callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back()))
+ {
+ // discard object
+ *ref_stack.back() = discarded;
+ }
+ }
+
+ assert(not ref_stack.empty());
+ assert(not keep_stack.empty());
+ ref_stack.pop_back();
+ keep_stack.pop_back();
+
+ if (not ref_stack.empty() and ref_stack.back())
+ {
+ // remove discarded value
+ if (ref_stack.back()->is_object())
+ {
+ for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it)
+ {
+ if (it->is_discarded())
+ {
+ ref_stack.back()->erase(it);
+ break;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ bool start_array(std::size_t len)
+ {
+ const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::array_start, discarded);
+ keep_stack.push_back(keep);
+
+ auto val = handle_value(BasicJsonType::value_t::array, true);
+ ref_stack.push_back(val.second);
+
+ // check array limit
+ if (ref_stack.back())
+ {
+ if (JSON_UNLIKELY(len != std::size_t(-1) and len > ref_stack.back()->max_size()))
+ {
+ JSON_THROW(out_of_range::create(408,
+ "excessive array size: " + std::to_string(len)));
+ }
+ }
+
+ return true;
+ }
+
+ bool end_array()
+ {
+ bool keep = true;
+
+ if (ref_stack.back())
+ {
+ keep = callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back());
+ if (not keep)
+ {
+ // discard array
+ *ref_stack.back() = discarded;
+ }
+ }
+
+ assert(not ref_stack.empty());
+ assert(not keep_stack.empty());
+ ref_stack.pop_back();
+ keep_stack.pop_back();
+
+ // remove discarded value
+ if (not keep and not ref_stack.empty())
+ {
+ if (ref_stack.back()->is_array())
+ {
+ ref_stack.back()->m_value.array->pop_back();
+ }
+ }
+
+ return true;
+ }
+
+ bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,
+ const detail::exception& ex)
+ {
+ errored = true;
+ if (allow_exceptions)
+ {
+ // determine the proper exception type from the id
+ switch ((ex.id / 100) % 100)
+ {
+ case 1:
+ JSON_THROW(*reinterpret_cast<const detail::parse_error*>(&ex));
+ case 4:
+ JSON_THROW(*reinterpret_cast<const detail::out_of_range*>(&ex));
+ // LCOV_EXCL_START
+ case 2:
+ JSON_THROW(*reinterpret_cast<const detail::invalid_iterator*>(&ex));
+ case 3:
+ JSON_THROW(*reinterpret_cast<const detail::type_error*>(&ex));
+ case 5:
+ JSON_THROW(*reinterpret_cast<const detail::other_error*>(&ex));
+ default:
+ assert(false);
+ // LCOV_EXCL_STOP
+ }
+ }
+ return false;
+ }
+
+ constexpr bool is_errored() const
+ {
+ return errored;
+ }
+
+ private:
+ /*!
+ @param[in] v value to add to the JSON value we build during parsing
+ @param[in] skip_callback whether we should skip calling the callback
+ function; this is required after start_array() and
+ start_object() SAX events, because otherwise we would call the
+ callback function with an empty array or object, respectively.
+
+ @invariant If the ref stack is empty, then the passed value will be the new
+ root.
+ @invariant If the ref stack contains a value, then it is an array or an
+ object to which we can add elements
+
+ @return pair of boolean (whether value should be kept) and pointer (to the
+ passed value in the ref_stack hierarchy; nullptr if not kept)
+ */
+ template<typename Value>
+ std::pair<bool, BasicJsonType*> handle_value(Value&& v, const bool skip_callback = false)
+ {
+ assert(not keep_stack.empty());
+
+ // do not handle this value if we know it would be added to a discarded
+ // container
+ if (not keep_stack.back())
+ {
+ return {false, nullptr};
+ }
+
+ // create value
+ auto value = BasicJsonType(std::forward<Value>(v));
+
+ // check callback
+ const bool keep = skip_callback or callback(static_cast<int>(ref_stack.size()), parse_event_t::value, value);
+
+ // do not handle this value if we just learnt it shall be discarded
+ if (not keep)
+ {
+ return {false, nullptr};
+ }
+
+ if (ref_stack.empty())
+ {
+ root = std::move(value);
+ return {true, &root};
+ }
+
+ // skip this value if we already decided to skip the parent
+ // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360)
+ if (not ref_stack.back())
+ {
+ return {false, nullptr};
+ }
+
+ // we now only expect arrays and objects
+ assert(ref_stack.back()->is_array() or ref_stack.back()->is_object());
+
+ if (ref_stack.back()->is_array())
+ {
+ ref_stack.back()->m_value.array->push_back(std::move(value));
+ return {true, &(ref_stack.back()->m_value.array->back())};
+ }
+ else
+ {
+ // check if we should store an element for the current key
+ assert(not key_keep_stack.empty());
+ const bool store_element = key_keep_stack.back();
+ key_keep_stack.pop_back();
+
+ if (not store_element)
+ {
+ return {false, nullptr};
+ }
+
+ assert(object_element);
+ *object_element = std::move(value);
+ return {true, object_element};
+ }
+ }
+
+ /// the parsed JSON value
+ BasicJsonType& root;
+ /// stack to model hierarchy of values
+ std::vector<BasicJsonType*> ref_stack;
+ /// stack to manage which values to keep
+ std::vector<bool> keep_stack;
+ /// stack to manage which object keys to keep
+ std::vector<bool> key_keep_stack;
+ /// helper to hold the reference for the next object element
+ BasicJsonType* object_element = nullptr;
+ /// whether a syntax error occurred
+ bool errored = false;
+ /// callback function
+ const parser_callback_t callback = nullptr;
+ /// whether to throw exceptions in case of errors
+ const bool allow_exceptions = true;
+ /// a discarded value for the callback
+ BasicJsonType discarded = BasicJsonType::value_t::discarded;
+};
+
+template<typename BasicJsonType>
+class json_sax_acceptor
+{
+ public:
+ using number_integer_t = typename BasicJsonType::number_integer_t;
+ using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+ using number_float_t = typename BasicJsonType::number_float_t;
+ using string_t = typename BasicJsonType::string_t;
+
+ bool null()
+ {
+ return true;
+ }
+
+ bool boolean(bool /*unused*/)
+ {
+ return true;
+ }
+
+ bool number_integer(number_integer_t /*unused*/)
+ {
+ return true;
+ }
+
+ bool number_unsigned(number_unsigned_t /*unused*/)
+ {
+ return true;
+ }
+
+ bool number_float(number_float_t /*unused*/, const string_t& /*unused*/)
+ {
+ return true;
+ }
+
+ bool string(string_t& /*unused*/)
+ {
+ return true;
+ }
+
+ bool start_object(std::size_t /*unused*/ = std::size_t(-1))
+ {
+ return true;
+ }
+
+ bool key(string_t& /*unused*/)
+ {
+ return true;
+ }
+
+ bool end_object()
+ {
+ return true;
+ }
+
+ bool start_array(std::size_t /*unused*/ = std::size_t(-1))
+ {
+ return true;
+ }
+
+ bool end_array()
+ {
+ return true;
+ }
+
+ bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& /*unused*/)
+ {
+ return false;
+ }
+};
+} // namespace detail
+
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/input/lexer.hpp b/include/lib/modernjson/detail/input/lexer.hpp
new file mode 100644
index 0000000..b61e289
--- /dev/null
+++ b/include/lib/modernjson/detail/input/lexer.hpp
@@ -0,0 +1,1506 @@
+#pragma once
+
+#include <clocale> // localeconv
+#include <cstddef> // size_t
+#include <cstdlib> // strtof, strtod, strtold, strtoll, strtoull
+#include <cstdio> // snprintf
+#include <initializer_list> // initializer_list
+#include <string> // char_traits, string
+#include <vector> // vector
+
+#include <lib/modernjson/detail/macro_scope.hpp>
+#include <lib/modernjson/detail/input/input_adapters.hpp>
+#include <lib/modernjson/detail/input/position_t.hpp>
+
+namespace nlohmann
+{
+namespace detail
+{
+///////////
+// lexer //
+///////////
+
+/*!
+@brief lexical analysis
+
+This class organizes the lexical analysis during JSON deserialization.
+*/
+template<typename BasicJsonType>
+class lexer
+{
+ using number_integer_t = typename BasicJsonType::number_integer_t;
+ using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+ using number_float_t = typename BasicJsonType::number_float_t;
+ using string_t = typename BasicJsonType::string_t;
+
+ public:
+ /// token types for the parser
+ enum class token_type
+ {
+ uninitialized, ///< indicating the scanner is uninitialized
+ literal_true, ///< the `true` literal
+ literal_false, ///< the `false` literal
+ literal_null, ///< the `null` literal
+ value_string, ///< a string -- use get_string() for actual value
+ value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value
+ value_integer, ///< a signed integer -- use get_number_integer() for actual value
+ value_float, ///< an floating point number -- use get_number_float() for actual value
+ begin_array, ///< the character for array begin `[`
+ begin_object, ///< the character for object begin `{`
+ end_array, ///< the character for array end `]`
+ end_object, ///< the character for object end `}`
+ name_separator, ///< the name separator `:`
+ value_separator, ///< the value separator `,`
+ parse_error, ///< indicating a parse error
+ end_of_input, ///< indicating the end of the input buffer
+ literal_or_value ///< a literal or the begin of a value (only for diagnostics)
+ };
+
+ /// return name of values of type token_type (only used for errors)
+ static const char* token_type_name(const token_type t) noexcept
+ {
+ switch (t)
+ {
+ case token_type::uninitialized:
+ return "<uninitialized>";
+ case token_type::literal_true:
+ return "true literal";
+ case token_type::literal_false:
+ return "false literal";
+ case token_type::literal_null:
+ return "null literal";
+ case token_type::value_string:
+ return "string literal";
+ case lexer::token_type::value_unsigned:
+ case lexer::token_type::value_integer:
+ case lexer::token_type::value_float:
+ return "number literal";
+ case token_type::begin_array:
+ return "'['";
+ case token_type::begin_object:
+ return "'{'";
+ case token_type::end_array:
+ return "']'";
+ case token_type::end_object:
+ return "'}'";
+ case token_type::name_separator:
+ return "':'";
+ case token_type::value_separator:
+ return "','";
+ case token_type::parse_error:
+ return "<parse error>";
+ case token_type::end_of_input:
+ return "end of input";
+ case token_type::literal_or_value:
+ return "'[', '{', or a literal";
+ // LCOV_EXCL_START
+ default: // catch non-enum values
+ return "unknown token";
+ // LCOV_EXCL_STOP
+ }
+ }
+
+ explicit lexer(detail::input_adapter_t&& adapter)
+ : ia(std::move(adapter)), decimal_point_char(get_decimal_point()) {}
+
+ // delete because of pointer members
+ lexer(const lexer&) = delete;
+ lexer(lexer&&) = delete;
+ lexer& operator=(lexer&) = delete;
+ lexer& operator=(lexer&&) = delete;
+ ~lexer() = default;
+
+ private:
+ /////////////////////
+ // locales
+ /////////////////////
+
+ /// return the locale-dependent decimal point
+ static char get_decimal_point() noexcept
+ {
+ const auto loc = localeconv();
+ assert(loc != nullptr);
+ return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point);
+ }
+
+ /////////////////////
+ // scan functions
+ /////////////////////
+
+ /*!
+ @brief get codepoint from 4 hex characters following `\u`
+
+ For input "\u c1 c2 c3 c4" the codepoint is:
+ (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4
+ = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0)
+
+ Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f'
+ must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The
+ conversion is done by subtracting the offset (0x30, 0x37, and 0x57)
+ between the ASCII value of the character and the desired integer value.
+
+ @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or
+ non-hex character)
+ */
+ int get_codepoint()
+ {
+ // this function only makes sense after reading `\u`
+ assert(current == 'u');
+ int codepoint = 0;
+
+ const auto factors = { 12, 8, 4, 0 };
+ for (const auto factor : factors)
+ {
+ get();
+
+ if (current >= '0' and current <= '9')
+ {
+ codepoint += ((current - 0x30) << factor);
+ }
+ else if (current >= 'A' and current <= 'F')
+ {
+ codepoint += ((current - 0x37) << factor);
+ }
+ else if (current >= 'a' and current <= 'f')
+ {
+ codepoint += ((current - 0x57) << factor);
+ }
+ else
+ {
+ return -1;
+ }
+ }
+
+ assert(0x0000 <= codepoint and codepoint <= 0xFFFF);
+ return codepoint;
+ }
+
+ /*!
+ @brief check if the next byte(s) are inside a given range
+
+ Adds the current byte and, for each passed range, reads a new byte and
+ checks if it is inside the range. If a violation was detected, set up an
+ error message and return false. Otherwise, return true.
+
+ @param[in] ranges list of integers; interpreted as list of pairs of
+ inclusive lower and upper bound, respectively
+
+ @pre The passed list @a ranges must have 2, 4, or 6 elements; that is,
+ 1, 2, or 3 pairs. This precondition is enforced by an assertion.
+
+ @return true if and only if no range violation was detected
+ */
+ bool next_byte_in_range(std::initializer_list<int> ranges)
+ {
+ assert(ranges.size() == 2 or ranges.size() == 4 or ranges.size() == 6);
+ add(current);
+
+ for (auto range = ranges.begin(); range != ranges.end(); ++range)
+ {
+ get();
+ if (JSON_LIKELY(*range <= current and current <= *(++range)))
+ {
+ add(current);
+ }
+ else
+ {
+ error_message = "invalid string: ill-formed UTF-8 byte";
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /*!
+ @brief scan a string literal
+
+ This function scans a string according to Sect. 7 of RFC 7159. While
+ scanning, bytes are escaped and copied into buffer token_buffer. Then the
+ function returns successfully, token_buffer is *not* null-terminated (as it
+ may contain \0 bytes), and token_buffer.size() is the number of bytes in the
+ string.
+
+ @return token_type::value_string if string could be successfully scanned,
+ token_type::parse_error otherwise
+
+ @note In case of errors, variable error_message contains a textual
+ description.
+ */
+ token_type scan_string()
+ {
+ // reset token_buffer (ignore opening quote)
+ reset();
+
+ // we entered the function by reading an open quote
+ assert(current == '\"');
+
+ while (true)
+ {
+ // get next character
+ switch (get())
+ {
+ // end of file while parsing string
+ case std::char_traits<char>::eof():
+ {
+ error_message = "invalid string: missing closing quote";
+ return token_type::parse_error;
+ }
+
+ // closing quote
+ case '\"':
+ {
+ return token_type::value_string;
+ }
+
+ // escapes
+ case '\\':
+ {
+ switch (get())
+ {
+ // quotation mark
+ case '\"':
+ add('\"');
+ break;
+ // reverse solidus
+ case '\\':
+ add('\\');
+ break;
+ // solidus
+ case '/':
+ add('/');
+ break;
+ // backspace
+ case 'b':
+ add('\b');
+ break;
+ // form feed
+ case 'f':
+ add('\f');
+ break;
+ // line feed
+ case 'n':
+ add('\n');
+ break;
+ // carriage return
+ case 'r':
+ add('\r');
+ break;
+ // tab
+ case 't':
+ add('\t');
+ break;
+
+ // unicode escapes
+ case 'u':
+ {
+ const int codepoint1 = get_codepoint();
+ int codepoint = codepoint1; // start with codepoint1
+
+ if (JSON_UNLIKELY(codepoint1 == -1))
+ {
+ error_message = "invalid string: '\\u' must be followed by 4 hex digits";
+ return token_type::parse_error;
+ }
+
+ // check if code point is a high surrogate
+ if (0xD800 <= codepoint1 and codepoint1 <= 0xDBFF)
+ {
+ // expect next \uxxxx entry
+ if (JSON_LIKELY(get() == '\\' and get() == 'u'))
+ {
+ const int codepoint2 = get_codepoint();
+
+ if (JSON_UNLIKELY(codepoint2 == -1))
+ {
+ error_message = "invalid string: '\\u' must be followed by 4 hex digits";
+ return token_type::parse_error;
+ }
+
+ // check if codepoint2 is a low surrogate
+ if (JSON_LIKELY(0xDC00 <= codepoint2 and codepoint2 <= 0xDFFF))
+ {
+ // overwrite codepoint
+ codepoint =
+ // high surrogate occupies the most significant 22 bits
+ (codepoint1 << 10)
+ // low surrogate occupies the least significant 15 bits
+ + codepoint2
+ // there is still the 0xD800, 0xDC00 and 0x10000 noise
+ // in the result so we have to subtract with:
+ // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00
+ - 0x35FDC00;
+ }
+ else
+ {
+ error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF";
+ return token_type::parse_error;
+ }
+ }
+ else
+ {
+ error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF";
+ return token_type::parse_error;
+ }
+ }
+ else
+ {
+ if (JSON_UNLIKELY(0xDC00 <= codepoint1 and codepoint1 <= 0xDFFF))
+ {
+ error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF";
+ return token_type::parse_error;
+ }
+ }
+
+ // result of the above calculation yields a proper codepoint
+ assert(0x00 <= codepoint and codepoint <= 0x10FFFF);
+
+ // translate codepoint into bytes
+ if (codepoint < 0x80)
+ {
+ // 1-byte characters: 0xxxxxxx (ASCII)
+ add(codepoint);
+ }
+ else if (codepoint <= 0x7FF)
+ {
+ // 2-byte characters: 110xxxxx 10xxxxxx
+ add(0xC0 | (codepoint >> 6));
+ add(0x80 | (codepoint & 0x3F));
+ }
+ else if (codepoint <= 0xFFFF)
+ {
+ // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx
+ add(0xE0 | (codepoint >> 12));
+ add(0x80 | ((codepoint >> 6) & 0x3F));
+ add(0x80 | (codepoint & 0x3F));
+ }
+ else
+ {
+ // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+ add(0xF0 | (codepoint >> 18));
+ add(0x80 | ((codepoint >> 12) & 0x3F));
+ add(0x80 | ((codepoint >> 6) & 0x3F));
+ add(0x80 | (codepoint & 0x3F));
+ }
+
+ break;
+ }
+
+ // other characters after escape
+ default:
+ error_message = "invalid string: forbidden character after backslash";
+ return token_type::parse_error;
+ }
+
+ break;
+ }
+
+ // invalid control characters
+ case 0x00:
+ {
+ error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000";
+ return token_type::parse_error;
+ }
+
+ case 0x01:
+ {
+ error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001";
+ return token_type::parse_error;
+ }
+
+ case 0x02:
+ {
+ error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002";
+ return token_type::parse_error;
+ }
+
+ case 0x03:
+ {
+ error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003";
+ return token_type::parse_error;
+ }
+
+ case 0x04:
+ {
+ error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004";
+ return token_type::parse_error;
+ }
+
+ case 0x05:
+ {
+ error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005";
+ return token_type::parse_error;
+ }
+
+ case 0x06:
+ {
+ error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006";
+ return token_type::parse_error;
+ }
+
+ case 0x07:
+ {
+ error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007";
+ return token_type::parse_error;
+ }
+
+ case 0x08:
+ {
+ error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b";
+ return token_type::parse_error;
+ }
+
+ case 0x09:
+ {
+ error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t";
+ return token_type::parse_error;
+ }
+
+ case 0x0A:
+ {
+ error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n";
+ return token_type::parse_error;
+ }
+
+ case 0x0B:
+ {
+ error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B";
+ return token_type::parse_error;
+ }
+
+ case 0x0C:
+ {
+ error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f";
+ return token_type::parse_error;
+ }
+
+ case 0x0D:
+ {
+ error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r";
+ return token_type::parse_error;
+ }
+
+ case 0x0E:
+ {
+ error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E";
+ return token_type::parse_error;
+ }
+
+ case 0x0F:
+ {
+ error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F";
+ return token_type::parse_error;
+ }
+
+ case 0x10:
+ {
+ error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010";
+ return token_type::parse_error;
+ }
+
+ case 0x11:
+ {
+ error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011";
+ return token_type::parse_error;
+ }
+
+ case 0x12:
+ {
+ error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012";
+ return token_type::parse_error;
+ }
+
+ case 0x13:
+ {
+ error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013";
+ return token_type::parse_error;
+ }
+
+ case 0x14:
+ {
+ error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014";
+ return token_type::parse_error;
+ }
+
+ case 0x15:
+ {
+ error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015";
+ return token_type::parse_error;
+ }
+
+ case 0x16:
+ {
+ error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016";
+ return token_type::parse_error;
+ }
+
+ case 0x17:
+ {
+ error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017";
+ return token_type::parse_error;
+ }
+
+ case 0x18:
+ {
+ error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018";
+ return token_type::parse_error;
+ }
+
+ case 0x19:
+ {
+ error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019";
+ return token_type::parse_error;
+ }
+
+ case 0x1A:
+ {
+ error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A";
+ return token_type::parse_error;
+ }
+
+ case 0x1B:
+ {
+ error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B";
+ return token_type::parse_error;
+ }
+
+ case 0x1C:
+ {
+ error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C";
+ return token_type::parse_error;
+ }
+
+ case 0x1D:
+ {
+ error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D";
+ return token_type::parse_error;
+ }
+
+ case 0x1E:
+ {
+ error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E";
+ return token_type::parse_error;
+ }
+
+ case 0x1F:
+ {
+ error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F";
+ return token_type::parse_error;
+ }
+
+ // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace))
+ case 0x20:
+ case 0x21:
+ case 0x23:
+ case 0x24:
+ case 0x25:
+ case 0x26:
+ case 0x27:
+ case 0x28:
+ case 0x29:
+ case 0x2A:
+ case 0x2B:
+ case 0x2C:
+ case 0x2D:
+ case 0x2E:
+ case 0x2F:
+ case 0x30:
+ case 0x31:
+ case 0x32:
+ case 0x33:
+ case 0x34:
+ case 0x35:
+ case 0x36:
+ case 0x37:
+ case 0x38:
+ case 0x39:
+ case 0x3A:
+ case 0x3B:
+ case 0x3C:
+ case 0x3D:
+ case 0x3E:
+ case 0x3F:
+ case 0x40:
+ case 0x41:
+ case 0x42:
+ case 0x43:
+ case 0x44:
+ case 0x45:
+ case 0x46:
+ case 0x47:
+ case 0x48:
+ case 0x49:
+ case 0x4A:
+ case 0x4B:
+ case 0x4C:
+ case 0x4D:
+ case 0x4E:
+ case 0x4F:
+ case 0x50:
+ case 0x51:
+ case 0x52:
+ case 0x53:
+ case 0x54:
+ case 0x55:
+ case 0x56:
+ case 0x57:
+ case 0x58:
+ case 0x59:
+ case 0x5A:
+ case 0x5B:
+ case 0x5D:
+ case 0x5E:
+ case 0x5F:
+ case 0x60:
+ case 0x61:
+ case 0x62:
+ case 0x63:
+ case 0x64:
+ case 0x65:
+ case 0x66:
+ case 0x67:
+ case 0x68:
+ case 0x69:
+ case 0x6A:
+ case 0x6B:
+ case 0x6C:
+ case 0x6D:
+ case 0x6E:
+ case 0x6F:
+ case 0x70:
+ case 0x71:
+ case 0x72:
+ case 0x73:
+ case 0x74:
+ case 0x75:
+ case 0x76:
+ case 0x77:
+ case 0x78:
+ case 0x79:
+ case 0x7A:
+ case 0x7B:
+ case 0x7C:
+ case 0x7D:
+ case 0x7E:
+ case 0x7F:
+ {
+ add(current);
+ break;
+ }
+
+ // U+0080..U+07FF: bytes C2..DF 80..BF
+ case 0xC2:
+ case 0xC3:
+ case 0xC4:
+ case 0xC5:
+ case 0xC6:
+ case 0xC7:
+ case 0xC8:
+ case 0xC9:
+ case 0xCA:
+ case 0xCB:
+ case 0xCC:
+ case 0xCD:
+ case 0xCE:
+ case 0xCF:
+ case 0xD0:
+ case 0xD1:
+ case 0xD2:
+ case 0xD3:
+ case 0xD4:
+ case 0xD5:
+ case 0xD6:
+ case 0xD7:
+ case 0xD8:
+ case 0xD9:
+ case 0xDA:
+ case 0xDB:
+ case 0xDC:
+ case 0xDD:
+ case 0xDE:
+ case 0xDF:
+ {
+ if (JSON_UNLIKELY(not next_byte_in_range({0x80, 0xBF})))
+ {
+ return token_type::parse_error;
+ }
+ break;
+ }
+
+ // U+0800..U+0FFF: bytes E0 A0..BF 80..BF
+ case 0xE0:
+ {
+ if (JSON_UNLIKELY(not (next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF}))))
+ {
+ return token_type::parse_error;
+ }
+ break;
+ }
+
+ // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF
+ // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF
+ case 0xE1:
+ case 0xE2:
+ case 0xE3:
+ case 0xE4:
+ case 0xE5:
+ case 0xE6:
+ case 0xE7:
+ case 0xE8:
+ case 0xE9:
+ case 0xEA:
+ case 0xEB:
+ case 0xEC:
+ case 0xEE:
+ case 0xEF:
+ {
+ if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF}))))
+ {
+ return token_type::parse_error;
+ }
+ break;
+ }
+
+ // U+D000..U+D7FF: bytes ED 80..9F 80..BF
+ case 0xED:
+ {
+ if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x9F, 0x80, 0xBF}))))
+ {
+ return token_type::parse_error;
+ }
+ break;
+ }
+
+ // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF
+ case 0xF0:
+ {
+ if (JSON_UNLIKELY(not (next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
+ {
+ return token_type::parse_error;
+ }
+ break;
+ }
+
+ // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF
+ case 0xF1:
+ case 0xF2:
+ case 0xF3:
+ {
+ if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
+ {
+ return token_type::parse_error;
+ }
+ break;
+ }
+
+ // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF
+ case 0xF4:
+ {
+ if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF}))))
+ {
+ return token_type::parse_error;
+ }
+ break;
+ }
+
+ // remaining bytes (80..C1 and F5..FF) are ill-formed
+ default:
+ {
+ error_message = "invalid string: ill-formed UTF-8 byte";
+ return token_type::parse_error;
+ }
+ }
+ }
+ }
+
+ static void strtof(float& f, const char* str, char** endptr) noexcept
+ {
+ f = std::strtof(str, endptr);
+ }
+
+ static void strtof(double& f, const char* str, char** endptr) noexcept
+ {
+ f = std::strtod(str, endptr);
+ }
+
+ static void strtof(long double& f, const char* str, char** endptr) noexcept
+ {
+ f = std::strtold(str, endptr);
+ }
+
+ /*!
+ @brief scan a number literal
+
+ This function scans a string according to Sect. 6 of RFC 7159.
+
+ The function is realized with a deterministic finite state machine derived
+ from the grammar described in RFC 7159. Starting in state "init", the
+ input is read and used to determined the next state. Only state "done"
+ accepts the number. State "error" is a trap state to model errors. In the
+ table below, "anything" means any character but the ones listed before.
+
+ state | 0 | 1-9 | e E | + | - | . | anything
+ ---------|----------|----------|----------|---------|---------|----------|-----------
+ init | zero | any1 | [error] | [error] | minus | [error] | [error]
+ minus | zero | any1 | [error] | [error] | [error] | [error] | [error]
+ zero | done | done | exponent | done | done | decimal1 | done
+ any1 | any1 | any1 | exponent | done | done | decimal1 | done
+ decimal1 | decimal2 | [error] | [error] | [error] | [error] | [error] | [error]
+ decimal2 | decimal2 | decimal2 | exponent | done | done | done | done
+ exponent | any2 | any2 | [error] | sign | sign | [error] | [error]
+ sign | any2 | any2 | [error] | [error] | [error] | [error] | [error]
+ any2 | any2 | any2 | done | done | done | done | done
+
+ The state machine is realized with one label per state (prefixed with
+ "scan_number_") and `goto` statements between them. The state machine
+ contains cycles, but any cycle can be left when EOF is read. Therefore,
+ the function is guaranteed to terminate.
+
+ During scanning, the read bytes are stored in token_buffer. This string is
+ then converted to a signed integer, an unsigned integer, or a
+ floating-point number.
+
+ @return token_type::value_unsigned, token_type::value_integer, or
+ token_type::value_float if number could be successfully scanned,
+ token_type::parse_error otherwise
+
+ @note The scanner is independent of the current locale. Internally, the
+ locale's decimal point is used instead of `.` to work with the
+ locale-dependent converters.
+ */
+ token_type scan_number() // lgtm [cpp/use-of-goto]
+ {
+ // reset token_buffer to store the number's bytes
+ reset();
+
+ // the type of the parsed number; initially set to unsigned; will be
+ // changed if minus sign, decimal point or exponent is read
+ token_type number_type = token_type::value_unsigned;
+
+ // state (init): we just found out we need to scan a number
+ switch (current)
+ {
+ case '-':
+ {
+ add(current);
+ goto scan_number_minus;
+ }
+
+ case '0':
+ {
+ add(current);
+ goto scan_number_zero;
+ }
+
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ {
+ add(current);
+ goto scan_number_any1;
+ }
+
+ // LCOV_EXCL_START
+ default:
+ {
+ // all other characters are rejected outside scan_number()
+ assert(false);
+ }
+ // LCOV_EXCL_STOP
+ }
+
+scan_number_minus:
+ // state: we just parsed a leading minus sign
+ number_type = token_type::value_integer;
+ switch (get())
+ {
+ case '0':
+ {
+ add(current);
+ goto scan_number_zero;
+ }
+
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ {
+ add(current);
+ goto scan_number_any1;
+ }
+
+ default:
+ {
+ error_message = "invalid number; expected digit after '-'";
+ return token_type::parse_error;
+ }
+ }
+
+scan_number_zero:
+ // state: we just parse a zero (maybe with a leading minus sign)
+ switch (get())
+ {
+ case '.':
+ {
+ add(decimal_point_char);
+ goto scan_number_decimal1;
+ }
+
+ case 'e':
+ case 'E':
+ {
+ add(current);
+ goto scan_number_exponent;
+ }
+
+ default:
+ goto scan_number_done;
+ }
+
+scan_number_any1:
+ // state: we just parsed a number 0-9 (maybe with a leading minus sign)
+ switch (get())
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ {
+ add(current);
+ goto scan_number_any1;
+ }
+
+ case '.':
+ {
+ add(decimal_point_char);
+ goto scan_number_decimal1;
+ }
+
+ case 'e':
+ case 'E':
+ {
+ add(current);
+ goto scan_number_exponent;
+ }
+
+ default:
+ goto scan_number_done;
+ }
+
+scan_number_decimal1:
+ // state: we just parsed a decimal point
+ number_type = token_type::value_float;
+ switch (get())
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ {
+ add(current);
+ goto scan_number_decimal2;
+ }
+
+ default:
+ {
+ error_message = "invalid number; expected digit after '.'";
+ return token_type::parse_error;
+ }
+ }
+
+scan_number_decimal2:
+ // we just parsed at least one number after a decimal point
+ switch (get())
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ {
+ add(current);
+ goto scan_number_decimal2;
+ }
+
+ case 'e':
+ case 'E':
+ {
+ add(current);
+ goto scan_number_exponent;
+ }
+
+ default:
+ goto scan_number_done;
+ }
+
+scan_number_exponent:
+ // we just parsed an exponent
+ number_type = token_type::value_float;
+ switch (get())
+ {
+ case '+':
+ case '-':
+ {
+ add(current);
+ goto scan_number_sign;
+ }
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ {
+ add(current);
+ goto scan_number_any2;
+ }
+
+ default:
+ {
+ error_message =
+ "invalid number; expected '+', '-', or digit after exponent";
+ return token_type::parse_error;
+ }
+ }
+
+scan_number_sign:
+ // we just parsed an exponent sign
+ switch (get())
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ {
+ add(current);
+ goto scan_number_any2;
+ }
+
+ default:
+ {
+ error_message = "invalid number; expected digit after exponent sign";
+ return token_type::parse_error;
+ }
+ }
+
+scan_number_any2:
+ // we just parsed a number after the exponent or exponent sign
+ switch (get())
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ {
+ add(current);
+ goto scan_number_any2;
+ }
+
+ default:
+ goto scan_number_done;
+ }
+
+scan_number_done:
+ // unget the character after the number (we only read it to know that
+ // we are done scanning a number)
+ unget();
+
+ char* endptr = nullptr;
+ errno = 0;
+
+ // try to parse integers first and fall back to floats
+ if (number_type == token_type::value_unsigned)
+ {
+ const auto x = std::strtoull(token_buffer.data(), &endptr, 10);
+
+ // we checked the number format before
+ assert(endptr == token_buffer.data() + token_buffer.size());
+
+ if (errno == 0)
+ {
+ value_unsigned = static_cast<number_unsigned_t>(x);
+ if (value_unsigned == x)
+ {
+ return token_type::value_unsigned;
+ }
+ }
+ }
+ else if (number_type == token_type::value_integer)
+ {
+ const auto x = std::strtoll(token_buffer.data(), &endptr, 10);
+
+ // we checked the number format before
+ assert(endptr == token_buffer.data() + token_buffer.size());
+
+ if (errno == 0)
+ {
+ value_integer = static_cast<number_integer_t>(x);
+ if (value_integer == x)
+ {
+ return token_type::value_integer;
+ }
+ }
+ }
+
+ // this code is reached if we parse a floating-point number or if an
+ // integer conversion above failed
+ strtof(value_float, token_buffer.data(), &endptr);
+
+ // we checked the number format before
+ assert(endptr == token_buffer.data() + token_buffer.size());
+
+ return token_type::value_float;
+ }
+
+ /*!
+ @param[in] literal_text the literal text to expect
+ @param[in] length the length of the passed literal text
+ @param[in] return_type the token type to return on success
+ */
+ token_type scan_literal(const char* literal_text, const std::size_t length,
+ token_type return_type)
+ {
+ assert(current == literal_text[0]);
+ for (std::size_t i = 1; i < length; ++i)
+ {
+ if (JSON_UNLIKELY(get() != literal_text[i]))
+ {
+ error_message = "invalid literal";
+ return token_type::parse_error;
+ }
+ }
+ return return_type;
+ }
+
+ /////////////////////
+ // input management
+ /////////////////////
+
+ /// reset token_buffer; current character is beginning of token
+ void reset() noexcept
+ {
+ token_buffer.clear();
+ token_string.clear();
+ token_string.push_back(std::char_traits<char>::to_char_type(current));
+ }
+
+ /*
+ @brief get next character from the input
+
+ This function provides the interface to the used input adapter. It does
+ not throw in case the input reached EOF, but returns a
+ `std::char_traits<char>::eof()` in that case. Stores the scanned characters
+ for use in error messages.
+
+ @return character read from the input
+ */
+ std::char_traits<char>::int_type get()
+ {
+ ++position.chars_read_total;
+ ++position.chars_read_current_line;
+
+ if (next_unget)
+ {
+ // just reset the next_unget variable and work with current
+ next_unget = false;
+ }
+ else
+ {
+ current = ia->get_character();
+ }
+
+ if (JSON_LIKELY(current != std::char_traits<char>::eof()))
+ {
+ token_string.push_back(std::char_traits<char>::to_char_type(current));
+ }
+
+ if (current == '\n')
+ {
+ ++position.lines_read;
+ ++position.chars_read_current_line = 0;
+ }
+
+ return current;
+ }
+
+ /*!
+ @brief unget current character (read it again on next get)
+
+ We implement unget by setting variable next_unget to true. The input is not
+ changed - we just simulate ungetting by modifying chars_read_total,
+ chars_read_current_line, and token_string. The next call to get() will
+ behave as if the unget character is read again.
+ */
+ void unget()
+ {
+ next_unget = true;
+
+ --position.chars_read_total;
+
+ // in case we "unget" a newline, we have to also decrement the lines_read
+ if (position.chars_read_current_line == 0)
+ {
+ if (position.lines_read > 0)
+ {
+ --position.lines_read;
+ }
+ }
+ else
+ {
+ --position.chars_read_current_line;
+ }
+
+ if (JSON_LIKELY(current != std::char_traits<char>::eof()))
+ {
+ assert(token_string.size() != 0);
+ token_string.pop_back();
+ }
+ }
+
+ /// add a character to token_buffer
+ void add(int c)
+ {
+ token_buffer.push_back(std::char_traits<char>::to_char_type(c));
+ }
+
+ public:
+ /////////////////////
+ // value getters
+ /////////////////////
+
+ /// return integer value
+ constexpr number_integer_t get_number_integer() const noexcept
+ {
+ return value_integer;
+ }
+
+ /// return unsigned integer value
+ constexpr number_unsigned_t get_number_unsigned() const noexcept
+ {
+ return value_unsigned;
+ }
+
+ /// return floating-point value
+ constexpr number_float_t get_number_float() const noexcept
+ {
+ return value_float;
+ }
+
+ /// return current string value (implicitly resets the token; useful only once)
+ string_t& get_string()
+ {
+ return token_buffer;
+ }
+
+ /////////////////////
+ // diagnostics
+ /////////////////////
+
+ /// return position of last read token
+ constexpr position_t get_position() const noexcept
+ {
+ return position;
+ }
+
+ /// return the last read token (for errors only). Will never contain EOF
+ /// (an arbitrary value that is not a valid char value, often -1), because
+ /// 255 may legitimately occur. May contain NUL, which should be escaped.
+ std::string get_token_string() const
+ {
+ // escape control characters
+ std::string result;
+ for (const auto c : token_string)
+ {
+ if ('\x00' <= c and c <= '\x1F')
+ {
+ // escape control characters
+ char cs[9];
+ (std::snprintf)(cs, 9, "<U+%.4X>", static_cast<unsigned char>(c));
+ result += cs;
+ }
+ else
+ {
+ // add character as is
+ result.push_back(c);
+ }
+ }
+
+ return result;
+ }
+
+ /// return syntax error message
+ constexpr const char* get_error_message() const noexcept
+ {
+ return error_message;
+ }
+
+ /////////////////////
+ // actual scanner
+ /////////////////////
+
+ /*!
+ @brief skip the UTF-8 byte order mark
+ @return true iff there is no BOM or the correct BOM has been skipped
+ */
+ bool skip_bom()
+ {
+ if (get() == 0xEF)
+ {
+ // check if we completely parse the BOM
+ return get() == 0xBB and get() == 0xBF;
+ }
+
+ // the first character is not the beginning of the BOM; unget it to
+ // process is later
+ unget();
+ return true;
+ }
+
+ token_type scan()
+ {
+ // initially, skip the BOM
+ if (position.chars_read_total == 0 and not skip_bom())
+ {
+ error_message = "invalid BOM; must be 0xEF 0xBB 0xBF if given";
+ return token_type::parse_error;
+ }
+
+ // read next character and ignore whitespace
+ do
+ {
+ get();
+ }
+ while (current == ' ' or current == '\t' or current == '\n' or current == '\r');
+
+ switch (current)
+ {
+ // structural characters
+ case '[':
+ return token_type::begin_array;
+ case ']':
+ return token_type::end_array;
+ case '{':
+ return token_type::begin_object;
+ case '}':
+ return token_type::end_object;
+ case ':':
+ return token_type::name_separator;
+ case ',':
+ return token_type::value_separator;
+
+ // literals
+ case 't':
+ return scan_literal("true", 4, token_type::literal_true);
+ case 'f':
+ return scan_literal("false", 5, token_type::literal_false);
+ case 'n':
+ return scan_literal("null", 4, token_type::literal_null);
+
+ // string
+ case '\"':
+ return scan_string();
+
+ // number
+ case '-':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ return scan_number();
+
+ // end of input (the null byte is needed when parsing from
+ // string literals)
+ case '\0':
+ case std::char_traits<char>::eof():
+ return token_type::end_of_input;
+
+ // error
+ default:
+ error_message = "invalid literal";
+ return token_type::parse_error;
+ }
+ }
+
+ private:
+ /// input adapter
+ detail::input_adapter_t ia = nullptr;
+
+ /// the current character
+ std::char_traits<char>::int_type current = std::char_traits<char>::eof();
+
+ /// whether the next get() call should just return current
+ bool next_unget = false;
+
+ /// the start position of the current token
+ position_t position;
+
+ /// raw input token string (for error messages)
+ std::vector<char> token_string {};
+
+ /// buffer for variable-length tokens (numbers, strings)
+ string_t token_buffer {};
+
+ /// a description of occurred lexer errors
+ const char* error_message = "";
+
+ // number values
+ number_integer_t value_integer = 0;
+ number_unsigned_t value_unsigned = 0;
+ number_float_t value_float = 0;
+
+ /// the decimal point
+ const char decimal_point_char = '.';
+};
+} // namespace detail
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/input/parser.hpp b/include/lib/modernjson/detail/input/parser.hpp
new file mode 100644
index 0000000..d205bbb
--- /dev/null
+++ b/include/lib/modernjson/detail/input/parser.hpp
@@ -0,0 +1,504 @@
+#pragma once
+
+#include <cassert> // assert
+#include <cmath> // isfinite
+#include <cstdint> // uint8_t
+#include <functional> // function
+#include <string> // string
+#include <utility> // move
+
+#include <lib/modernjson/detail/exceptions.hpp>
+#include <lib/modernjson/detail/macro_scope.hpp>
+#include <lib/modernjson/detail/meta/is_sax.hpp>
+#include <lib/modernjson/detail/input/input_adapters.hpp>
+#include <lib/modernjson/detail/input/json_sax.hpp>
+#include <lib/modernjson/detail/input/lexer.hpp>
+#include <lib/modernjson/detail/value_t.hpp>
+
+namespace nlohmann
+{
+namespace detail
+{
+////////////
+// parser //
+////////////
+
+/*!
+@brief syntax analysis
+
+This class implements a recursive decent parser.
+*/
+template<typename BasicJsonType>
+class parser
+{
+ using number_integer_t = typename BasicJsonType::number_integer_t;
+ using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+ using number_float_t = typename BasicJsonType::number_float_t;
+ using string_t = typename BasicJsonType::string_t;
+ using lexer_t = lexer<BasicJsonType>;
+ using token_type = typename lexer_t::token_type;
+
+ public:
+ enum class parse_event_t : uint8_t
+ {
+ /// the parser read `{` and started to process a JSON object
+ object_start,
+ /// the parser read `}` and finished processing a JSON object
+ object_end,
+ /// the parser read `[` and started to process a JSON array
+ array_start,
+ /// the parser read `]` and finished processing a JSON array
+ array_end,
+ /// the parser read a key of a value in an object
+ key,
+ /// the parser finished reading a JSON value
+ value
+ };
+
+ using parser_callback_t =
+ std::function<bool(int depth, parse_event_t event, BasicJsonType& parsed)>;
+
+ /// a parser reading from an input adapter
+ explicit parser(detail::input_adapter_t&& adapter,
+ const parser_callback_t cb = nullptr,
+ const bool allow_exceptions_ = true)
+ : callback(cb), m_lexer(std::move(adapter)), allow_exceptions(allow_exceptions_)
+ {
+ // read first token
+ get_token();
+ }
+
+ /*!
+ @brief public parser interface
+
+ @param[in] strict whether to expect the last token to be EOF
+ @param[in,out] result parsed JSON value
+
+ @throw parse_error.101 in case of an unexpected token
+ @throw parse_error.102 if to_unicode fails or surrogate error
+ @throw parse_error.103 if to_unicode fails
+ */
+ void parse(const bool strict, BasicJsonType& result)
+ {
+ if (callback)
+ {
+ json_sax_dom_callback_parser<BasicJsonType> sdp(result, callback, allow_exceptions);
+ sax_parse_internal(&sdp);
+ result.assert_invariant();
+
+ // in strict mode, input must be completely read
+ if (strict and (get_token() != token_type::end_of_input))
+ {
+ sdp.parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(),
+ exception_message(token_type::end_of_input, "value")));
+ }
+
+ // in case of an error, return discarded value
+ if (sdp.is_errored())
+ {
+ result = value_t::discarded;
+ return;
+ }
+
+ // set top-level value to null if it was discarded by the callback
+ // function
+ if (result.is_discarded())
+ {
+ result = nullptr;
+ }
+ }
+ else
+ {
+ json_sax_dom_parser<BasicJsonType> sdp(result, allow_exceptions);
+ sax_parse_internal(&sdp);
+ result.assert_invariant();
+
+ // in strict mode, input must be completely read
+ if (strict and (get_token() != token_type::end_of_input))
+ {
+ sdp.parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(),
+ exception_message(token_type::end_of_input, "value")));
+ }
+
+ // in case of an error, return discarded value
+ if (sdp.is_errored())
+ {
+ result = value_t::discarded;
+ return;
+ }
+ }
+ }
+
+ /*!
+ @brief public accept interface
+
+ @param[in] strict whether to expect the last token to be EOF
+ @return whether the input is a proper JSON text
+ */
+ bool accept(const bool strict = true)
+ {
+ json_sax_acceptor<BasicJsonType> sax_acceptor;
+ return sax_parse(&sax_acceptor, strict);
+ }
+
+ template <typename SAX>
+ bool sax_parse(SAX* sax, const bool strict = true)
+ {
+ (void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
+ const bool result = sax_parse_internal(sax);
+
+ // strict mode: next byte must be EOF
+ if (result and strict and (get_token() != token_type::end_of_input))
+ {
+ return sax->parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(),
+ exception_message(token_type::end_of_input, "value")));
+ }
+
+ return result;
+ }
+
+ private:
+ template <typename SAX>
+ bool sax_parse_internal(SAX* sax)
+ {
+ // stack to remember the hierarchy of structured values we are parsing
+ // true = array; false = object
+ std::vector<bool> states;
+ // value to avoid a goto (see comment where set to true)
+ bool skip_to_state_evaluation = false;
+
+ while (true)
+ {
+ if (not skip_to_state_evaluation)
+ {
+ // invariant: get_token() was called before each iteration
+ switch (last_token)
+ {
+ case token_type::begin_object:
+ {
+ if (JSON_UNLIKELY(not sax->start_object(std::size_t(-1))))
+ {
+ return false;
+ }
+
+ // closing } -> we are done
+ if (get_token() == token_type::end_object)
+ {
+ if (JSON_UNLIKELY(not sax->end_object()))
+ {
+ return false;
+ }
+ break;
+ }
+
+ // parse key
+ if (JSON_UNLIKELY(last_token != token_type::value_string))
+ {
+ return sax->parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(),
+ exception_message(token_type::value_string, "object key")));
+ }
+ if (JSON_UNLIKELY(not sax->key(m_lexer.get_string())))
+ {
+ return false;
+ }
+
+ // parse separator (:)
+ if (JSON_UNLIKELY(get_token() != token_type::name_separator))
+ {
+ return sax->parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(),
+ exception_message(token_type::name_separator, "object separator")));
+ }
+
+ // remember we are now inside an object
+ states.push_back(false);
+
+ // parse values
+ get_token();
+ continue;
+ }
+
+ case token_type::begin_array:
+ {
+ if (JSON_UNLIKELY(not sax->start_array(std::size_t(-1))))
+ {
+ return false;
+ }
+
+ // closing ] -> we are done
+ if (get_token() == token_type::end_array)
+ {
+ if (JSON_UNLIKELY(not sax->end_array()))
+ {
+ return false;
+ }
+ break;
+ }
+
+ // remember we are now inside an array
+ states.push_back(true);
+
+ // parse values (no need to call get_token)
+ continue;
+ }
+
+ case token_type::value_float:
+ {
+ const auto res = m_lexer.get_number_float();
+
+ if (JSON_UNLIKELY(not std::isfinite(res)))
+ {
+ return sax->parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ out_of_range::create(406, "number overflow parsing '" + m_lexer.get_token_string() + "'"));
+ }
+ else
+ {
+ if (JSON_UNLIKELY(not sax->number_float(res, m_lexer.get_string())))
+ {
+ return false;
+ }
+ break;
+ }
+ }
+
+ case token_type::literal_false:
+ {
+ if (JSON_UNLIKELY(not sax->boolean(false)))
+ {
+ return false;
+ }
+ break;
+ }
+
+ case token_type::literal_null:
+ {
+ if (JSON_UNLIKELY(not sax->null()))
+ {
+ return false;
+ }
+ break;
+ }
+
+ case token_type::literal_true:
+ {
+ if (JSON_UNLIKELY(not sax->boolean(true)))
+ {
+ return false;
+ }
+ break;
+ }
+
+ case token_type::value_integer:
+ {
+ if (JSON_UNLIKELY(not sax->number_integer(m_lexer.get_number_integer())))
+ {
+ return false;
+ }
+ break;
+ }
+
+ case token_type::value_string:
+ {
+ if (JSON_UNLIKELY(not sax->string(m_lexer.get_string())))
+ {
+ return false;
+ }
+ break;
+ }
+
+ case token_type::value_unsigned:
+ {
+ if (JSON_UNLIKELY(not sax->number_unsigned(m_lexer.get_number_unsigned())))
+ {
+ return false;
+ }
+ break;
+ }
+
+ case token_type::parse_error:
+ {
+ // using "uninitialized" to avoid "expected" message
+ return sax->parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(),
+ exception_message(token_type::uninitialized, "value")));
+ }
+
+ default: // the last token was unexpected
+ {
+ return sax->parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(),
+ exception_message(token_type::literal_or_value, "value")));
+ }
+ }
+ }
+ else
+ {
+ skip_to_state_evaluation = false;
+ }
+
+ // we reached this line after we successfully parsed a value
+ if (states.empty())
+ {
+ // empty stack: we reached the end of the hierarchy: done
+ return true;
+ }
+ else
+ {
+ if (states.back()) // array
+ {
+ // comma -> next value
+ if (get_token() == token_type::value_separator)
+ {
+ // parse a new value
+ get_token();
+ continue;
+ }
+
+ // closing ]
+ if (JSON_LIKELY(last_token == token_type::end_array))
+ {
+ if (JSON_UNLIKELY(not sax->end_array()))
+ {
+ return false;
+ }
+
+ // We are done with this array. Before we can parse a
+ // new value, we need to evaluate the new state first.
+ // By setting skip_to_state_evaluation to false, we
+ // are effectively jumping to the beginning of this if.
+ assert(not states.empty());
+ states.pop_back();
+ skip_to_state_evaluation = true;
+ continue;
+ }
+ else
+ {
+ return sax->parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(),
+ exception_message(token_type::end_array, "array")));
+ }
+ }
+ else // object
+ {
+ // comma -> next value
+ if (get_token() == token_type::value_separator)
+ {
+ // parse key
+ if (JSON_UNLIKELY(get_token() != token_type::value_string))
+ {
+ return sax->parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(),
+ exception_message(token_type::value_string, "object key")));
+ }
+ else
+ {
+ if (JSON_UNLIKELY(not sax->key(m_lexer.get_string())))
+ {
+ return false;
+ }
+ }
+
+ // parse separator (:)
+ if (JSON_UNLIKELY(get_token() != token_type::name_separator))
+ {
+ return sax->parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(),
+ exception_message(token_type::name_separator, "object separator")));
+ }
+
+ // parse values
+ get_token();
+ continue;
+ }
+
+ // closing }
+ if (JSON_LIKELY(last_token == token_type::end_object))
+ {
+ if (JSON_UNLIKELY(not sax->end_object()))
+ {
+ return false;
+ }
+
+ // We are done with this object. Before we can parse a
+ // new value, we need to evaluate the new state first.
+ // By setting skip_to_state_evaluation to false, we
+ // are effectively jumping to the beginning of this if.
+ assert(not states.empty());
+ states.pop_back();
+ skip_to_state_evaluation = true;
+ continue;
+ }
+ else
+ {
+ return sax->parse_error(m_lexer.get_position(),
+ m_lexer.get_token_string(),
+ parse_error::create(101, m_lexer.get_position(),
+ exception_message(token_type::end_object, "object")));
+ }
+ }
+ }
+ }
+ }
+
+ /// get next token from lexer
+ token_type get_token()
+ {
+ return (last_token = m_lexer.scan());
+ }
+
+ std::string exception_message(const token_type expected, const std::string& context)
+ {
+ std::string error_msg = "syntax error ";
+
+ if (not context.empty())
+ {
+ error_msg += "while parsing " + context + " ";
+ }
+
+ error_msg += "- ";
+
+ if (last_token == token_type::parse_error)
+ {
+ error_msg += std::string(m_lexer.get_error_message()) + "; last read: '" +
+ m_lexer.get_token_string() + "'";
+ }
+ else
+ {
+ error_msg += "unexpected " + std::string(lexer_t::token_type_name(last_token));
+ }
+
+ if (expected != token_type::uninitialized)
+ {
+ error_msg += "; expected " + std::string(lexer_t::token_type_name(expected));
+ }
+
+ return error_msg;
+ }
+
+ private:
+ /// callback function
+ const parser_callback_t callback = nullptr;
+ /// the type of the last read token
+ token_type last_token = token_type::uninitialized;
+ /// the lexer
+ lexer_t m_lexer;
+ /// whether to throw exceptions in case of errors
+ const bool allow_exceptions = true;
+};
+} // namespace detail
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/input/position_t.hpp b/include/lib/modernjson/detail/input/position_t.hpp
new file mode 100644
index 0000000..37f4ab1
--- /dev/null
+++ b/include/lib/modernjson/detail/input/position_t.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <cstddef> // size_t
+
+namespace nlohmann
+{
+namespace detail
+{
+/// struct to capture the start position of the current token
+struct position_t
+{
+ /// the total number of characters read
+ std::size_t chars_read_total = 0;
+ /// the number of characters read in the current line
+ std::size_t chars_read_current_line = 0;
+ /// the number of lines read
+ std::size_t lines_read = 0;
+
+ /// conversion to size_t to preserve SAX interface
+ constexpr operator size_t() const
+ {
+ return chars_read_total;
+ }
+};
+
+}
+}
diff --git a/include/lib/modernjson/detail/iterators/internal_iterator.hpp b/include/lib/modernjson/detail/iterators/internal_iterator.hpp
new file mode 100644
index 0000000..e1c12a8
--- /dev/null
+++ b/include/lib/modernjson/detail/iterators/internal_iterator.hpp
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <lib/modernjson/detail/iterators/primitive_iterator.hpp>
+
+namespace nlohmann
+{
+namespace detail
+{
+/*!
+@brief an iterator value
+
+@note This structure could easily be a union, but MSVC currently does not allow
+unions members with complex constructors, see https://github.com/nlohmann/json/pull/105.
+*/
+template<typename BasicJsonType> struct internal_iterator
+{
+ /// iterator for JSON objects
+ typename BasicJsonType::object_t::iterator object_iterator {};
+ /// iterator for JSON arrays
+ typename BasicJsonType::array_t::iterator array_iterator {};
+ /// generic iterator for all other types
+ primitive_iterator_t primitive_iterator {};
+};
+} // namespace detail
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/iterators/iter_impl.hpp b/include/lib/modernjson/detail/iterators/iter_impl.hpp
new file mode 100644
index 0000000..4ad94db
--- /dev/null
+++ b/include/lib/modernjson/detail/iterators/iter_impl.hpp
@@ -0,0 +1,614 @@
+#pragma once
+
+#include <ciso646> // not
+#include <iterator> // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next
+#include <type_traits> // conditional, is_const, remove_const
+
+#include <lib/modernjson/detail/exceptions.hpp>
+#include <lib/modernjson/detail/iterators/internal_iterator.hpp>
+#include <lib/modernjson/detail/iterators/primitive_iterator.hpp>
+#include <lib/modernjson/detail/macro_scope.hpp>
+#include <lib/modernjson/detail/meta/cpp_future.hpp>
+#include <lib/modernjson/detail/value_t.hpp>
+
+namespace nlohmann
+{
+namespace detail
+{
+// forward declare, to be able to friend it later on
+template<typename IteratorType> class iteration_proxy;
+
+/*!
+@brief a template for a bidirectional iterator for the @ref basic_json class
+
+This class implements a both iterators (iterator and const_iterator) for the
+@ref basic_json class.
+
+@note An iterator is called *initialized* when a pointer to a JSON value has
+ been set (e.g., by a constructor or a copy assignment). If the iterator is
+ default-constructed, it is *uninitialized* and most methods are undefined.
+ **The library uses assertions to detect calls on uninitialized iterators.**
+
+@requirement The class satisfies the following concept requirements:
+-
+[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator):
+ The iterator that can be moved can be moved in both directions (i.e.
+ incremented and decremented).
+
+@since version 1.0.0, simplified in version 2.0.9, change to bidirectional
+ iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593)
+*/
+template<typename BasicJsonType>
+class iter_impl
+{
+ /// allow basic_json to access private members
+ friend iter_impl<typename std::conditional<std::is_const<BasicJsonType>::value, typename std::remove_const<BasicJsonType>::type, const BasicJsonType>::type>;
+ friend BasicJsonType;
+ friend iteration_proxy<iter_impl>;
+
+ using object_t = typename BasicJsonType::object_t;
+ using array_t = typename BasicJsonType::array_t;
+ // make sure BasicJsonType is basic_json or const basic_json
+ static_assert(is_basic_json<typename std::remove_const<BasicJsonType>::type>::value,
+ "iter_impl only accepts (const) basic_json");
+
+ public:
+
+ /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17.
+ /// The C++ Standard has never required user-defined iterators to derive from std::iterator.
+ /// A user-defined iterator should provide publicly accessible typedefs named
+ /// iterator_category, value_type, difference_type, pointer, and reference.
+ /// Note that value_type is required to be non-const, even for constant iterators.
+ using iterator_category = std::bidirectional_iterator_tag;
+
+ /// the type of the values when the iterator is dereferenced
+ using value_type = typename BasicJsonType::value_type;
+ /// a type to represent differences between iterators
+ using difference_type = typename BasicJsonType::difference_type;
+ /// defines a pointer to the type iterated over (value_type)
+ using pointer = typename std::conditional<std::is_const<BasicJsonType>::value,
+ typename BasicJsonType::const_pointer,
+ typename BasicJsonType::pointer>::type;
+ /// defines a reference to the type iterated over (value_type)
+ using reference =
+ typename std::conditional<std::is_const<BasicJsonType>::value,
+ typename BasicJsonType::const_reference,
+ typename BasicJsonType::reference>::type;
+
+ /// default constructor
+ iter_impl() = default;
+
+ /*!
+ @brief constructor for a given JSON instance
+ @param[in] object pointer to a JSON object for this iterator
+ @pre object != nullptr
+ @post The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ explicit iter_impl(pointer object) noexcept : m_object(object)
+ {
+ assert(m_object != nullptr);
+
+ switch (m_object->m_type)
+ {
+ case value_t::object:
+ {
+ m_it.object_iterator = typename object_t::iterator();
+ break;
+ }
+
+ case value_t::array:
+ {
+ m_it.array_iterator = typename array_t::iterator();
+ break;
+ }
+
+ default:
+ {
+ m_it.primitive_iterator = primitive_iterator_t();
+ break;
+ }
+ }
+ }
+
+ /*!
+ @note The conventional copy constructor and copy assignment are implicitly
+ defined. Combined with the following converting constructor and
+ assignment, they support: (1) copy from iterator to iterator, (2)
+ copy from const iterator to const iterator, and (3) conversion from
+ iterator to const iterator. However conversion from const iterator
+ to iterator is not defined.
+ */
+
+ /*!
+ @brief converting constructor
+ @param[in] other non-const iterator to copy from
+ @note It is not checked whether @a other is initialized.
+ */
+ iter_impl(const iter_impl<typename std::remove_const<BasicJsonType>::type>& other) noexcept
+ : m_object(other.m_object), m_it(other.m_it) {}
+
+ /*!
+ @brief converting assignment
+ @param[in,out] other non-const iterator to copy from
+ @return const/non-const iterator
+ @note It is not checked whether @a other is initialized.
+ */
+ iter_impl& operator=(const iter_impl<typename std::remove_const<BasicJsonType>::type>& other) noexcept
+ {
+ m_object = other.m_object;
+ m_it = other.m_it;
+ return *this;
+ }
+
+ private:
+ /*!
+ @brief set the iterator to the first value
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ void set_begin() noexcept
+ {
+ assert(m_object != nullptr);
+
+ switch (m_object->m_type)
+ {
+ case value_t::object:
+ {
+ m_it.object_iterator = m_object->m_value.object->begin();
+ break;
+ }
+
+ case value_t::array:
+ {
+ m_it.array_iterator = m_object->m_value.array->begin();
+ break;
+ }
+
+ case value_t::null:
+ {
+ // set to end so begin()==end() is true: null is empty
+ m_it.primitive_iterator.set_end();
+ break;
+ }
+
+ default:
+ {
+ m_it.primitive_iterator.set_begin();
+ break;
+ }
+ }
+ }
+
+ /*!
+ @brief set the iterator past the last value
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ void set_end() noexcept
+ {
+ assert(m_object != nullptr);
+
+ switch (m_object->m_type)
+ {
+ case value_t::object:
+ {
+ m_it.object_iterator = m_object->m_value.object->end();
+ break;
+ }
+
+ case value_t::array:
+ {
+ m_it.array_iterator = m_object->m_value.array->end();
+ break;
+ }
+
+ default:
+ {
+ m_it.primitive_iterator.set_end();
+ break;
+ }
+ }
+ }
+
+ public:
+ /*!
+ @brief return a reference to the value pointed to by the iterator
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ reference operator*() const
+ {
+ assert(m_object != nullptr);
+
+ switch (m_object->m_type)
+ {
+ case value_t::object:
+ {
+ assert(m_it.object_iterator != m_object->m_value.object->end());
+ return m_it.object_iterator->second;
+ }
+
+ case value_t::array:
+ {
+ assert(m_it.array_iterator != m_object->m_value.array->end());
+ return *m_it.array_iterator;
+ }
+
+ case value_t::null:
+ JSON_THROW(invalid_iterator::create(214, "cannot get value"));
+
+ default:
+ {
+ if (JSON_LIKELY(m_it.primitive_iterator.is_begin()))
+ {
+ return *m_object;
+ }
+
+ JSON_THROW(invalid_iterator::create(214, "cannot get value"));
+ }
+ }
+ }
+
+ /*!
+ @brief dereference the iterator
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ pointer operator->() const
+ {
+ assert(m_object != nullptr);
+
+ switch (m_object->m_type)
+ {
+ case value_t::object:
+ {
+ assert(m_it.object_iterator != m_object->m_value.object->end());
+ return &(m_it.object_iterator->second);
+ }
+
+ case value_t::array:
+ {
+ assert(m_it.array_iterator != m_object->m_value.array->end());
+ return &*m_it.array_iterator;
+ }
+
+ default:
+ {
+ if (JSON_LIKELY(m_it.primitive_iterator.is_begin()))
+ {
+ return m_object;
+ }
+
+ JSON_THROW(invalid_iterator::create(214, "cannot get value"));
+ }
+ }
+ }
+
+ /*!
+ @brief post-increment (it++)
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ iter_impl const operator++(int)
+ {
+ auto result = *this;
+ ++(*this);
+ return result;
+ }
+
+ /*!
+ @brief pre-increment (++it)
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ iter_impl& operator++()
+ {
+ assert(m_object != nullptr);
+
+ switch (m_object->m_type)
+ {
+ case value_t::object:
+ {
+ std::advance(m_it.object_iterator, 1);
+ break;
+ }
+
+ case value_t::array:
+ {
+ std::advance(m_it.array_iterator, 1);
+ break;
+ }
+
+ default:
+ {
+ ++m_it.primitive_iterator;
+ break;
+ }
+ }
+
+ return *this;
+ }
+
+ /*!
+ @brief post-decrement (it--)
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ iter_impl const operator--(int)
+ {
+ auto result = *this;
+ --(*this);
+ return result;
+ }
+
+ /*!
+ @brief pre-decrement (--it)
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ iter_impl& operator--()
+ {
+ assert(m_object != nullptr);
+
+ switch (m_object->m_type)
+ {
+ case value_t::object:
+ {
+ std::advance(m_it.object_iterator, -1);
+ break;
+ }
+
+ case value_t::array:
+ {
+ std::advance(m_it.array_iterator, -1);
+ break;
+ }
+
+ default:
+ {
+ --m_it.primitive_iterator;
+ break;
+ }
+ }
+
+ return *this;
+ }
+
+ /*!
+ @brief comparison: equal
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ bool operator==(const iter_impl& other) const
+ {
+ // if objects are not the same, the comparison is undefined
+ if (JSON_UNLIKELY(m_object != other.m_object))
+ {
+ JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers"));
+ }
+
+ assert(m_object != nullptr);
+
+ switch (m_object->m_type)
+ {
+ case value_t::object:
+ return (m_it.object_iterator == other.m_it.object_iterator);
+
+ case value_t::array:
+ return (m_it.array_iterator == other.m_it.array_iterator);
+
+ default:
+ return (m_it.primitive_iterator == other.m_it.primitive_iterator);
+ }
+ }
+
+ /*!
+ @brief comparison: not equal
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ bool operator!=(const iter_impl& other) const
+ {
+ return not operator==(other);
+ }
+
+ /*!
+ @brief comparison: smaller
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ bool operator<(const iter_impl& other) const
+ {
+ // if objects are not the same, the comparison is undefined
+ if (JSON_UNLIKELY(m_object != other.m_object))
+ {
+ JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers"));
+ }
+
+ assert(m_object != nullptr);
+
+ switch (m_object->m_type)
+ {
+ case value_t::object:
+ JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators"));
+
+ case value_t::array:
+ return (m_it.array_iterator < other.m_it.array_iterator);
+
+ default:
+ return (m_it.primitive_iterator < other.m_it.primitive_iterator);
+ }
+ }
+
+ /*!
+ @brief comparison: less than or equal
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ bool operator<=(const iter_impl& other) const
+ {
+ return not other.operator < (*this);
+ }
+
+ /*!
+ @brief comparison: greater than
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ bool operator>(const iter_impl& other) const
+ {
+ return not operator<=(other);
+ }
+
+ /*!
+ @brief comparison: greater than or equal
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ bool operator>=(const iter_impl& other) const
+ {
+ return not operator<(other);
+ }
+
+ /*!
+ @brief add to iterator
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ iter_impl& operator+=(difference_type i)
+ {
+ assert(m_object != nullptr);
+
+ switch (m_object->m_type)
+ {
+ case value_t::object:
+ JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators"));
+
+ case value_t::array:
+ {
+ std::advance(m_it.array_iterator, i);
+ break;
+ }
+
+ default:
+ {
+ m_it.primitive_iterator += i;
+ break;
+ }
+ }
+
+ return *this;
+ }
+
+ /*!
+ @brief subtract from iterator
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ iter_impl& operator-=(difference_type i)
+ {
+ return operator+=(-i);
+ }
+
+ /*!
+ @brief add to iterator
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ iter_impl operator+(difference_type i) const
+ {
+ auto result = *this;
+ result += i;
+ return result;
+ }
+
+ /*!
+ @brief addition of distance and iterator
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ friend iter_impl operator+(difference_type i, const iter_impl& it)
+ {
+ auto result = it;
+ result += i;
+ return result;
+ }
+
+ /*!
+ @brief subtract from iterator
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ iter_impl operator-(difference_type i) const
+ {
+ auto result = *this;
+ result -= i;
+ return result;
+ }
+
+ /*!
+ @brief return difference
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ difference_type operator-(const iter_impl& other) const
+ {
+ assert(m_object != nullptr);
+
+ switch (m_object->m_type)
+ {
+ case value_t::object:
+ JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators"));
+
+ case value_t::array:
+ return m_it.array_iterator - other.m_it.array_iterator;
+
+ default:
+ return m_it.primitive_iterator - other.m_it.primitive_iterator;
+ }
+ }
+
+ /*!
+ @brief access to successor
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ reference operator[](difference_type n) const
+ {
+ assert(m_object != nullptr);
+
+ switch (m_object->m_type)
+ {
+ case value_t::object:
+ JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators"));
+
+ case value_t::array:
+ return *std::next(m_it.array_iterator, n);
+
+ case value_t::null:
+ JSON_THROW(invalid_iterator::create(214, "cannot get value"));
+
+ default:
+ {
+ if (JSON_LIKELY(m_it.primitive_iterator.get_value() == -n))
+ {
+ return *m_object;
+ }
+
+ JSON_THROW(invalid_iterator::create(214, "cannot get value"));
+ }
+ }
+ }
+
+ /*!
+ @brief return the key of an object iterator
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ const typename object_t::key_type& key() const
+ {
+ assert(m_object != nullptr);
+
+ if (JSON_LIKELY(m_object->is_object()))
+ {
+ return m_it.object_iterator->first;
+ }
+
+ JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators"));
+ }
+
+ /*!
+ @brief return the value of an iterator
+ @pre The iterator is initialized; i.e. `m_object != nullptr`.
+ */
+ reference value() const
+ {
+ return operator*();
+ }
+
+ private:
+ /// associated JSON instance
+ pointer m_object = nullptr;
+ /// the actual iterator of the associated instance
+ internal_iterator<typename std::remove_const<BasicJsonType>::type> m_it;
+};
+} // namespace detail
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/iterators/iteration_proxy.hpp b/include/lib/modernjson/detail/iterators/iteration_proxy.hpp
new file mode 100644
index 0000000..31cc9f0
--- /dev/null
+++ b/include/lib/modernjson/detail/iterators/iteration_proxy.hpp
@@ -0,0 +1,125 @@
+#pragma once
+
+#include <cstddef> // size_t
+#include <string> // string, to_string
+#include <iterator> // input_iterator_tag
+
+#include <lib/modernjson/detail/value_t.hpp>
+
+namespace nlohmann
+{
+namespace detail
+{
+/// proxy class for the items() function
+template<typename IteratorType> class iteration_proxy
+{
+ private:
+ /// helper class for iteration
+ class iteration_proxy_internal
+ {
+ public:
+ using difference_type = std::ptrdiff_t;
+ using value_type = iteration_proxy_internal;
+ using pointer = iteration_proxy_internal*;
+ using reference = iteration_proxy_internal&;
+ using iterator_category = std::input_iterator_tag;
+
+ private:
+ /// the iterator
+ IteratorType anchor;
+ /// an index for arrays (used to create key names)
+ std::size_t array_index = 0;
+ /// last stringified array index
+ mutable std::size_t array_index_last = 0;
+ /// a string representation of the array index
+ mutable std::string array_index_str = "0";
+ /// an empty string (to return a reference for primitive values)
+ const std::string empty_str = "";
+
+ public:
+ explicit iteration_proxy_internal(IteratorType it) noexcept : anchor(it) {}
+
+ /// dereference operator (needed for range-based for)
+ iteration_proxy_internal& operator*()
+ {
+ return *this;
+ }
+
+ /// increment operator (needed for range-based for)
+ iteration_proxy_internal& operator++()
+ {
+ ++anchor;
+ ++array_index;
+
+ return *this;
+ }
+
+ /// equality operator (needed for InputIterator)
+ bool operator==(const iteration_proxy_internal& o) const noexcept
+ {
+ return anchor == o.anchor;
+ }
+
+ /// inequality operator (needed for range-based for)
+ bool operator!=(const iteration_proxy_internal& o) const noexcept
+ {
+ return anchor != o.anchor;
+ }
+
+ /// return key of the iterator
+ const std::string& key() const
+ {
+ assert(anchor.m_object != nullptr);
+
+ switch (anchor.m_object->type())
+ {
+ // use integer array index as key
+ case value_t::array:
+ {
+ if (array_index != array_index_last)
+ {
+ array_index_str = std::to_string(array_index);
+ array_index_last = array_index;
+ }
+ return array_index_str;
+ }
+
+ // use key from the object
+ case value_t::object:
+ return anchor.key();
+
+ // use an empty key for all primitive types
+ default:
+ return empty_str;
+ }
+ }
+
+ /// return value of the iterator
+ typename IteratorType::reference value() const
+ {
+ return anchor.value();
+ }
+ };
+
+ /// the container to iterate
+ typename IteratorType::reference container;
+
+ public:
+ /// construct iteration proxy from a container
+ explicit iteration_proxy(typename IteratorType::reference cont) noexcept
+ : container(cont) {}
+
+ /// return iterator begin (needed for range-based for)
+ iteration_proxy_internal begin() noexcept
+ {
+ return iteration_proxy_internal(container.begin());
+ }
+
+ /// return iterator end (needed for range-based for)
+ iteration_proxy_internal end() noexcept
+ {
+ return iteration_proxy_internal(container.end());
+ }
+};
+} // namespace detail
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/iterators/json_reverse_iterator.hpp b/include/lib/modernjson/detail/iterators/json_reverse_iterator.hpp
new file mode 100644
index 0000000..f3b5b5d
--- /dev/null
+++ b/include/lib/modernjson/detail/iterators/json_reverse_iterator.hpp
@@ -0,0 +1,119 @@
+#pragma once
+
+#include <cstddef> // ptrdiff_t
+#include <iterator> // reverse_iterator
+#include <utility> // declval
+
+namespace nlohmann
+{
+namespace detail
+{
+//////////////////////
+// reverse_iterator //
+//////////////////////
+
+/*!
+@brief a template for a reverse iterator class
+
+@tparam Base the base iterator type to reverse. Valid types are @ref
+iterator (to create @ref reverse_iterator) and @ref const_iterator (to
+create @ref const_reverse_iterator).
+
+@requirement The class satisfies the following concept requirements:
+-
+[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator):
+ The iterator that can be moved can be moved in both directions (i.e.
+ incremented and decremented).
+- [OutputIterator](https://en.cppreference.com/w/cpp/named_req/OutputIterator):
+ It is possible to write to the pointed-to element (only if @a Base is
+ @ref iterator).
+
+@since version 1.0.0
+*/
+template<typename Base>
+class json_reverse_iterator : public std::reverse_iterator<Base>
+{
+ public:
+ using difference_type = std::ptrdiff_t;
+ /// shortcut to the reverse iterator adapter
+ using base_iterator = std::reverse_iterator<Base>;
+ /// the reference type for the pointed-to element
+ using reference = typename Base::reference;
+
+ /// create reverse iterator from iterator
+ explicit json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept
+ : base_iterator(it) {}
+
+ /// create reverse iterator from base class
+ explicit json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {}
+
+ /// post-increment (it++)
+ json_reverse_iterator const operator++(int)
+ {
+ return static_cast<json_reverse_iterator>(base_iterator::operator++(1));
+ }
+
+ /// pre-increment (++it)
+ json_reverse_iterator& operator++()
+ {
+ return static_cast<json_reverse_iterator&>(base_iterator::operator++());
+ }
+
+ /// post-decrement (it--)
+ json_reverse_iterator const operator--(int)
+ {
+ return static_cast<json_reverse_iterator>(base_iterator::operator--(1));
+ }
+
+ /// pre-decrement (--it)
+ json_reverse_iterator& operator--()
+ {
+ return static_cast<json_reverse_iterator&>(base_iterator::operator--());
+ }
+
+ /// add to iterator
+ json_reverse_iterator& operator+=(difference_type i)
+ {
+ return static_cast<json_reverse_iterator&>(base_iterator::operator+=(i));
+ }
+
+ /// add to iterator
+ json_reverse_iterator operator+(difference_type i) const
+ {
+ return static_cast<json_reverse_iterator>(base_iterator::operator+(i));
+ }
+
+ /// subtract from iterator
+ json_reverse_iterator operator-(difference_type i) const
+ {
+ return static_cast<json_reverse_iterator>(base_iterator::operator-(i));
+ }
+
+ /// return difference
+ difference_type operator-(const json_reverse_iterator& other) const
+ {
+ return base_iterator(*this) - base_iterator(other);
+ }
+
+ /// access to successor
+ reference operator[](difference_type n) const
+ {
+ return *(this->operator+(n));
+ }
+
+ /// return the key of an object iterator
+ auto key() const -> decltype(std::declval<Base>().key())
+ {
+ auto it = --this->base();
+ return it.key();
+ }
+
+ /// return the value of an iterator
+ reference value() const
+ {
+ auto it = --this->base();
+ return it.operator * ();
+ }
+};
+} // namespace detail
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/iterators/primitive_iterator.hpp b/include/lib/modernjson/detail/iterators/primitive_iterator.hpp
new file mode 100644
index 0000000..28d6f1a
--- /dev/null
+++ b/include/lib/modernjson/detail/iterators/primitive_iterator.hpp
@@ -0,0 +1,120 @@
+#pragma once
+
+#include <cstddef> // ptrdiff_t
+#include <limits> // numeric_limits
+
+namespace nlohmann
+{
+namespace detail
+{
+/*
+@brief an iterator for primitive JSON types
+
+This class models an iterator for primitive JSON types (boolean, number,
+string). It's only purpose is to allow the iterator/const_iterator classes
+to "iterate" over primitive values. Internally, the iterator is modeled by
+a `difference_type` variable. Value begin_value (`0`) models the begin,
+end_value (`1`) models past the end.
+*/
+class primitive_iterator_t
+{
+ private:
+ using difference_type = std::ptrdiff_t;
+ static constexpr difference_type begin_value = 0;
+ static constexpr difference_type end_value = begin_value + 1;
+
+ /// iterator as signed integer type
+ difference_type m_it = (std::numeric_limits<std::ptrdiff_t>::min)();
+
+ public:
+ constexpr difference_type get_value() const noexcept
+ {
+ return m_it;
+ }
+
+ /// set iterator to a defined beginning
+ void set_begin() noexcept
+ {
+ m_it = begin_value;
+ }
+
+ /// set iterator to a defined past the end
+ void set_end() noexcept
+ {
+ m_it = end_value;
+ }
+
+ /// return whether the iterator can be dereferenced
+ constexpr bool is_begin() const noexcept
+ {
+ return m_it == begin_value;
+ }
+
+ /// return whether the iterator is at end
+ constexpr bool is_end() const noexcept
+ {
+ return m_it == end_value;
+ }
+
+ friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
+ {
+ return lhs.m_it == rhs.m_it;
+ }
+
+ friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
+ {
+ return lhs.m_it < rhs.m_it;
+ }
+
+ primitive_iterator_t operator+(difference_type n) noexcept
+ {
+ auto result = *this;
+ result += n;
+ return result;
+ }
+
+ friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept
+ {
+ return lhs.m_it - rhs.m_it;
+ }
+
+ primitive_iterator_t& operator++() noexcept
+ {
+ ++m_it;
+ return *this;
+ }
+
+ primitive_iterator_t const operator++(int) noexcept
+ {
+ auto result = *this;
+ ++m_it;
+ return result;
+ }
+
+ primitive_iterator_t& operator--() noexcept
+ {
+ --m_it;
+ return *this;
+ }
+
+ primitive_iterator_t const operator--(int) noexcept
+ {
+ auto result = *this;
+ --m_it;
+ return result;
+ }
+
+ primitive_iterator_t& operator+=(difference_type n) noexcept
+ {
+ m_it += n;
+ return *this;
+ }
+
+ primitive_iterator_t& operator-=(difference_type n) noexcept
+ {
+ m_it -= n;
+ return *this;
+ }
+};
+} // namespace detail
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/json_pointer.hpp b/include/lib/modernjson/detail/json_pointer.hpp
new file mode 100644
index 0000000..a280c9d
--- /dev/null
+++ b/include/lib/modernjson/detail/json_pointer.hpp
@@ -0,0 +1,696 @@
+#pragma once
+
+#include <cassert> // assert
+#include <numeric> // accumulate
+#include <string> // string
+#include <vector> // vector
+
+#include <lib/modernjson/detail/macro_scope.hpp>
+#include <lib/modernjson/detail/exceptions.hpp>
+#include <lib/modernjson/detail/value_t.hpp>
+
+namespace nlohmann
+{
+template<typename BasicJsonType>
+class json_pointer
+{
+ // allow basic_json to access private members
+ NLOHMANN_BASIC_JSON_TPL_DECLARATION
+ friend class basic_json;
+
+ public:
+ /*!
+ @brief create JSON pointer
+
+ Create a JSON pointer according to the syntax described in
+ [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3).
+
+ @param[in] s string representing the JSON pointer; if omitted, the empty
+ string is assumed which references the whole JSON value
+
+ @throw parse_error.107 if the given JSON pointer @a s is nonempty and does
+ not begin with a slash (`/`); see example below
+
+ @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s is
+ not followed by `0` (representing `~`) or `1` (representing `/`); see
+ example below
+
+ @liveexample{The example shows the construction several valid JSON pointers
+ as well as the exceptional behavior.,json_pointer}
+
+ @since version 2.0.0
+ */
+ explicit json_pointer(const std::string& s = "")
+ : reference_tokens(split(s))
+ {}
+
+ /*!
+ @brief return a string representation of the JSON pointer
+
+ @invariant For each JSON pointer `ptr`, it holds:
+ @code {.cpp}
+ ptr == json_pointer(ptr.to_string());
+ @endcode
+
+ @return a string representation of the JSON pointer
+
+ @liveexample{The example shows the result of `to_string`.,
+ json_pointer__to_string}
+
+ @since version 2.0.0
+ */
+ std::string to_string() const
+ {
+ return std::accumulate(reference_tokens.begin(), reference_tokens.end(),
+ std::string{},
+ [](const std::string & a, const std::string & b)
+ {
+ return a + "/" + escape(b);
+ });
+ }
+
+ /// @copydoc to_string()
+ operator std::string() const
+ {
+ return to_string();
+ }
+
+ /*!
+ @param[in] s reference token to be converted into an array index
+
+ @return integer representation of @a s
+
+ @throw out_of_range.404 if string @a s could not be converted to an integer
+ */
+ static int array_index(const std::string& s)
+ {
+ std::size_t processed_chars = 0;
+ const int res = std::stoi(s, &processed_chars);
+
+ // check if the string was completely read
+ if (JSON_UNLIKELY(processed_chars != s.size()))
+ {
+ JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'"));
+ }
+
+ return res;
+ }
+
+ private:
+ /*!
+ @brief remove and return last reference pointer
+ @throw out_of_range.405 if JSON pointer has no parent
+ */
+ std::string pop_back()
+ {
+ if (JSON_UNLIKELY(is_root()))
+ {
+ JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent"));
+ }
+
+ auto last = reference_tokens.back();
+ reference_tokens.pop_back();
+ return last;
+ }
+
+ /// return whether pointer points to the root document
+ bool is_root() const noexcept
+ {
+ return reference_tokens.empty();
+ }
+
+ json_pointer top() const
+ {
+ if (JSON_UNLIKELY(is_root()))
+ {
+ JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent"));
+ }
+
+ json_pointer result = *this;
+ result.reference_tokens = {reference_tokens[0]};
+ return result;
+ }
+
+ /*!
+ @brief create and return a reference to the pointed to value
+
+ @complexity Linear in the number of reference tokens.
+
+ @throw parse_error.109 if array index is not a number
+ @throw type_error.313 if value cannot be unflattened
+ */
+ BasicJsonType& get_and_create(BasicJsonType& j) const
+ {
+ using size_type = typename BasicJsonType::size_type;
+ auto result = &j;
+
+ // in case no reference tokens exist, return a reference to the JSON value
+ // j which will be overwritten by a primitive value
+ for (const auto& reference_token : reference_tokens)
+ {
+ switch (result->m_type)
+ {
+ case detail::value_t::null:
+ {
+ if (reference_token == "0")
+ {
+ // start a new array if reference token is 0
+ result = &result->operator[](0);
+ }
+ else
+ {
+ // start a new object otherwise
+ result = &result->operator[](reference_token);
+ }
+ break;
+ }
+
+ case detail::value_t::object:
+ {
+ // create an entry in the object
+ result = &result->operator[](reference_token);
+ break;
+ }
+
+ case detail::value_t::array:
+ {
+ // create an entry in the array
+ JSON_TRY
+ {
+ result = &result->operator[](static_cast<size_type>(array_index(reference_token)));
+ }
+ JSON_CATCH(std::invalid_argument&)
+ {
+ JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
+ }
+ break;
+ }
+
+ /*
+ The following code is only reached if there exists a reference
+ token _and_ the current value is primitive. In this case, we have
+ an error situation, because primitive values may only occur as
+ single value; that is, with an empty list of reference tokens.
+ */
+ default:
+ JSON_THROW(detail::type_error::create(313, "invalid value to unflatten"));
+ }
+ }
+
+ return *result;
+ }
+
+ /*!
+ @brief return a reference to the pointed to value
+
+ @note This version does not throw if a value is not present, but tries to
+ create nested values instead. For instance, calling this function
+ with pointer `"/this/that"` on a null value is equivalent to calling
+ `operator[]("this").operator[]("that")` on that value, effectively
+ changing the null value to an object.
+
+ @param[in] ptr a JSON value
+
+ @return reference to the JSON value pointed to by the JSON pointer
+
+ @complexity Linear in the length of the JSON pointer.
+
+ @throw parse_error.106 if an array index begins with '0'
+ @throw parse_error.109 if an array index was not a number
+ @throw out_of_range.404 if the JSON pointer can not be resolved
+ */
+ BasicJsonType& get_unchecked(BasicJsonType* ptr) const
+ {
+ using size_type = typename BasicJsonType::size_type;
+ for (const auto& reference_token : reference_tokens)
+ {
+ // convert null values to arrays or objects before continuing
+ if (ptr->m_type == detail::value_t::null)
+ {
+ // check if reference token is a number
+ const bool nums =
+ std::all_of(reference_token.begin(), reference_token.end(),
+ [](const char x)
+ {
+ return (x >= '0' and x <= '9');
+ });
+
+ // change value to array for numbers or "-" or to object otherwise
+ *ptr = (nums or reference_token == "-")
+ ? detail::value_t::array
+ : detail::value_t::object;
+ }
+
+ switch (ptr->m_type)
+ {
+ case detail::value_t::object:
+ {
+ // use unchecked object access
+ ptr = &ptr->operator[](reference_token);
+ break;
+ }
+
+ case detail::value_t::array:
+ {
+ // error condition (cf. RFC 6901, Sect. 4)
+ if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
+ {
+ JSON_THROW(detail::parse_error::create(106, 0,
+ "array index '" + reference_token +
+ "' must not begin with '0'"));
+ }
+
+ if (reference_token == "-")
+ {
+ // explicitly treat "-" as index beyond the end
+ ptr = &ptr->operator[](ptr->m_value.array->size());
+ }
+ else
+ {
+ // convert array index to number; unchecked access
+ JSON_TRY
+ {
+ ptr = &ptr->operator[](
+ static_cast<size_type>(array_index(reference_token)));
+ }
+ JSON_CATCH(std::invalid_argument&)
+ {
+ JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
+ }
+ }
+ break;
+ }
+
+ default:
+ JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
+ }
+ }
+
+ return *ptr;
+ }
+
+ /*!
+ @throw parse_error.106 if an array index begins with '0'
+ @throw parse_error.109 if an array index was not a number
+ @throw out_of_range.402 if the array index '-' is used
+ @throw out_of_range.404 if the JSON pointer can not be resolved
+ */
+ BasicJsonType& get_checked(BasicJsonType* ptr) const
+ {
+ using size_type = typename BasicJsonType::size_type;
+ for (const auto& reference_token : reference_tokens)
+ {
+ switch (ptr->m_type)
+ {
+ case detail::value_t::object:
+ {
+ // note: at performs range check
+ ptr = &ptr->at(reference_token);
+ break;
+ }
+
+ case detail::value_t::array:
+ {
+ if (JSON_UNLIKELY(reference_token == "-"))
+ {
+ // "-" always fails the range check
+ JSON_THROW(detail::out_of_range::create(402,
+ "array index '-' (" + std::to_string(ptr->m_value.array->size()) +
+ ") is out of range"));
+ }
+
+ // error condition (cf. RFC 6901, Sect. 4)
+ if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
+ {
+ JSON_THROW(detail::parse_error::create(106, 0,
+ "array index '" + reference_token +
+ "' must not begin with '0'"));
+ }
+
+ // note: at performs range check
+ JSON_TRY
+ {
+ ptr = &ptr->at(static_cast<size_type>(array_index(reference_token)));
+ }
+ JSON_CATCH(std::invalid_argument&)
+ {
+ JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
+ }
+ break;
+ }
+
+ default:
+ JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
+ }
+ }
+
+ return *ptr;
+ }
+
+ /*!
+ @brief return a const reference to the pointed to value
+
+ @param[in] ptr a JSON value
+
+ @return const reference to the JSON value pointed to by the JSON
+ pointer
+
+ @throw parse_error.106 if an array index begins with '0'
+ @throw parse_error.109 if an array index was not a number
+ @throw out_of_range.402 if the array index '-' is used
+ @throw out_of_range.404 if the JSON pointer can not be resolved
+ */
+ const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const
+ {
+ using size_type = typename BasicJsonType::size_type;
+ for (const auto& reference_token : reference_tokens)
+ {
+ switch (ptr->m_type)
+ {
+ case detail::value_t::object:
+ {
+ // use unchecked object access
+ ptr = &ptr->operator[](reference_token);
+ break;
+ }
+
+ case detail::value_t::array:
+ {
+ if (JSON_UNLIKELY(reference_token == "-"))
+ {
+ // "-" cannot be used for const access
+ JSON_THROW(detail::out_of_range::create(402,
+ "array index '-' (" + std::to_string(ptr->m_value.array->size()) +
+ ") is out of range"));
+ }
+
+ // error condition (cf. RFC 6901, Sect. 4)
+ if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
+ {
+ JSON_THROW(detail::parse_error::create(106, 0,
+ "array index '" + reference_token +
+ "' must not begin with '0'"));
+ }
+
+ // use unchecked array access
+ JSON_TRY
+ {
+ ptr = &ptr->operator[](
+ static_cast<size_type>(array_index(reference_token)));
+ }
+ JSON_CATCH(std::invalid_argument&)
+ {
+ JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
+ }
+ break;
+ }
+
+ default:
+ JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
+ }
+ }
+
+ return *ptr;
+ }
+
+ /*!
+ @throw parse_error.106 if an array index begins with '0'
+ @throw parse_error.109 if an array index was not a number
+ @throw out_of_range.402 if the array index '-' is used
+ @throw out_of_range.404 if the JSON pointer can not be resolved
+ */
+ const BasicJsonType& get_checked(const BasicJsonType* ptr) const
+ {
+ using size_type = typename BasicJsonType::size_type;
+ for (const auto& reference_token : reference_tokens)
+ {
+ switch (ptr->m_type)
+ {
+ case detail::value_t::object:
+ {
+ // note: at performs range check
+ ptr = &ptr->at(reference_token);
+ break;
+ }
+
+ case detail::value_t::array:
+ {
+ if (JSON_UNLIKELY(reference_token == "-"))
+ {
+ // "-" always fails the range check
+ JSON_THROW(detail::out_of_range::create(402,
+ "array index '-' (" + std::to_string(ptr->m_value.array->size()) +
+ ") is out of range"));
+ }
+
+ // error condition (cf. RFC 6901, Sect. 4)
+ if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
+ {
+ JSON_THROW(detail::parse_error::create(106, 0,
+ "array index '" + reference_token +
+ "' must not begin with '0'"));
+ }
+
+ // note: at performs range check
+ JSON_TRY
+ {
+ ptr = &ptr->at(static_cast<size_type>(array_index(reference_token)));
+ }
+ JSON_CATCH(std::invalid_argument&)
+ {
+ JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number"));
+ }
+ break;
+ }
+
+ default:
+ JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'"));
+ }
+ }
+
+ return *ptr;
+ }
+
+ /*!
+ @brief split the string input to reference tokens
+
+ @note This function is only called by the json_pointer constructor.
+ All exceptions below are documented there.
+
+ @throw parse_error.107 if the pointer is not empty or begins with '/'
+ @throw parse_error.108 if character '~' is not followed by '0' or '1'
+ */
+ static std::vector<std::string> split(const std::string& reference_string)
+ {
+ std::vector<std::string> result;
+
+ // special case: empty reference string -> no reference tokens
+ if (reference_string.empty())
+ {
+ return result;
+ }
+
+ // check if nonempty reference string begins with slash
+ if (JSON_UNLIKELY(reference_string[0] != '/'))
+ {
+ JSON_THROW(detail::parse_error::create(107, 1,
+ "JSON pointer must be empty or begin with '/' - was: '" +
+ reference_string + "'"));
+ }
+
+ // extract the reference tokens:
+ // - slash: position of the last read slash (or end of string)
+ // - start: position after the previous slash
+ for (
+ // search for the first slash after the first character
+ std::size_t slash = reference_string.find_first_of('/', 1),
+ // set the beginning of the first reference token
+ start = 1;
+ // we can stop if start == 0 (if slash == std::string::npos)
+ start != 0;
+ // set the beginning of the next reference token
+ // (will eventually be 0 if slash == std::string::npos)
+ start = (slash == std::string::npos) ? 0 : slash + 1,
+ // find next slash
+ slash = reference_string.find_first_of('/', start))
+ {
+ // use the text between the beginning of the reference token
+ // (start) and the last slash (slash).
+ auto reference_token = reference_string.substr(start, slash - start);
+
+ // check reference tokens are properly escaped
+ for (std::size_t pos = reference_token.find_first_of('~');
+ pos != std::string::npos;
+ pos = reference_token.find_first_of('~', pos + 1))
+ {
+ assert(reference_token[pos] == '~');
+
+ // ~ must be followed by 0 or 1
+ if (JSON_UNLIKELY(pos == reference_token.size() - 1 or
+ (reference_token[pos + 1] != '0' and
+ reference_token[pos + 1] != '1')))
+ {
+ JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'"));
+ }
+ }
+
+ // finally, store the reference token
+ unescape(reference_token);
+ result.push_back(reference_token);
+ }
+
+ return result;
+ }
+
+ /*!
+ @brief replace all occurrences of a substring by another string
+
+ @param[in,out] s the string to manipulate; changed so that all
+ occurrences of @a f are replaced with @a t
+ @param[in] f the substring to replace with @a t
+ @param[in] t the string to replace @a f
+
+ @pre The search string @a f must not be empty. **This precondition is
+ enforced with an assertion.**
+
+ @since version 2.0.0
+ */
+ static void replace_substring(std::string& s, const std::string& f,
+ const std::string& t)
+ {
+ assert(not f.empty());
+ for (auto pos = s.find(f); // find first occurrence of f
+ pos != std::string::npos; // make sure f was found
+ s.replace(pos, f.size(), t), // replace with t, and
+ pos = s.find(f, pos + t.size())) // find next occurrence of f
+ {}
+ }
+
+ /// escape "~" to "~0" and "/" to "~1"
+ static std::string escape(std::string s)
+ {
+ replace_substring(s, "~", "~0");
+ replace_substring(s, "/", "~1");
+ return s;
+ }
+
+ /// unescape "~1" to tilde and "~0" to slash (order is important!)
+ static void unescape(std::string& s)
+ {
+ replace_substring(s, "~1", "/");
+ replace_substring(s, "~0", "~");
+ }
+
+ /*!
+ @param[in] reference_string the reference string to the current value
+ @param[in] value the value to consider
+ @param[in,out] result the result object to insert values to
+
+ @note Empty objects or arrays are flattened to `null`.
+ */
+ static void flatten(const std::string& reference_string,
+ const BasicJsonType& value,
+ BasicJsonType& result)
+ {
+ switch (value.m_type)
+ {
+ case detail::value_t::array:
+ {
+ if (value.m_value.array->empty())
+ {
+ // flatten empty array as null
+ result[reference_string] = nullptr;
+ }
+ else
+ {
+ // iterate array and use index as reference string
+ for (std::size_t i = 0; i < value.m_value.array->size(); ++i)
+ {
+ flatten(reference_string + "/" + std::to_string(i),
+ value.m_value.array->operator[](i), result);
+ }
+ }
+ break;
+ }
+
+ case detail::value_t::object:
+ {
+ if (value.m_value.object->empty())
+ {
+ // flatten empty object as null
+ result[reference_string] = nullptr;
+ }
+ else
+ {
+ // iterate object and use keys as reference string
+ for (const auto& element : *value.m_value.object)
+ {
+ flatten(reference_string + "/" + escape(element.first), element.second, result);
+ }
+ }
+ break;
+ }
+
+ default:
+ {
+ // add primitive value with its reference string
+ result[reference_string] = value;
+ break;
+ }
+ }
+ }
+
+ /*!
+ @param[in] value flattened JSON
+
+ @return unflattened JSON
+
+ @throw parse_error.109 if array index is not a number
+ @throw type_error.314 if value is not an object
+ @throw type_error.315 if object values are not primitive
+ @throw type_error.313 if value cannot be unflattened
+ */
+ static BasicJsonType
+ unflatten(const BasicJsonType& value)
+ {
+ if (JSON_UNLIKELY(not value.is_object()))
+ {
+ JSON_THROW(detail::type_error::create(314, "only objects can be unflattened"));
+ }
+
+ BasicJsonType result;
+
+ // iterate the JSON object values
+ for (const auto& element : *value.m_value.object)
+ {
+ if (JSON_UNLIKELY(not element.second.is_primitive()))
+ {
+ JSON_THROW(detail::type_error::create(315, "values in object must be primitive"));
+ }
+
+ // assign value to reference pointed to by JSON pointer; Note that if
+ // the JSON pointer is "" (i.e., points to the whole value), function
+ // get_and_create returns a reference to result itself. An assignment
+ // will then create a primitive value.
+ json_pointer(element.first).get_and_create(result) = element.second;
+ }
+
+ return result;
+ }
+
+ friend bool operator==(json_pointer const& lhs,
+ json_pointer const& rhs) noexcept
+ {
+ return (lhs.reference_tokens == rhs.reference_tokens);
+ }
+
+ friend bool operator!=(json_pointer const& lhs,
+ json_pointer const& rhs) noexcept
+ {
+ return not (lhs == rhs);
+ }
+
+ /// the reference tokens
+ std::vector<std::string> reference_tokens;
+};
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/json_ref.hpp b/include/lib/modernjson/detail/json_ref.hpp
new file mode 100644
index 0000000..c44aa66
--- /dev/null
+++ b/include/lib/modernjson/detail/json_ref.hpp
@@ -0,0 +1,69 @@
+#pragma once
+
+#include <initializer_list>
+#include <utility>
+
+#include <lib/modernjson/detail/meta/type_traits.hpp>
+
+namespace nlohmann
+{
+namespace detail
+{
+template<typename BasicJsonType>
+class json_ref
+{
+ public:
+ using value_type = BasicJsonType;
+
+ json_ref(value_type&& value)
+ : owned_value(std::move(value)), value_ref(&owned_value), is_rvalue(true)
+ {}
+
+ json_ref(const value_type& value)
+ : value_ref(const_cast<value_type*>(&value)), is_rvalue(false)
+ {}
+
+ json_ref(std::initializer_list<json_ref> init)
+ : owned_value(init), value_ref(&owned_value), is_rvalue(true)
+ {}
+
+ template <
+ class... Args,
+ enable_if_t<std::is_constructible<value_type, Args...>::value, int> = 0 >
+ json_ref(Args && ... args)
+ : owned_value(std::forward<Args>(args)...), value_ref(&owned_value),
+ is_rvalue(true) {}
+
+ // class should be movable only
+ json_ref(json_ref&&) = default;
+ json_ref(const json_ref&) = delete;
+ json_ref& operator=(const json_ref&) = delete;
+ json_ref& operator=(json_ref&&) = delete;
+ ~json_ref() = default;
+
+ value_type moved_or_copied() const
+ {
+ if (is_rvalue)
+ {
+ return std::move(*value_ref);
+ }
+ return *value_ref;
+ }
+
+ value_type const& operator*() const
+ {
+ return *static_cast<value_type const*>(value_ref);
+ }
+
+ value_type const* operator->() const
+ {
+ return static_cast<value_type const*>(value_ref);
+ }
+
+ private:
+ mutable value_type owned_value = nullptr;
+ value_type* value_ref = nullptr;
+ const bool is_rvalue;
+};
+} // namespace detail
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/macro_scope.hpp b/include/lib/modernjson/detail/macro_scope.hpp
new file mode 100644
index 0000000..26db667
--- /dev/null
+++ b/include/lib/modernjson/detail/macro_scope.hpp
@@ -0,0 +1,135 @@
+#pragma once
+
+// This file contains all internal macro definitions
+// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them
+
+// exclude unsupported compilers
+#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK)
+ #if defined(__clang__)
+ #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400
+ #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers"
+ #endif
+ #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER))
+ #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800
+ #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers"
+ #endif
+ #endif
+#endif
+
+// disable float-equal warnings on GCC/clang
+#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wfloat-equal"
+#endif
+
+// disable documentation warnings on clang
+#if defined(__clang__)
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wdocumentation"
+#endif
+
+// allow for portable deprecation warnings
+#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
+ #define JSON_DEPRECATED __attribute__((deprecated))
+#elif defined(_MSC_VER)
+ #define JSON_DEPRECATED __declspec(deprecated)
+#else
+ #define JSON_DEPRECATED
+#endif
+
+// allow to disable exceptions
+#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
+ #define JSON_THROW(exception) throw exception
+ #define JSON_TRY try
+ #define JSON_CATCH(exception) catch(exception)
+ #define JSON_INTERNAL_CATCH(exception) catch(exception)
+#else
+ #define JSON_THROW(exception) std::abort()
+ #define JSON_TRY if(true)
+ #define JSON_CATCH(exception) if(false)
+ #define JSON_INTERNAL_CATCH(exception) if(false)
+#endif
+
+// override exception macros
+#if defined(JSON_THROW_USER)
+ #undef JSON_THROW
+ #define JSON_THROW JSON_THROW_USER
+#endif
+#if defined(JSON_TRY_USER)
+ #undef JSON_TRY
+ #define JSON_TRY JSON_TRY_USER
+#endif
+#if defined(JSON_CATCH_USER)
+ #undef JSON_CATCH
+ #define JSON_CATCH JSON_CATCH_USER
+ #undef JSON_INTERNAL_CATCH
+ #define JSON_INTERNAL_CATCH JSON_CATCH_USER
+#endif
+#if defined(JSON_INTERNAL_CATCH_USER)
+ #undef JSON_INTERNAL_CATCH
+ #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER
+#endif
+
+// manual branch prediction
+#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
+ #define JSON_LIKELY(x) __builtin_expect(!!(x), 1)
+ #define JSON_UNLIKELY(x) __builtin_expect(!!(x), 0)
+#else
+ #define JSON_LIKELY(x) x
+ #define JSON_UNLIKELY(x) x
+#endif
+
+// C++ language standard detection
+#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
+ #define JSON_HAS_CPP_17
+ #define JSON_HAS_CPP_14
+#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1)
+ #define JSON_HAS_CPP_14
+#endif
+
+/*!
+@brief macro to briefly define a mapping between an enum and JSON
+@def NLOHMANN_JSON_SERIALIZE_ENUM
+@since version 3.4.0
+*/
+#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \
+ template<typename BasicJsonType> \
+ inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \
+ { \
+ static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
+ static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
+ auto it = std::find_if(std::begin(m), std::end(m), \
+ [e](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
+ { \
+ return ej_pair.first == e; \
+ }); \
+ j = ((it != std::end(m)) ? it : std::begin(m))->second; \
+ } \
+ template<typename BasicJsonType> \
+ inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \
+ { \
+ static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
+ static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
+ auto it = std::find_if(std::begin(m), std::end(m), \
+ [j](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \
+ { \
+ return ej_pair.second == j; \
+ }); \
+ e = ((it != std::end(m)) ? it : std::begin(m))->first; \
+ }
+
+// Ugly macros to avoid uglier copy-paste when specializing basic_json. They
+// may be removed in the future once the class is split.
+
+#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \
+ template<template<typename, typename, typename...> class ObjectType, \
+ template<typename, typename...> class ArrayType, \
+ class StringType, class BooleanType, class NumberIntegerType, \
+ class NumberUnsignedType, class NumberFloatType, \
+ template<typename> class AllocatorType, \
+ template<typename, typename = void> class JSONSerializer>
+
+#define NLOHMANN_BASIC_JSON_TPL \
+ basic_json<ObjectType, ArrayType, StringType, BooleanType, \
+ NumberIntegerType, NumberUnsignedType, NumberFloatType, \
+ AllocatorType, JSONSerializer>
diff --git a/include/lib/modernjson/detail/macro_unscope.hpp b/include/lib/modernjson/detail/macro_unscope.hpp
new file mode 100644
index 0000000..4c5aa91
--- /dev/null
+++ b/include/lib/modernjson/detail/macro_unscope.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+// restore GCC/clang diagnostic settings
+#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
+ #pragma GCC diagnostic pop
+#endif
+#if defined(__clang__)
+ #pragma GCC diagnostic pop
+#endif
+
+// clean up
+#undef JSON_INTERNAL_CATCH
+#undef JSON_CATCH
+#undef JSON_THROW
+#undef JSON_TRY
+#undef JSON_LIKELY
+#undef JSON_UNLIKELY
+#undef JSON_DEPRECATED
+#undef JSON_HAS_CPP_14
+#undef JSON_HAS_CPP_17
+#undef NLOHMANN_BASIC_JSON_TPL_DECLARATION
+#undef NLOHMANN_BASIC_JSON_TPL
diff --git a/include/lib/modernjson/detail/meta/cpp_future.hpp b/include/lib/modernjson/detail/meta/cpp_future.hpp
new file mode 100644
index 0000000..948cd4f
--- /dev/null
+++ b/include/lib/modernjson/detail/meta/cpp_future.hpp
@@ -0,0 +1,63 @@
+#pragma once
+
+#include <ciso646> // not
+#include <cstddef> // size_t
+#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type
+
+namespace nlohmann
+{
+namespace detail
+{
+// alias templates to reduce boilerplate
+template<bool B, typename T = void>
+using enable_if_t = typename std::enable_if<B, T>::type;
+
+template<typename T>
+using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
+
+// implementation of C++14 index_sequence and affiliates
+// source: https://stackoverflow.com/a/32223343
+template<std::size_t... Ints>
+struct index_sequence
+{
+ using type = index_sequence;
+ using value_type = std::size_t;
+ static constexpr std::size_t size() noexcept
+ {
+ return sizeof...(Ints);
+ }
+};
+
+template<class Sequence1, class Sequence2>
+struct merge_and_renumber;
+
+template<std::size_t... I1, std::size_t... I2>
+struct merge_and_renumber<index_sequence<I1...>, index_sequence<I2...>>
+ : index_sequence < I1..., (sizeof...(I1) + I2)... > {};
+
+template<std::size_t N>
+struct make_index_sequence
+ : merge_and_renumber < typename make_index_sequence < N / 2 >::type,
+ typename make_index_sequence < N - N / 2 >::type > {};
+
+template<> struct make_index_sequence<0> : index_sequence<> {};
+template<> struct make_index_sequence<1> : index_sequence<0> {};
+
+template<typename... Ts>
+using index_sequence_for = make_index_sequence<sizeof...(Ts)>;
+
+// dispatch utility (taken from ranges-v3)
+template<unsigned N> struct priority_tag : priority_tag < N - 1 > {};
+template<> struct priority_tag<0> {};
+
+// taken from ranges-v3
+template<typename T>
+struct static_const
+{
+ static constexpr T value{};
+};
+
+template<typename T>
+constexpr T static_const<T>::value;
+} // namespace detail
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/meta/detected.hpp b/include/lib/modernjson/detail/meta/detected.hpp
new file mode 100644
index 0000000..8fb318a
--- /dev/null
+++ b/include/lib/modernjson/detail/meta/detected.hpp
@@ -0,0 +1,56 @@
+#pragma once
+
+#include <type_traits>
+
+#include <lib/modernjson/detail/meta/void_t.hpp>
+
+// http://en.cppreference.com/w/cpp/experimental/is_detected
+namespace nlohmann
+{
+namespace detail
+{
+struct nonesuch
+{
+ nonesuch() = delete;
+ ~nonesuch() = delete;
+ nonesuch(nonesuch const&) = delete;
+ void operator=(nonesuch const&) = delete;
+};
+
+template <class Default,
+ class AlwaysVoid,
+ template <class...> class Op,
+ class... Args>
+struct detector
+{
+ using value_t = std::false_type;
+ using type = Default;
+};
+
+template <class Default, template <class...> class Op, class... Args>
+struct detector<Default, void_t<Op<Args...>>, Op, Args...>
+{
+ using value_t = std::true_type;
+ using type = Op<Args...>;
+};
+
+template <template <class...> class Op, class... Args>
+using is_detected = typename detector<nonesuch, void, Op, Args...>::value_t;
+
+template <template <class...> class Op, class... Args>
+using detected_t = typename detector<nonesuch, void, Op, Args...>::type;
+
+template <class Default, template <class...> class Op, class... Args>
+using detected_or = detector<Default, void, Op, Args...>;
+
+template <class Default, template <class...> class Op, class... Args>
+using detected_or_t = typename detected_or<Default, Op, Args...>::type;
+
+template <class Expected, template <class...> class Op, class... Args>
+using is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>;
+
+template <class To, template <class...> class Op, class... Args>
+using is_detected_convertible =
+ std::is_convertible<detected_t<Op, Args...>, To>;
+} // namespace detail
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/meta/is_sax.hpp b/include/lib/modernjson/detail/meta/is_sax.hpp
new file mode 100644
index 0000000..af63386
--- /dev/null
+++ b/include/lib/modernjson/detail/meta/is_sax.hpp
@@ -0,0 +1,141 @@
+#pragma once
+
+#include <cstdint> // size_t
+#include <utility> // declval
+
+#include <lib/modernjson/detail/meta/detected.hpp>
+#include <lib/modernjson/detail/meta/type_traits.hpp>
+
+namespace nlohmann
+{
+namespace detail
+{
+template <typename T>
+using null_function_t = decltype(std::declval<T&>().null());
+
+template <typename T>
+using boolean_function_t =
+ decltype(std::declval<T&>().boolean(std::declval<bool>()));
+
+template <typename T, typename Integer>
+using number_integer_function_t =
+ decltype(std::declval<T&>().number_integer(std::declval<Integer>()));
+
+template <typename T, typename Unsigned>
+using number_unsigned_function_t =
+ decltype(std::declval<T &>().number_unsigned(std::declval<Unsigned>()));
+
+template <typename T, typename Float, typename String>
+using number_float_function_t = decltype(std::declval<T &>().number_float(
+ std::declval<Float>(), std::declval<const String &>()));
+
+template <typename T, typename String>
+using string_function_t =
+ decltype(std::declval<T &>().string(std::declval<String &>()));
+
+template <typename T>
+using start_object_function_t =
+ decltype(std::declval<T &>().start_object(std::declval<std::size_t>()));
+
+template <typename T, typename String>
+using key_function_t =
+ decltype(std::declval<T &>().key(std::declval<String &>()));
+
+template <typename T>
+using end_object_function_t = decltype(std::declval<T &>().end_object());
+
+template <typename T>
+using start_array_function_t =
+ decltype(std::declval<T &>().start_array(std::declval<std::size_t>()));
+
+template <typename T>
+using end_array_function_t = decltype(std::declval<T &>().end_array());
+
+template <typename T, typename Exception>
+using parse_error_function_t = decltype(std::declval<T &>().parse_error(
+ std::declval<std::size_t>(), std::declval<const std::string &>(),
+ std::declval<const Exception &>()));
+
+template <typename SAX, typename BasicJsonType>
+struct is_sax
+{
+private:
+ static_assert(is_basic_json<BasicJsonType>::value,
+ "BasicJsonType must be of type basic_json<...>");
+
+ using number_integer_t = typename BasicJsonType::number_integer_t;
+ using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+ using number_float_t = typename BasicJsonType::number_float_t;
+ using string_t = typename BasicJsonType::string_t;
+ using exception_t = typename BasicJsonType::exception;
+
+public:
+ static constexpr bool value =
+ is_detected_exact<bool, null_function_t, SAX>::value &&
+ is_detected_exact<bool, boolean_function_t, SAX>::value &&
+ is_detected_exact<bool, number_integer_function_t, SAX,
+ number_integer_t>::value &&
+ is_detected_exact<bool, number_unsigned_function_t, SAX,
+ number_unsigned_t>::value &&
+ is_detected_exact<bool, number_float_function_t, SAX, number_float_t,
+ string_t>::value &&
+ is_detected_exact<bool, string_function_t, SAX, string_t>::value &&
+ is_detected_exact<bool, start_object_function_t, SAX>::value &&
+ is_detected_exact<bool, key_function_t, SAX, string_t>::value &&
+ is_detected_exact<bool, end_object_function_t, SAX>::value &&
+ is_detected_exact<bool, start_array_function_t, SAX>::value &&
+ is_detected_exact<bool, end_array_function_t, SAX>::value &&
+ is_detected_exact<bool, parse_error_function_t, SAX, exception_t>::value;
+};
+
+template <typename SAX, typename BasicJsonType>
+struct is_sax_static_asserts
+{
+private:
+ static_assert(is_basic_json<BasicJsonType>::value,
+ "BasicJsonType must be of type basic_json<...>");
+
+ using number_integer_t = typename BasicJsonType::number_integer_t;
+ using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+ using number_float_t = typename BasicJsonType::number_float_t;
+ using string_t = typename BasicJsonType::string_t;
+ using exception_t = typename BasicJsonType::exception;
+
+public:
+ static_assert(is_detected_exact<bool, null_function_t, SAX>::value,
+ "Missing/invalid function: bool null()");
+ static_assert(is_detected_exact<bool, boolean_function_t, SAX>::value,
+ "Missing/invalid function: bool boolean(bool)");
+ static_assert(is_detected_exact<bool, boolean_function_t, SAX>::value,
+ "Missing/invalid function: bool boolean(bool)");
+ static_assert(
+ is_detected_exact<bool, number_integer_function_t, SAX,
+ number_integer_t>::value,
+ "Missing/invalid function: bool number_integer(number_integer_t)");
+ static_assert(
+ is_detected_exact<bool, number_unsigned_function_t, SAX,
+ number_unsigned_t>::value,
+ "Missing/invalid function: bool number_unsigned(number_unsigned_t)");
+ static_assert(is_detected_exact<bool, number_float_function_t, SAX,
+ number_float_t, string_t>::value,
+ "Missing/invalid function: bool number_float(number_float_t, const string_t&)");
+ static_assert(
+ is_detected_exact<bool, string_function_t, SAX, string_t>::value,
+ "Missing/invalid function: bool string(string_t&)");
+ static_assert(is_detected_exact<bool, start_object_function_t, SAX>::value,
+ "Missing/invalid function: bool start_object(std::size_t)");
+ static_assert(is_detected_exact<bool, key_function_t, SAX, string_t>::value,
+ "Missing/invalid function: bool key(string_t&)");
+ static_assert(is_detected_exact<bool, end_object_function_t, SAX>::value,
+ "Missing/invalid function: bool end_object()");
+ static_assert(is_detected_exact<bool, start_array_function_t, SAX>::value,
+ "Missing/invalid function: bool start_array(std::size_t)");
+ static_assert(is_detected_exact<bool, end_array_function_t, SAX>::value,
+ "Missing/invalid function: bool end_array()");
+ static_assert(
+ is_detected_exact<bool, parse_error_function_t, SAX, exception_t>::value,
+ "Missing/invalid function: bool parse_error(std::size_t, const "
+ "std::string&, const exception&)");
+};
+} // namespace detail
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/meta/type_traits.hpp b/include/lib/modernjson/detail/meta/type_traits.hpp
new file mode 100644
index 0000000..2f0af28
--- /dev/null
+++ b/include/lib/modernjson/detail/meta/type_traits.hpp
@@ -0,0 +1,347 @@
+#pragma once
+
+#include <ciso646> // not
+#include <limits> // numeric_limits
+#include <type_traits> // false_type, is_constructible, is_integral, is_same, true_type
+#include <utility> // declval
+
+#include <lib/modernjson/json_fwd.h>
+#include <lib/modernjson/detail/meta/cpp_future.hpp>
+#include <lib/modernjson/detail/meta/detected.hpp>
+#include <lib/modernjson/detail/macro_scope.hpp>
+
+namespace nlohmann
+{
+/*!
+@brief detail namespace with internal helper functions
+
+This namespace collects functions that should not be exposed,
+implementations of some @ref basic_json methods, and meta-programming helpers.
+
+@since version 2.1.0
+*/
+namespace detail
+{
+/////////////
+// helpers //
+/////////////
+
+// Note to maintainers:
+//
+// Every trait in this file expects a non CV-qualified type.
+// The only exceptions are in the 'aliases for detected' section
+// (i.e. those of the form: decltype(T::member_function(std::declval<T>())))
+//
+// In this case, T has to be properly CV-qualified to constraint the function arguments
+// (e.g. to_json(BasicJsonType&, const T&))
+
+template<typename> struct is_basic_json : std::false_type {};
+
+NLOHMANN_BASIC_JSON_TPL_DECLARATION
+struct is_basic_json<NLOHMANN_BASIC_JSON_TPL> : std::true_type {};
+
+//////////////////////////
+// aliases for detected //
+//////////////////////////
+
+template <typename T>
+using mapped_type_t = typename T::mapped_type;
+
+template <typename T>
+using key_type_t = typename T::key_type;
+
+template <typename T>
+using value_type_t = typename T::value_type;
+
+template <typename T>
+using difference_type_t = typename T::difference_type;
+
+template <typename T>
+using pointer_t = typename T::pointer;
+
+template <typename T>
+using reference_t = typename T::reference;
+
+template <typename T>
+using iterator_category_t = typename T::iterator_category;
+
+template <typename T>
+using iterator_t = typename T::iterator;
+
+template <typename T, typename... Args>
+using to_json_function = decltype(T::to_json(std::declval<Args>()...));
+
+template <typename T, typename... Args>
+using from_json_function = decltype(T::from_json(std::declval<Args>()...));
+
+template <typename T, typename U>
+using get_template_function = decltype(std::declval<T>().template get<U>());
+
+// trait checking if JSONSerializer<T>::from_json(json const&, udt&) exists
+template <typename BasicJsonType, typename T, typename = void>
+struct has_from_json : std::false_type {};
+
+template <typename BasicJsonType, typename T>
+struct has_from_json<BasicJsonType, T,
+ enable_if_t<not is_basic_json<T>::value>>
+{
+ using serializer = typename BasicJsonType::template json_serializer<T, void>;
+
+ static constexpr bool value =
+ is_detected_exact<void, from_json_function, serializer,
+ const BasicJsonType&, T&>::value;
+};
+
+// This trait checks if JSONSerializer<T>::from_json(json const&) exists
+// this overload is used for non-default-constructible user-defined-types
+template <typename BasicJsonType, typename T, typename = void>
+struct has_non_default_from_json : std::false_type {};
+
+template<typename BasicJsonType, typename T>
+struct has_non_default_from_json<BasicJsonType, T, enable_if_t<not is_basic_json<T>::value>>
+{
+ using serializer = typename BasicJsonType::template json_serializer<T, void>;
+
+ static constexpr bool value =
+ is_detected_exact<T, from_json_function, serializer,
+ const BasicJsonType&>::value;
+};
+
+// This trait checks if BasicJsonType::json_serializer<T>::to_json exists
+// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion.
+template <typename BasicJsonType, typename T, typename = void>
+struct has_to_json : std::false_type {};
+
+template <typename BasicJsonType, typename T>
+struct has_to_json<BasicJsonType, T, enable_if_t<not is_basic_json<T>::value>>
+{
+ using serializer = typename BasicJsonType::template json_serializer<T, void>;
+
+ static constexpr bool value =
+ is_detected_exact<void, to_json_function, serializer, BasicJsonType&,
+ T>::value;
+};
+
+
+///////////////////
+// is_ functions //
+///////////////////
+
+template <typename T, typename = void>
+struct is_iterator_traits : std::false_type {};
+
+template <typename T>
+struct is_iterator_traits<std::iterator_traits<T>>
+{
+ private:
+ using traits = std::iterator_traits<T>;
+
+ public:
+ static constexpr auto value =
+ is_detected<value_type_t, traits>::value &&
+ is_detected<difference_type_t, traits>::value &&
+ is_detected<pointer_t, traits>::value &&
+ is_detected<iterator_category_t, traits>::value &&
+ is_detected<reference_t, traits>::value;
+};
+
+// source: https://stackoverflow.com/a/37193089/4116453
+
+template <typename T, typename = void>
+struct is_complete_type : std::false_type {};
+
+template <typename T>
+struct is_complete_type<T, decltype(void(sizeof(T)))> : std::true_type {};
+
+template <typename BasicJsonType, typename CompatibleObjectType,
+ typename = void>
+struct is_compatible_object_type_impl : std::false_type {};
+
+template <typename BasicJsonType, typename CompatibleObjectType>
+struct is_compatible_object_type_impl <
+ BasicJsonType, CompatibleObjectType,
+ enable_if_t<is_detected<mapped_type_t, CompatibleObjectType>::value and
+ is_detected<key_type_t, CompatibleObjectType>::value >>
+{
+
+ using object_t = typename BasicJsonType::object_t;
+
+ // macOS's is_constructible does not play well with nonesuch...
+ static constexpr bool value =
+ std::is_constructible<typename object_t::key_type,
+ typename CompatibleObjectType::key_type>::value and
+ std::is_constructible<typename object_t::mapped_type,
+ typename CompatibleObjectType::mapped_type>::value;
+};
+
+template <typename BasicJsonType, typename CompatibleObjectType>
+struct is_compatible_object_type
+ : is_compatible_object_type_impl<BasicJsonType, CompatibleObjectType> {};
+
+template <typename BasicJsonType, typename ConstructibleObjectType,
+ typename = void>
+struct is_constructible_object_type_impl : std::false_type {};
+
+template <typename BasicJsonType, typename ConstructibleObjectType>
+struct is_constructible_object_type_impl <
+ BasicJsonType, ConstructibleObjectType,
+ enable_if_t<is_detected<mapped_type_t, ConstructibleObjectType>::value and
+ is_detected<key_type_t, ConstructibleObjectType>::value >>
+{
+ using object_t = typename BasicJsonType::object_t;
+
+ static constexpr bool value =
+ (std::is_constructible<typename ConstructibleObjectType::key_type, typename object_t::key_type>::value and
+ std::is_same<typename object_t::mapped_type, typename ConstructibleObjectType::mapped_type>::value) or
+ (has_from_json<BasicJsonType, typename ConstructibleObjectType::mapped_type>::value or
+ has_non_default_from_json<BasicJsonType, typename ConstructibleObjectType::mapped_type >::value);
+};
+
+template <typename BasicJsonType, typename ConstructibleObjectType>
+struct is_constructible_object_type
+ : is_constructible_object_type_impl<BasicJsonType,
+ ConstructibleObjectType> {};
+
+template <typename BasicJsonType, typename CompatibleStringType,
+ typename = void>
+struct is_compatible_string_type_impl : std::false_type {};
+
+template <typename BasicJsonType, typename CompatibleStringType>
+struct is_compatible_string_type_impl <
+ BasicJsonType, CompatibleStringType,
+ enable_if_t<is_detected_exact<typename BasicJsonType::string_t::value_type,
+ value_type_t, CompatibleStringType>::value >>
+{
+ static constexpr auto value =
+ std::is_constructible<typename BasicJsonType::string_t, CompatibleStringType>::value;
+};
+
+template <typename BasicJsonType, typename ConstructibleStringType>
+struct is_compatible_string_type
+ : is_compatible_string_type_impl<BasicJsonType, ConstructibleStringType> {};
+
+template <typename BasicJsonType, typename ConstructibleStringType,
+ typename = void>
+struct is_constructible_string_type_impl : std::false_type {};
+
+template <typename BasicJsonType, typename ConstructibleStringType>
+struct is_constructible_string_type_impl <
+ BasicJsonType, ConstructibleStringType,
+ enable_if_t<is_detected_exact<typename BasicJsonType::string_t::value_type,
+ value_type_t, ConstructibleStringType>::value >>
+{
+ static constexpr auto value =
+ std::is_constructible<ConstructibleStringType,
+ typename BasicJsonType::string_t>::value;
+};
+
+template <typename BasicJsonType, typename ConstructibleStringType>
+struct is_constructible_string_type
+ : is_constructible_string_type_impl<BasicJsonType, ConstructibleStringType> {};
+
+template <typename BasicJsonType, typename CompatibleArrayType, typename = void>
+struct is_compatible_array_type_impl : std::false_type {};
+
+template <typename BasicJsonType, typename CompatibleArrayType>
+struct is_compatible_array_type_impl <
+ BasicJsonType, CompatibleArrayType,
+ enable_if_t<is_detected<value_type_t, CompatibleArrayType>::value and
+ is_detected<iterator_t, CompatibleArrayType>::value and
+// This is needed because json_reverse_iterator has a ::iterator type...
+// Therefore it is detected as a CompatibleArrayType.
+// The real fix would be to have an Iterable concept.
+ not is_iterator_traits<
+ std::iterator_traits<CompatibleArrayType>>::value >>
+{
+ static constexpr bool value =
+ std::is_constructible<BasicJsonType,
+ typename CompatibleArrayType::value_type>::value;
+};
+
+template <typename BasicJsonType, typename CompatibleArrayType>
+struct is_compatible_array_type
+ : is_compatible_array_type_impl<BasicJsonType, CompatibleArrayType> {};
+
+template <typename BasicJsonType, typename ConstructibleArrayType, typename = void>
+struct is_constructible_array_type_impl : std::false_type {};
+
+template <typename BasicJsonType, typename ConstructibleArrayType>
+struct is_constructible_array_type_impl <
+ BasicJsonType, ConstructibleArrayType,
+ enable_if_t<std::is_same<ConstructibleArrayType,
+ typename BasicJsonType::value_type>::value >>
+ : std::true_type {};
+
+template <typename BasicJsonType, typename ConstructibleArrayType>
+struct is_constructible_array_type_impl <
+ BasicJsonType, ConstructibleArrayType,
+ enable_if_t<not std::is_same<ConstructibleArrayType,
+ typename BasicJsonType::value_type>::value and
+ is_detected<value_type_t, ConstructibleArrayType>::value and
+ is_detected<iterator_t, ConstructibleArrayType>::value and
+ is_complete_type<
+ detected_t<value_type_t, ConstructibleArrayType>>::value >>
+{
+ static constexpr bool value =
+ // This is needed because json_reverse_iterator has a ::iterator type,
+ // furthermore, std::back_insert_iterator (and other iterators) have a base class `iterator`...
+ // Therefore it is detected as a ConstructibleArrayType.
+ // The real fix would be to have an Iterable concept.
+ not is_iterator_traits <
+ std::iterator_traits<ConstructibleArrayType >>::value and
+
+ (std::is_same<typename ConstructibleArrayType::value_type, typename BasicJsonType::array_t::value_type>::value or
+ has_from_json<BasicJsonType,
+ typename ConstructibleArrayType::value_type>::value or
+ has_non_default_from_json <
+ BasicJsonType, typename ConstructibleArrayType::value_type >::value);
+};
+
+template <typename BasicJsonType, typename ConstructibleArrayType>
+struct is_constructible_array_type
+ : is_constructible_array_type_impl<BasicJsonType, ConstructibleArrayType> {};
+
+template <typename RealIntegerType, typename CompatibleNumberIntegerType,
+ typename = void>
+struct is_compatible_integer_type_impl : std::false_type {};
+
+template <typename RealIntegerType, typename CompatibleNumberIntegerType>
+struct is_compatible_integer_type_impl <
+ RealIntegerType, CompatibleNumberIntegerType,
+ enable_if_t<std::is_integral<RealIntegerType>::value and
+ std::is_integral<CompatibleNumberIntegerType>::value and
+ not std::is_same<bool, CompatibleNumberIntegerType>::value >>
+{
+ // is there an assert somewhere on overflows?
+ using RealLimits = std::numeric_limits<RealIntegerType>;
+ using CompatibleLimits = std::numeric_limits<CompatibleNumberIntegerType>;
+
+ static constexpr auto value =
+ std::is_constructible<RealIntegerType,
+ CompatibleNumberIntegerType>::value and
+ CompatibleLimits::is_integer and
+ RealLimits::is_signed == CompatibleLimits::is_signed;
+};
+
+template <typename RealIntegerType, typename CompatibleNumberIntegerType>
+struct is_compatible_integer_type
+ : is_compatible_integer_type_impl<RealIntegerType,
+ CompatibleNumberIntegerType> {};
+
+template <typename BasicJsonType, typename CompatibleType, typename = void>
+struct is_compatible_type_impl: std::false_type {};
+
+template <typename BasicJsonType, typename CompatibleType>
+struct is_compatible_type_impl <
+ BasicJsonType, CompatibleType,
+ enable_if_t<is_complete_type<CompatibleType>::value >>
+{
+ static constexpr bool value =
+ has_to_json<BasicJsonType, CompatibleType>::value;
+};
+
+template <typename BasicJsonType, typename CompatibleType>
+struct is_compatible_type
+ : is_compatible_type_impl<BasicJsonType, CompatibleType> {};
+} // namespace detail
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/meta/void_t.hpp b/include/lib/modernjson/detail/meta/void_t.hpp
new file mode 100644
index 0000000..a256f85
--- /dev/null
+++ b/include/lib/modernjson/detail/meta/void_t.hpp
@@ -0,0 +1,13 @@
+#pragma once
+
+namespace nlohmann
+{
+namespace detail
+{
+template <typename ...Ts> struct make_void
+{
+ using type = void;
+};
+template <typename ...Ts> using void_t = typename make_void<Ts...>::type;
+} // namespace detail
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/output/binary_writer.hpp b/include/lib/modernjson/detail/output/binary_writer.hpp
new file mode 100644
index 0000000..1020e69
--- /dev/null
+++ b/include/lib/modernjson/detail/output/binary_writer.hpp
@@ -0,0 +1,1339 @@
+#pragma once
+
+#include <algorithm> // reverse
+#include <array> // array
+#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
+#include <cstring> // memcpy
+#include <limits> // numeric_limits
+
+#include <lib/modernjson/detail/input/binary_reader.hpp>
+#include <lib/modernjson/detail/output/output_adapters.hpp>
+
+namespace nlohmann
+{
+namespace detail
+{
+///////////////////
+// binary writer //
+///////////////////
+
+/*!
+@brief serialization to CBOR and MessagePack values
+*/
+template<typename BasicJsonType, typename CharType>
+class binary_writer
+{
+ using string_t = typename BasicJsonType::string_t;
+
+ public:
+ /*!
+ @brief create a binary writer
+
+ @param[in] adapter output adapter to write to
+ */
+ explicit binary_writer(output_adapter_t<CharType> adapter) : oa(adapter)
+ {
+ assert(oa);
+ }
+
+ /*!
+ @param[in] j JSON value to serialize
+ @pre j.type() == value_t::object
+ */
+ void write_bson(const BasicJsonType& j)
+ {
+ switch (j.type())
+ {
+ case value_t::object:
+ {
+ write_bson_object(*j.m_value.object);
+ break;
+ }
+
+ default:
+ {
+ JSON_THROW(type_error::create(317, "to serialize to BSON, top-level type must be object, but is " + std::string(j.type_name())));
+ }
+ }
+ }
+
+ /*!
+ @param[in] j JSON value to serialize
+ */
+ void write_cbor(const BasicJsonType& j)
+ {
+ switch (j.type())
+ {
+ case value_t::null:
+ {
+ oa->write_character(to_char_type(0xF6));
+ break;
+ }
+
+ case value_t::boolean:
+ {
+ oa->write_character(j.m_value.boolean
+ ? to_char_type(0xF5)
+ : to_char_type(0xF4));
+ break;
+ }
+
+ case value_t::number_integer:
+ {
+ if (j.m_value.number_integer >= 0)
+ {
+ // CBOR does not differentiate between positive signed
+ // integers and unsigned integers. Therefore, we used the
+ // code from the value_t::number_unsigned case here.
+ if (j.m_value.number_integer <= 0x17)
+ {
+ write_number(static_cast<uint8_t>(j.m_value.number_integer));
+ }
+ else if (j.m_value.number_integer <= (std::numeric_limits<uint8_t>::max)())
+ {
+ oa->write_character(to_char_type(0x18));
+ write_number(static_cast<uint8_t>(j.m_value.number_integer));
+ }
+ else if (j.m_value.number_integer <= (std::numeric_limits<uint16_t>::max)())
+ {
+ oa->write_character(to_char_type(0x19));
+ write_number(static_cast<uint16_t>(j.m_value.number_integer));
+ }
+ else if (j.m_value.number_integer <= (std::numeric_limits<uint32_t>::max)())
+ {
+ oa->write_character(to_char_type(0x1A));
+ write_number(static_cast<uint32_t>(j.m_value.number_integer));
+ }
+ else
+ {
+ oa->write_character(to_char_type(0x1B));
+ write_number(static_cast<uint64_t>(j.m_value.number_integer));
+ }
+ }
+ else
+ {
+ // The conversions below encode the sign in the first
+ // byte, and the value is converted to a positive number.
+ const auto positive_number = -1 - j.m_value.number_integer;
+ if (j.m_value.number_integer >= -24)
+ {
+ write_number(static_cast<uint8_t>(0x20 + positive_number));
+ }
+ else if (positive_number <= (std::numeric_limits<uint8_t>::max)())
+ {
+ oa->write_character(to_char_type(0x38));
+ write_number(static_cast<uint8_t>(positive_number));
+ }
+ else if (positive_number <= (std::numeric_limits<uint16_t>::max)())
+ {
+ oa->write_character(to_char_type(0x39));
+ write_number(static_cast<uint16_t>(positive_number));
+ }
+ else if (positive_number <= (std::numeric_limits<uint32_t>::max)())
+ {
+ oa->write_character(to_char_type(0x3A));
+ write_number(static_cast<uint32_t>(positive_number));
+ }
+ else
+ {
+ oa->write_character(to_char_type(0x3B));
+ write_number(static_cast<uint64_t>(positive_number));
+ }
+ }
+ break;
+ }
+
+ case value_t::number_unsigned:
+ {
+ if (j.m_value.number_unsigned <= 0x17)
+ {
+ write_number(static_cast<uint8_t>(j.m_value.number_unsigned));
+ }
+ else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)())
+ {
+ oa->write_character(to_char_type(0x18));
+ write_number(static_cast<uint8_t>(j.m_value.number_unsigned));
+ }
+ else if (j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max)())
+ {
+ oa->write_character(to_char_type(0x19));
+ write_number(static_cast<uint16_t>(j.m_value.number_unsigned));
+ }
+ else if (j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max)())
+ {
+ oa->write_character(to_char_type(0x1A));
+ write_number(static_cast<uint32_t>(j.m_value.number_unsigned));
+ }
+ else
+ {
+ oa->write_character(to_char_type(0x1B));
+ write_number(static_cast<uint64_t>(j.m_value.number_unsigned));
+ }
+ break;
+ }
+
+ case value_t::number_float:
+ {
+ oa->write_character(get_cbor_float_prefix(j.m_value.number_float));
+ write_number(j.m_value.number_float);
+ break;
+ }
+
+ case value_t::string:
+ {
+ // step 1: write control byte and the string length
+ const auto N = j.m_value.string->size();
+ if (N <= 0x17)
+ {
+ write_number(static_cast<uint8_t>(0x60 + N));
+ }
+ else if (N <= (std::numeric_limits<uint8_t>::max)())
+ {
+ oa->write_character(to_char_type(0x78));
+ write_number(static_cast<uint8_t>(N));
+ }
+ else if (N <= (std::numeric_limits<uint16_t>::max)())
+ {
+ oa->write_character(to_char_type(0x79));
+ write_number(static_cast<uint16_t>(N));
+ }
+ else if (N <= (std::numeric_limits<uint32_t>::max)())
+ {
+ oa->write_character(to_char_type(0x7A));
+ write_number(static_cast<uint32_t>(N));
+ }
+ // LCOV_EXCL_START
+ else if (N <= (std::numeric_limits<uint64_t>::max)())
+ {
+ oa->write_character(to_char_type(0x7B));
+ write_number(static_cast<uint64_t>(N));
+ }
+ // LCOV_EXCL_STOP
+
+ // step 2: write the string
+ oa->write_characters(
+ reinterpret_cast<const CharType*>(j.m_value.string->c_str()),
+ j.m_value.string->size());
+ break;
+ }
+
+ case value_t::array:
+ {
+ // step 1: write control byte and the array size
+ const auto N = j.m_value.array->size();
+ if (N <= 0x17)
+ {
+ write_number(static_cast<uint8_t>(0x80 + N));
+ }
+ else if (N <= (std::numeric_limits<uint8_t>::max)())
+ {
+ oa->write_character(to_char_type(0x98));
+ write_number(static_cast<uint8_t>(N));
+ }
+ else if (N <= (std::numeric_limits<uint16_t>::max)())
+ {
+ oa->write_character(to_char_type(0x99));
+ write_number(static_cast<uint16_t>(N));
+ }
+ else if (N <= (std::numeric_limits<uint32_t>::max)())
+ {
+ oa->write_character(to_char_type(0x9A));
+ write_number(static_cast<uint32_t>(N));
+ }
+ // LCOV_EXCL_START
+ else if (N <= (std::numeric_limits<uint64_t>::max)())
+ {
+ oa->write_character(to_char_type(0x9B));
+ write_number(static_cast<uint64_t>(N));
+ }
+ // LCOV_EXCL_STOP
+
+ // step 2: write each element
+ for (const auto& el : *j.m_value.array)
+ {
+ write_cbor(el);
+ }
+ break;
+ }
+
+ case value_t::object:
+ {
+ // step 1: write control byte and the object size
+ const auto N = j.m_value.object->size();
+ if (N <= 0x17)
+ {
+ write_number(static_cast<uint8_t>(0xA0 + N));
+ }
+ else if (N <= (std::numeric_limits<uint8_t>::max)())
+ {
+ oa->write_character(to_char_type(0xB8));
+ write_number(static_cast<uint8_t>(N));
+ }
+ else if (N <= (std::numeric_limits<uint16_t>::max)())
+ {
+ oa->write_character(to_char_type(0xB9));
+ write_number(static_cast<uint16_t>(N));
+ }
+ else if (N <= (std::numeric_limits<uint32_t>::max)())
+ {
+ oa->write_character(to_char_type(0xBA));
+ write_number(static_cast<uint32_t>(N));
+ }
+ // LCOV_EXCL_START
+ else if (N <= (std::numeric_limits<uint64_t>::max)())
+ {
+ oa->write_character(to_char_type(0xBB));
+ write_number(static_cast<uint64_t>(N));
+ }
+ // LCOV_EXCL_STOP
+
+ // step 2: write each element
+ for (const auto& el : *j.m_value.object)
+ {
+ write_cbor(el.first);
+ write_cbor(el.second);
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ /*!
+ @param[in] j JSON value to serialize
+ */
+ void write_msgpack(const BasicJsonType& j)
+ {
+ switch (j.type())
+ {
+ case value_t::null: // nil
+ {
+ oa->write_character(to_char_type(0xC0));
+ break;
+ }
+
+ case value_t::boolean: // true and false
+ {
+ oa->write_character(j.m_value.boolean
+ ? to_char_type(0xC3)
+ : to_char_type(0xC2));
+ break;
+ }
+
+ case value_t::number_integer:
+ {
+ if (j.m_value.number_integer >= 0)
+ {
+ // MessagePack does not differentiate between positive
+ // signed integers and unsigned integers. Therefore, we used
+ // the code from the value_t::number_unsigned case here.
+ if (j.m_value.number_unsigned < 128)
+ {
+ // positive fixnum
+ write_number(static_cast<uint8_t>(j.m_value.number_integer));
+ }
+ else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)())
+ {
+ // uint 8
+ oa->write_character(to_char_type(0xCC));
+ write_number(static_cast<uint8_t>(j.m_value.number_integer));
+ }
+ else if (j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max)())
+ {
+ // uint 16
+ oa->write_character(to_char_type(0xCD));
+ write_number(static_cast<uint16_t>(j.m_value.number_integer));
+ }
+ else if (j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max)())
+ {
+ // uint 32
+ oa->write_character(to_char_type(0xCE));
+ write_number(static_cast<uint32_t>(j.m_value.number_integer));
+ }
+ else if (j.m_value.number_unsigned <= (std::numeric_limits<uint64_t>::max)())
+ {
+ // uint 64
+ oa->write_character(to_char_type(0xCF));
+ write_number(static_cast<uint64_t>(j.m_value.number_integer));
+ }
+ }
+ else
+ {
+ if (j.m_value.number_integer >= -32)
+ {
+ // negative fixnum
+ write_number(static_cast<int8_t>(j.m_value.number_integer));
+ }
+ else if (j.m_value.number_integer >= (std::numeric_limits<int8_t>::min)() and
+ j.m_value.number_integer <= (std::numeric_limits<int8_t>::max)())
+ {
+ // int 8
+ oa->write_character(to_char_type(0xD0));
+ write_number(static_cast<int8_t>(j.m_value.number_integer));
+ }
+ else if (j.m_value.number_integer >= (std::numeric_limits<int16_t>::min)() and
+ j.m_value.number_integer <= (std::numeric_limits<int16_t>::max)())
+ {
+ // int 16
+ oa->write_character(to_char_type(0xD1));
+ write_number(static_cast<int16_t>(j.m_value.number_integer));
+ }
+ else if (j.m_value.number_integer >= (std::numeric_limits<int32_t>::min)() and
+ j.m_value.number_integer <= (std::numeric_limits<int32_t>::max)())
+ {
+ // int 32
+ oa->write_character(to_char_type(0xD2));
+ write_number(static_cast<int32_t>(j.m_value.number_integer));
+ }
+ else if (j.m_value.number_integer >= (std::numeric_limits<int64_t>::min)() and
+ j.m_value.number_integer <= (std::numeric_limits<int64_t>::max)())
+ {
+ // int 64
+ oa->write_character(to_char_type(0xD3));
+ write_number(static_cast<int64_t>(j.m_value.number_integer));
+ }
+ }
+ break;
+ }
+
+ case value_t::number_unsigned:
+ {
+ if (j.m_value.number_unsigned < 128)
+ {
+ // positive fixnum
+ write_number(static_cast<uint8_t>(j.m_value.number_integer));
+ }
+ else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)())
+ {
+ // uint 8
+ oa->write_character(to_char_type(0xCC));
+ write_number(static_cast<uint8_t>(j.m_value.number_integer));
+ }
+ else if (j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max)())
+ {
+ // uint 16
+ oa->write_character(to_char_type(0xCD));
+ write_number(static_cast<uint16_t>(j.m_value.number_integer));
+ }
+ else if (j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max)())
+ {
+ // uint 32
+ oa->write_character(to_char_type(0xCE));
+ write_number(static_cast<uint32_t>(j.m_value.number_integer));
+ }
+ else if (j.m_value.number_unsigned <= (std::numeric_limits<uint64_t>::max)())
+ {
+ // uint 64
+ oa->write_character(to_char_type(0xCF));
+ write_number(static_cast<uint64_t>(j.m_value.number_integer));
+ }
+ break;
+ }
+
+ case value_t::number_float:
+ {
+ oa->write_character(get_msgpack_float_prefix(j.m_value.number_float));
+ write_number(j.m_value.number_float);
+ break;
+ }
+
+ case value_t::string:
+ {
+ // step 1: write control byte and the string length
+ const auto N = j.m_value.string->size();
+ if (N <= 31)
+ {
+ // fixstr
+ write_number(static_cast<uint8_t>(0xA0 | N));
+ }
+ else if (N <= (std::numeric_limits<uint8_t>::max)())
+ {
+ // str 8
+ oa->write_character(to_char_type(0xD9));
+ write_number(static_cast<uint8_t>(N));
+ }
+ else if (N <= (std::numeric_limits<uint16_t>::max)())
+ {
+ // str 16
+ oa->write_character(to_char_type(0xDA));
+ write_number(static_cast<uint16_t>(N));
+ }
+ else if (N <= (std::numeric_limits<uint32_t>::max)())
+ {
+ // str 32
+ oa->write_character(to_char_type(0xDB));
+ write_number(static_cast<uint32_t>(N));
+ }
+
+ // step 2: write the string
+ oa->write_characters(
+ reinterpret_cast<const CharType*>(j.m_value.string->c_str()),
+ j.m_value.string->size());
+ break;
+ }
+
+ case value_t::array:
+ {
+ // step 1: write control byte and the array size
+ const auto N = j.m_value.array->size();
+ if (N <= 15)
+ {
+ // fixarray
+ write_number(static_cast<uint8_t>(0x90 | N));
+ }
+ else if (N <= (std::numeric_limits<uint16_t>::max)())
+ {
+ // array 16
+ oa->write_character(to_char_type(0xDC));
+ write_number(static_cast<uint16_t>(N));
+ }
+ else if (N <= (std::numeric_limits<uint32_t>::max)())
+ {
+ // array 32
+ oa->write_character(to_char_type(0xDD));
+ write_number(static_cast<uint32_t>(N));
+ }
+
+ // step 2: write each element
+ for (const auto& el : *j.m_value.array)
+ {
+ write_msgpack(el);
+ }
+ break;
+ }
+
+ case value_t::object:
+ {
+ // step 1: write control byte and the object size
+ const auto N = j.m_value.object->size();
+ if (N <= 15)
+ {
+ // fixmap
+ write_number(static_cast<uint8_t>(0x80 | (N & 0xF)));
+ }
+ else if (N <= (std::numeric_limits<uint16_t>::max)())
+ {
+ // map 16
+ oa->write_character(to_char_type(0xDE));
+ write_number(static_cast<uint16_t>(N));
+ }
+ else if (N <= (std::numeric_limits<uint32_t>::max)())
+ {
+ // map 32
+ oa->write_character(to_char_type(0xDF));
+ write_number(static_cast<uint32_t>(N));
+ }
+
+ // step 2: write each element
+ for (const auto& el : *j.m_value.object)
+ {
+ write_msgpack(el.first);
+ write_msgpack(el.second);
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ /*!
+ @param[in] j JSON value to serialize
+ @param[in] use_count whether to use '#' prefixes (optimized format)
+ @param[in] use_type whether to use '$' prefixes (optimized format)
+ @param[in] add_prefix whether prefixes need to be used for this value
+ */
+ void write_ubjson(const BasicJsonType& j, const bool use_count,
+ const bool use_type, const bool add_prefix = true)
+ {
+ switch (j.type())
+ {
+ case value_t::null:
+ {
+ if (add_prefix)
+ {
+ oa->write_character(to_char_type('Z'));
+ }
+ break;
+ }
+
+ case value_t::boolean:
+ {
+ if (add_prefix)
+ {
+ oa->write_character(j.m_value.boolean
+ ? to_char_type('T')
+ : to_char_type('F'));
+ }
+ break;
+ }
+
+ case value_t::number_integer:
+ {
+ write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix);
+ break;
+ }
+
+ case value_t::number_unsigned:
+ {
+ write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix);
+ break;
+ }
+
+ case value_t::number_float:
+ {
+ write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix);
+ break;
+ }
+
+ case value_t::string:
+ {
+ if (add_prefix)
+ {
+ oa->write_character(to_char_type('S'));
+ }
+ write_number_with_ubjson_prefix(j.m_value.string->size(), true);
+ oa->write_characters(
+ reinterpret_cast<const CharType*>(j.m_value.string->c_str()),
+ j.m_value.string->size());
+ break;
+ }
+
+ case value_t::array:
+ {
+ if (add_prefix)
+ {
+ oa->write_character(to_char_type('['));
+ }
+
+ bool prefix_required = true;
+ if (use_type and not j.m_value.array->empty())
+ {
+ assert(use_count);
+ const CharType first_prefix = ubjson_prefix(j.front());
+ const bool same_prefix = std::all_of(j.begin() + 1, j.end(),
+ [this, first_prefix](const BasicJsonType & v)
+ {
+ return ubjson_prefix(v) == first_prefix;
+ });
+
+ if (same_prefix)
+ {
+ prefix_required = false;
+ oa->write_character(to_char_type('$'));
+ oa->write_character(first_prefix);
+ }
+ }
+
+ if (use_count)
+ {
+ oa->write_character(to_char_type('#'));
+ write_number_with_ubjson_prefix(j.m_value.array->size(), true);
+ }
+
+ for (const auto& el : *j.m_value.array)
+ {
+ write_ubjson(el, use_count, use_type, prefix_required);
+ }
+
+ if (not use_count)
+ {
+ oa->write_character(to_char_type(']'));
+ }
+
+ break;
+ }
+
+ case value_t::object:
+ {
+ if (add_prefix)
+ {
+ oa->write_character(to_char_type('{'));
+ }
+
+ bool prefix_required = true;
+ if (use_type and not j.m_value.object->empty())
+ {
+ assert(use_count);
+ const CharType first_prefix = ubjson_prefix(j.front());
+ const bool same_prefix = std::all_of(j.begin(), j.end(),
+ [this, first_prefix](const BasicJsonType & v)
+ {
+ return ubjson_prefix(v) == first_prefix;
+ });
+
+ if (same_prefix)
+ {
+ prefix_required = false;
+ oa->write_character(to_char_type('$'));
+ oa->write_character(first_prefix);
+ }
+ }
+
+ if (use_count)
+ {
+ oa->write_character(to_char_type('#'));
+ write_number_with_ubjson_prefix(j.m_value.object->size(), true);
+ }
+
+ for (const auto& el : *j.m_value.object)
+ {
+ write_number_with_ubjson_prefix(el.first.size(), true);
+ oa->write_characters(
+ reinterpret_cast<const CharType*>(el.first.c_str()),
+ el.first.size());
+ write_ubjson(el.second, use_count, use_type, prefix_required);
+ }
+
+ if (not use_count)
+ {
+ oa->write_character(to_char_type('}'));
+ }
+
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ private:
+ //////////
+ // BSON //
+ //////////
+
+ /*!
+ @return The size of a BSON document entry header, including the id marker
+ and the entry name size (and its null-terminator).
+ */
+ static std::size_t calc_bson_entry_header_size(const string_t& name)
+ {
+ const auto it = name.find(static_cast<typename string_t::value_type>(0));
+ if (JSON_UNLIKELY(it != BasicJsonType::string_t::npos))
+ {
+ JSON_THROW(out_of_range::create(409,
+ "BSON key cannot contain code point U+0000 (at byte " + std::to_string(it) + ")"));
+ }
+
+ return /*id*/ 1ul + name.size() + /*zero-terminator*/1u;
+ }
+
+ /*!
+ @brief Writes the given @a element_type and @a name to the output adapter
+ */
+ void write_bson_entry_header(const string_t& name,
+ const std::uint8_t element_type)
+ {
+ oa->write_character(to_char_type(element_type)); // boolean
+ oa->write_characters(
+ reinterpret_cast<const CharType*>(name.c_str()),
+ name.size() + 1u);
+ }
+
+ /*!
+ @brief Writes a BSON element with key @a name and boolean value @a value
+ */
+ void write_bson_boolean(const string_t& name,
+ const bool value)
+ {
+ write_bson_entry_header(name, 0x08);
+ oa->write_character(value ? to_char_type(0x01) : to_char_type(0x00));
+ }
+
+ /*!
+ @brief Writes a BSON element with key @a name and double value @a value
+ */
+ void write_bson_double(const string_t& name,
+ const double value)
+ {
+ write_bson_entry_header(name, 0x01);
+ write_number<double, true>(value);
+ }
+
+ /*!
+ @return The size of the BSON-encoded string in @a value
+ */
+ static std::size_t calc_bson_string_size(const string_t& value)
+ {
+ return sizeof(std::int32_t) + value.size() + 1ul;
+ }
+
+ /*!
+ @brief Writes a BSON element with key @a name and string value @a value
+ */
+ void write_bson_string(const string_t& name,
+ const string_t& value)
+ {
+ write_bson_entry_header(name, 0x02);
+
+ write_number<std::int32_t, true>(static_cast<std::int32_t>(value.size() + 1ul));
+ oa->write_characters(
+ reinterpret_cast<const CharType*>(value.c_str()),
+ value.size() + 1);
+ }
+
+ /*!
+ @brief Writes a BSON element with key @a name and null value
+ */
+ void write_bson_null(const string_t& name)
+ {
+ write_bson_entry_header(name, 0x0A);
+ }
+
+ /*!
+ @return The size of the BSON-encoded integer @a value
+ */
+ static std::size_t calc_bson_integer_size(const std::int64_t value)
+ {
+ if ((std::numeric_limits<std::int32_t>::min)() <= value and value <= (std::numeric_limits<std::int32_t>::max)())
+ {
+ return sizeof(std::int32_t);
+ }
+ else
+ {
+ return sizeof(std::int64_t);
+ }
+ }
+
+ /*!
+ @brief Writes a BSON element with key @a name and integer @a value
+ */
+ void write_bson_integer(const string_t& name,
+ const std::int64_t value)
+ {
+ if ((std::numeric_limits<std::int32_t>::min)() <= value and value <= (std::numeric_limits<std::int32_t>::max)())
+ {
+ write_bson_entry_header(name, 0x10); // int32
+ write_number<std::int32_t, true>(static_cast<std::int32_t>(value));
+ }
+ else
+ {
+ write_bson_entry_header(name, 0x12); // int64
+ write_number<std::int64_t, true>(static_cast<std::int64_t>(value));
+ }
+ }
+
+ /*!
+ @return The size of the BSON-encoded unsigned integer in @a j
+ */
+ static constexpr std::size_t calc_bson_unsigned_size(const std::uint64_t value) noexcept
+ {
+ return (value <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))
+ ? sizeof(std::int32_t)
+ : sizeof(std::int64_t);
+ }
+
+ /*!
+ @brief Writes a BSON element with key @a name and unsigned @a value
+ */
+ void write_bson_unsigned(const string_t& name,
+ const std::uint64_t value)
+ {
+ if (value <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))
+ {
+ write_bson_entry_header(name, 0x10 /* int32 */);
+ write_number<std::int32_t, true>(static_cast<std::int32_t>(value));
+ }
+ else if (value <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
+ {
+ write_bson_entry_header(name, 0x12 /* int64 */);
+ write_number<std::int64_t, true>(static_cast<std::int64_t>(value));
+ }
+ else
+ {
+ JSON_THROW(out_of_range::create(407, "integer number " + std::to_string(value) + " cannot be represented by BSON as it does not fit int64"));
+ }
+ }
+
+ /*!
+ @brief Writes a BSON element with key @a name and object @a value
+ */
+ void write_bson_object_entry(const string_t& name,
+ const typename BasicJsonType::object_t& value)
+ {
+ write_bson_entry_header(name, 0x03); // object
+ write_bson_object(value);
+ }
+
+ /*!
+ @return The size of the BSON-encoded array @a value
+ */
+ static std::size_t calc_bson_array_size(const typename BasicJsonType::array_t& value)
+ {
+ std::size_t embedded_document_size = 0ul;
+ std::size_t array_index = 0ul;
+
+ for (const auto& el : value)
+ {
+ embedded_document_size += calc_bson_element_size(std::to_string(array_index++), el);
+ }
+
+ return sizeof(std::int32_t) + embedded_document_size + 1ul;
+ }
+
+ /*!
+ @brief Writes a BSON element with key @a name and array @a value
+ */
+ void write_bson_array(const string_t& name,
+ const typename BasicJsonType::array_t& value)
+ {
+ write_bson_entry_header(name, 0x04); // array
+ write_number<std::int32_t, true>(static_cast<std::int32_t>(calc_bson_array_size(value)));
+
+ std::size_t array_index = 0ul;
+
+ for (const auto& el : value)
+ {
+ write_bson_element(std::to_string(array_index++), el);
+ }
+
+ oa->write_character(to_char_type(0x00));
+ }
+
+ /*!
+ @brief Calculates the size necessary to serialize the JSON value @a j with its @a name
+ @return The calculated size for the BSON document entry for @a j with the given @a name.
+ */
+ static std::size_t calc_bson_element_size(const string_t& name,
+ const BasicJsonType& j)
+ {
+ const auto header_size = calc_bson_entry_header_size(name);
+ switch (j.type())
+ {
+ case value_t::object:
+ return header_size + calc_bson_object_size(*j.m_value.object);
+
+ case value_t::array:
+ return header_size + calc_bson_array_size(*j.m_value.array);
+
+ case value_t::boolean:
+ return header_size + 1ul;
+
+ case value_t::number_float:
+ return header_size + 8ul;
+
+ case value_t::number_integer:
+ return header_size + calc_bson_integer_size(j.m_value.number_integer);
+
+ case value_t::number_unsigned:
+ return header_size + calc_bson_unsigned_size(j.m_value.number_unsigned);
+
+ case value_t::string:
+ return header_size + calc_bson_string_size(*j.m_value.string);
+
+ case value_t::null:
+ return header_size + 0ul;
+
+ // LCOV_EXCL_START
+ default:
+ assert(false);
+ return 0ul;
+ // LCOV_EXCL_STOP
+ };
+ }
+
+ /*!
+ @brief Serializes the JSON value @a j to BSON and associates it with the
+ key @a name.
+ @param name The name to associate with the JSON entity @a j within the
+ current BSON document
+ @return The size of the BSON entry
+ */
+ void write_bson_element(const string_t& name,
+ const BasicJsonType& j)
+ {
+ switch (j.type())
+ {
+ case value_t::object:
+ return write_bson_object_entry(name, *j.m_value.object);
+
+ case value_t::array:
+ return write_bson_array(name, *j.m_value.array);
+
+ case value_t::boolean:
+ return write_bson_boolean(name, j.m_value.boolean);
+
+ case value_t::number_float:
+ return write_bson_double(name, j.m_value.number_float);
+
+ case value_t::number_integer:
+ return write_bson_integer(name, j.m_value.number_integer);
+
+ case value_t::number_unsigned:
+ return write_bson_unsigned(name, j.m_value.number_unsigned);
+
+ case value_t::string:
+ return write_bson_string(name, *j.m_value.string);
+
+ case value_t::null:
+ return write_bson_null(name);
+
+ // LCOV_EXCL_START
+ default:
+ assert(false);
+ return;
+ // LCOV_EXCL_STOP
+ };
+ }
+
+ /*!
+ @brief Calculates the size of the BSON serialization of the given
+ JSON-object @a j.
+ @param[in] j JSON value to serialize
+ @pre j.type() == value_t::object
+ */
+ static std::size_t calc_bson_object_size(const typename BasicJsonType::object_t& value)
+ {
+ std::size_t document_size = std::accumulate(value.begin(), value.end(), 0ul,
+ [](size_t result, const typename BasicJsonType::object_t::value_type & el)
+ {
+ return result += calc_bson_element_size(el.first, el.second);
+ });
+
+ return sizeof(std::int32_t) + document_size + 1ul;
+ }
+
+ /*!
+ @param[in] j JSON value to serialize
+ @pre j.type() == value_t::object
+ */
+ void write_bson_object(const typename BasicJsonType::object_t& value)
+ {
+ write_number<std::int32_t, true>(static_cast<std::int32_t>(calc_bson_object_size(value)));
+
+ for (const auto& el : value)
+ {
+ write_bson_element(el.first, el.second);
+ }
+
+ oa->write_character(to_char_type(0x00));
+ }
+
+ //////////
+ // CBOR //
+ //////////
+
+ static constexpr CharType get_cbor_float_prefix(float /*unused*/)
+ {
+ return to_char_type(0xFA); // Single-Precision Float
+ }
+
+ static constexpr CharType get_cbor_float_prefix(double /*unused*/)
+ {
+ return to_char_type(0xFB); // Double-Precision Float
+ }
+
+ /////////////
+ // MsgPack //
+ /////////////
+
+ static constexpr CharType get_msgpack_float_prefix(float /*unused*/)
+ {
+ return to_char_type(0xCA); // float 32
+ }
+
+ static constexpr CharType get_msgpack_float_prefix(double /*unused*/)
+ {
+ return to_char_type(0xCB); // float 64
+ }
+
+ ////////////
+ // UBJSON //
+ ////////////
+
+ // UBJSON: write number (floating point)
+ template<typename NumberType, typename std::enable_if<
+ std::is_floating_point<NumberType>::value, int>::type = 0>
+ void write_number_with_ubjson_prefix(const NumberType n,
+ const bool add_prefix)
+ {
+ if (add_prefix)
+ {
+ oa->write_character(get_ubjson_float_prefix(n));
+ }
+ write_number(n);
+ }
+
+ // UBJSON: write number (unsigned integer)
+ template<typename NumberType, typename std::enable_if<
+ std::is_unsigned<NumberType>::value, int>::type = 0>
+ void write_number_with_ubjson_prefix(const NumberType n,
+ const bool add_prefix)
+ {
+ if (n <= static_cast<uint64_t>((std::numeric_limits<int8_t>::max)()))
+ {
+ if (add_prefix)
+ {
+ oa->write_character(to_char_type('i')); // int8
+ }
+ write_number(static_cast<uint8_t>(n));
+ }
+ else if (n <= (std::numeric_limits<uint8_t>::max)())
+ {
+ if (add_prefix)
+ {
+ oa->write_character(to_char_type('U')); // uint8
+ }
+ write_number(static_cast<uint8_t>(n));
+ }
+ else if (n <= static_cast<uint64_t>((std::numeric_limits<int16_t>::max)()))
+ {
+ if (add_prefix)
+ {
+ oa->write_character(to_char_type('I')); // int16
+ }
+ write_number(static_cast<int16_t>(n));
+ }
+ else if (n <= static_cast<uint64_t>((std::numeric_limits<int32_t>::max)()))
+ {
+ if (add_prefix)
+ {
+ oa->write_character(to_char_type('l')); // int32
+ }
+ write_number(static_cast<int32_t>(n));
+ }
+ else if (n <= static_cast<uint64_t>((std::numeric_limits<int64_t>::max)()))
+ {
+ if (add_prefix)
+ {
+ oa->write_character(to_char_type('L')); // int64
+ }
+ write_number(static_cast<int64_t>(n));
+ }
+ else
+ {
+ JSON_THROW(out_of_range::create(407, "integer number " + std::to_string(n) + " cannot be represented by UBJSON as it does not fit int64"));
+ }
+ }
+
+ // UBJSON: write number (signed integer)
+ template<typename NumberType, typename std::enable_if<
+ std::is_signed<NumberType>::value and
+ not std::is_floating_point<NumberType>::value, int>::type = 0>
+ void write_number_with_ubjson_prefix(const NumberType n,
+ const bool add_prefix)
+ {
+ if ((std::numeric_limits<int8_t>::min)() <= n and n <= (std::numeric_limits<int8_t>::max)())
+ {
+ if (add_prefix)
+ {
+ oa->write_character(to_char_type('i')); // int8
+ }
+ write_number(static_cast<int8_t>(n));
+ }
+ else if (static_cast<int64_t>((std::numeric_limits<uint8_t>::min)()) <= n and n <= static_cast<int64_t>((std::numeric_limits<uint8_t>::max)()))
+ {
+ if (add_prefix)
+ {
+ oa->write_character(to_char_type('U')); // uint8
+ }
+ write_number(static_cast<uint8_t>(n));
+ }
+ else if ((std::numeric_limits<int16_t>::min)() <= n and n <= (std::numeric_limits<int16_t>::max)())
+ {
+ if (add_prefix)
+ {
+ oa->write_character(to_char_type('I')); // int16
+ }
+ write_number(static_cast<int16_t>(n));
+ }
+ else if ((std::numeric_limits<int32_t>::min)() <= n and n <= (std::numeric_limits<int32_t>::max)())
+ {
+ if (add_prefix)
+ {
+ oa->write_character(to_char_type('l')); // int32
+ }
+ write_number(static_cast<int32_t>(n));
+ }
+ else if ((std::numeric_limits<int64_t>::min)() <= n and n <= (std::numeric_limits<int64_t>::max)())
+ {
+ if (add_prefix)
+ {
+ oa->write_character(to_char_type('L')); // int64
+ }
+ write_number(static_cast<int64_t>(n));
+ }
+ // LCOV_EXCL_START
+ else
+ {
+ JSON_THROW(out_of_range::create(407, "integer number " + std::to_string(n) + " cannot be represented by UBJSON as it does not fit int64"));
+ }
+ // LCOV_EXCL_STOP
+ }
+
+ /*!
+ @brief determine the type prefix of container values
+
+ @note This function does not need to be 100% accurate when it comes to
+ integer limits. In case a number exceeds the limits of int64_t,
+ this will be detected by a later call to function
+ write_number_with_ubjson_prefix. Therefore, we return 'L' for any
+ value that does not fit the previous limits.
+ */
+ CharType ubjson_prefix(const BasicJsonType& j) const noexcept
+ {
+ switch (j.type())
+ {
+ case value_t::null:
+ return 'Z';
+
+ case value_t::boolean:
+ return j.m_value.boolean ? 'T' : 'F';
+
+ case value_t::number_integer:
+ {
+ if ((std::numeric_limits<int8_t>::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits<int8_t>::max)())
+ {
+ return 'i';
+ }
+ if ((std::numeric_limits<uint8_t>::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits<uint8_t>::max)())
+ {
+ return 'U';
+ }
+ if ((std::numeric_limits<int16_t>::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits<int16_t>::max)())
+ {
+ return 'I';
+ }
+ if ((std::numeric_limits<int32_t>::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits<int32_t>::max)())
+ {
+ return 'l';
+ }
+ // no check and assume int64_t (see note above)
+ return 'L';
+ }
+
+ case value_t::number_unsigned:
+ {
+ if (j.m_value.number_unsigned <= (std::numeric_limits<int8_t>::max)())
+ {
+ return 'i';
+ }
+ if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)())
+ {
+ return 'U';
+ }
+ if (j.m_value.number_unsigned <= (std::numeric_limits<int16_t>::max)())
+ {
+ return 'I';
+ }
+ if (j.m_value.number_unsigned <= (std::numeric_limits<int32_t>::max)())
+ {
+ return 'l';
+ }
+ // no check and assume int64_t (see note above)
+ return 'L';
+ }
+
+ case value_t::number_float:
+ return get_ubjson_float_prefix(j.m_value.number_float);
+
+ case value_t::string:
+ return 'S';
+
+ case value_t::array:
+ return '[';
+
+ case value_t::object:
+ return '{';
+
+ default: // discarded values
+ return 'N';
+ }
+ }
+
+ static constexpr CharType get_ubjson_float_prefix(float /*unused*/)
+ {
+ return 'd'; // float 32
+ }
+
+ static constexpr CharType get_ubjson_float_prefix(double /*unused*/)
+ {
+ return 'D'; // float 64
+ }
+
+ ///////////////////////
+ // Utility functions //
+ ///////////////////////
+
+ /*
+ @brief write a number to output input
+ @param[in] n number of type @a NumberType
+ @tparam NumberType the type of the number
+ @tparam OutputIsLittleEndian Set to true if output data is
+ required to be little endian
+
+ @note This function needs to respect the system's endianess, because bytes
+ in CBOR, MessagePack, and UBJSON are stored in network order (big
+ endian) and therefore need reordering on little endian systems.
+ */
+ template<typename NumberType, bool OutputIsLittleEndian = false>
+ void write_number(const NumberType n)
+ {
+ // step 1: write number to array of length NumberType
+ std::array<CharType, sizeof(NumberType)> vec;
+ std::memcpy(vec.data(), &n, sizeof(NumberType));
+
+ // step 2: write array to output (with possible reordering)
+ if (is_little_endian and not OutputIsLittleEndian)
+ {
+ // reverse byte order prior to conversion if necessary
+ std::reverse(vec.begin(), vec.end());
+ }
+
+ oa->write_characters(vec.data(), sizeof(NumberType));
+ }
+
+ public:
+ // The following to_char_type functions are implement the conversion
+ // between uint8_t and CharType. In case CharType is not unsigned,
+ // such a conversion is required to allow values greater than 128.
+ // See <https://github.com/nlohmann/json/issues/1286> for a discussion.
+ template < typename C = CharType,
+ enable_if_t < std::is_signed<C>::value and std::is_signed<char>::value > * = nullptr >
+ static constexpr CharType to_char_type(std::uint8_t x) noexcept
+ {
+ return *reinterpret_cast<char*>(&x);
+ }
+
+ template < typename C = CharType,
+ enable_if_t < std::is_signed<C>::value and std::is_unsigned<char>::value > * = nullptr >
+ static CharType to_char_type(std::uint8_t x) noexcept
+ {
+ static_assert(sizeof(std::uint8_t) == sizeof(CharType), "size of CharType must be equal to std::uint8_t");
+ static_assert(std::is_pod<CharType>::value, "CharType must be POD");
+ CharType result;
+ std::memcpy(&result, &x, sizeof(x));
+ return result;
+ }
+
+ template<typename C = CharType,
+ enable_if_t<std::is_unsigned<C>::value>* = nullptr>
+ static constexpr CharType to_char_type(std::uint8_t x) noexcept
+ {
+ return x;
+ }
+
+ template < typename InputCharType, typename C = CharType,
+ enable_if_t <
+ std::is_signed<C>::value and
+ std::is_signed<char>::value and
+ std::is_same<char, typename std::remove_cv<InputCharType>::type>::value
+ > * = nullptr >
+ static constexpr CharType to_char_type(InputCharType x) noexcept
+ {
+ return x;
+ }
+
+ private:
+ /// whether we can assume little endianess
+ const bool is_little_endian = binary_reader<BasicJsonType>::little_endianess();
+
+ /// the output
+ output_adapter_t<CharType> oa = nullptr;
+};
+} // namespace detail
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/output/output_adapters.hpp b/include/lib/modernjson/detail/output/output_adapters.hpp
new file mode 100644
index 0000000..699757d
--- /dev/null
+++ b/include/lib/modernjson/detail/output/output_adapters.hpp
@@ -0,0 +1,119 @@
+#pragma once
+
+#include <algorithm> // copy
+#include <cstddef> // size_t
+#include <ios> // streamsize
+#include <iterator> // back_inserter
+#include <memory> // shared_ptr, make_shared
+#include <ostream> // basic_ostream
+#include <string> // basic_string
+#include <vector> // vector
+
+namespace nlohmann
+{
+namespace detail
+{
+/// abstract output adapter interface
+template<typename CharType> struct output_adapter_protocol
+{
+ virtual void write_character(CharType c) = 0;
+ virtual void write_characters(const CharType* s, std::size_t length) = 0;
+ virtual ~output_adapter_protocol() = default;
+};
+
+/// a type to simplify interfaces
+template<typename CharType>
+using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
+
+/// output adapter for byte vectors
+template<typename CharType>
+class output_vector_adapter : public output_adapter_protocol<CharType>
+{
+ public:
+ explicit output_vector_adapter(std::vector<CharType>& vec) noexcept
+ : v(vec)
+ {}
+
+ void write_character(CharType c) override
+ {
+ v.push_back(c);
+ }
+
+ void write_characters(const CharType* s, std::size_t length) override
+ {
+ std::copy(s, s + length, std::back_inserter(v));
+ }
+
+ private:
+ std::vector<CharType>& v;
+};
+
+/// output adapter for output streams
+template<typename CharType>
+class output_stream_adapter : public output_adapter_protocol<CharType>
+{
+ public:
+ explicit output_stream_adapter(std::basic_ostream<CharType>& s) noexcept
+ : stream(s)
+ {}
+
+ void write_character(CharType c) override
+ {
+ stream.put(c);
+ }
+
+ void write_characters(const CharType* s, std::size_t length) override
+ {
+ stream.write(s, static_cast<std::streamsize>(length));
+ }
+
+ private:
+ std::basic_ostream<CharType>& stream;
+};
+
+/// output adapter for basic_string
+template<typename CharType, typename StringType = std::basic_string<CharType>>
+class output_string_adapter : public output_adapter_protocol<CharType>
+{
+ public:
+ explicit output_string_adapter(StringType& s) noexcept
+ : str(s)
+ {}
+
+ void write_character(CharType c) override
+ {
+ str.push_back(c);
+ }
+
+ void write_characters(const CharType* s, std::size_t length) override
+ {
+ str.append(s, length);
+ }
+
+ private:
+ StringType& str;
+};
+
+template<typename CharType, typename StringType = std::basic_string<CharType>>
+class output_adapter
+{
+ public:
+ output_adapter(std::vector<CharType>& vec)
+ : oa(std::make_shared<output_vector_adapter<CharType>>(vec)) {}
+
+ output_adapter(std::basic_ostream<CharType>& s)
+ : oa(std::make_shared<output_stream_adapter<CharType>>(s)) {}
+
+ output_adapter(StringType& s)
+ : oa(std::make_shared<output_string_adapter<CharType, StringType>>(s)) {}
+
+ operator output_adapter_t<CharType>()
+ {
+ return oa;
+ }
+
+ private:
+ output_adapter_t<CharType> oa = nullptr;
+};
+} // namespace detail
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/output/serializer.hpp b/include/lib/modernjson/detail/output/serializer.hpp
new file mode 100644
index 0000000..a34a452
--- /dev/null
+++ b/include/lib/modernjson/detail/output/serializer.hpp
@@ -0,0 +1,741 @@
+#pragma once
+
+#include <algorithm> // reverse, remove, fill, find, none_of
+#include <array> // array
+#include <cassert> // assert
+#include <ciso646> // and, or
+#include <clocale> // localeconv, lconv
+#include <cmath> // labs, isfinite, isnan, signbit
+#include <cstddef> // size_t, ptrdiff_t
+#include <cstdint> // uint8_t
+#include <cstdio> // snprintf
+#include <limits> // numeric_limits
+#include <string> // string
+#include <type_traits> // is_same
+
+#include <lib/modernjson/detail/exceptions.hpp>
+#include <lib/modernjson/detail/conversions/to_chars.hpp>
+#include <lib/modernjson/detail/macro_scope.hpp>
+#include <lib/modernjson/detail/meta/cpp_future.hpp>
+#include <lib/modernjson/detail/output/binary_writer.hpp>
+#include <lib/modernjson/detail/output/output_adapters.hpp>
+#include <lib/modernjson/detail/value_t.hpp>
+
+namespace nlohmann
+{
+namespace detail
+{
+///////////////////
+// serialization //
+///////////////////
+
+/// how to treat decoding errors
+enum class error_handler_t
+{
+ strict, ///< throw a type_error exception in case of invalid UTF-8
+ replace, ///< replace invalid UTF-8 sequences with U+FFFD
+ ignore ///< ignore invalid UTF-8 sequences
+};
+
+template<typename BasicJsonType>
+class serializer
+{
+ using string_t = typename BasicJsonType::string_t;
+ using number_float_t = typename BasicJsonType::number_float_t;
+ using number_integer_t = typename BasicJsonType::number_integer_t;
+ using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+ static constexpr uint8_t UTF8_ACCEPT = 0;
+ static constexpr uint8_t UTF8_REJECT = 1;
+
+ public:
+ /*!
+ @param[in] s output stream to serialize to
+ @param[in] ichar indentation character to use
+ @param[in] error_handler_ how to react on decoding errors
+ */
+ serializer(output_adapter_t<char> s, const char ichar,
+ error_handler_t error_handler_ = error_handler_t::strict)
+ : o(std::move(s))
+ , loc(std::localeconv())
+ , thousands_sep(loc->thousands_sep == nullptr ? '\0' : * (loc->thousands_sep))
+ , decimal_point(loc->decimal_point == nullptr ? '\0' : * (loc->decimal_point))
+ , indent_char(ichar)
+ , indent_string(512, indent_char)
+ , error_handler(error_handler_)
+ {}
+
+ // delete because of pointer members
+ serializer(const serializer&) = delete;
+ serializer& operator=(const serializer&) = delete;
+ serializer(serializer&&) = delete;
+ serializer& operator=(serializer&&) = delete;
+ ~serializer() = default;
+
+ /*!
+ @brief internal implementation of the serialization function
+
+ This function is called by the public member function dump and organizes
+ the serialization internally. The indentation level is propagated as
+ additional parameter. In case of arrays and objects, the function is
+ called recursively.
+
+ - strings and object keys are escaped using `escape_string()`
+ - integer numbers are converted implicitly via `operator<<`
+ - floating-point numbers are converted to a string using `"%g"` format
+
+ @param[in] val value to serialize
+ @param[in] pretty_print whether the output shall be pretty-printed
+ @param[in] indent_step the indent level
+ @param[in] current_indent the current indent level (only used internally)
+ */
+ void dump(const BasicJsonType& val, const bool pretty_print,
+ const bool ensure_ascii,
+ const unsigned int indent_step,
+ const unsigned int current_indent = 0)
+ {
+ switch (val.m_type)
+ {
+ case value_t::object:
+ {
+ if (val.m_value.object->empty())
+ {
+ o->write_characters("{}", 2);
+ return;
+ }
+
+ if (pretty_print)
+ {
+ o->write_characters("{\n", 2);
+
+ // variable to hold indentation for recursive calls
+ const auto new_indent = current_indent + indent_step;
+ if (JSON_UNLIKELY(indent_string.size() < new_indent))
+ {
+ indent_string.resize(indent_string.size() * 2, ' ');
+ }
+
+ // first n-1 elements
+ auto i = val.m_value.object->cbegin();
+ for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i)
+ {
+ o->write_characters(indent_string.c_str(), new_indent);
+ o->write_character('\"');
+ dump_escaped(i->first, ensure_ascii);
+ o->write_characters("\": ", 3);
+ dump(i->second, true, ensure_ascii, indent_step, new_indent);
+ o->write_characters(",\n", 2);
+ }
+
+ // last element
+ assert(i != val.m_value.object->cend());
+ assert(std::next(i) == val.m_value.object->cend());
+ o->write_characters(indent_string.c_str(), new_indent);
+ o->write_character('\"');
+ dump_escaped(i->first, ensure_ascii);
+ o->write_characters("\": ", 3);
+ dump(i->second, true, ensure_ascii, indent_step, new_indent);
+
+ o->write_character('\n');
+ o->write_characters(indent_string.c_str(), current_indent);
+ o->write_character('}');
+ }
+ else
+ {
+ o->write_character('{');
+
+ // first n-1 elements
+ auto i = val.m_value.object->cbegin();
+ for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i)
+ {
+ o->write_character('\"');
+ dump_escaped(i->first, ensure_ascii);
+ o->write_characters("\":", 2);
+ dump(i->second, false, ensure_ascii, indent_step, current_indent);
+ o->write_character(',');
+ }
+
+ // last element
+ assert(i != val.m_value.object->cend());
+ assert(std::next(i) == val.m_value.object->cend());
+ o->write_character('\"');
+ dump_escaped(i->first, ensure_ascii);
+ o->write_characters("\":", 2);
+ dump(i->second, false, ensure_ascii, indent_step, current_indent);
+
+ o->write_character('}');
+ }
+
+ return;
+ }
+
+ case value_t::array:
+ {
+ if (val.m_value.array->empty())
+ {
+ o->write_characters("[]", 2);
+ return;
+ }
+
+ if (pretty_print)
+ {
+ o->write_characters("[\n", 2);
+
+ // variable to hold indentation for recursive calls
+ const auto new_indent = current_indent + indent_step;
+ if (JSON_UNLIKELY(indent_string.size() < new_indent))
+ {
+ indent_string.resize(indent_string.size() * 2, ' ');
+ }
+
+ // first n-1 elements
+ for (auto i = val.m_value.array->cbegin();
+ i != val.m_value.array->cend() - 1; ++i)
+ {
+ o->write_characters(indent_string.c_str(), new_indent);
+ dump(*i, true, ensure_ascii, indent_step, new_indent);
+ o->write_characters(",\n", 2);
+ }
+
+ // last element
+ assert(not val.m_value.array->empty());
+ o->write_characters(indent_string.c_str(), new_indent);
+ dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent);
+
+ o->write_character('\n');
+ o->write_characters(indent_string.c_str(), current_indent);
+ o->write_character(']');
+ }
+ else
+ {
+ o->write_character('[');
+
+ // first n-1 elements
+ for (auto i = val.m_value.array->cbegin();
+ i != val.m_value.array->cend() - 1; ++i)
+ {
+ dump(*i, false, ensure_ascii, indent_step, current_indent);
+ o->write_character(',');
+ }
+
+ // last element
+ assert(not val.m_value.array->empty());
+ dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent);
+
+ o->write_character(']');
+ }
+
+ return;
+ }
+
+ case value_t::string:
+ {
+ o->write_character('\"');
+ dump_escaped(*val.m_value.string, ensure_ascii);
+ o->write_character('\"');
+ return;
+ }
+
+ case value_t::boolean:
+ {
+ if (val.m_value.boolean)
+ {
+ o->write_characters("true", 4);
+ }
+ else
+ {
+ o->write_characters("false", 5);
+ }
+ return;
+ }
+
+ case value_t::number_integer:
+ {
+ dump_integer(val.m_value.number_integer);
+ return;
+ }
+
+ case value_t::number_unsigned:
+ {
+ dump_integer(val.m_value.number_unsigned);
+ return;
+ }
+
+ case value_t::number_float:
+ {
+ dump_float(val.m_value.number_float);
+ return;
+ }
+
+ case value_t::discarded:
+ {
+ o->write_characters("<discarded>", 11);
+ return;
+ }
+
+ case value_t::null:
+ {
+ o->write_characters("null", 4);
+ return;
+ }
+ }
+ }
+
+ private:
+ /*!
+ @brief dump escaped string
+
+ Escape a string by replacing certain special characters by a sequence of an
+ escape character (backslash) and another character and other control
+ characters by a sequence of "\u" followed by a four-digit hex
+ representation. The escaped string is written to output stream @a o.
+
+ @param[in] s the string to escape
+ @param[in] ensure_ascii whether to escape non-ASCII characters with
+ \uXXXX sequences
+
+ @complexity Linear in the length of string @a s.
+ */
+ void dump_escaped(const string_t& s, const bool ensure_ascii)
+ {
+ uint32_t codepoint;
+ uint8_t state = UTF8_ACCEPT;
+ std::size_t bytes = 0; // number of bytes written to string_buffer
+
+ // number of bytes written at the point of the last valid byte
+ std::size_t bytes_after_last_accept = 0;
+ std::size_t undumped_chars = 0;
+
+ for (std::size_t i = 0; i < s.size(); ++i)
+ {
+ const auto byte = static_cast<uint8_t>(s[i]);
+
+ switch (decode(state, codepoint, byte))
+ {
+ case UTF8_ACCEPT: // decode found a new code point
+ {
+ switch (codepoint)
+ {
+ case 0x08: // backspace
+ {
+ string_buffer[bytes++] = '\\';
+ string_buffer[bytes++] = 'b';
+ break;
+ }
+
+ case 0x09: // horizontal tab
+ {
+ string_buffer[bytes++] = '\\';
+ string_buffer[bytes++] = 't';
+ break;
+ }
+
+ case 0x0A: // newline
+ {
+ string_buffer[bytes++] = '\\';
+ string_buffer[bytes++] = 'n';
+ break;
+ }
+
+ case 0x0C: // formfeed
+ {
+ string_buffer[bytes++] = '\\';
+ string_buffer[bytes++] = 'f';
+ break;
+ }
+
+ case 0x0D: // carriage return
+ {
+ string_buffer[bytes++] = '\\';
+ string_buffer[bytes++] = 'r';
+ break;
+ }
+
+ case 0x22: // quotation mark
+ {
+ string_buffer[bytes++] = '\\';
+ string_buffer[bytes++] = '\"';
+ break;
+ }
+
+ case 0x5C: // reverse solidus
+ {
+ string_buffer[bytes++] = '\\';
+ string_buffer[bytes++] = '\\';
+ break;
+ }
+
+ default:
+ {
+ // escape control characters (0x00..0x1F) or, if
+ // ensure_ascii parameter is used, non-ASCII characters
+ if ((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F)))
+ {
+ if (codepoint <= 0xFFFF)
+ {
+ (std::snprintf)(string_buffer.data() + bytes, 7, "\\u%04x",
+ static_cast<uint16_t>(codepoint));
+ bytes += 6;
+ }
+ else
+ {
+ (std::snprintf)(string_buffer.data() + bytes, 13, "\\u%04x\\u%04x",
+ static_cast<uint16_t>(0xD7C0 + (codepoint >> 10)),
+ static_cast<uint16_t>(0xDC00 + (codepoint & 0x3FF)));
+ bytes += 12;
+ }
+ }
+ else
+ {
+ // copy byte to buffer (all previous bytes
+ // been copied have in default case above)
+ string_buffer[bytes++] = s[i];
+ }
+ break;
+ }
+ }
+
+ // write buffer and reset index; there must be 13 bytes
+ // left, as this is the maximal number of bytes to be
+ // written ("\uxxxx\uxxxx\0") for one code point
+ if (string_buffer.size() - bytes < 13)
+ {
+ o->write_characters(string_buffer.data(), bytes);
+ bytes = 0;
+ }
+
+ // remember the byte position of this accept
+ bytes_after_last_accept = bytes;
+ undumped_chars = 0;
+ break;
+ }
+
+ case UTF8_REJECT: // decode found invalid UTF-8 byte
+ {
+ switch (error_handler)
+ {
+ case error_handler_t::strict:
+ {
+ std::string sn(3, '\0');
+ (std::snprintf)(&sn[0], sn.size(), "%.2X", byte);
+ JSON_THROW(type_error::create(316, "invalid UTF-8 byte at index " + std::to_string(i) + ": 0x" + sn));
+ }
+
+ case error_handler_t::ignore:
+ case error_handler_t::replace:
+ {
+ // in case we saw this character the first time, we
+ // would like to read it again, because the byte
+ // may be OK for itself, but just not OK for the
+ // previous sequence
+ if (undumped_chars > 0)
+ {
+ --i;
+ }
+
+ // reset length buffer to the last accepted index;
+ // thus removing/ignoring the invalid characters
+ bytes = bytes_after_last_accept;
+
+ if (error_handler == error_handler_t::replace)
+ {
+ // add a replacement character
+ if (ensure_ascii)
+ {
+ string_buffer[bytes++] = '\\';
+ string_buffer[bytes++] = 'u';
+ string_buffer[bytes++] = 'f';
+ string_buffer[bytes++] = 'f';
+ string_buffer[bytes++] = 'f';
+ string_buffer[bytes++] = 'd';
+ }
+ else
+ {
+ string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\xEF');
+ string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\xBF');
+ string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\xBD');
+ }
+ bytes_after_last_accept = bytes;
+ }
+
+ undumped_chars = 0;
+
+ // continue processing the string
+ state = UTF8_ACCEPT;
+ break;
+ }
+ }
+ break;
+ }
+
+ default: // decode found yet incomplete multi-byte code point
+ {
+ if (not ensure_ascii)
+ {
+ // code point will not be escaped - copy byte to buffer
+ string_buffer[bytes++] = s[i];
+ }
+ ++undumped_chars;
+ break;
+ }
+ }
+ }
+
+ // we finished processing the string
+ if (JSON_LIKELY(state == UTF8_ACCEPT))
+ {
+ // write buffer
+ if (bytes > 0)
+ {
+ o->write_characters(string_buffer.data(), bytes);
+ }
+ }
+ else
+ {
+ // we finish reading, but do not accept: string was incomplete
+ switch (error_handler)
+ {
+ case error_handler_t::strict:
+ {
+ std::string sn(3, '\0');
+ (std::snprintf)(&sn[0], sn.size(), "%.2X", static_cast<uint8_t>(s.back()));
+ JSON_THROW(type_error::create(316, "incomplete UTF-8 string; last byte: 0x" + sn));
+ }
+
+ case error_handler_t::ignore:
+ {
+ // write all accepted bytes
+ o->write_characters(string_buffer.data(), bytes_after_last_accept);
+ break;
+ }
+
+ case error_handler_t::replace:
+ {
+ // write all accepted bytes
+ o->write_characters(string_buffer.data(), bytes_after_last_accept);
+ // add a replacement character
+ if (ensure_ascii)
+ {
+ o->write_characters("\\ufffd", 6);
+ }
+ else
+ {
+ o->write_characters("\xEF\xBF\xBD", 3);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ /*!
+ @brief dump an integer
+
+ Dump a given integer to output stream @a o. Works internally with
+ @a number_buffer.
+
+ @param[in] x integer number (signed or unsigned) to dump
+ @tparam NumberType either @a number_integer_t or @a number_unsigned_t
+ */
+ template<typename NumberType, detail::enable_if_t<
+ std::is_same<NumberType, number_unsigned_t>::value or
+ std::is_same<NumberType, number_integer_t>::value,
+ int> = 0>
+ void dump_integer(NumberType x)
+ {
+ // special case for "0"
+ if (x == 0)
+ {
+ o->write_character('0');
+ return;
+ }
+
+ const bool is_negative = std::is_same<NumberType, number_integer_t>::value and not (x >= 0); // see issue #755
+ std::size_t i = 0;
+
+ while (x != 0)
+ {
+ // spare 1 byte for '\0'
+ assert(i < number_buffer.size() - 1);
+
+ const auto digit = std::labs(static_cast<long>(x % 10));
+ number_buffer[i++] = static_cast<char>('0' + digit);
+ x /= 10;
+ }
+
+ if (is_negative)
+ {
+ // make sure there is capacity for the '-'
+ assert(i < number_buffer.size() - 2);
+ number_buffer[i++] = '-';
+ }
+
+ std::reverse(number_buffer.begin(), number_buffer.begin() + i);
+ o->write_characters(number_buffer.data(), i);
+ }
+
+ /*!
+ @brief dump a floating-point number
+
+ Dump a given floating-point number to output stream @a o. Works internally
+ with @a number_buffer.
+
+ @param[in] x floating-point number to dump
+ */
+ void dump_float(number_float_t x)
+ {
+ // NaN / inf
+ if (not std::isfinite(x))
+ {
+ o->write_characters("null", 4);
+ return;
+ }
+
+ // If number_float_t is an IEEE-754 single or double precision number,
+ // use the Grisu2 algorithm to produce short numbers which are
+ // guaranteed to round-trip, using strtof and strtod, resp.
+ //
+ // NB: The test below works if <long double> == <double>.
+ static constexpr bool is_ieee_single_or_double
+ = (std::numeric_limits<number_float_t>::is_iec559 and std::numeric_limits<number_float_t>::digits == 24 and std::numeric_limits<number_float_t>::max_exponent == 128) or
+ (std::numeric_limits<number_float_t>::is_iec559 and std::numeric_limits<number_float_t>::digits == 53 and std::numeric_limits<number_float_t>::max_exponent == 1024);
+
+ dump_float(x, std::integral_constant<bool, is_ieee_single_or_double>());
+ }
+
+ void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)
+ {
+ char* begin = number_buffer.data();
+ char* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x);
+
+ o->write_characters(begin, static_cast<size_t>(end - begin));
+ }
+
+ void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)
+ {
+ // get number of digits for a float -> text -> float round-trip
+ static constexpr auto d = std::numeric_limits<number_float_t>::max_digits10;
+
+ // the actual conversion
+ std::ptrdiff_t len = (std::snprintf)(number_buffer.data(), number_buffer.size(), "%.*g", d, x);
+
+ // negative value indicates an error
+ assert(len > 0);
+ // check if buffer was large enough
+ assert(static_cast<std::size_t>(len) < number_buffer.size());
+
+ // erase thousands separator
+ if (thousands_sep != '\0')
+ {
+ const auto end = std::remove(number_buffer.begin(),
+ number_buffer.begin() + len, thousands_sep);
+ std::fill(end, number_buffer.end(), '\0');
+ assert((end - number_buffer.begin()) <= len);
+ len = (end - number_buffer.begin());
+ }
+
+ // convert decimal point to '.'
+ if (decimal_point != '\0' and decimal_point != '.')
+ {
+ const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point);
+ if (dec_pos != number_buffer.end())
+ {
+ *dec_pos = '.';
+ }
+ }
+
+ o->write_characters(number_buffer.data(), static_cast<std::size_t>(len));
+
+ // determine if need to append ".0"
+ const bool value_is_int_like =
+ std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1,
+ [](char c)
+ {
+ return (c == '.' or c == 'e');
+ });
+
+ if (value_is_int_like)
+ {
+ o->write_characters(".0", 2);
+ }
+ }
+
+ /*!
+ @brief check whether a string is UTF-8 encoded
+
+ The function checks each byte of a string whether it is UTF-8 encoded. The
+ result of the check is stored in the @a state parameter. The function must
+ be called initially with state 0 (accept). State 1 means the string must
+ be rejected, because the current byte is not allowed. If the string is
+ completely processed, but the state is non-zero, the string ended
+ prematurely; that is, the last byte indicated more bytes should have
+ followed.
+
+ @param[in,out] state the state of the decoding
+ @param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT)
+ @param[in] byte next byte to decode
+ @return new state
+
+ @note The function has been edited: a std::array is used.
+
+ @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
+ @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
+ */
+ static uint8_t decode(uint8_t& state, uint32_t& codep, const uint8_t byte) noexcept
+ {
+ static const std::array<uint8_t, 400> utf8d =
+ {
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF
+ 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF
+ 0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF
+ 0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF
+ 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
+ 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
+ 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
+ 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8
+ }
+ };
+
+ const uint8_t type = utf8d[byte];
+
+ codep = (state != UTF8_ACCEPT)
+ ? (byte & 0x3fu) | (codep << 6)
+ : static_cast<uint32_t>(0xff >> type) & (byte);
+
+ state = utf8d[256u + state * 16u + type];
+ return state;
+ }
+
+ private:
+ /// the output of the serializer
+ output_adapter_t<char> o = nullptr;
+
+ /// a (hopefully) large enough character buffer
+ std::array<char, 64> number_buffer{{}};
+
+ /// the locale
+ const std::lconv* loc = nullptr;
+ /// the locale's thousand separator character
+ const char thousands_sep = '\0';
+ /// the locale's decimal point character
+ const char decimal_point = '\0';
+
+ /// string buffer
+ std::array<char, 512> string_buffer{{}};
+
+ /// the indentation character
+ const char indent_char;
+ /// the indentation string
+ string_t indent_string;
+
+ /// error_handler how to react on decoding errors
+ const error_handler_t error_handler;
+};
+} // namespace detail
+} // namespace nlohmann
diff --git a/include/lib/modernjson/detail/value_t.hpp b/include/lib/modernjson/detail/value_t.hpp
new file mode 100644
index 0000000..10d555b
--- /dev/null
+++ b/include/lib/modernjson/detail/value_t.hpp
@@ -0,0 +1,76 @@
+#pragma once
+
+#include <array> // array
+#include <ciso646> // and
+#include <cstddef> // size_t
+#include <cstdint> // uint8_t
+
+namespace nlohmann
+{
+namespace detail
+{
+///////////////////////////
+// JSON type enumeration //
+///////////////////////////
+
+/*!
+@brief the JSON type enumeration
+
+This enumeration collects the different JSON types. It is internally used to
+distinguish the stored values, and the functions @ref basic_json::is_null(),
+@ref basic_json::is_object(), @ref basic_json::is_array(),
+@ref basic_json::is_string(), @ref basic_json::is_boolean(),
+@ref basic_json::is_number() (with @ref basic_json::is_number_integer(),
+@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()),
+@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and
+@ref basic_json::is_structured() rely on it.
+
+@note There are three enumeration entries (number_integer, number_unsigned, and
+number_float), because the library distinguishes these three types for numbers:
+@ref basic_json::number_unsigned_t is used for unsigned integers,
+@ref basic_json::number_integer_t is used for signed integers, and
+@ref basic_json::number_float_t is used for floating-point numbers or to
+approximate integers which do not fit in the limits of their respective type.
+
+@sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON
+value with the default value for a given type
+
+@since version 1.0.0
+*/
+enum class value_t : std::uint8_t
+{
+ null, ///< null value
+ object, ///< object (unordered set of name/value pairs)
+ array, ///< array (ordered collection of values)
+ string, ///< string value
+ boolean, ///< boolean value
+ number_integer, ///< number value (signed integer)
+ number_unsigned, ///< number value (unsigned integer)
+ number_float, ///< number value (floating-point)
+ discarded ///< discarded by the the parser callback function
+};
+
+/*!
+@brief comparison operator for JSON types
+
+Returns an ordering that is similar to Python:
+- order: null < boolean < number < object < array < string
+- furthermore, each type is not smaller than itself
+- discarded values are not comparable
+
+@since version 1.0.0
+*/
+inline bool operator<(const value_t lhs, const value_t rhs) noexcept
+{
+ static constexpr std::array<std::uint8_t, 8> order = {{
+ 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */,
+ 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */
+ }
+ };
+
+ const auto l_index = static_cast<std::size_t>(lhs);
+ const auto r_index = static_cast<std::size_t>(rhs);
+ return l_index < order.size() and r_index < order.size() and order[l_index] < order[r_index];
+}
+} // namespace detail
+} // namespace nlohmann
diff --git a/include/lib/modernjson/json.h b/include/lib/modernjson/json.h
new file mode 100644
index 0000000..4c50e9b
--- /dev/null
+++ b/include/lib/modernjson/json.h
@@ -0,0 +1,7992 @@
+/*
+ __ _____ _____ _____
+ __| | __| | | | JSON for Modern C++
+| | |__ | | | | | | version 3.4.0
+|_____|_____|_____|_|___| https://github.com/nlohmann/json
+
+Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+SPDX-License-Identifier: MIT
+Copyright (c) 2013-2018 Niels Lohmann <http://nlohmann.me>.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#ifndef NLOHMANN_JSON_HPP
+#define NLOHMANN_JSON_HPP
+
+#define NLOHMANN_JSON_VERSION_MAJOR 3
+#define NLOHMANN_JSON_VERSION_MINOR 4
+#define NLOHMANN_JSON_VERSION_PATCH 0
+
+#include <algorithm> // all_of, find, for_each
+#include <cassert> // assert
+#include <ciso646> // and, not, or
+#include <cstddef> // nullptr_t, ptrdiff_t, size_t
+#include <functional> // hash, less
+#include <initializer_list> // initializer_list
+#include <iosfwd> // istream, ostream
+#include <iterator> // iterator_traits, random_access_iterator_tag
+#include <numeric> // accumulate
+#include <string> // string, stoi, to_string
+#include <utility> // declval, forward, move, pair, swap
+
+#include <lib/modernjson/json_fwd.h>
+#include <lib/modernjson/detail/macro_scope.hpp>
+#include <lib/modernjson/detail/meta/cpp_future.hpp>
+#include <lib/modernjson/detail/meta/type_traits.hpp>
+#include <lib/modernjson/detail/exceptions.hpp>
+#include <lib/modernjson/detail/value_t.hpp>
+#include <lib/modernjson/detail/conversions/from_json.hpp>
+#include <lib/modernjson/detail/conversions/to_json.hpp>
+#include <lib/modernjson/detail/input/input_adapters.hpp>
+#include <lib/modernjson/detail/input/lexer.hpp>
+#include <lib/modernjson/detail/input/parser.hpp>
+#include <lib/modernjson/detail/iterators/primitive_iterator.hpp>
+#include <lib/modernjson/detail/iterators/internal_iterator.hpp>
+#include <lib/modernjson/detail/iterators/iter_impl.hpp>
+#include <lib/modernjson/detail/iterators/iteration_proxy.hpp>
+#include <lib/modernjson/detail/iterators/json_reverse_iterator.hpp>
+#include <lib/modernjson/detail/output/output_adapters.hpp>
+#include <lib/modernjson/detail/input/binary_reader.hpp>
+#include <lib/modernjson/detail/output/binary_writer.hpp>
+#include <lib/modernjson/detail/output/serializer.hpp>
+#include <lib/modernjson/detail/json_ref.hpp>
+#include <lib/modernjson/detail/json_pointer.hpp>
+#include <lib/modernjson/adl_serializer.hpp>
+
+/*!
+@brief namespace for Niels Lohmann
+@see https://github.com/nlohmann
+@since version 1.0.0
+*/
+namespace nlohmann
+{
+
+/*!
+@brief a class to store JSON values
+
+@tparam ObjectType type for JSON objects (`std::map` by default; will be used
+in @ref object_t)
+@tparam ArrayType type for JSON arrays (`std::vector` by default; will be used
+in @ref array_t)
+@tparam StringType type for JSON strings and object keys (`std::string` by
+default; will be used in @ref string_t)
+@tparam BooleanType type for JSON booleans (`bool` by default; will be used
+in @ref boolean_t)
+@tparam NumberIntegerType type for JSON integer numbers (`int64_t` by
+default; will be used in @ref number_integer_t)
+@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c
+`uint64_t` by default; will be used in @ref number_unsigned_t)
+@tparam NumberFloatType type for JSON floating-point numbers (`double` by
+default; will be used in @ref number_float_t)
+@tparam AllocatorType type of the allocator to use (`std::allocator` by
+default)
+@tparam JSONSerializer the serializer to resolve internal calls to `to_json()`
+and `from_json()` (@ref adl_serializer by default)
+
+@requirement The class satisfies the following concept requirements:
+- Basic
+ - [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible):
+ JSON values can be default constructed. The result will be a JSON null
+ value.
+ - [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible):
+ A JSON value can be constructed from an rvalue argument.
+ - [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible):
+ A JSON value can be copy-constructed from an lvalue expression.
+ - [MoveAssignable](https://en.cppreference.com/w/cpp/named_req/MoveAssignable):
+ A JSON value van be assigned from an rvalue argument.
+ - [CopyAssignable](https://en.cppreference.com/w/cpp/named_req/CopyAssignable):
+ A JSON value can be copy-assigned from an lvalue expression.
+ - [Destructible](https://en.cppreference.com/w/cpp/named_req/Destructible):
+ JSON values can be destructed.
+- Layout
+ - [StandardLayoutType](https://en.cppreference.com/w/cpp/named_req/StandardLayoutType):
+ JSON values have
+ [standard layout](https://en.cppreference.com/w/cpp/language/data_members#Standard_layout):
+ All non-static data members are private and standard layout types, the
+ class has no virtual functions or (virtual) base classes.
+- Library-wide
+ - [EqualityComparable](https://en.cppreference.com/w/cpp/named_req/EqualityComparable):
+ JSON values can be compared with `==`, see @ref
+ operator==(const_reference,const_reference).
+ - [LessThanComparable](https://en.cppreference.com/w/cpp/named_req/LessThanComparable):
+ JSON values can be compared with `<`, see @ref
+ operator<(const_reference,const_reference).
+ - [Swappable](https://en.cppreference.com/w/cpp/named_req/Swappable):
+ Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of
+ other compatible types, using unqualified function call @ref swap().
+ - [NullablePointer](https://en.cppreference.com/w/cpp/named_req/NullablePointer):
+ JSON values can be compared against `std::nullptr_t` objects which are used
+ to model the `null` value.
+- Container
+ - [Container](https://en.cppreference.com/w/cpp/named_req/Container):
+ JSON values can be used like STL containers and provide iterator access.
+ - [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer);
+ JSON values can be used like STL containers and provide reverse iterator
+ access.
+
+@invariant The member variables @a m_value and @a m_type have the following
+relationship:
+- If `m_type == value_t::object`, then `m_value.object != nullptr`.
+- If `m_type == value_t::array`, then `m_value.array != nullptr`.
+- If `m_type == value_t::string`, then `m_value.string != nullptr`.
+The invariants are checked by member function assert_invariant().
+
+@internal
+@note ObjectType trick from http://stackoverflow.com/a/9860911
+@endinternal
+
+@see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange
+Format](http://rfc7159.net/rfc7159)
+
+@since version 1.0.0
+
+@nosubgrouping
+*/
+NLOHMANN_BASIC_JSON_TPL_DECLARATION
+class basic_json
+{
+ private:
+ template<detail::value_t> friend struct detail::external_constructor;
+ friend ::nlohmann::json_pointer<basic_json>;
+ friend ::nlohmann::detail::parser<basic_json>;
+ friend ::nlohmann::detail::serializer<basic_json>;
+ template<typename BasicJsonType>
+ friend class ::nlohmann::detail::iter_impl;
+ template<typename BasicJsonType, typename CharType>
+ friend class ::nlohmann::detail::binary_writer;
+ template<typename BasicJsonType, typename SAX>
+ friend class ::nlohmann::detail::binary_reader;
+ template<typename BasicJsonType>
+ friend class ::nlohmann::detail::json_sax_dom_parser;
+ template<typename BasicJsonType>
+ friend class ::nlohmann::detail::json_sax_dom_callback_parser;
+
+ /// workaround type for MSVC
+ using basic_json_t = NLOHMANN_BASIC_JSON_TPL;
+
+ // convenience aliases for types residing in namespace detail;
+ using lexer = ::nlohmann::detail::lexer<basic_json>;
+ using parser = ::nlohmann::detail::parser<basic_json>;
+
+ using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t;
+ template<typename BasicJsonType>
+ using internal_iterator = ::nlohmann::detail::internal_iterator<BasicJsonType>;
+ template<typename BasicJsonType>
+ using iter_impl = ::nlohmann::detail::iter_impl<BasicJsonType>;
+ template<typename Iterator>
+ using iteration_proxy = ::nlohmann::detail::iteration_proxy<Iterator>;
+ template<typename Base> using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator<Base>;
+
+ template<typename CharType>
+ using output_adapter_t = ::nlohmann::detail::output_adapter_t<CharType>;
+
+ using binary_reader = ::nlohmann::detail::binary_reader<basic_json>;
+ template<typename CharType> using binary_writer = ::nlohmann::detail::binary_writer<basic_json, CharType>;
+
+ using serializer = ::nlohmann::detail::serializer<basic_json>;
+
+ public:
+ using value_t = detail::value_t;
+ /// JSON Pointer, see @ref nlohmann::json_pointer
+ using json_pointer = ::nlohmann::json_pointer<basic_json>;
+ template<typename T, typename SFINAE>
+ using json_serializer = JSONSerializer<T, SFINAE>;
+ /// how to treat decoding errors
+ using error_handler_t = detail::error_handler_t;
+ /// helper type for initializer lists of basic_json values
+ using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>;
+
+ using input_format_t = detail::input_format_t;
+ /// SAX interface type, see @ref nlohmann::json_sax
+ using json_sax_t = json_sax<basic_json>;
+
+ ////////////////
+ // exceptions //
+ ////////////////
+
+ /// @name exceptions
+ /// Classes to implement user-defined exceptions.
+ /// @{
+
+ /// @copydoc detail::exception
+ using exception = detail::exception;
+ /// @copydoc detail::parse_error
+ using parse_error = detail::parse_error;
+ /// @copydoc detail::invalid_iterator
+ using invalid_iterator = detail::invalid_iterator;
+ /// @copydoc detail::type_error
+ using type_error = detail::type_error;
+ /// @copydoc detail::out_of_range
+ using out_of_range = detail::out_of_range;
+ /// @copydoc detail::other_error
+ using other_error = detail::other_error;
+
+ /// @}
+
+
+ /////////////////////
+ // container types //
+ /////////////////////
+
+ /// @name container types
+ /// The canonic container types to use @ref basic_json like any other STL
+ /// container.
+ /// @{
+
+ /// the type of elements in a basic_json container
+ using value_type = basic_json;
+
+ /// the type of an element reference
+ using reference = value_type&;
+ /// the type of an element const reference
+ using const_reference = const value_type&;
+
+ /// a type to represent differences between iterators
+ using difference_type = std::ptrdiff_t;
+ /// a type to represent container sizes
+ using size_type = std::size_t;
+
+ /// the allocator type
+ using allocator_type = AllocatorType<basic_json>;
+
+ /// the type of an element pointer
+ using pointer = typename std::allocator_traits<allocator_type>::pointer;
+ /// the type of an element const pointer
+ using const_pointer = typename std::allocator_traits<allocator_type>::const_pointer;
+
+ /// an iterator for a basic_json container
+ using iterator = iter_impl<basic_json>;
+ /// a const iterator for a basic_json container
+ using const_iterator = iter_impl<const basic_json>;
+ /// a reverse iterator for a basic_json container
+ using reverse_iterator = json_reverse_iterator<typename basic_json::iterator>;
+ /// a const reverse iterator for a basic_json container
+ using const_reverse_iterator = json_reverse_iterator<typename basic_json::const_iterator>;
+
+ /// @}
+
+
+ /*!
+ @brief returns the allocator associated with the container
+ */
+ static allocator_type get_allocator()
+ {
+ return allocator_type();
+ }
+
+ /*!
+ @brief returns version information on the library
+
+ This function returns a JSON object with information about the library,
+ including the version number and information on the platform and compiler.
+
+ @return JSON object holding version information
+ key | description
+ ----------- | ---------------
+ `compiler` | Information on the used compiler. It is an object with the following keys: `c++` (the used C++ standard), `family` (the compiler family; possible values are `clang`, `icc`, `gcc`, `ilecpp`, `msvc`, `pgcpp`, `sunpro`, and `unknown`), and `version` (the compiler version).
+ `copyright` | The copyright line for the library as string.
+ `name` | The name of the library as string.
+ `platform` | The used platform as string. Possible values are `win32`, `linux`, `apple`, `unix`, and `unknown`.
+ `url` | The URL of the project as string.
+ `version` | The version of the library. It is an object with the following keys: `major`, `minor`, and `patch` as defined by [Semantic Versioning](http://semver.org), and `string` (the version string).
+
+ @liveexample{The following code shows an example output of the `meta()`
+ function.,meta}
+
+ @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+ changes to any JSON value.
+
+ @complexity Constant.
+
+ @since 2.1.0
+ */
+ static basic_json meta()
+ {
+ basic_json result;
+
+ result["copyright"] = "(C) 2013-2017 Niels Lohmann";
+ result["name"] = "JSON for Modern C++";
+ result["url"] = "https://github.com/nlohmann/json";
+ result["version"]["string"] =
+ std::to_string(NLOHMANN_JSON_VERSION_MAJOR) + "." +
+ std::to_string(NLOHMANN_JSON_VERSION_MINOR) + "." +
+ std::to_string(NLOHMANN_JSON_VERSION_PATCH);
+ result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR;
+ result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR;
+ result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH;
+
+#ifdef _WIN32
+ result["platform"] = "win32";
+#elif defined __linux__
+ result["platform"] = "linux";
+#elif defined __APPLE__
+ result["platform"] = "apple";
+#elif defined __unix__
+ result["platform"] = "unix";
+#else
+ result["platform"] = "unknown";
+#endif
+
+#if defined(__ICC) || defined(__INTEL_COMPILER)
+ result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}};
+#elif defined(__clang__)
+ result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}};
+#elif defined(__GNUC__) || defined(__GNUG__)
+ result["compiler"] = {{"family", "gcc"}, {"version", std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__)}};
+#elif defined(__HP_cc) || defined(__HP_aCC)
+ result["compiler"] = "hp"
+#elif defined(__IBMCPP__)
+ result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}};
+#elif defined(_MSC_VER)
+ result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}};
+#elif defined(__PGI)
+ result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}};
+#elif defined(__SUNPRO_CC)
+ result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}};
+#else
+ result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}};
+#endif
+
+#ifdef __cplusplus
+ result["compiler"]["c++"] = std::to_string(__cplusplus);
+#else
+ result["compiler"]["c++"] = "unknown";
+#endif
+ return result;
+ }
+
+
+ ///////////////////////////
+ // JSON value data types //
+ ///////////////////////////
+
+ /// @name JSON value data types
+ /// The data types to store a JSON value. These types are derived from
+ /// the template arguments passed to class @ref basic_json.
+ /// @{
+
+#if defined(JSON_HAS_CPP_14)
+ // Use transparent comparator if possible, combined with perfect forwarding
+ // on find() and count() calls prevents unnecessary string construction.
+ using object_comparator_t = std::less<>;
+#else
+ using object_comparator_t = std::less<StringType>;
+#endif
+
+ /*!
+ @brief a type for an object
+
+ [RFC 7159](http://rfc7159.net/rfc7159) describes JSON objects as follows:
+ > An object is an unordered collection of zero or more name/value pairs,
+ > where a name is a string and a value is a string, number, boolean, null,
+ > object, or array.
+
+ To store objects in C++, a type is defined by the template parameters
+ described below.
+
+ @tparam ObjectType the container to store objects (e.g., `std::map` or
+ `std::unordered_map`)
+ @tparam StringType the type of the keys or names (e.g., `std::string`).
+ The comparison function `std::less<StringType>` is used to order elements
+ inside the container.
+ @tparam AllocatorType the allocator to use for objects (e.g.,
+ `std::allocator`)
+
+ #### Default type
+
+ With the default values for @a ObjectType (`std::map`), @a StringType
+ (`std::string`), and @a AllocatorType (`std::allocator`), the default
+ value for @a object_t is:
+
+ @code {.cpp}
+ std::map<
+ std::string, // key_type
+ basic_json, // value_type
+ std::less<std::string>, // key_compare
+ std::allocator<std::pair<const std::string, basic_json>> // allocator_type
+ >
+ @endcode
+
+ #### Behavior
+
+ The choice of @a object_t influences the behavior of the JSON class. With
+ the default type, objects have the following behavior:
+
+ - When all names are unique, objects will be interoperable in the sense
+ that all software implementations receiving that object will agree on
+ the name-value mappings.
+ - When the names within an object are not unique, it is unspecified which
+ one of the values for a given key will be chosen. For instance,
+ `{"key": 2, "key": 1}` could be equal to either `{"key": 1}` or
+ `{"key": 2}`.
+ - Internally, name/value pairs are stored in lexicographical order of the
+ names. Objects will also be serialized (see @ref dump) in this order.
+ For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored
+ and serialized as `{"a": 2, "b": 1}`.
+ - When comparing objects, the order of the name/value pairs is irrelevant.
+ This makes objects interoperable in the sense that they will not be
+ affected by these differences. For instance, `{"b": 1, "a": 2}` and
+ `{"a": 2, "b": 1}` will be treated as equal.
+
+ #### Limits
+
+ [RFC 7159](http://rfc7159.net/rfc7159) specifies:
+ > An implementation may set limits on the maximum depth of nesting.
+
+ In this class, the object's limit of nesting is not explicitly constrained.
+ However, a maximum depth of nesting may be introduced by the compiler or
+ runtime environment. A theoretical limit can be queried by calling the
+ @ref max_size function of a JSON object.
+
+ #### Storage
+
+ Objects are stored as pointers in a @ref basic_json type. That is, for any
+ access to object values, a pointer of type `object_t*` must be
+ dereferenced.
+
+ @sa @ref array_t -- type for an array value
+
+ @since version 1.0.0
+
+ @note The order name/value pairs are added to the object is *not*
+ preserved by the library. Therefore, iterating an object may return
+ name/value pairs in a different order than they were originally stored. In
+ fact, keys will be traversed in alphabetical order as `std::map` with
+ `std::less` is used by default. Please note this behavior conforms to [RFC
+ 7159](http://rfc7159.net/rfc7159), because any order implements the
+ specified "unordered" nature of JSON objects.
+ */
+ using object_t = ObjectType<StringType,
+ basic_json,
+ object_comparator_t,
+ AllocatorType<std::pair<const StringType,
+ basic_json>>>;
+
+ /*!
+ @brief a type for an array
+
+ [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows:
+ > An array is an ordered sequence of zero or more values.
+
+ To store objects in C++, a type is defined by the template parameters
+ explained below.
+
+ @tparam ArrayType container type to store arrays (e.g., `std::vector` or
+ `std::list`)
+ @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`)
+
+ #### Default type
+
+ With the default values for @a ArrayType (`std::vector`) and @a
+ AllocatorType (`std::allocator`), the default value for @a array_t is:
+
+ @code {.cpp}
+ std::vector<
+ basic_json, // value_type
+ std::allocator<basic_json> // allocator_type
+ >
+ @endcode
+
+ #### Limits
+
+ [RFC 7159](http://rfc7159.net/rfc7159) specifies:
+ > An implementation may set limits on the maximum depth of nesting.
+
+ In this class, the array's limit of nesting is not explicitly constrained.
+ However, a maximum depth of nesting may be introduced by the compiler or
+ runtime environment. A theoretical limit can be queried by calling the
+ @ref max_size function of a JSON array.
+
+ #### Storage
+
+ Arrays are stored as pointers in a @ref basic_json type. That is, for any
+ access to array values, a pointer of type `array_t*` must be dereferenced.
+
+ @sa @ref object_t -- type for an object value
+
+ @since version 1.0.0
+ */
+ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
+
+ /*!
+ @brief a type for a string
+
+ [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows:
+ > A string is a sequence of zero or more Unicode characters.
+
+ To store objects in C++, a type is defined by the template parameter
+ described below. Unicode values are split by the JSON class into
+ byte-sized characters during deserialization.
+
+ @tparam StringType the container to store strings (e.g., `std::string`).
+ Note this container is used for keys/names in objects, see @ref object_t.
+
+ #### Default type
+
+ With the default values for @a StringType (`std::string`), the default
+ value for @a string_t is:
+
+ @code {.cpp}
+ std::string
+ @endcode
+
+ #### Encoding
+
+ Strings are stored in UTF-8 encoding. Therefore, functions like
+ `std::string::size()` or `std::string::length()` return the number of
+ bytes in the string rather than the number of characters or glyphs.
+
+ #### String comparison
+
+ [RFC 7159](http://rfc7159.net/rfc7159) states:
+ > Software implementations are typically required to test names of object
+ > members for equality. Implementations that transform the textual
+ > representation into sequences of Unicode code units and then perform the
+ > comparison numerically, code unit by code unit, are interoperable in the
+ > sense that implementations will agree in all cases on equality or
+ > inequality of two strings. For example, implementations that compare
+ > strings with escaped characters unconverted may incorrectly find that
+ > `"a\\b"` and `"a\u005Cb"` are not equal.
+
+ This implementation is interoperable as it does compare strings code unit
+ by code unit.
+
+ #### Storage
+
+ String values are stored as pointers in a @ref basic_json type. That is,
+ for any access to string values, a pointer of type `string_t*` must be
+ dereferenced.
+
+ @since version 1.0.0
+ */
+ using string_t = StringType;
+
+ /*!
+ @brief a type for a boolean
+
+ [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a
+ type which differentiates the two literals `true` and `false`.
+
+ To store objects in C++, a type is defined by the template parameter @a
+ BooleanType which chooses the type to use.
+
+ #### Default type
+
+ With the default values for @a BooleanType (`bool`), the default value for
+ @a boolean_t is:
+
+ @code {.cpp}
+ bool
+ @endcode
+
+ #### Storage
+
+ Boolean values are stored directly inside a @ref basic_json type.
+
+ @since version 1.0.0
+ */
+ using boolean_t = BooleanType;
+
+ /*!
+ @brief a type for a number (integer)
+
+ [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows:
+ > The representation of numbers is similar to that used in most
+ > programming languages. A number is represented in base 10 using decimal
+ > digits. It contains an integer component that may be prefixed with an
+ > optional minus sign, which may be followed by a fraction part and/or an
+ > exponent part. Leading zeros are not allowed. (...) Numeric values that
+ > cannot be represented in the grammar below (such as Infinity and NaN)
+ > are not permitted.
+
+ This description includes both integer and floating-point numbers.
+ However, C++ allows more precise storage if it is known whether the number
+ is a signed integer, an unsigned integer or a floating-point number.
+ Therefore, three different types, @ref number_integer_t, @ref
+ number_unsigned_t and @ref number_float_t are used.
+
+ To store integer numbers in C++, a type is defined by the template
+ parameter @a NumberIntegerType which chooses the type to use.
+
+ #### Default type
+
+ With the default values for @a NumberIntegerType (`int64_t`), the default
+ value for @a number_integer_t is:
+
+ @code {.cpp}
+ int64_t
+ @endcode
+
+ #### Default behavior
+
+ - The restrictions about leading zeros is not enforced in C++. Instead,
+ leading zeros in integer literals lead to an interpretation as octal
+ number. Internally, the value will be stored as decimal number. For
+ instance, the C++ integer literal `010` will be serialized to `8`.
+ During deserialization, leading zeros yield an error.
+ - Not-a-number (NaN) values will be serialized to `null`.
+
+ #### Limits
+
+ [RFC 7159](http://rfc7159.net/rfc7159) specifies:
+ > An implementation may set limits on the range and precision of numbers.
+
+ When the default type is used, the maximal integer number that can be
+ stored is `9223372036854775807` (INT64_MAX) and the minimal integer number
+ that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers
+ that are out of range will yield over/underflow when used in a
+ constructor. During deserialization, too large or small integer numbers
+ will be automatically be stored as @ref number_unsigned_t or @ref
+ number_float_t.
+
+ [RFC 7159](http://rfc7159.net/rfc7159) further states:
+ > Note that when such software is used, numbers that are integers and are
+ > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense
+ > that implementations will agree exactly on their numeric values.
+
+ As this range is a subrange of the exactly supported range [INT64_MIN,
+ INT64_MAX], this class's integer type is interoperable.
+
+ #### Storage
+
+ Integer number values are stored directly inside a @ref basic_json type.
+
+ @sa @ref number_float_t -- type for number values (floating-point)
+
+ @sa @ref number_unsigned_t -- type for number values (unsigned integer)
+
+ @since version 1.0.0
+ */
+ using number_integer_t = NumberIntegerType;
+
+ /*!
+ @brief a type for a number (unsigned)
+
+ [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows:
+ > The representation of numbers is similar to that used in most
+ > programming languages. A number is represented in base 10 using decimal
+ > digits. It contains an integer component that may be prefixed with an
+ > optional minus sign, which may be followed by a fraction part and/or an
+ > exponent part. Leading zeros are not allowed. (...) Numeric values that
+ > cannot be represented in the grammar below (such as Infinity and NaN)
+ > are not permitted.
+
+ This description includes both integer and floating-point numbers.
+ However, C++ allows more precise storage if it is known whether the number
+ is a signed integer, an unsigned integer or a floating-point number.
+ Therefore, three different types, @ref number_integer_t, @ref
+ number_unsigned_t and @ref number_float_t are used.
+
+ To store unsigned integer numbers in C++, a type is defined by the
+ template parameter @a NumberUnsignedType which chooses the type to use.
+
+ #### Default type
+
+ With the default values for @a NumberUnsignedType (`uint64_t`), the
+ default value for @a number_unsigned_t is:
+
+ @code {.cpp}
+ uint64_t
+ @endcode
+
+ #### Default behavior
+
+ - The restrictions about leading zeros is not enforced in C++. Instead,
+ leading zeros in integer literals lead to an interpretation as octal
+ number. Internally, the value will be stored as decimal number. For
+ instance, the C++ integer literal `010` will be serialized to `8`.
+ During deserialization, leading zeros yield an error.
+ - Not-a-number (NaN) values will be serialized to `null`.
+
+ #### Limits
+
+ [RFC 7159](http://rfc7159.net/rfc7159) specifies:
+ > An implementation may set limits on the range and precision of numbers.
+
+ When the default type is used, the maximal integer number that can be
+ stored is `18446744073709551615` (UINT64_MAX) and the minimal integer
+ number that can be stored is `0`. Integer numbers that are out of range
+ will yield over/underflow when used in a constructor. During
+ deserialization, too large or small integer numbers will be automatically
+ be stored as @ref number_integer_t or @ref number_float_t.
+
+ [RFC 7159](http://rfc7159.net/rfc7159) further states:
+ > Note that when such software is used, numbers that are integers and are
+ > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense
+ > that implementations will agree exactly on their numeric values.
+
+ As this range is a subrange (when considered in conjunction with the
+ number_integer_t type) of the exactly supported range [0, UINT64_MAX],
+ this class's integer type is interoperable.
+
+ #### Storage
+
+ Integer number values are stored directly inside a @ref basic_json type.
+
+ @sa @ref number_float_t -- type for number values (floating-point)
+ @sa @ref number_integer_t -- type for number values (integer)
+
+ @since version 2.0.0
+ */
+ using number_unsigned_t = NumberUnsignedType;
+
+ /*!
+ @brief a type for a number (floating-point)
+
+ [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows:
+ > The representation of numbers is similar to that used in most
+ > programming languages. A number is represented in base 10 using decimal
+ > digits. It contains an integer component that may be prefixed with an
+ > optional minus sign, which may be followed by a fraction part and/or an
+ > exponent part. Leading zeros are not allowed. (...) Numeric values that
+ > cannot be represented in the grammar below (such as Infinity and NaN)
+ > are not permitted.
+
+ This description includes both integer and floating-point numbers.
+ However, C++ allows more precise storage if it is known whether the number
+ is a signed integer, an unsigned integer or a floating-point number.
+ Therefore, three different types, @ref number_integer_t, @ref
+ number_unsigned_t and @ref number_float_t are used.
+
+ To store floating-point numbers in C++, a type is defined by the template
+ parameter @a NumberFloatType which chooses the type to use.
+
+ #### Default type
+
+ With the default values for @a NumberFloatType (`double`), the default
+ value for @a number_float_t is:
+
+ @code {.cpp}
+ double
+ @endcode
+
+ #### Default behavior
+
+ - The restrictions about leading zeros is not enforced in C++. Instead,
+ leading zeros in floating-point literals will be ignored. Internally,
+ the value will be stored as decimal number. For instance, the C++
+ floating-point literal `01.2` will be serialized to `1.2`. During
+ deserialization, leading zeros yield an error.
+ - Not-a-number (NaN) values will be serialized to `null`.
+
+ #### Limits
+
+ [RFC 7159](http://rfc7159.net/rfc7159) states:
+ > This specification allows implementations to set limits on the range and
+ > precision of numbers accepted. Since software that implements IEEE
+ > 754-2008 binary64 (double precision) numbers is generally available and
+ > widely used, good interoperability can be achieved by implementations
+ > that expect no more precision or range than these provide, in the sense
+ > that implementations will approximate JSON numbers within the expected
+ > precision.
+
+ This implementation does exactly follow this approach, as it uses double
+ precision floating-point numbers. Note values smaller than
+ `-1.79769313486232e+308` and values greater than `1.79769313486232e+308`
+ will be stored as NaN internally and be serialized to `null`.
+
+ #### Storage
+
+ Floating-point number values are stored directly inside a @ref basic_json
+ type.
+
+ @sa @ref number_integer_t -- type for number values (integer)
+
+ @sa @ref number_unsigned_t -- type for number values (unsigned integer)
+
+ @since version 1.0.0
+ */
+ using number_float_t = NumberFloatType;
+
+ /// @}
+
+ private:
+
+ /// helper for exception-safe object creation
+ template<typename T, typename... Args>
+ static T* create(Args&& ... args)
+ {
+ AllocatorType<T> alloc;
+ using AllocatorTraits = std::allocator_traits<AllocatorType<T>>;
+
+ auto deleter = [&](T * object)
+ {
+ AllocatorTraits::deallocate(alloc, object, 1);
+ };
+ std::unique_ptr<T, decltype(deleter)> object(AllocatorTraits::allocate(alloc, 1), deleter);
+ AllocatorTraits::construct(alloc, object.get(), std::forward<Args>(args)...);
+ assert(object != nullptr);
+ return object.release();
+ }
+
+ ////////////////////////
+ // JSON value storage //
+ ////////////////////////
+
+ /*!
+ @brief a JSON value
+
+ The actual storage for a JSON value of the @ref basic_json class. This
+ union combines the different storage types for the JSON value types
+ defined in @ref value_t.
+
+ JSON type | value_t type | used type
+ --------- | --------------- | ------------------------
+ object | object | pointer to @ref object_t
+ array | array | pointer to @ref array_t
+ string | string | pointer to @ref string_t
+ boolean | boolean | @ref boolean_t
+ number | number_integer | @ref number_integer_t
+ number | number_unsigned | @ref number_unsigned_t
+ number | number_float | @ref number_float_t
+ null | null | *no value is stored*
+
+ @note Variable-length types (objects, arrays, and strings) are stored as
+ pointers. The size of the union should not exceed 64 bits if the default
+ value types are used.
+
+ @since version 1.0.0
+ */
+ union json_value
+ {
+ /// object (stored with pointer to save storage)
+ object_t* object;
+ /// array (stored with pointer to save storage)
+ array_t* array;
+ /// string (stored with pointer to save storage)
+ string_t* string;
+ /// boolean
+ boolean_t boolean;
+ /// number (integer)
+ number_integer_t number_integer;
+ /// number (unsigned integer)
+ number_unsigned_t number_unsigned;
+ /// number (floating-point)
+ number_float_t number_float;
+
+ /// default constructor (for null values)
+ json_value() = default;
+ /// constructor for booleans
+ json_value(boolean_t v) noexcept : boolean(v) {}
+ /// constructor for numbers (integer)
+ json_value(number_integer_t v) noexcept : number_integer(v) {}
+ /// constructor for numbers (unsigned)
+ json_value(number_unsigned_t v) noexcept : number_unsigned(v) {}
+ /// constructor for numbers (floating-point)
+ json_value(number_float_t v) noexcept : number_float(v) {}
+ /// constructor for empty values of a given type
+ json_value(value_t t)
+ {
+ switch (t)
+ {
+ case value_t::object:
+ {
+ object = create<object_t>();
+ break;
+ }
+
+ case value_t::array:
+ {
+ array = create<array_t>();
+ break;
+ }
+
+ case value_t::string:
+ {
+ string = create<string_t>("");
+ break;
+ }
+
+ case value_t::boolean:
+ {
+ boolean = boolean_t(false);
+ break;
+ }
+
+ case value_t::number_integer:
+ {
+ number_integer = number_integer_t(0);
+ break;
+ }
+
+ case value_t::number_unsigned:
+ {
+ number_unsigned = number_unsigned_t(0);
+ break;
+ }
+
+ case value_t::number_float:
+ {
+ number_float = number_float_t(0.0);
+ break;
+ }
+
+ case value_t::null:
+ {
+ object = nullptr; // silence warning, see #821
+ break;
+ }
+
+ default:
+ {
+ object = nullptr; // silence warning, see #821
+ if (JSON_UNLIKELY(t == value_t::null))
+ {
+ JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.4.0")); // LCOV_EXCL_LINE
+ }
+ break;
+ }
+ }
+ }
+
+ /// constructor for strings
+ json_value(const string_t& value)
+ {
+ string = create<string_t>(value);
+ }
+
+ /// constructor for rvalue strings
+ json_value(string_t&& value)
+ {
+ string = create<string_t>(std::move(value));
+ }
+
+ /// constructor for objects
+ json_value(const object_t& value)
+ {
+ object = create<object_t>(value);
+ }
+
+ /// constructor for rvalue objects
+ json_value(object_t&& value)
+ {
+ object = create<object_t>(std::move(value));
+ }
+
+ /// constructor for arrays
+ json_value(const array_t& value)
+ {
+ array = create<array_t>(value);
+ }
+
+ /// constructor for rvalue arrays
+ json_value(array_t&& value)
+ {
+ array = create<array_t>(std::move(value));
+ }
+
+ void destroy(value_t t) noexcept
+ {
+ switch (t)
+ {
+ case value_t::object:
+ {
+ AllocatorType<object_t> alloc;
+ std::allocator_traits<decltype(alloc)>::destroy(alloc, object);
+ std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);
+ break;
+ }
+
+ case value_t::array:
+ {
+ AllocatorType<array_t> alloc;
+ std::allocator_traits<decltype(alloc)>::destroy(alloc, array);
+ std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);
+ break;
+ }
+
+ case value_t::string:
+ {
+ AllocatorType<string_t> alloc;
+ std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
+ std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
+ break;
+ }
+
+ default:
+ {
+ break;
+ }
+ }
+ }
+ };
+
+ /*!
+ @brief checks the class invariants
+
+ This function asserts the class invariants. It needs to be called at the
+ end of every constructor to make sure that created objects respect the
+ invariant. Furthermore, it has to be called each time the type of a JSON
+ value is changed, because the invariant expresses a relationship between
+ @a m_type and @a m_value.
+ */
+ void assert_invariant() const noexcept
+ {
+ assert(m_type != value_t::object or m_value.object != nullptr);
+ assert(m_type != value_t::array or m_value.array != nullptr);
+ assert(m_type != value_t::string or m_value.string != nullptr);
+ }
+
+ public:
+ //////////////////////////
+ // JSON parser callback //
+ //////////////////////////
+
+ /*!
+ @brief parser event types
+
+ The parser callback distinguishes the following events:
+ - `object_start`: the parser read `{` and started to process a JSON object
+ - `key`: the parser read a key of a value in an object
+ - `object_end`: the parser read `}` and finished processing a JSON object
+ - `array_start`: the parser read `[` and started to process a JSON array
+ - `array_end`: the parser read `]` and finished processing a JSON array
+ - `value`: the parser finished reading a JSON value
+
+ @image html callback_events.png "Example when certain parse events are triggered"
+
+ @sa @ref parser_callback_t for more information and examples
+ */
+ using parse_event_t = typename parser::parse_event_t;
+
+ /*!
+ @brief per-element parser callback type
+
+ With a parser callback function, the result of parsing a JSON text can be
+ influenced. When passed to @ref parse, it is called on certain events
+ (passed as @ref parse_event_t via parameter @a event) with a set recursion
+ depth @a depth and context JSON value @a parsed. The return value of the
+ callback function is a boolean indicating whether the element that emitted
+ the callback shall be kept or not.
+
+ We distinguish six scenarios (determined by the event type) in which the
+ callback function can be called. The following table describes the values
+ of the parameters @a depth, @a event, and @a parsed.
+
+ parameter @a event | description | parameter @a depth | parameter @a parsed
+ ------------------ | ----------- | ------------------ | -------------------
+ parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded
+ parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key
+ parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object
+ parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded
+ parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array
+ parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value
+
+ @image html callback_events.png "Example when certain parse events are triggered"
+
+ Discarding a value (i.e., returning `false`) has different effects
+ depending on the context in which function was called:
+
+ - Discarded values in structured types are skipped. That is, the parser
+ will behave as if the discarded value was never read.
+ - In case a value outside a structured type is skipped, it is replaced
+ with `null`. This case happens if the top-level element is skipped.
+
+ @param[in] depth the depth of the recursion during parsing
+
+ @param[in] event an event of type parse_event_t indicating the context in
+ the callback function has been called
+
+ @param[in,out] parsed the current intermediate parse result; note that
+ writing to this value has no effect for parse_event_t::key events
+
+ @return Whether the JSON value which called the function during parsing
+ should be kept (`true`) or not (`false`). In the latter case, it is either
+ skipped completely or replaced by an empty discarded object.
+
+ @sa @ref parse for examples
+
+ @since version 1.0.0
+ */
+ using parser_callback_t = typename parser::parser_callback_t;
+
+ //////////////////
+ // constructors //
+ //////////////////
+
+ /// @name constructors and destructors
+ /// Constructors of class @ref basic_json, copy/move constructor, copy
+ /// assignment, static functions creating objects, and the destructor.
+ /// @{
+
+ /*!
+ @brief create an empty value with a given type
+
+ Create an empty JSON value with a given type. The value will be default
+ initialized with an empty value which depends on the type:
+
+ Value type | initial value
+ ----------- | -------------
+ null | `null`
+ boolean | `false`
+ string | `""`
+ number | `0`
+ object | `{}`
+ array | `[]`
+
+ @param[in] v the type of the value to create
+
+ @complexity Constant.
+
+ @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+ changes to any JSON value.
+
+ @liveexample{The following code shows the constructor for different @ref
+ value_t values,basic_json__value_t}
+
+ @sa @ref clear() -- restores the postcondition of this constructor
+
+ @since version 1.0.0
+ */
+ basic_json(const value_t v)
+ : m_type(v), m_value(v)
+ {
+ assert_invariant();
+ }
+
+ /*!
+ @brief create a null object
+
+ Create a `null` JSON value. It either takes a null pointer as parameter
+ (explicitly creating `null`) or no parameter (implicitly creating `null`).
+ The passed null pointer itself is not read -- it is only used to choose
+ the right constructor.
+
+ @complexity Constant.
+
+ @exceptionsafety No-throw guarantee: this constructor never throws
+ exceptions.
+
+ @liveexample{The following code shows the constructor with and without a
+ null pointer parameter.,basic_json__nullptr_t}
+
+ @since version 1.0.0
+ */
+ basic_json(std::nullptr_t = nullptr) noexcept
+ : basic_json(value_t::null)
+ {
+ assert_invariant();
+ }
+
+ /*!
+ @brief create a JSON value
+
+ This is a "catch all" constructor for all compatible JSON types; that is,
+ types for which a `to_json()` method exists. The constructor forwards the
+ parameter @a val to that method (to `json_serializer<U>::to_json` method
+ with `U = uncvref_t<CompatibleType>`, to be exact).
+
+ Template type @a CompatibleType includes, but is not limited to, the
+ following types:
+ - **arrays**: @ref array_t and all kinds of compatible containers such as
+ `std::vector`, `std::deque`, `std::list`, `std::forward_list`,
+ `std::array`, `std::valarray`, `std::set`, `std::unordered_set`,
+ `std::multiset`, and `std::unordered_multiset` with a `value_type` from
+ which a @ref basic_json value can be constructed.
+ - **objects**: @ref object_t and all kinds of compatible associative
+ containers such as `std::map`, `std::unordered_map`, `std::multimap`,
+ and `std::unordered_multimap` with a `key_type` compatible to
+ @ref string_t and a `value_type` from which a @ref basic_json value can
+ be constructed.
+ - **strings**: @ref string_t, string literals, and all compatible string
+ containers can be used.
+ - **numbers**: @ref number_integer_t, @ref number_unsigned_t,
+ @ref number_float_t, and all convertible number types such as `int`,
+ `size_t`, `int64_t`, `float` or `double` can be used.
+ - **boolean**: @ref boolean_t / `bool` can be used.
+
+ See the examples below.
+
+ @tparam CompatibleType a type such that:
+ - @a CompatibleType is not derived from `std::istream`,
+ - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move
+ constructors),
+ - @a CompatibleType is not a different @ref basic_json type (i.e. with different template arguments)
+ - @a CompatibleType is not a @ref basic_json nested type (e.g.,
+ @ref json_pointer, @ref iterator, etc ...)
+ - @ref @ref json_serializer<U> has a
+ `to_json(basic_json_t&, CompatibleType&&)` method
+
+ @tparam U = `uncvref_t<CompatibleType>`
+
+ @param[in] val the value to be forwarded to the respective constructor
+
+ @complexity Usually linear in the size of the passed @a val, also
+ depending on the implementation of the called `to_json()`
+ method.
+
+ @exceptionsafety Depends on the called constructor. For types directly
+ supported by the library (i.e., all types for which no `to_json()` function
+ was provided), strong guarantee holds: if an exception is thrown, there are
+ no changes to any JSON value.
+
+ @liveexample{The following code shows the constructor with several
+ compatible types.,basic_json__CompatibleType}
+
+ @since version 2.1.0
+ */
+ template <typename CompatibleType,
+ typename U = detail::uncvref_t<CompatibleType>,
+ detail::enable_if_t<
+ not detail::is_basic_json<U>::value and detail::is_compatible_type<basic_json_t, U>::value, int> = 0>
+ basic_json(CompatibleType && val) noexcept(noexcept(
+ JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
+ std::forward<CompatibleType>(val))))
+ {
+ JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val));
+ assert_invariant();
+ }
+
+ /*!
+ @brief create a JSON value from an existing one
+
+ This is a constructor for existing @ref basic_json types.
+ It does not hijack copy/move constructors, since the parameter has different
+ template arguments than the current ones.
+
+ The constructor tries to convert the internal @ref m_value of the parameter.
+
+ @tparam BasicJsonType a type such that:
+ - @a BasicJsonType is a @ref basic_json type.
+ - @a BasicJsonType has different template arguments than @ref basic_json_t.
+
+ @param[in] val the @ref basic_json value to be converted.
+
+ @complexity Usually linear in the size of the passed @a val, also
+ depending on the implementation of the called `to_json()`
+ method.
+
+ @exceptionsafety Depends on the called constructor. For types directly
+ supported by the library (i.e., all types for which no `to_json()` function
+ was provided), strong guarantee holds: if an exception is thrown, there are
+ no changes to any JSON value.
+
+ @since version 3.2.0
+ */
+ template <typename BasicJsonType,
+ detail::enable_if_t<
+ detail::is_basic_json<BasicJsonType>::value and not std::is_same<basic_json, BasicJsonType>::value, int> = 0>
+ basic_json(const BasicJsonType& val)
+ {
+ using other_boolean_t = typename BasicJsonType::boolean_t;
+ using other_number_float_t = typename BasicJsonType::number_float_t;
+ using other_number_integer_t = typename BasicJsonType::number_integer_t;
+ using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t;
+ using other_string_t = typename BasicJsonType::string_t;
+ using other_object_t = typename BasicJsonType::object_t;
+ using other_array_t = typename BasicJsonType::array_t;
+
+ switch (val.type())
+ {
+ case value_t::boolean:
+ JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>());
+ break;
+ case value_t::number_float:
+ JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>());
+ break;
+ case value_t::number_integer:
+ JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>());
+ break;
+ case value_t::number_unsigned:
+ JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>());
+ break;
+ case value_t::string:
+ JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t&>());
+ break;
+ case value_t::object:
+ JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t&>());
+ break;
+ case value_t::array:
+ JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t&>());
+ break;
+ case value_t::null:
+ *this = nullptr;
+ break;
+ case value_t::discarded:
+ m_type = value_t::discarded;
+ break;
+ }
+ assert_invariant();
+ }
+
+ /*!
+ @brief create a container (array or object) from an initializer list
+
+ Creates a JSON value of type array or object from the passed initializer
+ list @a init. In case @a type_deduction is `true` (default), the type of
+ the JSON value to be created is deducted from the initializer list @a init
+ according to the following rules:
+
+ 1. If the list is empty, an empty JSON object value `{}` is created.
+ 2. If the list consists of pairs whose first element is a string, a JSON
+ object value is created where the first elements of the pairs are
+ treated as keys and the second elements are as values.
+ 3. In all other cases, an array is created.
+
+ The rules aim to create the best fit between a C++ initializer list and
+ JSON values. The rationale is as follows:
+
+ 1. The empty initializer list is written as `{}` which is exactly an empty
+ JSON object.
+ 2. C++ has no way of describing mapped types other than to list a list of
+ pairs. As JSON requires that keys must be of type string, rule 2 is the
+ weakest constraint one can pose on initializer lists to interpret them
+ as an object.
+ 3. In all other cases, the initializer list could not be interpreted as
+ JSON object type, so interpreting it as JSON array type is safe.
+
+ With the rules described above, the following JSON values cannot be
+ expressed by an initializer list:
+
+ - the empty array (`[]`): use @ref array(initializer_list_t)
+ with an empty initializer list in this case
+ - arrays whose elements satisfy rule 2: use @ref
+ array(initializer_list_t) with the same initializer list
+ in this case
+
+ @note When used without parentheses around an empty initializer list, @ref
+ basic_json() is called instead of this function, yielding the JSON null
+ value.
+
+ @param[in] init initializer list with JSON values
+
+ @param[in] type_deduction internal parameter; when set to `true`, the type
+ of the JSON value is deducted from the initializer list @a init; when set
+ to `false`, the type provided via @a manual_type is forced. This mode is
+ used by the functions @ref array(initializer_list_t) and
+ @ref object(initializer_list_t).
+
+ @param[in] manual_type internal parameter; when @a type_deduction is set
+ to `false`, the created JSON value will use the provided type (only @ref
+ value_t::array and @ref value_t::object are valid); when @a type_deduction
+ is set to `true`, this parameter has no effect
+
+ @throw type_error.301 if @a type_deduction is `false`, @a manual_type is
+ `value_t::object`, but @a init contains an element which is not a pair
+ whose first element is a string. In this case, the constructor could not
+ create an object. If @a type_deduction would have be `true`, an array
+ would have been created. See @ref object(initializer_list_t)
+ for an example.
+
+ @complexity Linear in the size of the initializer list @a init.
+
+ @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+ changes to any JSON value.
+
+ @liveexample{The example below shows how JSON values are created from
+ initializer lists.,basic_json__list_init_t}
+
+ @sa @ref array(initializer_list_t) -- create a JSON array
+ value from an initializer list
+ @sa @ref object(initializer_list_t) -- create a JSON object
+ value from an initializer list
+
+ @since version 1.0.0
+ */
+ basic_json(initializer_list_t init,
+ bool type_deduction = true,
+ value_t manual_type = value_t::array)
+ {
+ // check if each element is an array with two elements whose first
+ // element is a string
+ bool is_an_object = std::all_of(init.begin(), init.end(),
+ [](const detail::json_ref<basic_json>& element_ref)
+ {
+ return (element_ref->is_array() and element_ref->size() == 2 and (*element_ref)[0].is_string());
+ });
+
+ // adjust type if type deduction is not wanted
+ if (not type_deduction)
+ {
+ // if array is wanted, do not create an object though possible
+ if (manual_type == value_t::array)
+ {
+ is_an_object = false;
+ }
+
+ // if object is wanted but impossible, throw an exception
+ if (JSON_UNLIKELY(manual_type == value_t::object and not is_an_object))
+ {
+ JSON_THROW(type_error::create(301, "cannot create object from initializer list"));
+ }
+ }
+
+ if (is_an_object)
+ {
+ // the initializer list is a list of pairs -> create object
+ m_type = value_t::object;
+ m_value = value_t::object;
+
+ std::for_each(init.begin(), init.end(), [this](const detail::json_ref<basic_json>& element_ref)
+ {
+ auto element = element_ref.moved_or_copied();
+ m_value.object->emplace(
+ std::move(*((*element.m_value.array)[0].m_value.string)),
+ std::move((*element.m_value.array)[1]));
+ });
+ }
+ else
+ {
+ // the initializer list describes an array -> create array
+ m_type = value_t::array;
+ m_value.array = create<array_t>(init.begin(), init.end());
+ }
+
+ assert_invariant();
+ }
+
+ /*!
+ @brief explicitly create an array from an initializer list
+
+ Creates a JSON array value from a given initializer list. That is, given a
+ list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the
+ initializer list is empty, the empty array `[]` is created.
+
+ @note This function is only needed to express two edge cases that cannot
+ be realized with the initializer list constructor (@ref
+ basic_json(initializer_list_t, bool, value_t)). These cases
+ are:
+ 1. creating an array whose elements are all pairs whose first element is a
+ string -- in this case, the initializer list constructor would create an
+ object, taking the first elements as keys
+ 2. creating an empty array -- passing the empty initializer list to the
+ initializer list constructor yields an empty object
+
+ @param[in] init initializer list with JSON values to create an array from
+ (optional)
+
+ @return JSON array value
+
+ @complexity Linear in the size of @a init.
+
+ @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+ changes to any JSON value.
+
+ @liveexample{The following code shows an example for the `array`
+ function.,array}
+
+ @sa @ref basic_json(initializer_list_t, bool, value_t) --
+ create a JSON value from an initializer list
+ @sa @ref object(initializer_list_t) -- create a JSON object
+ value from an initializer list
+
+ @since version 1.0.0
+ */
+ static basic_json array(initializer_list_t init = {})
+ {
+ return basic_json(init, false, value_t::array);
+ }
+
+ /*!
+ @brief explicitly create an object from an initializer list
+
+ Creates a JSON object value from a given initializer list. The initializer
+ lists elements must be pairs, and their first elements must be strings. If
+ the initializer list is empty, the empty object `{}` is created.
+
+ @note This function is only added for symmetry reasons. In contrast to the
+ related function @ref array(initializer_list_t), there are
+ no cases which can only be expressed by this function. That is, any
+ initializer list @a init can also be passed to the initializer list
+ constructor @ref basic_json(initializer_list_t, bool, value_t).
+
+ @param[in] init initializer list to create an object from (optional)
+
+ @return JSON object value
+
+ @throw type_error.301 if @a init is not a list of pairs whose first
+ elements are strings. In this case, no object can be created. When such a
+ value is passed to @ref basic_json(initializer_list_t, bool, value_t),
+ an array would have been created from the passed initializer list @a init.
+ See example below.
+
+ @complexity Linear in the size of @a init.
+
+ @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+ changes to any JSON value.
+
+ @liveexample{The following code shows an example for the `object`
+ function.,object}
+
+ @sa @ref basic_json(initializer_list_t, bool, value_t) --
+ create a JSON value from an initializer list
+ @sa @ref array(initializer_list_t) -- create a JSON array
+ value from an initializer list
+
+ @since version 1.0.0
+ */
+ static basic_json object(initializer_list_t init = {})
+ {
+ return basic_json(init, false, value_t::object);
+ }
+
+ /*!
+ @brief construct an array with count copies of given value
+
+ Constructs a JSON array value by creating @a cnt copies of a passed value.
+ In case @a cnt is `0`, an empty array is created.
+
+ @param[in] cnt the number of JSON copies of @a val to create
+ @param[in] val the JSON value to copy
+
+ @post `std::distance(begin(),end()) == cnt` holds.
+
+ @complexity Linear in @a cnt.
+
+ @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+ changes to any JSON value.
+
+ @liveexample{The following code shows examples for the @ref
+ basic_json(size_type\, const basic_json&)
+ constructor.,basic_json__size_type_basic_json}
+
+ @since version 1.0.0
+ */
+ basic_json(size_type cnt, const basic_json& val)
+ : m_type(value_t::array)
+ {
+ m_value.array = create<array_t>(cnt, val);
+ assert_invariant();
+ }
+
+ /*!
+ @brief construct a JSON container given an iterator range
+
+ Constructs the JSON value with the contents of the range `[first, last)`.
+ The semantics depends on the different types a JSON value can have:
+ - In case of a null type, invalid_iterator.206 is thrown.
+ - In case of other primitive types (number, boolean, or string), @a first
+ must be `begin()` and @a last must be `end()`. In this case, the value is
+ copied. Otherwise, invalid_iterator.204 is thrown.
+ - In case of structured types (array, object), the constructor behaves as
+ similar versions for `std::vector` or `std::map`; that is, a JSON array
+ or object is constructed from the values in the range.
+
+ @tparam InputIT an input iterator type (@ref iterator or @ref
+ const_iterator)
+
+ @param[in] first begin of the range to copy from (included)
+ @param[in] last end of the range to copy from (excluded)
+
+ @pre Iterators @a first and @a last must be initialized. **This
+ precondition is enforced with an assertion (see warning).** If
+ assertions are switched off, a violation of this precondition yields
+ undefined behavior.
+
+ @pre Range `[first, last)` is valid. Usually, this precondition cannot be
+ checked efficiently. Only certain edge cases are detected; see the
+ description of the exceptions below. A violation of this precondition
+ yields undefined behavior.
+
+ @warning A precondition is enforced with a runtime assertion that will
+ result in calling `std::abort` if this precondition is not met.
+ Assertions can be disabled by defining `NDEBUG` at compile time.
+ See https://en.cppreference.com/w/cpp/error/assert for more
+ information.
+
+ @throw invalid_iterator.201 if iterators @a first and @a last are not
+ compatible (i.e., do not belong to the same JSON value). In this case,
+ the range `[first, last)` is undefined.
+ @throw invalid_iterator.204 if iterators @a first and @a last belong to a
+ primitive type (number, boolean, or string), but @a first does not point
+ to the first element any more. In this case, the range `[first, last)` is
+ undefined. See example code below.
+ @throw invalid_iterator.206 if iterators @a first and @a last belong to a
+ null value. In this case, the range `[first, last)` is undefined.
+
+ @complexity Linear in distance between @a first and @a last.
+
+ @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+ changes to any JSON value.
+
+ @liveexample{The example below shows several ways to create JSON values by
+ specifying a subrange with iterators.,basic_json__InputIt_InputIt}
+
+ @since version 1.0.0
+ */
+ template<class InputIT, typename std::enable_if<
+ std::is_same<InputIT, typename basic_json_t::iterator>::value or
+ std::is_same<InputIT, typename basic_json_t::const_iterator>::value, int>::type = 0>
+ basic_json(InputIT first, InputIT last)
+ {
+ assert(first.m_object != nullptr);
+ assert(last.m_object != nullptr);
+
+ // make sure iterator fits the current value
+ if (JSON_UNLIKELY(first.m_object != last.m_object))
+ {
+ JSON_THROW(invalid_iterator::create(201, "iterators are not compatible"));
+ }
+
+ // copy type from first iterator
+ m_type = first.m_object->m_type;
+
+ // check if iterator range is complete for primitive values
+ switch (m_type)
+ {
+ case value_t::boolean:
+ case value_t::number_float:
+ case value_t::number_integer:
+ case value_t::number_unsigned:
+ case value_t::string:
+ {
+ if (JSON_UNLIKELY(not first.m_it.primitive_iterator.is_begin()
+ or not last.m_it.primitive_iterator.is_end()))
+ {
+ JSON_THROW(invalid_iterator::create(204, "iterators out of range"));
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ switch (m_type)
+ {
+ case value_t::number_integer:
+ {
+ m_value.number_integer = first.m_object->m_value.number_integer;
+ break;
+ }
+
+ case value_t::number_unsigned:
+ {
+ m_value.number_unsigned = first.m_object->m_value.number_unsigned;
+ break;
+ }
+
+ case value_t::number_float:
+ {
+ m_value.number_float = first.m_object->m_value.number_float;
+ break;
+ }
+
+ case value_t::boolean:
+ {
+ m_value.boolean = first.m_object->m_value.boolean;
+ break;
+ }
+
+ case value_t::string:
+ {
+ m_value = *first.m_object->m_value.string;
+ break;
+ }
+
+ case value_t::object:
+ {
+ m_value.object = create<object_t>(first.m_it.object_iterator,
+ last.m_it.object_iterator);
+ break;
+ }
+
+ case value_t::array:
+ {
+ m_value.array = create<array_t>(first.m_it.array_iterator,
+ last.m_it.array_iterator);
+ break;
+ }
+
+ default:
+ JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from " +
+ std::string(first.m_object->type_name())));
+ }
+
+ assert_invariant();
+ }
+
+
+ ///////////////////////////////////////
+ // other constructors and destructor //
+ ///////////////////////////////////////
+
+ /// @private
+ basic_json(const detail::json_ref<basic_json>& ref)
+ : basic_json(ref.moved_or_copied())
+ {}
+
+ /*!
+ @brief copy constructor
+
+ Creates a copy of a given JSON value.
+
+ @param[in] other the JSON value to copy
+
+ @post `*this == other`
+
+ @complexity Linear in the size of @a other.
+
+ @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+ changes to any JSON value.
+
+ @requirement This function helps `basic_json` satisfying the
+ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
+ requirements:
+ - The complexity is linear.
+ - As postcondition, it holds: `other == basic_json(other)`.
+
+ @liveexample{The following code shows an example for the copy
+ constructor.,basic_json__basic_json}
+
+ @since version 1.0.0
+ */
+ basic_json(const basic_json& other)
+ : m_type(other.m_type)
+ {
+ // check of passed value is valid
+ other.assert_invariant();
+
+ switch (m_type)
+ {
+ case value_t::object:
+ {
+ m_value = *other.m_value.object;
+ break;
+ }
+
+ case value_t::array:
+ {
+ m_value = *other.m_value.array;
+ break;
+ }
+
+ case value_t::string:
+ {
+ m_value = *other.m_value.string;
+ break;
+ }
+
+ case value_t::boolean:
+ {
+ m_value = other.m_value.boolean;
+ break;
+ }
+
+ case value_t::number_integer:
+ {
+ m_value = other.m_value.number_integer;
+ break;
+ }
+
+ case value_t::number_unsigned:
+ {
+ m_value = other.m_value.number_unsigned;
+ break;
+ }
+
+ case value_t::number_float:
+ {
+ m_value = other.m_value.number_float;
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ assert_invariant();
+ }
+
+ /*!
+ @brief move constructor
+
+ Move constructor. Constructs a JSON value with the contents of the given
+ value @a other using move semantics. It "steals" the resources from @a
+ other and leaves it as JSON null value.
+
+ @param[in,out] other value to move to this object
+
+ @post `*this` has the same value as @a other before the call.
+ @post @a other is a JSON null value.
+
+ @complexity Constant.
+
+ @exceptionsafety No-throw guarantee: this constructor never throws
+ exceptions.
+
+ @requirement This function helps `basic_json` satisfying the
+ [MoveConstructible](https://en.cppreference.com/w/cpp/named_req/MoveConstructible)
+ requirements.
+
+ @liveexample{The code below shows the move constructor explicitly called
+ via std::move.,basic_json__moveconstructor}
+
+ @since version 1.0.0
+ */
+ basic_json(basic_json&& other) noexcept
+ : m_type(std::move(other.m_type)),
+ m_value(std::move(other.m_value))
+ {
+ // check that passed value is valid
+ other.assert_invariant();
+
+ // invalidate payload
+ other.m_type = value_t::null;
+ other.m_value = {};
+
+ assert_invariant();
+ }
+
+ /*!
+ @brief copy assignment
+
+ Copy assignment operator. Copies a JSON value via the "copy and swap"
+ strategy: It is expressed in terms of the copy constructor, destructor,
+ and the `swap()` member function.
+
+ @param[in] other value to copy from
+
+ @complexity Linear.
+
+ @requirement This function helps `basic_json` satisfying the
+ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
+ requirements:
+ - The complexity is linear.
+
+ @liveexample{The code below shows and example for the copy assignment. It
+ creates a copy of value `a` which is then swapped with `b`. Finally\, the
+ copy of `a` (which is the null value after the swap) is
+ destroyed.,basic_json__copyassignment}
+
+ @since version 1.0.0
+ */
+ basic_json& operator=(basic_json other) noexcept (
+ std::is_nothrow_move_constructible<value_t>::value and
+ std::is_nothrow_move_assignable<value_t>::value and
+ std::is_nothrow_move_constructible<json_value>::value and
+ std::is_nothrow_move_assignable<json_value>::value
+ )
+ {
+ // check that passed value is valid
+ other.assert_invariant();
+
+ using std::swap;
+ swap(m_type, other.m_type);
+ swap(m_value, other.m_value);
+
+ assert_invariant();
+ return *this;
+ }
+
+ /*!
+ @brief destructor
+
+ Destroys the JSON value and frees all allocated memory.
+
+ @complexity Linear.
+
+ @requirement This function helps `basic_json` satisfying the
+ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
+ requirements:
+ - The complexity is linear.
+ - All stored elements are destroyed and all memory is freed.
+
+ @since version 1.0.0
+ */
+ ~basic_json() noexcept
+ {
+ assert_invariant();
+ m_value.destroy(m_type);
+ }
+
+ /// @}
+
+ public:
+ ///////////////////////
+ // object inspection //
+ ///////////////////////
+
+ /// @name object inspection
+ /// Functions to inspect the type of a JSON value.
+ /// @{
+
+ /*!
+ @brief serialization
+
+ Serialization function for JSON values. The function tries to mimic
+ Python's `json.dumps()` function, and currently supports its @a indent
+ and @a ensure_ascii parameters.
+
+ @param[in] indent If indent is nonnegative, then array elements and object
+ members will be pretty-printed with that indent level. An indent level of
+ `0` will only insert newlines. `-1` (the default) selects the most compact
+ representation.
+ @param[in] indent_char The character to use for indentation if @a indent is
+ greater than `0`. The default is ` ` (space).
+ @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters
+ in the output are escaped with `\uXXXX` sequences, and the result consists
+ of ASCII characters only.
+ @param[in] error_handler how to react on decoding errors; there are three
+ possible values: `strict` (throws and exception in case a decoding error
+ occurs; default), `replace` (replace invalid UTF-8 sequences with U+FFFD),
+ and `ignore` (ignore invalid UTF-8 sequences during serialization).
+
+ @return string containing the serialization of the JSON value
+
+ @throw type_error.316 if a string stored inside the JSON value is not
+ UTF-8 encoded
+
+ @complexity Linear.
+
+ @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+ changes in the JSON value.
+
+ @liveexample{The following example shows the effect of different @a indent\,
+ @a indent_char\, and @a ensure_ascii parameters to the result of the
+ serialization.,dump}
+
+ @see https://docs.python.org/2/library/json.html#json.dump
+
+ @since version 1.0.0; indentation character @a indent_char, option
+ @a ensure_ascii and exceptions added in version 3.0.0; error
+ handlers added in version 3.4.0.
+ */
+ string_t dump(const int indent = -1,
+ const char indent_char = ' ',
+ const bool ensure_ascii = false,
+ const error_handler_t error_handler = error_handler_t::strict) const
+ {
+ string_t result;
+ serializer s(detail::output_adapter<char, string_t>(result), indent_char, error_handler);
+
+ if (indent >= 0)
+ {
+ s.dump(*this, true, ensure_ascii, static_cast<unsigned int>(indent));
+ }
+ else
+ {
+ s.dump(*this, false, ensure_ascii, 0);
+ }
+
+ return result;
+ }
+
+ /*!
+ @brief return the type of the JSON value (explicit)
+
+ Return the type of the JSON value as a value from the @ref value_t
+ enumeration.
+
+ @return the type of the JSON value
+ Value type | return value
+ ------------------------- | -------------------------
+ null | value_t::null
+ boolean | value_t::boolean
+ string | value_t::string
+ number (integer) | value_t::number_integer
+ number (unsigned integer) | value_t::number_unsigned
+ number (floating-point) | value_t::number_float
+ object | value_t::object
+ array | value_t::array
+ discarded | value_t::discarded
+
+ @complexity Constant.
+
+ @exceptionsafety No-throw guarantee: this member function never throws
+ exceptions.
+
+ @liveexample{The following code exemplifies `type()` for all JSON
+ types.,type}
+
+ @sa @ref operator value_t() -- return the type of the JSON value (implicit)
+ @sa @ref type_name() -- return the type as string
+
+ @since version 1.0.0
+ */
+ constexpr value_t type() const noexcept
+ {
+ return m_type;
+ }
+
+ /*!
+ @brief return whether type is primitive
+
+ This function returns true if and only if the JSON type is primitive
+ (string, number, boolean, or null).
+
+ @return `true` if type is primitive (string, number, boolean, or null),
+ `false` otherwise.
+
+ @complexity Constant.
+
+ @exceptionsafety No-throw guarantee: this member function never throws
+ exceptions.
+
+ @liveexample{The following code exemplifies `is_primitive()` for all JSON
+ types.,is_primitive}
+
+ @sa @ref is_structured() -- returns whether JSON value is structured
+ @sa @ref is_null() -- returns whether JSON value is `null`
+ @sa @ref is_string() -- returns whether JSON value is a string
+ @sa @ref is_boolean() -- returns whether JSON value is a boolean
+ @sa @ref is_number() -- returns whether JSON value is a number
+
+ @since version 1.0.0
+ */
+ constexpr bool is_primitive() const noexcept
+ {
+ return is_null() or is_string() or is_boolean() or is_number();
+ }
+
+ /*!
+ @brief return whether type is structured
+
+ This function returns true if and only if the JSON type is structured
+ (array or object).
+
+ @return `true` if type is structured (array or object), `false` otherwise.
+
+ @complexity Constant.
+
+ @exceptionsafety No-throw guarantee: this member function never throws
+ exceptions.
+
+ @liveexample{The following code exemplifies `is_structured()` for all JSON
+ types.,is_structured}
+
+ @sa @ref is_primitive() -- returns whether value is primitive
+ @sa @ref is_array() -- returns whether value is an array
+ @sa @ref is_object() -- returns whether value is an object
+
+ @since version 1.0.0
+ */
+ constexpr bool is_structured() const noexcept
+ {
+ return is_array() or is_object();
+ }
+
+ /*!
+ @brief return whether value is null
+
+ This function returns true if and only if the JSON value is null.
+
+ @return `true` if type is null, `false` otherwise.
+
+ @complexity Constant.
+
+ @exceptionsafety No-throw guarantee: this member function never throws
+ exceptions.
+
+ @liveexample{The following code exemplifies `is_null()` for all JSON
+ types.,is_null}
+
+ @since version 1.0.0
+ */
+ constexpr bool is_null() const noexcept
+ {
+ return (m_type == value_t::null);
+ }
+
+ /*!
+ @brief return whether value is a boolean
+
+ This function returns true if and only if the JSON value is a boolean.
+
+ @return `true` if type is boolean, `false` otherwise.
+
+ @complexity Constant.
+
+ @exceptionsafety No-throw guarantee: this member function never throws
+ exceptions.
+
+ @liveexample{The following code exemplifies `is_boolean()` for all JSON
+ types.,is_boolean}
+
+ @since version 1.0.0
+ */
+ constexpr bool is_boolean() const noexcept
+ {
+ return (m_type == value_t::boolean);
+ }
+
+ /*!
+ @brief return whether value is a number
+
+ This function returns true if and only if the JSON value is a number. This
+ includes both integer (signed and unsigned) and floating-point values.
+
+ @return `true` if type is number (regardless whether integer, unsigned
+ integer or floating-type), `false` otherwise.
+
+ @complexity Constant.
+
+ @exceptionsafety No-throw guarantee: this member function never throws
+ exceptions.
+
+ @liveexample{The following code exemplifies `is_number()` for all JSON
+ types.,is_number}
+
+ @sa @ref is_number_integer() -- check if value is an integer or unsigned
+ integer number
+ @sa @ref is_number_unsigned() -- check if value is an unsigned integer
+ number
+ @sa @ref is_number_float() -- check if value is a floating-point number
+
+ @since version 1.0.0
+ */
+ constexpr bool is_number() const noexcept
+ {
+ return is_number_integer() or is_number_float();
+ }
+
+ /*!
+ @brief return whether value is an integer number
+
+ This function returns true if and only if the JSON value is a signed or
+ unsigned integer number. This excludes floating-point values.
+
+ @return `true` if type is an integer or unsigned integer number, `false`
+ otherwise.
+
+ @complexity Constant.
+
+ @exceptionsafety No-throw guarantee: this member function never throws
+ exceptions.
+
+ @liveexample{The following code exemplifies `is_number_integer()` for all
+ JSON types.,is_number_integer}
+
+ @sa @ref is_number() -- check if value is a number
+ @sa @ref is_number_unsigned() -- check if value is an unsigned integer
+ number
+ @sa @ref is_number_float() -- check if value is a floating-point number
+
+ @since version 1.0.0
+ */
+ constexpr bool is_number_integer() const noexcept
+ {
+ return (m_type == value_t::number_integer or m_type == value_t::number_unsigned);
+ }
+
+ /*!
+ @brief return whether value is an unsigned integer number
+
+ This function returns true if and only if the JSON value is an unsigned
+ integer number. This excludes floating-point and signed integer values.
+
+ @return `true` if type is an unsigned integer number, `false` otherwise.
+
+ @complexity Constant.
+
+ @exceptionsafety No-throw guarantee: this member function never throws
+ exceptions.
+
+ @liveexample{The following code exemplifies `is_number_unsigned()` for all
+ JSON types.,is_number_unsigned}
+
+ @sa @ref is_number() -- check if value is a number
+ @sa @ref is_number_integer() -- check if value is an integer or unsigned
+ integer number
+ @sa @ref is_number_float() -- check if value is a floating-point number
+
+ @since version 2.0.0
+ */
+ constexpr bool is_number_unsigned() const noexcept
+ {
+ return (m_type == value_t::number_unsigned);
+ }
+
+ /*!
+ @brief return whether value is a floating-point number
+
+ This function returns true if and only if the JSON value is a
+ floating-point number. This excludes signed and unsigned integer values.
+
+ @return `true` if type is a floating-point number, `false` otherwise.
+
+ @complexity Constant.
+
+ @exceptionsafety No-throw guarantee: this member function never throws
+ exceptions.
+
+ @liveexample{The following code exemplifies `is_number_float()` for all
+ JSON types.,is_number_float}
+
+ @sa @ref is_number() -- check if value is number
+ @sa @ref is_number_integer() -- check if value is an integer number
+ @sa @ref is_number_unsigned() -- check if value is an unsigned integer
+ number
+
+ @since version 1.0.0
+ */
+ constexpr bool is_number_float() const noexcept
+ {
+ return (m_type == value_t::number_float);
+ }
+
+ /*!
+ @brief return whether value is an object
+
+ This function returns true if and only if the JSON value is an object.
+
+ @return `true` if type is object, `false` otherwise.
+
+ @complexity Constant.
+
+ @exceptionsafety No-throw guarantee: this member function never throws
+ exceptions.
+
+ @liveexample{The following code exemplifies `is_object()` for all JSON
+ types.,is_object}
+
+ @since version 1.0.0
+ */
+ constexpr bool is_object() const noexcept
+ {
+ return (m_type == value_t::object);
+ }
+
+ /*!
+ @brief return whether value is an array
+
+ This function returns true if and only if the JSON value is an array.
+
+ @return `true` if type is array, `false` otherwise.
+
+ @complexity Constant.
+
+ @exceptionsafety No-throw guarantee: this member function never throws
+ exceptions.
+
+ @liveexample{The following code exemplifies `is_array()` for all JSON
+ types.,is_array}
+
+ @since version 1.0.0
+ */
+ constexpr bool is_array() const noexcept
+ {
+ return (m_type == value_t::array);
+ }
+
+ /*!
+ @brief return whether value is a string
+
+ This function returns true if and only if the JSON value is a string.
+
+ @return `true` if type is string, `false` otherwise.
+
+ @complexity Constant.
+
+ @exceptionsafety No-throw guarantee: this member function never throws
+ exceptions.
+
+ @liveexample{The following code exemplifies `is_string()` for all JSON
+ types.,is_string}
+
+ @since version 1.0.0
+ */
+ constexpr bool is_string() const noexcept
+ {
+ return (m_type == value_t::string);
+ }
+
+ /*!
+ @brief return whether value is discarded
+
+ This function returns true if and only if the JSON value was discarded
+ during parsing with a callback function (see @ref parser_callback_t).
+
+ @note This function will always be `false` for JSON values after parsing.
+ That is, discarded values can only occur during parsing, but will be
+ removed when inside a structured value or replaced by null in other cases.
+
+ @return `true` if type is discarded, `false` otherwise.
+
+ @complexity Constant.
+
+ @exceptionsafety No-throw guarantee: this member function never throws
+ exceptions.
+
+ @liveexample{The following code exemplifies `is_discarded()` for all JSON
+ types.,is_discarded}
+
+ @since version 1.0.0
+ */
+ constexpr bool is_discarded() const noexcept
+ {
+ return (m_type == value_t::discarded);
+ }
+
+ /*!
+ @brief return the type of the JSON value (implicit)
+
+ Implicitly return the type of the JSON value as a value from the @ref
+ value_t enumeration.
+
+ @return the type of the JSON value
+
+ @complexity Constant.
+
+ @exceptionsafety No-throw guarantee: this member function never throws
+ exceptions.
+
+ @liveexample{The following code exemplifies the @ref value_t operator for
+ all JSON types.,operator__value_t}
+
+ @sa @ref type() -- return the type of the JSON value (explicit)
+ @sa @ref type_name() -- return the type as string
+
+ @since version 1.0.0
+ */
+ constexpr operator value_t() const noexcept
+ {
+ return m_type;
+ }
+
+ /// @}
+
+ private:
+ //////////////////
+ // value access //
+ //////////////////
+
+ /// get a boolean (explicit)
+ boolean_t get_impl(boolean_t* /*unused*/) const
+ {
+ if (JSON_LIKELY(is_boolean()))
+ {
+ return m_value.boolean;
+ }
+
+ JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(type_name())));
+ }
+
+ /// get a pointer to the value (object)
+ object_t* get_impl_ptr(object_t* /*unused*/) noexcept
+ {
+ return is_object() ? m_value.object : nullptr;
+ }
+
+ /// get a pointer to the value (object)
+ constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept
+ {
+ return is_object() ? m_value.object : nullptr;
+ }
+
+ /// get a pointer to the value (array)
+ array_t* get_impl_ptr(array_t* /*unused*/) noexcept
+ {
+ return is_array() ? m_value.array : nullptr;
+ }
+
+ /// get a pointer to the value (array)
+ constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept
+ {
+ return is_array() ? m_value.array : nullptr;
+ }
+
+ /// get a pointer to the value (string)
+ string_t* get_impl_ptr(string_t* /*unused*/) noexcept
+ {
+ return is_string() ? m_value.string : nullptr;
+ }
+
+ /// get a pointer to the value (string)
+ constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept
+ {
+ return is_string() ? m_value.string : nullptr;
+ }
+
+ /// get a pointer to the value (boolean)
+ boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept
+ {
+ return is_boolean() ? &m_value.boolean : nullptr;
+ }
+
+ /// get a pointer to the value (boolean)
+ constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept
+ {
+ return is_boolean() ? &m_value.boolean : nullptr;
+ }
+
+ /// get a pointer to the value (integer number)
+ number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept
+ {
+ return is_number_integer() ? &m_value.number_integer : nullptr;
+ }
+
+ /// get a pointer to the value (integer number)
+ constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept
+ {
+ return is_number_integer() ? &m_value.number_integer : nullptr;
+ }
+
+ /// get a pointer to the value (unsigned number)
+ number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept
+ {
+ return is_number_unsigned() ? &m_value.number_unsigned : nullptr;
+ }
+
+ /// get a pointer to the value (unsigned number)
+ constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept
+ {
+ return is_number_unsigned() ? &m_value.number_unsigned : nullptr;
+ }
+
+ /// get a pointer to the value (floating-point number)
+ number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept
+ {
+ return is_number_float() ? &m_value.number_float : nullptr;
+ }
+
+ /// get a pointer to the value (floating-point number)
+ constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept
+ {
+ return is_number_float() ? &m_value.number_float : nullptr;
+ }
+
+ /*!
+ @brief helper function to implement get_ref()
+
+ This function helps to implement get_ref() without code duplication for
+ const and non-const overloads
+
+ @tparam ThisType will be deduced as `basic_json` or `const basic_json`
+
+ @throw type_error.303 if ReferenceType does not match underlying value
+ type of the current JSON
+ */
+ template<typename ReferenceType, typename ThisType>
+ static ReferenceType get_ref_impl(ThisType& obj)
+ {
+ // delegate the call to get_ptr<>()
+ auto ptr = obj.template get_ptr<typename std::add_pointer<ReferenceType>::type>();
+
+ if (JSON_LIKELY(ptr != nullptr))
+ {
+ return *ptr;
+ }
+
+ JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is " + std::string(obj.type_name())));
+ }
+
+ public:
+ /// @name value access
+ /// Direct access to the stored value of a JSON value.
+ /// @{
+
+ /*!
+ @brief get special-case overload
+
+ This overloads avoids a lot of template boilerplate, it can be seen as the
+ identity method
+
+ @tparam BasicJsonType == @ref basic_json
+
+ @return a copy of *this
+
+ @complexity Constant.
+
+ @since version 2.1.0
+ */
+ template<typename BasicJsonType, detail::enable_if_t<
+ std::is_same<typename std::remove_const<BasicJsonType>::type, basic_json_t>::value,
+ int> = 0>
+ basic_json get() const
+ {
+ return *this;
+ }
+
+ /*!
+ @brief get special-case overload
+
+ This overloads converts the current @ref basic_json in a different
+ @ref basic_json type
+
+ @tparam BasicJsonType == @ref basic_json
+
+ @return a copy of *this, converted into @tparam BasicJsonType
+
+ @complexity Depending on the implementation of the called `from_json()`
+ method.
+
+ @since version 3.2.0
+ */
+ template<typename BasicJsonType, detail::enable_if_t<
+ not std::is_same<BasicJsonType, basic_json>::value and
+ detail::is_basic_json<BasicJsonType>::value, int> = 0>
+ BasicJsonType get() const
+ {
+ return *this;
+ }
+
+ /*!
+ @brief get a value (explicit)
+
+ Explicit type conversion between the JSON value and a compatible value
+ which is [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible)
+ and [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible).
+ The value is converted by calling the @ref json_serializer<ValueType>
+ `from_json()` method.
+
+ The function is equivalent to executing
+ @code {.cpp}
+ ValueType ret;
+ JSONSerializer<ValueType>::from_json(*this, ret);
+ return ret;
+ @endcode
+
+ This overloads is chosen if:
+ - @a ValueType is not @ref basic_json,
+ - @ref json_serializer<ValueType> has a `from_json()` method of the form
+ `void from_json(const basic_json&, ValueType&)`, and
+ - @ref json_serializer<ValueType> does not have a `from_json()` method of
+ the form `ValueType from_json(const basic_json&)`
+
+ @tparam ValueTypeCV the provided value type
+ @tparam ValueType the returned value type
+
+ @return copy of the JSON value, converted to @a ValueType
+
+ @throw what @ref json_serializer<ValueType> `from_json()` method throws
+
+ @liveexample{The example below shows several conversions from JSON values
+ to other types. There a few things to note: (1) Floating-point numbers can
+ be converted to integers\, (2) A JSON array can be converted to a standard
+ `std::vector<short>`\, (3) A JSON object can be converted to C++
+ associative containers such as `std::unordered_map<std::string\,
+ json>`.,get__ValueType_const}
+
+ @since version 2.1.0
+ */
+ template<typename ValueTypeCV, typename ValueType = detail::uncvref_t<ValueTypeCV>,
+ detail::enable_if_t <
+ not detail::is_basic_json<ValueType>::value and
+ detail::has_from_json<basic_json_t, ValueType>::value and
+ not detail::has_non_default_from_json<basic_json_t, ValueType>::value,
+ int> = 0>
+ ValueType get() const noexcept(noexcept(
+ JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), std::declval<ValueType&>())))
+ {
+ // we cannot static_assert on ValueTypeCV being non-const, because
+ // there is support for get<const basic_json_t>(), which is why we
+ // still need the uncvref
+ static_assert(not std::is_reference<ValueTypeCV>::value,
+ "get() cannot be used with reference types, you might want to use get_ref()");
+ static_assert(std::is_default_constructible<ValueType>::value,
+ "types must be DefaultConstructible when used with get()");
+
+ ValueType ret;
+ JSONSerializer<ValueType>::from_json(*this, ret);
+ return ret;
+ }
+
+ /*!
+ @brief get a value (explicit); special case
+
+ Explicit type conversion between the JSON value and a compatible value
+ which is **not** [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible)
+ and **not** [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible).
+ The value is converted by calling the @ref json_serializer<ValueType>
+ `from_json()` method.
+
+ The function is equivalent to executing
+ @code {.cpp}
+ return JSONSerializer<ValueTypeCV>::from_json(*this);
+ @endcode
+
+ This overloads is chosen if:
+ - @a ValueType is not @ref basic_json and
+ - @ref json_serializer<ValueType> has a `from_json()` method of the form
+ `ValueType from_json(const basic_json&)`
+
+ @note If @ref json_serializer<ValueType> has both overloads of
+ `from_json()`, this one is chosen.
+
+ @tparam ValueTypeCV the provided value type
+ @tparam ValueType the returned value type
+
+ @return copy of the JSON value, converted to @a ValueType
+
+ @throw what @ref json_serializer<ValueType> `from_json()` method throws
+
+ @since version 2.1.0
+ */
+ template<typename ValueTypeCV, typename ValueType = detail::uncvref_t<ValueTypeCV>,
+ detail::enable_if_t<not std::is_same<basic_json_t, ValueType>::value and
+ detail::has_non_default_from_json<basic_json_t, ValueType>::value,
+ int> = 0>
+ ValueType get() const noexcept(noexcept(
+ JSONSerializer<ValueTypeCV>::from_json(std::declval<const basic_json_t&>())))
+ {
+ static_assert(not std::is_reference<ValueTypeCV>::value,
+ "get() cannot be used with reference types, you might want to use get_ref()");
+ return JSONSerializer<ValueTypeCV>::from_json(*this);
+ }
+
+ /*!
+ @brief get a value (explicit)
+
+ Explicit type conversion between the JSON value and a compatible value.
+ The value is filled into the input parameter by calling the @ref json_serializer<ValueType>
+ `from_json()` method.
+
+ The function is equivalent to executing
+ @code {.cpp}
+ ValueType v;
+ JSONSerializer<ValueType>::from_json(*this, v);
+ @endcode
+
+ This overloads is chosen if:
+ - @a ValueType is not @ref basic_json,
+ - @ref json_serializer<ValueType> has a `from_json()` method of the form
+ `void from_json(const basic_json&, ValueType&)`, and
+
+ @tparam ValueType the input parameter type.
+
+ @return the input parameter, allowing chaining calls.
+
+ @throw what @ref json_serializer<ValueType> `from_json()` method throws
+
+ @liveexample{The example below shows several conversions from JSON values
+ to other types. There a few things to note: (1) Floating-point numbers can
+ be converted to integers\, (2) A JSON array can be converted to a standard
+ `std::vector<short>`\, (3) A JSON object can be converted to C++
+ associative containers such as `std::unordered_map<std::string\,
+ json>`.,get_to}
+
+ @since version 3.3.0
+ */
+ template<typename ValueType,
+ detail::enable_if_t <
+ not detail::is_basic_json<ValueType>::value and
+ detail::has_from_json<basic_json_t, ValueType>::value,
+ int> = 0>
+ ValueType & get_to(ValueType& v) const noexcept(noexcept(
+ JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), v)))
+ {
+ JSONSerializer<ValueType>::from_json(*this, v);
+ return v;
+ }
+
+
+ /*!
+ @brief get a pointer value (implicit)
+
+ Implicit pointer access to the internally stored JSON value. No copies are
+ made.
+
+ @warning Writing data to the pointee of the result yields an undefined
+ state.
+
+ @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref
+ object_t, @ref string_t, @ref boolean_t, @ref number_integer_t,
+ @ref number_unsigned_t, or @ref number_float_t. Enforced by a static
+ assertion.
+
+ @return pointer to the internally stored JSON value if the requested
+ pointer type @a PointerType fits to the JSON value; `nullptr` otherwise
+
+ @complexity Constant.
+
+ @liveexample{The example below shows how pointers to internal values of a
+ JSON value can be requested. Note that no type conversions are made and a
+ `nullptr` is returned if the value and the requested pointer type does not
+ match.,get_ptr}
+
+ @since version 1.0.0
+ */
+ template<typename PointerType, typename std::enable_if<
+ std::is_pointer<PointerType>::value, int>::type = 0>
+ auto get_ptr() noexcept -> decltype(std::declval<basic_json_t&>().get_impl_ptr(std::declval<PointerType>()))
+ {
+ // delegate the call to get_impl_ptr<>()
+ return get_impl_ptr(static_cast<PointerType>(nullptr));
+ }
+
+ /*!
+ @brief get a pointer value (implicit)
+ @copydoc get_ptr()
+ */
+ template<typename PointerType, typename std::enable_if<
+ std::is_pointer<PointerType>::value and
+ std::is_const<typename std::remove_pointer<PointerType>::type>::value, int>::type = 0>
+ constexpr auto get_ptr() const noexcept -> decltype(std::declval<const basic_json_t&>().get_impl_ptr(std::declval<PointerType>()))
+ {
+ // delegate the call to get_impl_ptr<>() const
+ return get_impl_ptr(static_cast<PointerType>(nullptr));
+ }
+
+ /*!
+ @brief get a pointer value (explicit)
+
+ Explicit pointer access to the internally stored JSON value. No copies are
+ made.
+
+ @warning The pointer becomes invalid if the underlying JSON object
+ changes.
+
+ @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref
+ object_t, @ref string_t, @ref boolean_t, @ref number_integer_t,
+ @ref number_unsigned_t, or @ref number_float_t.
+
+ @return pointer to the internally stored JSON value if the requested
+ pointer type @a PointerType fits to the JSON value; `nullptr` otherwise
+
+ @complexity Constant.
+
+ @liveexample{The example below shows how pointers to internal values of a
+ JSON value can be requested. Note that no type conversions are made and a
+ `nullptr` is returned if the value and the requested pointer type does not
+ match.,get__PointerType}
+
+ @sa @ref get_ptr() for explicit pointer-member access
+
+ @since version 1.0.0
+ */
+ template<typename PointerType, typename std::enable_if<
+ std::is_pointer<PointerType>::value, int>::type = 0>
+ auto get() noexcept -> decltype(std::declval<basic_json_t&>().template get_ptr<PointerType>())
+ {
+ // delegate the call to get_ptr
+ return get_ptr<PointerType>();
+ }
+
+ /*!
+ @brief get a pointer value (explicit)
+ @copydoc get()
+ */
+ template<typename PointerType, typename std::enable_if<
+ std::is_pointer<PointerType>::value, int>::type = 0>
+ constexpr auto get() const noexcept -> decltype(std::declval<const basic_json_t&>().template get_ptr<PointerType>())
+ {
+ // delegate the call to get_ptr
+ return get_ptr<PointerType>();
+ }
+
+ /*!
+ @brief get a reference value (implicit)
+
+ Implicit reference access to the internally stored JSON value. No copies
+ are made.
+
+ @warning Writing data to the referee of the result yields an undefined
+ state.
+
+ @tparam ReferenceType reference type; must be a reference to @ref array_t,
+ @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or
+ @ref number_float_t. Enforced by static assertion.
+
+ @return reference to the internally stored JSON value if the requested
+ reference type @a ReferenceType fits to the JSON value; throws
+ type_error.303 otherwise
+
+ @throw type_error.303 in case passed type @a ReferenceType is incompatible
+ with the stored JSON value; see example below
+
+ @complexity Constant.
+
+ @liveexample{The example shows several calls to `get_ref()`.,get_ref}
+
+ @since version 1.1.0
+ */
+ template<typename ReferenceType, typename std::enable_if<
+ std::is_reference<ReferenceType>::value, int>::type = 0>
+ ReferenceType get_ref()
+ {
+ // delegate call to get_ref_impl
+ return get_ref_impl<ReferenceType>(*this);
+ }
+
+ /*!
+ @brief get a reference value (implicit)
+ @copydoc get_ref()
+ */
+ template<typename ReferenceType, typename std::enable_if<
+ std::is_reference<ReferenceType>::value and
+ std::is_const<typename std::remove_reference<ReferenceType>::type>::value, int>::type = 0>
+ ReferenceType get_ref() const
+ {
+ // delegate call to get_ref_impl
+ return get_ref_impl<ReferenceType>(*this);
+ }
+
+ /*!
+ @brief get a value (implicit)
+
+ Implicit type conversion between the JSON value and a compatible value.
+ The call is realized by calling @ref get() const.
+
+ @tparam ValueType non-pointer type compatible to the JSON value, for
+ instance `int` for JSON integer numbers, `bool` for JSON booleans, or
+ `std::vector` types for JSON arrays. The character type of @ref string_t
+ as well as an initializer list of this type is excluded to avoid
+ ambiguities as these types implicitly convert to `std::string`.
+
+ @return copy of the JSON value, converted to type @a ValueType
+
+ @throw type_error.302 in case passed type @a ValueType is incompatible
+ to the JSON value type (e.g., the JSON value is of type boolean, but a
+ string is requested); see example below
+
+ @complexity Linear in the size of the JSON value.
+
+ @liveexample{The example below shows several conversions from JSON values
+ to other types. There a few things to note: (1) Floating-point numbers can
+ be converted to integers\, (2) A JSON array can be converted to a standard
+ `std::vector<short>`\, (3) A JSON object can be converted to C++
+ associative containers such as `std::unordered_map<std::string\,
+ json>`.,operator__ValueType}
+
+ @since version 1.0.0
+ */
+ template < typename ValueType, typename std::enable_if <
+ not std::is_pointer<ValueType>::value and
+ not std::is_same<ValueType, detail::json_ref<basic_json>>::value and
+ not std::is_same<ValueType, typename string_t::value_type>::value and
+ not detail::is_basic_json<ValueType>::value
+
+#ifndef _MSC_VER // fix for issue #167 operator<< ambiguity under VS2015
+ and not std::is_same<ValueType, std::initializer_list<typename string_t::value_type>>::value
+#if defined(JSON_HAS_CPP_17) && defined(_MSC_VER) and _MSC_VER <= 1914
+ and not std::is_same<ValueType, typename std::string_view>::value
+#endif
+#endif
+ and detail::is_detected<detail::get_template_function, const basic_json_t&, ValueType>::value
+ , int >::type = 0 >
+ operator ValueType() const
+ {
+ // delegate the call to get<>() const
+ return get<ValueType>();
+ }
+
+ /// @}
+
+
+ ////////////////////
+ // element access //
+ ////////////////////
+
+ /// @name element access
+ /// Access to the JSON value.
+ /// @{
+
+ /*!
+ @brief access specified array element with bounds checking
+
+ Returns a reference to the element at specified location @a idx, with
+ bounds checking.
+
+ @param[in] idx index of the element to access
+
+ @return reference to the element at index @a idx
+
+ @throw type_error.304 if the JSON value is not an array; in this case,
+ calling `at` with an index makes no sense. See example below.
+ @throw out_of_range.401 if the index @a idx is out of range of the array;
+ that is, `idx >= size()`. See example below.
+
+ @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+ changes in the JSON value.
+
+ @complexity Constant.
+
+ @since version 1.0.0
+
+ @liveexample{The example below shows how array elements can be read and
+ written using `at()`. It also demonstrates the different exceptions that
+ can be thrown.,at__size_type}
+ */
+ reference at(size_type idx)
+ {
+ // at only works for arrays
+ if (JSON_LIKELY(is_array()))
+ {
+ JSON_TRY
+ {
+ return m_value.array->at(idx);
+ }
+ JSON_CATCH (std::out_of_range&)
+ {
+ // create better exception explanation
+ JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range"));
+ }
+ }
+ else
+ {
+ JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name())));
+ }
+ }
+
+ /*!
+ @brief access specified array element with bounds checking
+
+ Returns a const reference to the element at specified location @a idx,
+ with bounds checking.
+
+ @param[in] idx index of the element to access
+
+ @return const reference to the element at index @a idx
+
+ @throw type_error.304 if the JSON value is not an array; in this case,
+ calling `at` with an index makes no sense. See example below.
+ @throw out_of_range.401 if the index @a idx is out of range of the array;
+ that is, `idx >= size()`. See example below.
+
+ @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+ changes in the JSON value.
+
+ @complexity Constant.
+
+ @since version 1.0.0
+
+ @liveexample{The example below shows how array elements can be read using
+ `at()`. It also demonstrates the different exceptions that can be thrown.,
+ at__size_type_const}
+ */
+ const_reference at(size_type idx) const
+ {
+ // at only works for arrays
+ if (JSON_LIKELY(is_array()))
+ {
+ JSON_TRY
+ {
+ return m_value.array->at(idx);
+ }
+ JSON_CATCH (std::out_of_range&)
+ {
+ // create better exception explanation
+ JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range"));
+ }
+ }
+ else
+ {
+ JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name())));
+ }
+ }
+
+ /*!
+ @brief access specified object element with bounds checking
+
+ Returns a reference to the element at with specified key @a key, with
+ bounds checking.
+
+ @param[in] key key of the element to access
+
+ @return reference to the element at key @a key
+
+ @throw type_error.304 if the JSON value is not an object; in this case,
+ calling `at` with a key makes no sense. See example below.
+ @throw out_of_range.403 if the key @a key is is not stored in the object;
+ that is, `find(key) == end()`. See example below.
+
+ @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+ changes in the JSON value.
+
+ @complexity Logarithmic in the size of the container.
+
+ @sa @ref operator[](const typename object_t::key_type&) for unchecked
+ access by reference
+ @sa @ref value() for access by value with a default value
+
+ @since version 1.0.0
+
+ @liveexample{The example below shows how object elements can be read and
+ written using `at()`. It also demonstrates the different exceptions that
+ can be thrown.,at__object_t_key_type}
+ */
+ reference at(const typename object_t::key_type& key)
+ {
+ // at only works for objects
+ if (JSON_LIKELY(is_object()))
+ {
+ JSON_TRY
+ {
+ return m_value.object->at(key);
+ }
+ JSON_CATCH (std::out_of_range&)
+ {
+ // create better exception explanation
+ JSON_THROW(out_of_range::create(403, "key '" + key + "' not found"));
+ }
+ }
+ else
+ {
+ JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name())));
+ }
+ }
+
+ /*!
+ @brief access specified object element with bounds checking
+
+ Returns a const reference to the element at with specified key @a key,
+ with bounds checking.
+
+ @param[in] key key of the element to access
+
+ @return const reference to the element at key @a key
+
+ @throw type_error.304 if the JSON value is not an object; in this case,
+ calling `at` with a key makes no sense. See example below.
+ @throw out_of_range.403 if the key @a key is is not stored in the object;
+ that is, `find(key) == end()`. See example below.
+
+ @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+ changes in the JSON value.
+
+ @complexity Logarithmic in the size of the container.
+
+ @sa @ref operator[](const typename object_t::key_type&) for unchecked
+ access by reference
+ @sa @ref value() for access by value with a default value
+
+ @since version 1.0.0
+
+ @liveexample{The example below shows how object elements can be read using
+ `at()`. It also demonstrates the different exceptions that can be thrown.,
+ at__object_t_key_type_const}
+ */
+ const_reference at(const typename object_t::key_type& key) const
+ {
+ // at only works for objects
+ if (JSON_LIKELY(is_object()))
+ {
+ JSON_TRY
+ {
+ return m_value.object->at(key);
+ }
+ JSON_CATCH (std::out_of_range&)
+ {
+ // create better exception explanation
+ JSON_THROW(out_of_range::create(403, "key '" + key + "' not found"));
+ }
+ }
+ else
+ {
+ JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name())));
+ }
+ }
+
+ /*!
+ @brief access specified array element
+
+ Returns a reference to the element at specified location @a idx.
+
+ @note If @a idx is beyond the range of the array (i.e., `idx >= size()`),
+ then the array is silently filled up with `null` values to make `idx` a
+ valid reference to the last stored element.
+
+ @param[in] idx index of the element to access
+
+ @return reference to the element at index @a idx
+
+ @throw type_error.305 if the JSON value is not an array or null; in that
+ cases, using the [] operator with an index makes no sense.
+
+ @complexity Constant if @a idx is in the range of the array. Otherwise
+ linear in `idx - size()`.
+
+ @liveexample{The example below shows how array elements can be read and
+ written using `[]` operator. Note the addition of `null`
+ values.,operatorarray__size_type}
+
+ @since version 1.0.0
+ */
+ reference operator[](size_type idx)
+ {
+ // implicitly convert null value to an empty array
+ if (is_null())
+ {
+ m_type = value_t::array;
+ m_value.array = create<array_t>();
+ assert_invariant();
+ }
+
+ // operator[] only works for arrays
+ if (JSON_LIKELY(is_array()))
+ {
+ // fill up array with null values if given idx is outside range
+ if (idx >= m_value.array->size())
+ {
+ m_value.array->insert(m_value.array->end(),
+ idx - m_value.array->size() + 1,
+ basic_json());
+ }
+
+ return m_value.array->operator[](idx);
+ }
+
+ JSON_THROW(type_error::create(305, "cannot use operator[] with a numeric argument with " + std::string(type_name())));
+ }
+
+ /*!
+ @brief access specified array element
+
+ Returns a const reference to the element at specified location @a idx.
+
+ @param[in] idx index of the element to access
+
+ @return const reference to the element at index @a idx
+
+ @throw type_error.305 if the JSON value is not an array; in that case,
+ using the [] operator with an index makes no sense.
+
+ @complexity Constant.
+
+ @liveexample{The example below shows how array elements can be read using
+ the `[]` operator.,operatorarray__size_type_const}
+
+ @since version 1.0.0
+ */
+ const_reference operator[](size_type idx) const
+ {
+ // const operator[] only works for arrays
+ if (JSON_LIKELY(is_array()))
+ {
+ return m_value.array->operator[](idx);
+ }
+
+ JSON_THROW(type_error::create(305, "cannot use operator[] with a numeric argument with " + std::string(type_name())));
+ }
+
+ /*!
+ @brief access specified object element
+
+ Returns a reference to the element at with specified key @a key.
+
+ @note If @a key is not found in the object, then it is silently added to
+ the object and filled with a `null` value to make `key` a valid reference.
+ In case the value was `null` before, it is converted to an object.
+
+ @param[in] key key of the element to access
+
+ @return reference to the element at key @a key
+
+ @throw type_error.305 if the JSON value is not an object or null; in that
+ cases, using the [] operator with a key makes no sense.
+
+ @complexity Logarithmic in the size of the container.
+
+ @liveexample{The example below shows how object elements can be read and
+ written using the `[]` operator.,operatorarray__key_type}
+
+ @sa @ref at(const typename object_t::key_type&) for access by reference
+ with range checking
+ @sa @ref value() for access by value with a default value
+
+ @since version 1.0.0
+ */
+ reference operator[](const typename object_t::key_type& key)
+ {
+ // implicitly convert null value to an empty object
+ if (is_null())
+ {
+ m_type = value_t::object;
+ m_value.object = create<object_t>();
+ assert_invariant();
+ }
+
+ // operator[] only works for objects
+ if (JSON_LIKELY(is_object()))
+ {
+ return m_value.object->operator[](key);
+ }
+
+ JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name())));
+ }
+
+ /*!
+ @brief read-only access specified object element
+
+ Returns a const reference to the element at with specified key @a key. No
+ bounds checking is performed.
+
+ @warning If the element with key @a key does not exist, the behavior is
+ undefined.
+
+ @param[in] key key of the element to access
+
+ @return const reference to the element at key @a key
+
+ @pre The element with key @a key must exist. **This precondition is
+ enforced with an assertion.**
+
+ @throw type_error.305 if the JSON value is not an object; in that case,
+ using the [] operator with a key makes no sense.
+
+ @complexity Logarithmic in the size of the container.
+
+ @liveexample{The example below shows how object elements can be read using
+ the `[]` operator.,operatorarray__key_type_const}
+
+ @sa @ref at(const typename object_t::key_type&) for access by reference
+ with range checking
+ @sa @ref value() for access by value with a default value
+
+ @since version 1.0.0
+ */
+ const_reference operator[](const typename object_t::key_type& key) const
+ {
+ // const operator[] only works for objects
+ if (JSON_LIKELY(is_object()))
+ {
+ assert(m_value.object->find(key) != m_value.object->end());
+ return m_value.object->find(key)->second;
+ }
+
+ JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name())));
+ }
+
+ /*!
+ @brief access specified object element
+
+ Returns a reference to the element at with specified key @a key.
+
+ @note If @a key is not found in the object, then it is silently added to
+ the object and filled with a `null` value to make `key` a valid reference.
+ In case the value was `null` before, it is converted to an object.
+
+ @param[in] key key of the element to access
+
+ @return reference to the element at key @a key
+
+ @throw type_error.305 if the JSON value is not an object or null; in that
+ cases, using the [] operator with a key makes no sense.
+
+ @complexity Logarithmic in the size of the container.
+
+ @liveexample{The example below shows how object elements can be read and
+ written using the `[]` operator.,operatorarray__key_type}
+
+ @sa @ref at(const typename object_t::key_type&) for access by reference
+ with range checking
+ @sa @ref value() for access by value with a default value
+
+ @since version 1.1.0
+ */
+ template<typename T>
+ reference operator[](T* key)
+ {
+ // implicitly convert null to object
+ if (is_null())
+ {
+ m_type = value_t::object;
+ m_value = value_t::object;
+ assert_invariant();
+ }
+
+ // at only works for objects
+ if (JSON_LIKELY(is_object()))
+ {
+ return m_value.object->operator[](key);
+ }
+
+ JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name())));
+ }
+
+ /*!
+ @brief read-only access specified object element
+
+ Returns a const reference to the element at with specified key @a key. No
+ bounds checking is performed.
+
+ @warning If the element with key @a key does not exist, the behavior is
+ undefined.
+
+ @param[in] key key of the element to access
+
+ @return const reference to the element at key @a key
+
+ @pre The element with key @a key must exist. **This precondition is
+ enforced with an assertion.**
+
+ @throw type_error.305 if the JSON value is not an object; in that case,
+ using the [] operator with a key makes no sense.
+
+ @complexity Logarithmic in the size of the container.
+
+ @liveexample{The example below shows how object elements can be read using
+ the `[]` operator.,operatorarray__key_type_const}
+
+ @sa @ref at(const typename object_t::key_type&) for access by reference
+ with range checking
+ @sa @ref value() for access by value with a default value
+
+ @since version 1.1.0
+ */
+ template<typename T>
+ const_reference operator[](T* key) const
+ {
+ // at only works for objects
+ if (JSON_LIKELY(is_object()))
+ {
+ assert(m_value.object->find(key) != m_value.object->end());
+ return m_value.object->find(key)->second;
+ }
+
+ JSON_THROW(type_error::create(305, "cannot use operator[] with a string argument with " + std::string(type_name())));
+ }
+
+ /*!
+ @brief access specified object element with default value
+
+ Returns either a copy of an object's element at the specified key @a key
+ or a given default value if no element with key @a key exists.
+
+ The function is basically equivalent to executing
+ @code {.cpp}
+ try {
+ return at(key);
+ } catch(out_of_range) {
+ return default_value;
+ }
+ @endcode
+
+ @note Unlike @ref at(const typename object_t::key_type&), this function
+ does not throw if the given key @a key was not found.
+
+ @note Unlike @ref operator[](const typename object_t::key_type& key), this
+ function does not implicitly add an element to the position defined by @a
+ key. This function is furthermore also applicable to const objects.
+
+ @param[in] key key of the element to access
+ @param[in] default_value the value to return if @a key is not found
+
+ @tparam ValueType type compatible to JSON values, for instance `int` for
+ JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for
+ JSON arrays. Note the type of the expected value at @a key and the default
+ value @a default_value must be compatible.
+
+ @return copy of the element at key @a key or @a default_value if @a key
+ is not found
+
+ @throw type_error.306 if the JSON value is not an object; in that case,
+ using `value()` with a key makes no sense.
+
+ @complexity Logarithmic in the size of the container.
+
+ @liveexample{The example below shows how object elements can be queried
+ with a default value.,basic_json__value}
+
+ @sa @ref at(const typename object_t::key_type&) for access by reference
+ with range checking
+ @sa @ref operator[](const typename object_t::key_type&) for unchecked
+ access by reference
+
+ @since version 1.0.0
+ */
+ template<class ValueType, typename std::enable_if<
+ std::is_convertible<basic_json_t, ValueType>::value, int>::type = 0>
+ ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const
+ {
+ // at only works for objects
+ if (JSON_LIKELY(is_object()))
+ {
+ // if key is found, return value and given default value otherwise
+ const auto it = find(key);
+ if (it != end())
+ {
+ return *it;
+ }
+
+ return default_value;
+ }
+
+ JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name())));
+ }
+
+ /*!
+ @brief overload for a default value of type const char*
+ @copydoc basic_json::value(const typename object_t::key_type&, const ValueType&) const
+ */
+ string_t value(const typename object_t::key_type& key, const char* default_value) const
+ {
+ return value(key, string_t(default_value));
+ }
+
+ /*!
+ @brief access specified object element via JSON Pointer with default value
+
+ Returns either a copy of an object's element at the specified key @a key
+ or a given default value if no element with key @a key exists.
+
+ The function is basically equivalent to executing
+ @code {.cpp}
+ try {
+ return at(ptr);
+ } catch(out_of_range) {
+ return default_value;
+ }
+ @endcode
+
+ @note Unlike @ref at(const json_pointer&), this function does not throw
+ if the given key @a key was not found.
+
+ @param[in] ptr a JSON pointer to the element to access
+ @param[in] default_value the value to return if @a ptr found no value
+
+ @tparam ValueType type compatible to JSON values, for instance `int` for
+ JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for
+ JSON arrays. Note the type of the expected value at @a key and the default
+ value @a default_value must be compatible.
+
+ @return copy of the element at key @a key or @a default_value if @a key
+ is not found
+
+ @throw type_error.306 if the JSON value is not an object; in that case,
+ using `value()` with a key makes no sense.
+
+ @complexity Logarithmic in the size of the container.
+
+ @liveexample{The example below shows how object elements can be queried
+ with a default value.,basic_json__value_ptr}
+
+ @sa @ref operator[](const json_pointer&) for unchecked access by reference
+
+ @since version 2.0.2
+ */
+ template<class ValueType, typename std::enable_if<
+ std::is_convertible<basic_json_t, ValueType>::value, int>::type = 0>
+ ValueType value(const json_pointer& ptr, const ValueType& default_value) const
+ {
+ // at only works for objects
+ if (JSON_LIKELY(is_object()))
+ {
+ // if pointer resolves a value, return it or use default value
+ JSON_TRY
+ {
+ return ptr.get_checked(this);
+ }
+ JSON_INTERNAL_CATCH (out_of_range&)
+ {
+ return default_value;
+ }
+ }
+
+ JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name())));
+ }
+
+ /*!
+ @brief overload for a default value of type const char*
+ @copydoc basic_json::value(const json_pointer&, ValueType) const
+ */
+ string_t value(const json_pointer& ptr, const char* default_value) const
+ {
+ return value(ptr, string_t(default_value));
+ }
+
+ /*!
+ @brief access the first element
+
+ Returns a reference to the first element in the container. For a JSON
+ container `c`, the expression `c.front()` is equivalent to `*c.begin()`.
+
+ @return In case of a structured type (array or object), a reference to the
+ first element is returned. In case of number, string, or boolean values, a
+ reference to the value is returned.
+
+ @complexity Constant.
+
+ @pre The JSON value must not be `null` (would throw `std::out_of_range`)
+ or an empty array or object (undefined behavior, **guarded by
+ assertions**).
+ @post The JSON value remains unchanged.
+
+ @throw invalid_iterator.214 when called on `null` value
+
+ @liveexample{The following code shows an example for `front()`.,front}
+
+ @sa @ref back() -- access the last element
+
+ @since version 1.0.0
+ */
+ reference front()
+ {
+ return *begin();
+ }
+
+ /*!
+ @copydoc basic_json::front()
+ */
+ const_reference front() const
+ {
+ return *cbegin();
+ }
+
+ /*!
+ @brief access the last element
+
+ Returns a reference to the last element in the container. For a JSON
+ container `c`, the expression `c.back()` is equivalent to
+ @code {.cpp}
+ auto tmp = c.end();
+ --tmp;
+ return *tmp;
+ @endcode
+
+ @return In case of a structured type (array or object), a reference to the
+ last element is returned. In case of number, string, or boolean values, a
+ reference to the value is returned.
+
+ @complexity Constant.
+
+ @pre The JSON value must not be `null` (would throw `std::out_of_range`)
+ or an empty array or object (undefined behavior, **guarded by
+ assertions**).
+ @post The JSON value remains unchanged.
+
+ @throw invalid_iterator.214 when called on a `null` value. See example
+ below.
+
+ @liveexample{The following code shows an example for `back()`.,back}
+
+ @sa @ref front() -- access the first element
+
+ @since version 1.0.0
+ */
+ reference back()
+ {
+ auto tmp = end();
+ --tmp;
+ return *tmp;
+ }
+
+ /*!
+ @copydoc basic_json::back()
+ */
+ const_reference back() const
+ {
+ auto tmp = cend();
+ --tmp;
+ return *tmp;
+ }
+
+ /*!
+ @brief remove element given an iterator
+
+ Removes the element specified by iterator @a pos. The iterator @a pos must
+ be valid and dereferenceable. Thus the `end()` iterator (which is valid,
+ but is not dereferenceable) cannot be used as a value for @a pos.
+
+ If called on a primitive type other than `null`, the resulting JSON value
+ will be `null`.
+
+ @param[in] pos iterator to the element to remove
+ @return Iterator following the last removed element. If the iterator @a
+ pos refers to the last element, the `end()` iterator is returned.
+
+ @tparam IteratorType an @ref iterator or @ref const_iterator
+
+ @post Invalidates iterators and references at or after the point of the
+ erase, including the `end()` iterator.
+
+ @throw type_error.307 if called on a `null` value; example: `"cannot use
+ erase() with null"`
+ @throw invalid_iterator.202 if called on an iterator which does not belong
+ to the current JSON value; example: `"iterator does not fit current
+ value"`
+ @throw invalid_iterator.205 if called on a primitive type with invalid
+ iterator (i.e., any iterator which is not `begin()`); example: `"iterator
+ out of range"`
+
+ @complexity The complexity depends on the type:
+ - objects: amortized constant
+ - arrays: linear in distance between @a pos and the end of the container
+ - strings: linear in the length of the string
+ - other types: constant
+
+ @liveexample{The example shows the result of `erase()` for different JSON
+ types.,erase__IteratorType}
+
+ @sa @ref erase(IteratorType, IteratorType) -- removes the elements in
+ the given range
+ @sa @ref erase(const typename object_t::key_type&) -- removes the element
+ from an object at the given key
+ @sa @ref erase(const size_type) -- removes the element from an array at
+ the given index
+
+ @since version 1.0.0
+ */
+ template<class IteratorType, typename std::enable_if<
+ std::is_same<IteratorType, typename basic_json_t::iterator>::value or
+ std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int>::type
+ = 0>
+ IteratorType erase(IteratorType pos)
+ {
+ // make sure iterator fits the current value
+ if (JSON_UNLIKELY(this != pos.m_object))
+ {
+ JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value"));
+ }
+
+ IteratorType result = end();
+
+ switch (m_type)
+ {
+ case value_t::boolean:
+ case value_t::number_float:
+ case value_t::number_integer:
+ case value_t::number_unsigned:
+ case value_t::string:
+ {
+ if (JSON_UNLIKELY(not pos.m_it.primitive_iterator.is_begin()))
+ {
+ JSON_THROW(invalid_iterator::create(205, "iterator out of range"));
+ }
+
+ if (is_string())
+ {
+ AllocatorType<string_t> alloc;
+ std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.string);
+ std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.string, 1);
+ m_value.string = nullptr;
+ }
+
+ m_type = value_t::null;
+ assert_invariant();
+ break;
+ }
+
+ case value_t::object:
+ {
+ result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator);
+ break;
+ }
+
+ case value_t::array:
+ {
+ result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator);
+ break;
+ }
+
+ default:
+ JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name())));
+ }
+
+ return result;
+ }
+
+ /*!
+ @brief remove elements given an iterator range
+
+ Removes the element specified by the range `[first; last)`. The iterator
+ @a first does not need to be dereferenceable if `first == last`: erasing
+ an empty range is a no-op.
+
+ If called on a primitive type other than `null`, the resulting JSON value
+ will be `null`.
+
+ @param[in] first iterator to the beginning of the range to remove
+ @param[in] last iterator past the end of the range to remove
+ @return Iterator following the last removed element. If the iterator @a
+ second refers to the last element, the `end()` iterator is returned.
+
+ @tparam IteratorType an @ref iterator or @ref const_iterator
+
+ @post Invalidates iterators and references at or after the point of the
+ erase, including the `end()` iterator.
+
+ @throw type_error.307 if called on a `null` value; example: `"cannot use
+ erase() with null"`
+ @throw invalid_iterator.203 if called on iterators which does not belong
+ to the current JSON value; example: `"iterators do not fit current value"`
+ @throw invalid_iterator.204 if called on a primitive type with invalid
+ iterators (i.e., if `first != begin()` and `last != end()`); example:
+ `"iterators out of range"`
+
+ @complexity The complexity depends on the type:
+ - objects: `log(size()) + std::distance(first, last)`
+ - arrays: linear in the distance between @a first and @a last, plus linear
+ in the distance between @a last and end of the container
+ - strings: linear in the length of the string
+ - other types: constant
+
+ @liveexample{The example shows the result of `erase()` for different JSON
+ types.,erase__IteratorType_IteratorType}
+
+ @sa @ref erase(IteratorType) -- removes the element at a given position
+ @sa @ref erase(const typename object_t::key_type&) -- removes the element
+ from an object at the given key
+ @sa @ref erase(const size_type) -- removes the element from an array at
+ the given index
+
+ @since version 1.0.0
+ */
+ template<class IteratorType, typename std::enable_if<
+ std::is_same<IteratorType, typename basic_json_t::iterator>::value or
+ std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int>::type
+ = 0>
+ IteratorType erase(IteratorType first, IteratorType last)
+ {
+ // make sure iterator fits the current value
+ if (JSON_UNLIKELY(this != first.m_object or this != last.m_object))
+ {
+ JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value"));
+ }
+
+ IteratorType result = end();
+
+ switch (m_type)
+ {
+ case value_t::boolean:
+ case value_t::number_float:
+ case value_t::number_integer:
+ case value_t::number_unsigned:
+ case value_t::string:
+ {
+ if (JSON_LIKELY(not first.m_it.primitive_iterator.is_begin()
+ or not last.m_it.primitive_iterator.is_end()))
+ {
+ JSON_THROW(invalid_iterator::create(204, "iterators out of range"));
+ }
+
+ if (is_string())
+ {
+ AllocatorType<string_t> alloc;
+ std::allocator_traits<decltype(alloc)>::destroy(alloc, m_value.string);
+ std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_value.string, 1);
+ m_value.string = nullptr;
+ }
+
+ m_type = value_t::null;
+ assert_invariant();
+ break;
+ }
+
+ case value_t::object:
+ {
+ result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator,
+ last.m_it.object_iterator);
+ break;
+ }
+
+ case value_t::array:
+ {
+ result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator,
+ last.m_it.array_iterator);
+ break;
+ }
+
+ default:
+ JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name())));
+ }
+
+ return result;
+ }
+
+ /*!
+ @brief remove element from a JSON object given a key
+
+ Removes elements from a JSON object with the key value @a key.
+
+ @param[in] key value of the elements to remove
+
+ @return Number of elements removed. If @a ObjectType is the default
+ `std::map` type, the return value will always be `0` (@a key was not
+ found) or `1` (@a key was found).
+
+ @post References and iterators to the erased elements are invalidated.
+ Other references and iterators are not affected.
+
+ @throw type_error.307 when called on a type other than JSON object;
+ example: `"cannot use erase() with null"`
+
+ @complexity `log(size()) + count(key)`
+
+ @liveexample{The example shows the effect of `erase()`.,erase__key_type}
+
+ @sa @ref erase(IteratorType) -- removes the element at a given position
+ @sa @ref erase(IteratorType, IteratorType) -- removes the elements in
+ the given range
+ @sa @ref erase(const size_type) -- removes the element from an array at
+ the given index
+
+ @since version 1.0.0
+ */
+ size_type erase(const typename object_t::key_type& key)
+ {
+ // this erase only works for objects
+ if (JSON_LIKELY(is_object()))
+ {
+ return m_value.object->erase(key);
+ }
+
+ JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name())));
+ }
+
+ /*!
+ @brief remove element from a JSON array given an index
+
+ Removes element from a JSON array at the index @a idx.
+
+ @param[in] idx index of the element to remove
+
+ @throw type_error.307 when called on a type other than JSON object;
+ example: `"cannot use erase() with null"`
+ @throw out_of_range.401 when `idx >= size()`; example: `"array index 17
+ is out of range"`
+
+ @complexity Linear in distance between @a idx and the end of the container.
+
+ @liveexample{The example shows the effect of `erase()`.,erase__size_type}
+
+ @sa @ref erase(IteratorType) -- removes the element at a given position
+ @sa @ref erase(IteratorType, IteratorType) -- removes the elements in
+ the given range
+ @sa @ref erase(const typename object_t::key_type&) -- removes the element
+ from an object at the given key
+
+ @since version 1.0.0
+ */
+ void erase(const size_type idx)
+ {
+ // this erase only works for arrays
+ if (JSON_LIKELY(is_array()))
+ {
+ if (JSON_UNLIKELY(idx >= size()))
+ {
+ JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range"));
+ }
+
+ m_value.array->erase(m_value.array->begin() + static_cast<difference_type>(idx));
+ }
+ else
+ {
+ JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name())));
+ }
+ }
+
+ /// @}
+
+
+ ////////////
+ // lookup //
+ ////////////
+
+ /// @name lookup
+ /// @{
+
+ /*!
+ @brief find an element in a JSON object
+
+ Finds an element in a JSON object with key equivalent to @a key. If the
+ element is not found or the JSON value is not an object, end() is
+ returned.
+
+ @note This method always returns @ref end() when executed on a JSON type
+ that is not an object.
+
+ @param[in] key key value of the element to search for.
+
+ @return Iterator to an element with key equivalent to @a key. If no such
+ element is found or the JSON value is not an object, past-the-end (see
+ @ref end()) iterator is returned.
+
+ @complexity Logarithmic in the size of the JSON object.
+
+ @liveexample{The example shows how `find()` is used.,find__key_type}
+
+ @since version 1.0.0
+ */
+ template<typename KeyT>
+ iterator find(KeyT&& key)
+ {
+ auto result = end();
+
+ if (is_object())
+ {
+ result.m_it.object_iterator = m_value.object->find(std::forward<KeyT>(key));
+ }
+
+ return result;
+ }
+
+ /*!
+ @brief find an element in a JSON object
+ @copydoc find(KeyT&&)
+ */
+ template<typename KeyT>
+ const_iterator find(KeyT&& key) const
+ {
+ auto result = cend();
+
+ if (is_object())
+ {
+ result.m_it.object_iterator = m_value.object->find(std::forward<KeyT>(key));
+ }
+
+ return result;
+ }
+
+ /*!
+ @brief returns the number of occurrences of a key in a JSON object
+
+ Returns the number of elements with key @a key. If ObjectType is the
+ default `std::map` type, the return value will always be `0` (@a key was
+ not found) or `1` (@a key was found).
+
+ @note This method always returns `0` when executed on a JSON type that is
+ not an object.
+
+ @param[in] key key value of the element to count
+
+ @return Number of elements with key @a key. If the JSON value is not an
+ object, the return value will be `0`.
+
+ @complexity Logarithmic in the size of the JSON object.
+
+ @liveexample{The example shows how `count()` is used.,count}
+
+ @since version 1.0.0
+ */
+ template<typename KeyT>
+ size_type count(KeyT&& key) const
+ {
+ // return 0 for all nonobject types
+ return is_object() ? m_value.object->count(std::forward<KeyT>(key)) : 0;
+ }
+
+ /// @}
+
+
+ ///////////////
+ // iterators //
+ ///////////////
+
+ /// @name iterators
+ /// @{
+
+ /*!
+ @brief returns an iterator to the first element
+
+ Returns an iterator to the first element.
+
+ @image html range-begin-end.svg "Illustration from cppreference.com"
+
+ @return iterator to the first element
+
+ @complexity Constant.
+
+ @requirement This function helps `basic_json` satisfying the
+ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
+ requirements:
+ - The complexity is constant.
+
+ @liveexample{The following code shows an example for `begin()`.,begin}
+
+ @sa @ref cbegin() -- returns a const iterator to the beginning
+ @sa @ref end() -- returns an iterator to the end
+ @sa @ref cend() -- returns a const iterator to the end
+
+ @since version 1.0.0
+ */
+ iterator begin() noexcept
+ {
+ iterator result(this);
+ result.set_begin();
+ return result;
+ }
+
+ /*!
+ @copydoc basic_json::cbegin()
+ */
+ const_iterator begin() const noexcept
+ {
+ return cbegin();
+ }
+
+ /*!
+ @brief returns a const iterator to the first element
+
+ Returns a const iterator to the first element.
+
+ @image html range-begin-end.svg "Illustration from cppreference.com"
+
+ @return const iterator to the first element
+
+ @complexity Constant.
+
+ @requirement This function helps `basic_json` satisfying the
+ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
+ requirements:
+ - The complexity is constant.
+ - Has the semantics of `const_cast<const basic_json&>(*this).begin()`.
+
+ @liveexample{The following code shows an example for `cbegin()`.,cbegin}
+
+ @sa @ref begin() -- returns an iterator to the beginning
+ @sa @ref end() -- returns an iterator to the end
+ @sa @ref cend() -- returns a const iterator to the end
+
+ @since version 1.0.0
+ */
+ const_iterator cbegin() const noexcept
+ {
+ const_iterator result(this);
+ result.set_begin();
+ return result;
+ }
+
+ /*!
+ @brief returns an iterator to one past the last element
+
+ Returns an iterator to one past the last element.
+
+ @image html range-begin-end.svg "Illustration from cppreference.com"
+
+ @return iterator one past the last element
+
+ @complexity Constant.
+
+ @requirement This function helps `basic_json` satisfying the
+ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
+ requirements:
+ - The complexity is constant.
+
+ @liveexample{The following code shows an example for `end()`.,end}
+
+ @sa @ref cend() -- returns a const iterator to the end
+ @sa @ref begin() -- returns an iterator to the beginning
+ @sa @ref cbegin() -- returns a const iterator to the beginning
+
+ @since version 1.0.0
+ */
+ iterator end() noexcept
+ {
+ iterator result(this);
+ result.set_end();
+ return result;
+ }
+
+ /*!
+ @copydoc basic_json::cend()
+ */
+ const_iterator end() const noexcept
+ {
+ return cend();
+ }
+
+ /*!
+ @brief returns a const iterator to one past the last element
+
+ Returns a const iterator to one past the last element.
+
+ @image html range-begin-end.svg "Illustration from cppreference.com"
+
+ @return const iterator one past the last element
+
+ @complexity Constant.
+
+ @requirement This function helps `basic_json` satisfying the
+ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
+ requirements:
+ - The complexity is constant.
+ - Has the semantics of `const_cast<const basic_json&>(*this).end()`.
+
+ @liveexample{The following code shows an example for `cend()`.,cend}
+
+ @sa @ref end() -- returns an iterator to the end
+ @sa @ref begin() -- returns an iterator to the beginning
+ @sa @ref cbegin() -- returns a const iterator to the beginning
+
+ @since version 1.0.0
+ */
+ const_iterator cend() const noexcept
+ {
+ const_iterator result(this);
+ result.set_end();
+ return result;
+ }
+
+ /*!
+ @brief returns an iterator to the reverse-beginning
+
+ Returns an iterator to the reverse-beginning; that is, the last element.
+
+ @image html range-rbegin-rend.svg "Illustration from cppreference.com"
+
+ @complexity Constant.
+
+ @requirement This function helps `basic_json` satisfying the
+ [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer)
+ requirements:
+ - The complexity is constant.
+ - Has the semantics of `reverse_iterator(end())`.
+
+ @liveexample{The following code shows an example for `rbegin()`.,rbegin}
+
+ @sa @ref crbegin() -- returns a const reverse iterator to the beginning
+ @sa @ref rend() -- returns a reverse iterator to the end
+ @sa @ref crend() -- returns a const reverse iterator to the end
+
+ @since version 1.0.0
+ */
+ reverse_iterator rbegin() noexcept
+ {
+ return reverse_iterator(end());
+ }
+
+ /*!
+ @copydoc basic_json::crbegin()
+ */
+ const_reverse_iterator rbegin() const noexcept
+ {
+ return crbegin();
+ }
+
+ /*!
+ @brief returns an iterator to the reverse-end
+
+ Returns an iterator to the reverse-end; that is, one before the first
+ element.
+
+ @image html range-rbegin-rend.svg "Illustration from cppreference.com"
+
+ @complexity Constant.
+
+ @requirement This function helps `basic_json` satisfying the
+ [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer)
+ requirements:
+ - The complexity is constant.
+ - Has the semantics of `reverse_iterator(begin())`.
+
+ @liveexample{The following code shows an example for `rend()`.,rend}
+
+ @sa @ref crend() -- returns a const reverse iterator to the end
+ @sa @ref rbegin() -- returns a reverse iterator to the beginning
+ @sa @ref crbegin() -- returns a const reverse iterator to the beginning
+
+ @since version 1.0.0
+ */
+ reverse_iterator rend() noexcept
+ {
+ return reverse_iterator(begin());
+ }
+
+ /*!
+ @copydoc basic_json::crend()
+ */
+ const_reverse_iterator rend() const noexcept
+ {
+ return crend();
+ }
+
+ /*!
+ @brief returns a const reverse iterator to the last element
+
+ Returns a const iterator to the reverse-beginning; that is, the last
+ element.
+
+ @image html range-rbegin-rend.svg "Illustration from cppreference.com"
+
+ @complexity Constant.
+
+ @requirement This function helps `basic_json` satisfying the
+ [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer)
+ requirements:
+ - The complexity is constant.
+ - Has the semantics of `const_cast<const basic_json&>(*this).rbegin()`.
+
+ @liveexample{The following code shows an example for `crbegin()`.,crbegin}
+
+ @sa @ref rbegin() -- returns a reverse iterator to the beginning
+ @sa @ref rend() -- returns a reverse iterator to the end
+ @sa @ref crend() -- returns a const reverse iterator to the end
+
+ @since version 1.0.0
+ */
+ const_reverse_iterator crbegin() const noexcept
+ {
+ return const_reverse_iterator(cend());
+ }
+
+ /*!
+ @brief returns a const reverse iterator to one before the first
+
+ Returns a const reverse iterator to the reverse-end; that is, one before
+ the first element.
+
+ @image html range-rbegin-rend.svg "Illustration from cppreference.com"
+
+ @complexity Constant.
+
+ @requirement This function helps `basic_json` satisfying the
+ [ReversibleContainer](https://en.cppreference.com/w/cpp/named_req/ReversibleContainer)
+ requirements:
+ - The complexity is constant.
+ - Has the semantics of `const_cast<const basic_json&>(*this).rend()`.
+
+ @liveexample{The following code shows an example for `crend()`.,crend}
+
+ @sa @ref rend() -- returns a reverse iterator to the end
+ @sa @ref rbegin() -- returns a reverse iterator to the beginning
+ @sa @ref crbegin() -- returns a const reverse iterator to the beginning
+
+ @since version 1.0.0
+ */
+ const_reverse_iterator crend() const noexcept
+ {
+ return const_reverse_iterator(cbegin());
+ }
+
+ public:
+ /*!
+ @brief wrapper to access iterator member functions in range-based for
+
+ This function allows to access @ref iterator::key() and @ref
+ iterator::value() during range-based for loops. In these loops, a
+ reference to the JSON values is returned, so there is no access to the
+ underlying iterator.
+
+ For loop without iterator_wrapper:
+
+ @code{cpp}
+ for (auto it = j_object.begin(); it != j_object.end(); ++it)
+ {
+ std::cout << "key: " << it.key() << ", value:" << it.value() << '\n';
+ }
+ @endcode
+
+ Range-based for loop without iterator proxy:
+
+ @code{cpp}
+ for (auto it : j_object)
+ {
+ // "it" is of type json::reference and has no key() member
+ std::cout << "value: " << it << '\n';
+ }
+ @endcode
+
+ Range-based for loop with iterator proxy:
+
+ @code{cpp}
+ for (auto it : json::iterator_wrapper(j_object))
+ {
+ std::cout << "key: " << it.key() << ", value:" << it.value() << '\n';
+ }
+ @endcode
+
+ @note When iterating over an array, `key()` will return the index of the
+ element as string (see example).
+
+ @param[in] ref reference to a JSON value
+ @return iteration proxy object wrapping @a ref with an interface to use in
+ range-based for loops
+
+ @liveexample{The following code shows how the wrapper is used,iterator_wrapper}
+
+ @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+ changes in the JSON value.
+
+ @complexity Constant.
+
+ @note The name of this function is not yet final and may change in the
+ future.
+
+ @deprecated This stream operator is deprecated and will be removed in
+ future 4.0.0 of the library. Please use @ref items() instead;
+ that is, replace `json::iterator_wrapper(j)` with `j.items()`.
+ */
+ JSON_DEPRECATED
+ static iteration_proxy<iterator> iterator_wrapper(reference ref) noexcept
+ {
+ return ref.items();
+ }
+
+ /*!
+ @copydoc iterator_wrapper(reference)
+ */
+ JSON_DEPRECATED
+ static iteration_proxy<const_iterator> iterator_wrapper(const_reference ref) noexcept
+ {
+ return ref.items();
+ }
+
+ /*!
+ @brief helper to access iterator member functions in range-based for
+
+ This function allows to access @ref iterator::key() and @ref
+ iterator::value() during range-based for loops. In these loops, a
+ reference to the JSON values is returned, so there is no access to the
+ underlying iterator.
+
+ For loop without `items()` function:
+
+ @code{cpp}
+ for (auto it = j_object.begin(); it != j_object.end(); ++it)
+ {
+ std::cout << "key: " << it.key() << ", value:" << it.value() << '\n';
+ }
+ @endcode
+
+ Range-based for loop without `items()` function:
+
+ @code{cpp}
+ for (auto it : j_object)
+ {
+ // "it" is of type json::reference and has no key() member
+ std::cout << "value: " << it << '\n';
+ }
+ @endcode
+
+ Range-based for loop with `items()` function:
+
+ @code{cpp}
+ for (auto it : j_object.items())
+ {
+ std::cout << "key: " << it.key() << ", value:" << it.value() << '\n';
+ }
+ @endcode
+
+ @note When iterating over an array, `key()` will return the index of the
+ element as string (see example). For primitive types (e.g., numbers),
+ `key()` returns an empty string.
+
+ @return iteration proxy object wrapping @a ref with an interface to use in
+ range-based for loops
+
+ @liveexample{The following code shows how the function is used.,items}
+
+ @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+ changes in the JSON value.
+
+ @complexity Constant.
+
+ @since version 3.1.0.
+ */
+ iteration_proxy<iterator> items() noexcept
+ {
+ return iteration_proxy<iterator>(*this);
+ }
+
+ /*!
+ @copydoc items()
+ */
+ iteration_proxy<const_iterator> items() const noexcept
+ {
+ return iteration_proxy<const_iterator>(*this);
+ }
+
+ /// @}
+
+
+ //////////////
+ // capacity //
+ //////////////
+
+ /// @name capacity
+ /// @{
+
+ /*!
+ @brief checks whether the container is empty.
+
+ Checks if a JSON value has no elements (i.e. whether its @ref size is `0`).
+
+ @return The return value depends on the different types and is
+ defined as follows:
+ Value type | return value
+ ----------- | -------------
+ null | `true`
+ boolean | `false`
+ string | `false`
+ number | `false`
+ object | result of function `object_t::empty()`
+ array | result of function `array_t::empty()`
+
+ @liveexample{The following code uses `empty()` to check if a JSON
+ object contains any elements.,empty}
+
+ @complexity Constant, as long as @ref array_t and @ref object_t satisfy
+ the Container concept; that is, their `empty()` functions have constant
+ complexity.
+
+ @iterators No changes.
+
+ @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+ @note This function does not return whether a string stored as JSON value
+ is empty - it returns whether the JSON container itself is empty which is
+ false in the case of a string.
+
+ @requirement This function helps `basic_json` satisfying the
+ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
+ requirements:
+ - The complexity is constant.
+ - Has the semantics of `begin() == end()`.
+
+ @sa @ref size() -- returns the number of elements
+
+ @since version 1.0.0
+ */
+ bool empty() const noexcept
+ {
+ switch (m_type)
+ {
+ case value_t::null:
+ {
+ // null values are empty
+ return true;
+ }
+
+ case value_t::array:
+ {
+ // delegate call to array_t::empty()
+ return m_value.array->empty();
+ }
+
+ case value_t::object:
+ {
+ // delegate call to object_t::empty()
+ return m_value.object->empty();
+ }
+
+ default:
+ {
+ // all other types are nonempty
+ return false;
+ }
+ }
+ }
+
+ /*!
+ @brief returns the number of elements
+
+ Returns the number of elements in a JSON value.
+
+ @return The return value depends on the different types and is
+ defined as follows:
+ Value type | return value
+ ----------- | -------------
+ null | `0`
+ boolean | `1`
+ string | `1`
+ number | `1`
+ object | result of function object_t::size()
+ array | result of function array_t::size()
+
+ @liveexample{The following code calls `size()` on the different value
+ types.,size}
+
+ @complexity Constant, as long as @ref array_t and @ref object_t satisfy
+ the Container concept; that is, their size() functions have constant
+ complexity.
+
+ @iterators No changes.
+
+ @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+ @note This function does not return the length of a string stored as JSON
+ value - it returns the number of elements in the JSON value which is 1 in
+ the case of a string.
+
+ @requirement This function helps `basic_json` satisfying the
+ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
+ requirements:
+ - The complexity is constant.
+ - Has the semantics of `std::distance(begin(), end())`.
+
+ @sa @ref empty() -- checks whether the container is empty
+ @sa @ref max_size() -- returns the maximal number of elements
+
+ @since version 1.0.0
+ */
+ size_type size() const noexcept
+ {
+ switch (m_type)
+ {
+ case value_t::null:
+ {
+ // null values are empty
+ return 0;
+ }
+
+ case value_t::array:
+ {
+ // delegate call to array_t::size()
+ return m_value.array->size();
+ }
+
+ case value_t::object:
+ {
+ // delegate call to object_t::size()
+ return m_value.object->size();
+ }
+
+ default:
+ {
+ // all other types have size 1
+ return 1;
+ }
+ }
+ }
+
+ /*!
+ @brief returns the maximum possible number of elements
+
+ Returns the maximum number of elements a JSON value is able to hold due to
+ system or library implementation limitations, i.e. `std::distance(begin(),
+ end())` for the JSON value.
+
+ @return The return value depends on the different types and is
+ defined as follows:
+ Value type | return value
+ ----------- | -------------
+ null | `0` (same as `size()`)
+ boolean | `1` (same as `size()`)
+ string | `1` (same as `size()`)
+ number | `1` (same as `size()`)
+ object | result of function `object_t::max_size()`
+ array | result of function `array_t::max_size()`
+
+ @liveexample{The following code calls `max_size()` on the different value
+ types. Note the output is implementation specific.,max_size}
+
+ @complexity Constant, as long as @ref array_t and @ref object_t satisfy
+ the Container concept; that is, their `max_size()` functions have constant
+ complexity.
+
+ @iterators No changes.
+
+ @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+ @requirement This function helps `basic_json` satisfying the
+ [Container](https://en.cppreference.com/w/cpp/named_req/Container)
+ requirements:
+ - The complexity is constant.
+ - Has the semantics of returning `b.size()` where `b` is the largest
+ possible JSON value.
+
+ @sa @ref size() -- returns the number of elements
+
+ @since version 1.0.0
+ */
+ size_type max_size() const noexcept
+ {
+ switch (m_type)
+ {
+ case value_t::array:
+ {
+ // delegate call to array_t::max_size()
+ return m_value.array->max_size();
+ }
+
+ case value_t::object:
+ {
+ // delegate call to object_t::max_size()
+ return m_value.object->max_size();
+ }
+
+ default:
+ {
+ // all other types have max_size() == size()
+ return size();
+ }
+ }
+ }
+
+ /// @}
+
+
+ ///////////////
+ // modifiers //
+ ///////////////
+
+ /// @name modifiers
+ /// @{
+
+ /*!
+ @brief clears the contents
+
+ Clears the content of a JSON value and resets it to the default value as
+ if @ref basic_json(value_t) would have been called with the current value
+ type from @ref type():
+
+ Value type | initial value
+ ----------- | -------------
+ null | `null`
+ boolean | `false`
+ string | `""`
+ number | `0`
+ object | `{}`
+ array | `[]`
+
+ @post Has the same effect as calling
+ @code {.cpp}
+ *this = basic_json(type());
+ @endcode
+
+ @liveexample{The example below shows the effect of `clear()` to different
+ JSON types.,clear}
+
+ @complexity Linear in the size of the JSON value.
+
+ @iterators All iterators, pointers and references related to this container
+ are invalidated.
+
+ @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+ @sa @ref basic_json(value_t) -- constructor that creates an object with the
+ same value than calling `clear()`
+
+ @since version 1.0.0
+ */
+ void clear() noexcept
+ {
+ switch (m_type)
+ {
+ case value_t::number_integer:
+ {
+ m_value.number_integer = 0;
+ break;
+ }
+
+ case value_t::number_unsigned:
+ {
+ m_value.number_unsigned = 0;
+ break;
+ }
+
+ case value_t::number_float:
+ {
+ m_value.number_float = 0.0;
+ break;
+ }
+
+ case value_t::boolean:
+ {
+ m_value.boolean = false;
+ break;
+ }
+
+ case value_t::string:
+ {
+ m_value.string->clear();
+ break;
+ }
+
+ case value_t::array:
+ {
+ m_value.array->clear();
+ break;
+ }
+
+ case value_t::object:
+ {
+ m_value.object->clear();
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ /*!
+ @brief add an object to an array
+
+ Appends the given element @a val to the end of the JSON value. If the
+ function is called on a JSON null value, an empty array is created before
+ appending @a val.
+
+ @param[in] val the value to add to the JSON array
+
+ @throw type_error.308 when called on a type other than JSON array or
+ null; example: `"cannot use push_back() with number"`
+
+ @complexity Amortized constant.
+
+ @liveexample{The example shows how `push_back()` and `+=` can be used to
+ add elements to a JSON array. Note how the `null` value was silently
+ converted to a JSON array.,push_back}
+
+ @since version 1.0.0
+ */
+ void push_back(basic_json&& val)
+ {
+ // push_back only works for null objects or arrays
+ if (JSON_UNLIKELY(not(is_null() or is_array())))
+ {
+ JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name())));
+ }
+
+ // transform null object into an array
+ if (is_null())
+ {
+ m_type = value_t::array;
+ m_value = value_t::array;
+ assert_invariant();
+ }
+
+ // add element to array (move semantics)
+ m_value.array->push_back(std::move(val));
+ // invalidate object
+ val.m_type = value_t::null;
+ }
+
+ /*!
+ @brief add an object to an array
+ @copydoc push_back(basic_json&&)
+ */
+ reference operator+=(basic_json&& val)
+ {
+ push_back(std::move(val));
+ return *this;
+ }
+
+ /*!
+ @brief add an object to an array
+ @copydoc push_back(basic_json&&)
+ */
+ void push_back(const basic_json& val)
+ {
+ // push_back only works for null objects or arrays
+ if (JSON_UNLIKELY(not(is_null() or is_array())))
+ {
+ JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name())));
+ }
+
+ // transform null object into an array
+ if (is_null())
+ {
+ m_type = value_t::array;
+ m_value = value_t::array;
+ assert_invariant();
+ }
+
+ // add element to array
+ m_value.array->push_back(val);
+ }
+
+ /*!
+ @brief add an object to an array
+ @copydoc push_back(basic_json&&)
+ */
+ reference operator+=(const basic_json& val)
+ {
+ push_back(val);
+ return *this;
+ }
+
+ /*!
+ @brief add an object to an object
+
+ Inserts the given element @a val to the JSON object. If the function is
+ called on a JSON null value, an empty object is created before inserting
+ @a val.
+
+ @param[in] val the value to add to the JSON object
+
+ @throw type_error.308 when called on a type other than JSON object or
+ null; example: `"cannot use push_back() with number"`
+
+ @complexity Logarithmic in the size of the container, O(log(`size()`)).
+
+ @liveexample{The example shows how `push_back()` and `+=` can be used to
+ add elements to a JSON object. Note how the `null` value was silently
+ converted to a JSON object.,push_back__object_t__value}
+
+ @since version 1.0.0
+ */
+ void push_back(const typename object_t::value_type& val)
+ {
+ // push_back only works for null objects or objects
+ if (JSON_UNLIKELY(not(is_null() or is_object())))
+ {
+ JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name())));
+ }
+
+ // transform null object into an object
+ if (is_null())
+ {
+ m_type = value_t::object;
+ m_value = value_t::object;
+ assert_invariant();
+ }
+
+ // add element to array
+ m_value.object->insert(val);
+ }
+
+ /*!
+ @brief add an object to an object
+ @copydoc push_back(const typename object_t::value_type&)
+ */
+ reference operator+=(const typename object_t::value_type& val)
+ {
+ push_back(val);
+ return *this;
+ }
+
+ /*!
+ @brief add an object to an object
+
+ This function allows to use `push_back` with an initializer list. In case
+
+ 1. the current value is an object,
+ 2. the initializer list @a init contains only two elements, and
+ 3. the first element of @a init is a string,
+
+ @a init is converted into an object element and added using
+ @ref push_back(const typename object_t::value_type&). Otherwise, @a init
+ is converted to a JSON value and added using @ref push_back(basic_json&&).
+
+ @param[in] init an initializer list
+
+ @complexity Linear in the size of the initializer list @a init.
+
+ @note This function is required to resolve an ambiguous overload error,
+ because pairs like `{"key", "value"}` can be both interpreted as
+ `object_t::value_type` or `std::initializer_list<basic_json>`, see
+ https://github.com/nlohmann/json/issues/235 for more information.
+
+ @liveexample{The example shows how initializer lists are treated as
+ objects when possible.,push_back__initializer_list}
+ */
+ void push_back(initializer_list_t init)
+ {
+ if (is_object() and init.size() == 2 and (*init.begin())->is_string())
+ {
+ basic_json&& key = init.begin()->moved_or_copied();
+ push_back(typename object_t::value_type(
+ std::move(key.get_ref<string_t&>()), (init.begin() + 1)->moved_or_copied()));
+ }
+ else
+ {
+ push_back(basic_json(init));
+ }
+ }
+
+ /*!
+ @brief add an object to an object
+ @copydoc push_back(initializer_list_t)
+ */
+ reference operator+=(initializer_list_t init)
+ {
+ push_back(init);
+ return *this;
+ }
+
+ /*!
+ @brief add an object to an array
+
+ Creates a JSON value from the passed parameters @a args to the end of the
+ JSON value. If the function is called on a JSON null value, an empty array
+ is created before appending the value created from @a args.
+
+ @param[in] args arguments to forward to a constructor of @ref basic_json
+ @tparam Args compatible types to create a @ref basic_json object
+
+ @throw type_error.311 when called on a type other than JSON array or
+ null; example: `"cannot use emplace_back() with number"`
+
+ @complexity Amortized constant.
+
+ @liveexample{The example shows how `push_back()` can be used to add
+ elements to a JSON array. Note how the `null` value was silently converted
+ to a JSON array.,emplace_back}
+
+ @since version 2.0.8
+ */
+ template<class... Args>
+ void emplace_back(Args&& ... args)
+ {
+ // emplace_back only works for null objects or arrays
+ if (JSON_UNLIKELY(not(is_null() or is_array())))
+ {
+ JSON_THROW(type_error::create(311, "cannot use emplace_back() with " + std::string(type_name())));
+ }
+
+ // transform null object into an array
+ if (is_null())
+ {
+ m_type = value_t::array;
+ m_value = value_t::array;
+ assert_invariant();
+ }
+
+ // add element to array (perfect forwarding)
+ m_value.array->emplace_back(std::forward<Args>(args)...);
+ }
+
+ /*!
+ @brief add an object to an object if key does not exist
+
+ Inserts a new element into a JSON object constructed in-place with the
+ given @a args if there is no element with the key in the container. If the
+ function is called on a JSON null value, an empty object is created before
+ appending the value created from @a args.
+
+ @param[in] args arguments to forward to a constructor of @ref basic_json
+ @tparam Args compatible types to create a @ref basic_json object
+
+ @return a pair consisting of an iterator to the inserted element, or the
+ already-existing element if no insertion happened, and a bool
+ denoting whether the insertion took place.
+
+ @throw type_error.311 when called on a type other than JSON object or
+ null; example: `"cannot use emplace() with number"`
+
+ @complexity Logarithmic in the size of the container, O(log(`size()`)).
+
+ @liveexample{The example shows how `emplace()` can be used to add elements
+ to a JSON object. Note how the `null` value was silently converted to a
+ JSON object. Further note how no value is added if there was already one
+ value stored with the same key.,emplace}
+
+ @since version 2.0.8
+ */
+ template<class... Args>
+ std::pair<iterator, bool> emplace(Args&& ... args)
+ {
+ // emplace only works for null objects or arrays
+ if (JSON_UNLIKELY(not(is_null() or is_object())))
+ {
+ JSON_THROW(type_error::create(311, "cannot use emplace() with " + std::string(type_name())));
+ }
+
+ // transform null object into an object
+ if (is_null())
+ {
+ m_type = value_t::object;
+ m_value = value_t::object;
+ assert_invariant();
+ }
+
+ // add element to array (perfect forwarding)
+ auto res = m_value.object->emplace(std::forward<Args>(args)...);
+ // create result iterator and set iterator to the result of emplace
+ auto it = begin();
+ it.m_it.object_iterator = res.first;
+
+ // return pair of iterator and boolean
+ return {it, res.second};
+ }
+
+ /// Helper for insertion of an iterator
+ /// @note: This uses std::distance to support GCC 4.8,
+ /// see https://github.com/nlohmann/json/pull/1257
+ template<typename... Args>
+ iterator insert_iterator(const_iterator pos, Args&& ... args)
+ {
+ iterator result(this);
+ assert(m_value.array != nullptr);
+
+ auto insert_pos = std::distance(m_value.array->begin(), pos.m_it.array_iterator);
+ m_value.array->insert(pos.m_it.array_iterator, std::forward<Args>(args)...);
+ result.m_it.array_iterator = m_value.array->begin() + insert_pos;
+
+ // This could have been written as:
+ // result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val);
+ // but the return value of insert is missing in GCC 4.8, so it is written this way instead.
+
+ return result;
+ }
+
+ /*!
+ @brief inserts element
+
+ Inserts element @a val before iterator @a pos.
+
+ @param[in] pos iterator before which the content will be inserted; may be
+ the end() iterator
+ @param[in] val element to insert
+ @return iterator pointing to the inserted @a val.
+
+ @throw type_error.309 if called on JSON values other than arrays;
+ example: `"cannot use insert() with string"`
+ @throw invalid_iterator.202 if @a pos is not an iterator of *this;
+ example: `"iterator does not fit current value"`
+
+ @complexity Constant plus linear in the distance between @a pos and end of
+ the container.
+
+ @liveexample{The example shows how `insert()` is used.,insert}
+
+ @since version 1.0.0
+ */
+ iterator insert(const_iterator pos, const basic_json& val)
+ {
+ // insert only works for arrays
+ if (JSON_LIKELY(is_array()))
+ {
+ // check if iterator pos fits to this JSON value
+ if (JSON_UNLIKELY(pos.m_object != this))
+ {
+ JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value"));
+ }
+
+ // insert to array and return iterator
+ return insert_iterator(pos, val);
+ }
+
+ JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name())));
+ }
+
+ /*!
+ @brief inserts element
+ @copydoc insert(const_iterator, const basic_json&)
+ */
+ iterator insert(const_iterator pos, basic_json&& val)
+ {
+ return insert(pos, val);
+ }
+
+ /*!
+ @brief inserts elements
+
+ Inserts @a cnt copies of @a val before iterator @a pos.
+
+ @param[in] pos iterator before which the content will be inserted; may be
+ the end() iterator
+ @param[in] cnt number of copies of @a val to insert
+ @param[in] val element to insert
+ @return iterator pointing to the first element inserted, or @a pos if
+ `cnt==0`
+
+ @throw type_error.309 if called on JSON values other than arrays; example:
+ `"cannot use insert() with string"`
+ @throw invalid_iterator.202 if @a pos is not an iterator of *this;
+ example: `"iterator does not fit current value"`
+
+ @complexity Linear in @a cnt plus linear in the distance between @a pos
+ and end of the container.
+
+ @liveexample{The example shows how `insert()` is used.,insert__count}
+
+ @since version 1.0.0
+ */
+ iterator insert(const_iterator pos, size_type cnt, const basic_json& val)
+ {
+ // insert only works for arrays
+ if (JSON_LIKELY(is_array()))
+ {
+ // check if iterator pos fits to this JSON value
+ if (JSON_UNLIKELY(pos.m_object != this))
+ {
+ JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value"));
+ }
+
+ // insert to array and return iterator
+ return insert_iterator(pos, cnt, val);
+ }
+
+ JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name())));
+ }
+
+ /*!
+ @brief inserts elements
+
+ Inserts elements from range `[first, last)` before iterator @a pos.
+
+ @param[in] pos iterator before which the content will be inserted; may be
+ the end() iterator
+ @param[in] first begin of the range of elements to insert
+ @param[in] last end of the range of elements to insert
+
+ @throw type_error.309 if called on JSON values other than arrays; example:
+ `"cannot use insert() with string"`
+ @throw invalid_iterator.202 if @a pos is not an iterator of *this;
+ example: `"iterator does not fit current value"`
+ @throw invalid_iterator.210 if @a first and @a last do not belong to the
+ same JSON value; example: `"iterators do not fit"`
+ @throw invalid_iterator.211 if @a first or @a last are iterators into
+ container for which insert is called; example: `"passed iterators may not
+ belong to container"`
+
+ @return iterator pointing to the first element inserted, or @a pos if
+ `first==last`
+
+ @complexity Linear in `std::distance(first, last)` plus linear in the
+ distance between @a pos and end of the container.
+
+ @liveexample{The example shows how `insert()` is used.,insert__range}
+
+ @since version 1.0.0
+ */
+ iterator insert(const_iterator pos, const_iterator first, const_iterator last)
+ {
+ // insert only works for arrays
+ if (JSON_UNLIKELY(not is_array()))
+ {
+ JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name())));
+ }
+
+ // check if iterator pos fits to this JSON value
+ if (JSON_UNLIKELY(pos.m_object != this))
+ {
+ JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value"));
+ }
+
+ // check if range iterators belong to the same JSON object
+ if (JSON_UNLIKELY(first.m_object != last.m_object))
+ {
+ JSON_THROW(invalid_iterator::create(210, "iterators do not fit"));
+ }
+
+ if (JSON_UNLIKELY(first.m_object == this))
+ {
+ JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container"));
+ }
+
+ // insert to array and return iterator
+ return insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator);
+ }
+
+ /*!
+ @brief inserts elements
+
+ Inserts elements from initializer list @a ilist before iterator @a pos.
+
+ @param[in] pos iterator before which the content will be inserted; may be
+ the end() iterator
+ @param[in] ilist initializer list to insert the values from
+
+ @throw type_error.309 if called on JSON values other than arrays; example:
+ `"cannot use insert() with string"`
+ @throw invalid_iterator.202 if @a pos is not an iterator of *this;
+ example: `"iterator does not fit current value"`
+
+ @return iterator pointing to the first element inserted, or @a pos if
+ `ilist` is empty
+
+ @complexity Linear in `ilist.size()` plus linear in the distance between
+ @a pos and end of the container.
+
+ @liveexample{The example shows how `insert()` is used.,insert__ilist}
+
+ @since version 1.0.0
+ */
+ iterator insert(const_iterator pos, initializer_list_t ilist)
+ {
+ // insert only works for arrays
+ if (JSON_UNLIKELY(not is_array()))
+ {
+ JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name())));
+ }
+
+ // check if iterator pos fits to this JSON value
+ if (JSON_UNLIKELY(pos.m_object != this))
+ {
+ JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value"));
+ }
+
+ // insert to array and return iterator
+ return insert_iterator(pos, ilist.begin(), ilist.end());
+ }
+
+ /*!
+ @brief inserts elements
+
+ Inserts elements from range `[first, last)`.
+
+ @param[in] first begin of the range of elements to insert
+ @param[in] last end of the range of elements to insert
+
+ @throw type_error.309 if called on JSON values other than objects; example:
+ `"cannot use insert() with string"`
+ @throw invalid_iterator.202 if iterator @a first or @a last does does not
+ point to an object; example: `"iterators first and last must point to
+ objects"`
+ @throw invalid_iterator.210 if @a first and @a last do not belong to the
+ same JSON value; example: `"iterators do not fit"`
+
+ @complexity Logarithmic: `O(N*log(size() + N))`, where `N` is the number
+ of elements to insert.
+
+ @liveexample{The example shows how `insert()` is used.,insert__range_object}
+
+ @since version 3.0.0
+ */
+ void insert(const_iterator first, const_iterator last)
+ {
+ // insert only works for objects
+ if (JSON_UNLIKELY(not is_object()))
+ {
+ JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name())));
+ }
+
+ // check if range iterators belong to the same JSON object
+ if (JSON_UNLIKELY(first.m_object != last.m_object))
+ {
+ JSON_THROW(invalid_iterator::create(210, "iterators do not fit"));
+ }
+
+ // passed iterators must belong to objects
+ if (JSON_UNLIKELY(not first.m_object->is_object()))
+ {
+ JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects"));
+ }
+
+ m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator);
+ }
+
+ /*!
+ @brief updates a JSON object from another object, overwriting existing keys
+
+ Inserts all values from JSON object @a j and overwrites existing keys.
+
+ @param[in] j JSON object to read values from
+
+ @throw type_error.312 if called on JSON values other than objects; example:
+ `"cannot use update() with string"`
+
+ @complexity O(N*log(size() + N)), where N is the number of elements to
+ insert.
+
+ @liveexample{The example shows how `update()` is used.,update}
+
+ @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update
+
+ @since version 3.0.0
+ */
+ void update(const_reference j)
+ {
+ // implicitly convert null value to an empty object
+ if (is_null())
+ {
+ m_type = value_t::object;
+ m_value.object = create<object_t>();
+ assert_invariant();
+ }
+
+ if (JSON_UNLIKELY(not is_object()))
+ {
+ JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name())));
+ }
+ if (JSON_UNLIKELY(not j.is_object()))
+ {
+ JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(j.type_name())));
+ }
+
+ for (auto it = j.cbegin(); it != j.cend(); ++it)
+ {
+ m_value.object->operator[](it.key()) = it.value();
+ }
+ }
+
+ /*!
+ @brief updates a JSON object from another object, overwriting existing keys
+
+ Inserts all values from from range `[first, last)` and overwrites existing
+ keys.
+
+ @param[in] first begin of the range of elements to insert
+ @param[in] last end of the range of elements to insert
+
+ @throw type_error.312 if called on JSON values other than objects; example:
+ `"cannot use update() with string"`
+ @throw invalid_iterator.202 if iterator @a first or @a last does does not
+ point to an object; example: `"iterators first and last must point to
+ objects"`
+ @throw invalid_iterator.210 if @a first and @a last do not belong to the
+ same JSON value; example: `"iterators do not fit"`
+
+ @complexity O(N*log(size() + N)), where N is the number of elements to
+ insert.
+
+ @liveexample{The example shows how `update()` is used__range.,update}
+
+ @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update
+
+ @since version 3.0.0
+ */
+ void update(const_iterator first, const_iterator last)
+ {
+ // implicitly convert null value to an empty object
+ if (is_null())
+ {
+ m_type = value_t::object;
+ m_value.object = create<object_t>();
+ assert_invariant();
+ }
+
+ if (JSON_UNLIKELY(not is_object()))
+ {
+ JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name())));
+ }
+
+ // check if range iterators belong to the same JSON object
+ if (JSON_UNLIKELY(first.m_object != last.m_object))
+ {
+ JSON_THROW(invalid_iterator::create(210, "iterators do not fit"));
+ }
+
+ // passed iterators must belong to objects
+ if (JSON_UNLIKELY(not first.m_object->is_object()
+ or not last.m_object->is_object()))
+ {
+ JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects"));
+ }
+
+ for (auto it = first; it != last; ++it)
+ {
+ m_value.object->operator[](it.key()) = it.value();
+ }
+ }
+
+ /*!
+ @brief exchanges the values
+
+ Exchanges the contents of the JSON value with those of @a other. Does not
+ invoke any move, copy, or swap operations on individual elements. All
+ iterators and references remain valid. The past-the-end iterator is
+ invalidated.
+
+ @param[in,out] other JSON value to exchange the contents with
+
+ @complexity Constant.
+
+ @liveexample{The example below shows how JSON values can be swapped with
+ `swap()`.,swap__reference}
+
+ @since version 1.0.0
+ */
+ void swap(reference other) noexcept (
+ std::is_nothrow_move_constructible<value_t>::value and
+ std::is_nothrow_move_assignable<value_t>::value and
+ std::is_nothrow_move_constructible<json_value>::value and
+ std::is_nothrow_move_assignable<json_value>::value
+ )
+ {
+ std::swap(m_type, other.m_type);
+ std::swap(m_value, other.m_value);
+ assert_invariant();
+ }
+
+ /*!
+ @brief exchanges the values
+
+ Exchanges the contents of a JSON array with those of @a other. Does not
+ invoke any move, copy, or swap operations on individual elements. All
+ iterators and references remain valid. The past-the-end iterator is
+ invalidated.
+
+ @param[in,out] other array to exchange the contents with
+
+ @throw type_error.310 when JSON value is not an array; example: `"cannot
+ use swap() with string"`
+
+ @complexity Constant.
+
+ @liveexample{The example below shows how arrays can be swapped with
+ `swap()`.,swap__array_t}
+
+ @since version 1.0.0
+ */
+ void swap(array_t& other)
+ {
+ // swap only works for arrays
+ if (JSON_LIKELY(is_array()))
+ {
+ std::swap(*(m_value.array), other);
+ }
+ else
+ {
+ JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name())));
+ }
+ }
+
+ /*!
+ @brief exchanges the values
+
+ Exchanges the contents of a JSON object with those of @a other. Does not
+ invoke any move, copy, or swap operations on individual elements. All
+ iterators and references remain valid. The past-the-end iterator is
+ invalidated.
+
+ @param[in,out] other object to exchange the contents with
+
+ @throw type_error.310 when JSON value is not an object; example:
+ `"cannot use swap() with string"`
+
+ @complexity Constant.
+
+ @liveexample{The example below shows how objects can be swapped with
+ `swap()`.,swap__object_t}
+
+ @since version 1.0.0
+ */
+ void swap(object_t& other)
+ {
+ // swap only works for objects
+ if (JSON_LIKELY(is_object()))
+ {
+ std::swap(*(m_value.object), other);
+ }
+ else
+ {
+ JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name())));
+ }
+ }
+
+ /*!
+ @brief exchanges the values
+
+ Exchanges the contents of a JSON string with those of @a other. Does not
+ invoke any move, copy, or swap operations on individual elements. All
+ iterators and references remain valid. The past-the-end iterator is
+ invalidated.
+
+ @param[in,out] other string to exchange the contents with
+
+ @throw type_error.310 when JSON value is not a string; example: `"cannot
+ use swap() with boolean"`
+
+ @complexity Constant.
+
+ @liveexample{The example below shows how strings can be swapped with
+ `swap()`.,swap__string_t}
+
+ @since version 1.0.0
+ */
+ void swap(string_t& other)
+ {
+ // swap only works for strings
+ if (JSON_LIKELY(is_string()))
+ {
+ std::swap(*(m_value.string), other);
+ }
+ else
+ {
+ JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name())));
+ }
+ }
+
+ /// @}
+
+ public:
+ //////////////////////////////////////////
+ // lexicographical comparison operators //
+ //////////////////////////////////////////
+
+ /// @name lexicographical comparison operators
+ /// @{
+
+ /*!
+ @brief comparison: equal
+
+ Compares two JSON values for equality according to the following rules:
+ - Two JSON values are equal if (1) they are from the same type and (2)
+ their stored values are the same according to their respective
+ `operator==`.
+ - Integer and floating-point numbers are automatically converted before
+ comparison. Note than two NaN values are always treated as unequal.
+ - Two JSON null values are equal.
+
+ @note Floating-point inside JSON values numbers are compared with
+ `json::number_float_t::operator==` which is `double::operator==` by
+ default. To compare floating-point while respecting an epsilon, an alternative
+ [comparison function](https://github.com/mariokonrad/marnav/blob/master/src/marnav/math/floatingpoint.hpp#L34-#L39)
+ could be used, for instance
+ @code {.cpp}
+ template<typename T, typename = typename std::enable_if<std::is_floating_point<T>::value, T>::type>
+ inline bool is_same(T a, T b, T epsilon = std::numeric_limits<T>::epsilon()) noexcept
+ {
+ return std::abs(a - b) <= epsilon;
+ }
+ @endcode
+
+ @note NaN values never compare equal to themselves or to other NaN values.
+
+ @param[in] lhs first JSON value to consider
+ @param[in] rhs second JSON value to consider
+ @return whether the values @a lhs and @a rhs are equal
+
+ @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+ @complexity Linear.
+
+ @liveexample{The example demonstrates comparing several JSON
+ types.,operator__equal}
+
+ @since version 1.0.0
+ */
+ friend bool operator==(const_reference lhs, const_reference rhs) noexcept
+ {
+ const auto lhs_type = lhs.type();
+ const auto rhs_type = rhs.type();
+
+ if (lhs_type == rhs_type)
+ {
+ switch (lhs_type)
+ {
+ case value_t::array:
+ return (*lhs.m_value.array == *rhs.m_value.array);
+
+ case value_t::object:
+ return (*lhs.m_value.object == *rhs.m_value.object);
+
+ case value_t::null:
+ return true;
+
+ case value_t::string:
+ return (*lhs.m_value.string == *rhs.m_value.string);
+
+ case value_t::boolean:
+ return (lhs.m_value.boolean == rhs.m_value.boolean);
+
+ case value_t::number_integer:
+ return (lhs.m_value.number_integer == rhs.m_value.number_integer);
+
+ case value_t::number_unsigned:
+ return (lhs.m_value.number_unsigned == rhs.m_value.number_unsigned);
+
+ case value_t::number_float:
+ return (lhs.m_value.number_float == rhs.m_value.number_float);
+
+ default:
+ return false;
+ }
+ }
+ else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float)
+ {
+ return (static_cast<number_float_t>(lhs.m_value.number_integer) == rhs.m_value.number_float);
+ }
+ else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer)
+ {
+ return (lhs.m_value.number_float == static_cast<number_float_t>(rhs.m_value.number_integer));
+ }
+ else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float)
+ {
+ return (static_cast<number_float_t>(lhs.m_value.number_unsigned) == rhs.m_value.number_float);
+ }
+ else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned)
+ {
+ return (lhs.m_value.number_float == static_cast<number_float_t>(rhs.m_value.number_unsigned));
+ }
+ else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer)
+ {
+ return (static_cast<number_integer_t>(lhs.m_value.number_unsigned) == rhs.m_value.number_integer);
+ }
+ else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned)
+ {
+ return (lhs.m_value.number_integer == static_cast<number_integer_t>(rhs.m_value.number_unsigned));
+ }
+
+ return false;
+ }
+
+ /*!
+ @brief comparison: equal
+ @copydoc operator==(const_reference, const_reference)
+ */
+ template<typename ScalarType, typename std::enable_if<
+ std::is_scalar<ScalarType>::value, int>::type = 0>
+ friend bool operator==(const_reference lhs, const ScalarType rhs) noexcept
+ {
+ return (lhs == basic_json(rhs));
+ }
+
+ /*!
+ @brief comparison: equal
+ @copydoc operator==(const_reference, const_reference)
+ */
+ template<typename ScalarType, typename std::enable_if<
+ std::is_scalar<ScalarType>::value, int>::type = 0>
+ friend bool operator==(const ScalarType lhs, const_reference rhs) noexcept
+ {
+ return (basic_json(lhs) == rhs);
+ }
+
+ /*!
+ @brief comparison: not equal
+
+ Compares two JSON values for inequality by calculating `not (lhs == rhs)`.
+
+ @param[in] lhs first JSON value to consider
+ @param[in] rhs second JSON value to consider
+ @return whether the values @a lhs and @a rhs are not equal
+
+ @complexity Linear.
+
+ @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+ @liveexample{The example demonstrates comparing several JSON
+ types.,operator__notequal}
+
+ @since version 1.0.0
+ */
+ friend bool operator!=(const_reference lhs, const_reference rhs) noexcept
+ {
+ return not (lhs == rhs);
+ }
+
+ /*!
+ @brief comparison: not equal
+ @copydoc operator!=(const_reference, const_reference)
+ */
+ template<typename ScalarType, typename std::enable_if<
+ std::is_scalar<ScalarType>::value, int>::type = 0>
+ friend bool operator!=(const_reference lhs, const ScalarType rhs) noexcept
+ {
+ return (lhs != basic_json(rhs));
+ }
+
+ /*!
+ @brief comparison: not equal
+ @copydoc operator!=(const_reference, const_reference)
+ */
+ template<typename ScalarType, typename std::enable_if<
+ std::is_scalar<ScalarType>::value, int>::type = 0>
+ friend bool operator!=(const ScalarType lhs, const_reference rhs) noexcept
+ {
+ return (basic_json(lhs) != rhs);
+ }
+
+ /*!
+ @brief comparison: less than
+
+ Compares whether one JSON value @a lhs is less than another JSON value @a
+ rhs according to the following rules:
+ - If @a lhs and @a rhs have the same type, the values are compared using
+ the default `<` operator.
+ - Integer and floating-point numbers are automatically converted before
+ comparison
+ - In case @a lhs and @a rhs have different types, the values are ignored
+ and the order of the types is considered, see
+ @ref operator<(const value_t, const value_t).
+
+ @param[in] lhs first JSON value to consider
+ @param[in] rhs second JSON value to consider
+ @return whether @a lhs is less than @a rhs
+
+ @complexity Linear.
+
+ @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+ @liveexample{The example demonstrates comparing several JSON
+ types.,operator__less}
+
+ @since version 1.0.0
+ */
+ friend bool operator<(const_reference lhs, const_reference rhs) noexcept
+ {
+ const auto lhs_type = lhs.type();
+ const auto rhs_type = rhs.type();
+
+ if (lhs_type == rhs_type)
+ {
+ switch (lhs_type)
+ {
+ case value_t::array:
+ return (*lhs.m_value.array) < (*rhs.m_value.array);
+
+ case value_t::object:
+ return *lhs.m_value.object < *rhs.m_value.object;
+
+ case value_t::null:
+ return false;
+
+ case value_t::string:
+ return *lhs.m_value.string < *rhs.m_value.string;
+
+ case value_t::boolean:
+ return lhs.m_value.boolean < rhs.m_value.boolean;
+
+ case value_t::number_integer:
+ return lhs.m_value.number_integer < rhs.m_value.number_integer;
+
+ case value_t::number_unsigned:
+ return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned;
+
+ case value_t::number_float:
+ return lhs.m_value.number_float < rhs.m_value.number_float;
+
+ default:
+ return false;
+ }
+ }
+ else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float)
+ {
+ return static_cast<number_float_t>(lhs.m_value.number_integer) < rhs.m_value.number_float;
+ }
+ else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer)
+ {
+ return lhs.m_value.number_float < static_cast<number_float_t>(rhs.m_value.number_integer);
+ }
+ else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float)
+ {
+ return static_cast<number_float_t>(lhs.m_value.number_unsigned) < rhs.m_value.number_float;
+ }
+ else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned)
+ {
+ return lhs.m_value.number_float < static_cast<number_float_t>(rhs.m_value.number_unsigned);
+ }
+ else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned)
+ {
+ return lhs.m_value.number_integer < static_cast<number_integer_t>(rhs.m_value.number_unsigned);
+ }
+ else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer)
+ {
+ return static_cast<number_integer_t>(lhs.m_value.number_unsigned) < rhs.m_value.number_integer;
+ }
+
+ // We only reach this line if we cannot compare values. In that case,
+ // we compare types. Note we have to call the operator explicitly,
+ // because MSVC has problems otherwise.
+ return operator<(lhs_type, rhs_type);
+ }
+
+ /*!
+ @brief comparison: less than
+ @copydoc operator<(const_reference, const_reference)
+ */
+ template<typename ScalarType, typename std::enable_if<
+ std::is_scalar<ScalarType>::value, int>::type = 0>
+ friend bool operator<(const_reference lhs, const ScalarType rhs) noexcept
+ {
+ return (lhs < basic_json(rhs));
+ }
+
+ /*!
+ @brief comparison: less than
+ @copydoc operator<(const_reference, const_reference)
+ */
+ template<typename ScalarType, typename std::enable_if<
+ std::is_scalar<ScalarType>::value, int>::type = 0>
+ friend bool operator<(const ScalarType lhs, const_reference rhs) noexcept
+ {
+ return (basic_json(lhs) < rhs);
+ }
+
+ /*!
+ @brief comparison: less than or equal
+
+ Compares whether one JSON value @a lhs is less than or equal to another
+ JSON value by calculating `not (rhs < lhs)`.
+
+ @param[in] lhs first JSON value to consider
+ @param[in] rhs second JSON value to consider
+ @return whether @a lhs is less than or equal to @a rhs
+
+ @complexity Linear.
+
+ @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+ @liveexample{The example demonstrates comparing several JSON
+ types.,operator__greater}
+
+ @since version 1.0.0
+ */
+ friend bool operator<=(const_reference lhs, const_reference rhs) noexcept
+ {
+ return not (rhs < lhs);
+ }
+
+ /*!
+ @brief comparison: less than or equal
+ @copydoc operator<=(const_reference, const_reference)
+ */
+ template<typename ScalarType, typename std::enable_if<
+ std::is_scalar<ScalarType>::value, int>::type = 0>
+ friend bool operator<=(const_reference lhs, const ScalarType rhs) noexcept
+ {
+ return (lhs <= basic_json(rhs));
+ }
+
+ /*!
+ @brief comparison: less than or equal
+ @copydoc operator<=(const_reference, const_reference)
+ */
+ template<typename ScalarType, typename std::enable_if<
+ std::is_scalar<ScalarType>::value, int>::type = 0>
+ friend bool operator<=(const ScalarType lhs, const_reference rhs) noexcept
+ {
+ return (basic_json(lhs) <= rhs);
+ }
+
+ /*!
+ @brief comparison: greater than
+
+ Compares whether one JSON value @a lhs is greater than another
+ JSON value by calculating `not (lhs <= rhs)`.
+
+ @param[in] lhs first JSON value to consider
+ @param[in] rhs second JSON value to consider
+ @return whether @a lhs is greater than to @a rhs
+
+ @complexity Linear.
+
+ @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+ @liveexample{The example demonstrates comparing several JSON
+ types.,operator__lessequal}
+
+ @since version 1.0.0
+ */
+ friend bool operator>(const_reference lhs, const_reference rhs) noexcept
+ {
+ return not (lhs <= rhs);
+ }
+
+ /*!
+ @brief comparison: greater than
+ @copydoc operator>(const_reference, const_reference)
+ */
+ template<typename ScalarType, typename std::enable_if<
+ std::is_scalar<ScalarType>::value, int>::type = 0>
+ friend bool operator>(const_reference lhs, const ScalarType rhs) noexcept
+ {
+ return (lhs > basic_json(rhs));
+ }
+
+ /*!
+ @brief comparison: greater than
+ @copydoc operator>(const_reference, const_reference)
+ */
+ template<typename ScalarType, typename std::enable_if<
+ std::is_scalar<ScalarType>::value, int>::type = 0>
+ friend bool operator>(const ScalarType lhs, const_reference rhs) noexcept
+ {
+ return (basic_json(lhs) > rhs);
+ }
+
+ /*!
+ @brief comparison: greater than or equal
+
+ Compares whether one JSON value @a lhs is greater than or equal to another
+ JSON value by calculating `not (lhs < rhs)`.
+
+ @param[in] lhs first JSON value to consider
+ @param[in] rhs second JSON value to consider
+ @return whether @a lhs is greater than or equal to @a rhs
+
+ @complexity Linear.
+
+ @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+ @liveexample{The example demonstrates comparing several JSON
+ types.,operator__greaterequal}
+
+ @since version 1.0.0
+ */
+ friend bool operator>=(const_reference lhs, const_reference rhs) noexcept
+ {
+ return not (lhs < rhs);
+ }
+
+ /*!
+ @brief comparison: greater than or equal
+ @copydoc operator>=(const_reference, const_reference)
+ */
+ template<typename ScalarType, typename std::enable_if<
+ std::is_scalar<ScalarType>::value, int>::type = 0>
+ friend bool operator>=(const_reference lhs, const ScalarType rhs) noexcept
+ {
+ return (lhs >= basic_json(rhs));
+ }
+
+ /*!
+ @brief comparison: greater than or equal
+ @copydoc operator>=(const_reference, const_reference)
+ */
+ template<typename ScalarType, typename std::enable_if<
+ std::is_scalar<ScalarType>::value, int>::type = 0>
+ friend bool operator>=(const ScalarType lhs, const_reference rhs) noexcept
+ {
+ return (basic_json(lhs) >= rhs);
+ }
+
+ /// @}
+
+ ///////////////////
+ // serialization //
+ ///////////////////
+
+ /// @name serialization
+ /// @{
+
+ /*!
+ @brief serialize to stream
+
+ Serialize the given JSON value @a j to the output stream @a o. The JSON
+ value will be serialized using the @ref dump member function.
+
+ - The indentation of the output can be controlled with the member variable
+ `width` of the output stream @a o. For instance, using the manipulator
+ `std::setw(4)` on @a o sets the indentation level to `4` and the
+ serialization result is the same as calling `dump(4)`.
+
+ - The indentation character can be controlled with the member variable
+ `fill` of the output stream @a o. For instance, the manipulator
+ `std::setfill('\\t')` sets indentation to use a tab character rather than
+ the default space character.
+
+ @param[in,out] o stream to serialize to
+ @param[in] j JSON value to serialize
+
+ @return the stream @a o
+
+ @throw type_error.316 if a string stored inside the JSON value is not
+ UTF-8 encoded
+
+ @complexity Linear.
+
+ @liveexample{The example below shows the serialization with different
+ parameters to `width` to adjust the indentation level.,operator_serialize}
+
+ @since version 1.0.0; indentation character added in version 3.0.0
+ */
+ friend std::ostream& operator<<(std::ostream& o, const basic_json& j)
+ {
+ // read width member and use it as indentation parameter if nonzero
+ const bool pretty_print = (o.width() > 0);
+ const auto indentation = (pretty_print ? o.width() : 0);
+
+ // reset width to 0 for subsequent calls to this stream
+ o.width(0);
+
+ // do the actual serialization
+ serializer s(detail::output_adapter<char>(o), o.fill());
+ s.dump(j, pretty_print, false, static_cast<unsigned int>(indentation));
+ return o;
+ }
+
+ /*!
+ @brief serialize to stream
+ @deprecated This stream operator is deprecated and will be removed in
+ future 4.0.0 of the library. Please use
+ @ref operator<<(std::ostream&, const basic_json&)
+ instead; that is, replace calls like `j >> o;` with `o << j;`.
+ @since version 1.0.0; deprecated since version 3.0.0
+ */
+ JSON_DEPRECATED
+ friend std::ostream& operator>>(const basic_json& j, std::ostream& o)
+ {
+ return o << j;
+ }
+
+ /// @}
+
+
+ /////////////////////
+ // deserialization //
+ /////////////////////
+
+ /// @name deserialization
+ /// @{
+
+ /*!
+ @brief deserialize from a compatible input
+
+ This function reads from a compatible input. Examples are:
+ - an array of 1-byte values
+ - strings with character/literal type with size of 1 byte
+ - input streams
+ - container with contiguous storage of 1-byte values. Compatible container
+ types include `std::vector`, `std::string`, `std::array`,
+ `std::valarray`, and `std::initializer_list`. Furthermore, C-style
+ arrays can be used with `std::begin()`/`std::end()`. User-defined
+ containers can be used as long as they implement random-access iterators
+ and a contiguous storage.
+
+ @pre Each element of the container has a size of 1 byte. Violating this
+ precondition yields undefined behavior. **This precondition is enforced
+ with a static assertion.**
+
+ @pre The container storage is contiguous. Violating this precondition
+ yields undefined behavior. **This precondition is enforced with an
+ assertion.**
+ @pre Each element of the container has a size of 1 byte. Violating this
+ precondition yields undefined behavior. **This precondition is enforced
+ with a static assertion.**
+
+ @warning There is no way to enforce all preconditions at compile-time. If
+ the function is called with a noncompliant container and with
+ assertions switched off, the behavior is undefined and will most
+ likely yield segmentation violation.
+
+ @param[in] i input to read from
+ @param[in] cb a parser callback function of type @ref parser_callback_t
+ which is used to control the deserialization by filtering unwanted values
+ (optional)
+ @param[in] allow_exceptions whether to throw exceptions in case of a
+ parse error (optional, true by default)
+
+ @return result of the deserialization
+
+ @throw parse_error.101 if a parse error occurs; example: `""unexpected end
+ of input; expected string literal""`
+ @throw parse_error.102 if to_unicode fails or surrogate error
+ @throw parse_error.103 if to_unicode fails
+
+ @complexity Linear in the length of the input. The parser is a predictive
+ LL(1) parser. The complexity can be higher if the parser callback function
+ @a cb has a super-linear complexity.
+
+ @note A UTF-8 byte order mark is silently ignored.
+
+ @liveexample{The example below demonstrates the `parse()` function reading
+ from an array.,parse__array__parser_callback_t}
+
+ @liveexample{The example below demonstrates the `parse()` function with
+ and without callback function.,parse__string__parser_callback_t}
+
+ @liveexample{The example below demonstrates the `parse()` function with
+ and without callback function.,parse__istream__parser_callback_t}
+
+ @liveexample{The example below demonstrates the `parse()` function reading
+ from a contiguous container.,parse__contiguouscontainer__parser_callback_t}
+
+ @since version 2.0.3 (contiguous containers)
+ */
+ static basic_json parse(detail::input_adapter&& i,
+ const parser_callback_t cb = nullptr,
+ const bool allow_exceptions = true)
+ {
+ basic_json result;
+ parser(i, cb, allow_exceptions).parse(true, result);
+ return result;
+ }
+
+ static bool accept(detail::input_adapter&& i)
+ {
+ return parser(i).accept(true);
+ }
+
+ /*!
+ @brief generate SAX events
+
+ The SAX event lister must follow the interface of @ref json_sax.
+
+ This function reads from a compatible input. Examples are:
+ - an array of 1-byte values
+ - strings with character/literal type with size of 1 byte
+ - input streams
+ - container with contiguous storage of 1-byte values. Compatible container
+ types include `std::vector`, `std::string`, `std::array`,
+ `std::valarray`, and `std::initializer_list`. Furthermore, C-style
+ arrays can be used with `std::begin()`/`std::end()`. User-defined
+ containers can be used as long as they implement random-access iterators
+ and a contiguous storage.
+
+ @pre Each element of the container has a size of 1 byte. Violating this
+ precondition yields undefined behavior. **This precondition is enforced
+ with a static assertion.**
+
+ @pre The container storage is contiguous. Violating this precondition
+ yields undefined behavior. **This precondition is enforced with an
+ assertion.**
+ @pre Each element of the container has a size of 1 byte. Violating this
+ precondition yields undefined behavior. **This precondition is enforced
+ with a static assertion.**
+
+ @warning There is no way to enforce all preconditions at compile-time. If
+ the function is called with a noncompliant container and with
+ assertions switched off, the behavior is undefined and will most
+ likely yield segmentation violation.
+
+ @param[in] i input to read from
+ @param[in,out] sax SAX event listener
+ @param[in] format the format to parse (JSON, CBOR, MessagePack, or UBJSON)
+ @param[in] strict whether the input has to be consumed completely
+
+ @return return value of the last processed SAX event
+
+ @throw parse_error.101 if a parse error occurs; example: `""unexpected end
+ of input; expected string literal""`
+ @throw parse_error.102 if to_unicode fails or surrogate error
+ @throw parse_error.103 if to_unicode fails
+
+ @complexity Linear in the length of the input. The parser is a predictive
+ LL(1) parser. The complexity can be higher if the SAX consumer @a sax has
+ a super-linear complexity.
+
+ @note A UTF-8 byte order mark is silently ignored.
+
+ @liveexample{The example below demonstrates the `sax_parse()` function
+ reading from string and processing the events with a user-defined SAX
+ event consumer.,sax_parse}
+
+ @since version 3.2.0
+ */
+ template <typename SAX>
+ static bool sax_parse(detail::input_adapter&& i, SAX* sax,
+ input_format_t format = input_format_t::json,
+ const bool strict = true)
+ {
+ assert(sax);
+ switch (format)
+ {
+ case input_format_t::json:
+ return parser(std::move(i)).sax_parse(sax, strict);
+ default:
+ return detail::binary_reader<basic_json, SAX>(std::move(i)).sax_parse(format, sax, strict);
+ }
+ }
+
+ /*!
+ @brief deserialize from an iterator range with contiguous storage
+
+ This function reads from an iterator range of a container with contiguous
+ storage of 1-byte values. Compatible container types include
+ `std::vector`, `std::string`, `std::array`, `std::valarray`, and
+ `std::initializer_list`. Furthermore, C-style arrays can be used with
+ `std::begin()`/`std::end()`. User-defined containers can be used as long
+ as they implement random-access iterators and a contiguous storage.
+
+ @pre The iterator range is contiguous. Violating this precondition yields
+ undefined behavior. **This precondition is enforced with an assertion.**
+ @pre Each element in the range has a size of 1 byte. Violating this
+ precondition yields undefined behavior. **This precondition is enforced
+ with a static assertion.**
+
+ @warning There is no way to enforce all preconditions at compile-time. If
+ the function is called with noncompliant iterators and with
+ assertions switched off, the behavior is undefined and will most
+ likely yield segmentation violation.
+
+ @tparam IteratorType iterator of container with contiguous storage
+ @param[in] first begin of the range to parse (included)
+ @param[in] last end of the range to parse (excluded)
+ @param[in] cb a parser callback function of type @ref parser_callback_t
+ which is used to control the deserialization by filtering unwanted values
+ (optional)
+ @param[in] allow_exceptions whether to throw exceptions in case of a
+ parse error (optional, true by default)
+
+ @return result of the deserialization
+
+ @throw parse_error.101 in case of an unexpected token
+ @throw parse_error.102 if to_unicode fails or surrogate error
+ @throw parse_error.103 if to_unicode fails
+
+ @complexity Linear in the length of the input. The parser is a predictive
+ LL(1) parser. The complexity can be higher if the parser callback function
+ @a cb has a super-linear complexity.
+
+ @note A UTF-8 byte order mark is silently ignored.
+
+ @liveexample{The example below demonstrates the `parse()` function reading
+ from an iterator range.,parse__iteratortype__parser_callback_t}
+
+ @since version 2.0.3
+ */
+ template<class IteratorType, typename std::enable_if<
+ std::is_base_of<
+ std::random_access_iterator_tag,
+ typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0>
+ static basic_json parse(IteratorType first, IteratorType last,
+ const parser_callback_t cb = nullptr,
+ const bool allow_exceptions = true)
+ {
+ basic_json result;
+ parser(detail::input_adapter(first, last), cb, allow_exceptions).parse(true, result);
+ return result;
+ }
+
+ template<class IteratorType, typename std::enable_if<
+ std::is_base_of<
+ std::random_access_iterator_tag,
+ typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0>
+ static bool accept(IteratorType first, IteratorType last)
+ {
+ return parser(detail::input_adapter(first, last)).accept(true);
+ }
+
+ template<class IteratorType, class SAX, typename std::enable_if<
+ std::is_base_of<
+ std::random_access_iterator_tag,
+ typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0>
+ static bool sax_parse(IteratorType first, IteratorType last, SAX* sax)
+ {
+ return parser(detail::input_adapter(first, last)).sax_parse(sax);
+ }
+
+ /*!
+ @brief deserialize from stream
+ @deprecated This stream operator is deprecated and will be removed in
+ version 4.0.0 of the library. Please use
+ @ref operator>>(std::istream&, basic_json&)
+ instead; that is, replace calls like `j << i;` with `i >> j;`.
+ @since version 1.0.0; deprecated since version 3.0.0
+ */
+ JSON_DEPRECATED
+ friend std::istream& operator<<(basic_json& j, std::istream& i)
+ {
+ return operator>>(i, j);
+ }
+
+ /*!
+ @brief deserialize from stream
+
+ Deserializes an input stream to a JSON value.
+
+ @param[in,out] i input stream to read a serialized JSON value from
+ @param[in,out] j JSON value to write the deserialized input to
+
+ @throw parse_error.101 in case of an unexpected token
+ @throw parse_error.102 if to_unicode fails or surrogate error
+ @throw parse_error.103 if to_unicode fails
+
+ @complexity Linear in the length of the input. The parser is a predictive
+ LL(1) parser.
+
+ @note A UTF-8 byte order mark is silently ignored.
+
+ @liveexample{The example below shows how a JSON value is constructed by
+ reading a serialization from a stream.,operator_deserialize}
+
+ @sa parse(std::istream&, const parser_callback_t) for a variant with a
+ parser callback function to filter values while parsing
+
+ @since version 1.0.0
+ */
+ friend std::istream& operator>>(std::istream& i, basic_json& j)
+ {
+ parser(detail::input_adapter(i)).parse(false, j);
+ return i;
+ }
+
+ /// @}
+
+ ///////////////////////////
+ // convenience functions //
+ ///////////////////////////
+
+ /*!
+ @brief return the type as string
+
+ Returns the type name as string to be used in error messages - usually to
+ indicate that a function was called on a wrong JSON type.
+
+ @return a string representation of a the @a m_type member:
+ Value type | return value
+ ----------- | -------------
+ null | `"null"`
+ boolean | `"boolean"`
+ string | `"string"`
+ number | `"number"` (for all number types)
+ object | `"object"`
+ array | `"array"`
+ discarded | `"discarded"`
+
+ @exceptionsafety No-throw guarantee: this function never throws exceptions.
+
+ @complexity Constant.
+
+ @liveexample{The following code exemplifies `type_name()` for all JSON
+ types.,type_name}
+
+ @sa @ref type() -- return the type of the JSON value
+ @sa @ref operator value_t() -- return the type of the JSON value (implicit)
+
+ @since version 1.0.0, public since 2.1.0, `const char*` and `noexcept`
+ since 3.0.0
+ */
+ const char* type_name() const noexcept
+ {
+ {
+ switch (m_type)
+ {
+ case value_t::null:
+ return "null";
+ case value_t::object:
+ return "object";
+ case value_t::array:
+ return "array";
+ case value_t::string:
+ return "string";
+ case value_t::boolean:
+ return "boolean";
+ case value_t::discarded:
+ return "discarded";
+ default:
+ return "number";
+ }
+ }
+ }
+
+
+ private:
+ //////////////////////
+ // member variables //
+ //////////////////////
+
+ /// the type of the current element
+ value_t m_type = value_t::null;
+
+ /// the value of the current element
+ json_value m_value = {};
+
+ //////////////////////////////////////////
+ // binary serialization/deserialization //
+ //////////////////////////////////////////
+
+ /// @name binary serialization/deserialization support
+ /// @{
+
+ public:
+ /*!
+ @brief create a CBOR serialization of a given JSON value
+
+ Serializes a given JSON value @a j to a byte vector using the CBOR (Concise
+ Binary Object Representation) serialization format. CBOR is a binary
+ serialization format which aims to be more compact than JSON itself, yet
+ more efficient to parse.
+
+ The library uses the following mapping from JSON values types to
+ CBOR types according to the CBOR specification (RFC 7049):
+
+ JSON value type | value/range | CBOR type | first byte
+ --------------- | ------------------------------------------ | ---------------------------------- | ---------------
+ null | `null` | Null | 0xF6
+ boolean | `true` | True | 0xF5
+ boolean | `false` | False | 0xF4
+ number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3B
+ number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3A
+ number_integer | -32768..-129 | Negative integer (2 bytes follow) | 0x39
+ number_integer | -128..-25 | Negative integer (1 byte follow) | 0x38
+ number_integer | -24..-1 | Negative integer | 0x20..0x37
+ number_integer | 0..23 | Integer | 0x00..0x17
+ number_integer | 24..255 | Unsigned integer (1 byte follow) | 0x18
+ number_integer | 256..65535 | Unsigned integer (2 bytes follow) | 0x19
+ number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A
+ number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B
+ number_unsigned | 0..23 | Integer | 0x00..0x17
+ number_unsigned | 24..255 | Unsigned integer (1 byte follow) | 0x18
+ number_unsigned | 256..65535 | Unsigned integer (2 bytes follow) | 0x19
+ number_unsigned | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1A
+ number_unsigned | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1B
+ number_float | *any value* | Double-Precision Float | 0xFB
+ string | *length*: 0..23 | UTF-8 string | 0x60..0x77
+ string | *length*: 23..255 | UTF-8 string (1 byte follow) | 0x78
+ string | *length*: 256..65535 | UTF-8 string (2 bytes follow) | 0x79
+ string | *length*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7A
+ string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7B
+ array | *size*: 0..23 | array | 0x80..0x97
+ array | *size*: 23..255 | array (1 byte follow) | 0x98
+ array | *size*: 256..65535 | array (2 bytes follow) | 0x99
+ array | *size*: 65536..4294967295 | array (4 bytes follow) | 0x9A
+ array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9B
+ object | *size*: 0..23 | map | 0xA0..0xB7
+ object | *size*: 23..255 | map (1 byte follow) | 0xB8
+ object | *size*: 256..65535 | map (2 bytes follow) | 0xB9
+ object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xBA
+ object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xBB
+
+ @note The mapping is **complete** in the sense that any JSON value type
+ can be converted to a CBOR value.
+
+ @note If NaN or Infinity are stored inside a JSON number, they are
+ serialized properly. This behavior differs from the @ref dump()
+ function which serializes NaN or Infinity to `null`.
+
+ @note The following CBOR types are not used in the conversion:
+ - byte strings (0x40..0x5F)
+ - UTF-8 strings terminated by "break" (0x7F)
+ - arrays terminated by "break" (0x9F)
+ - maps terminated by "break" (0xBF)
+ - date/time (0xC0..0xC1)
+ - bignum (0xC2..0xC3)
+ - decimal fraction (0xC4)
+ - bigfloat (0xC5)
+ - tagged items (0xC6..0xD4, 0xD8..0xDB)
+ - expected conversions (0xD5..0xD7)
+ - simple values (0xE0..0xF3, 0xF8)
+ - undefined (0xF7)
+ - half and single-precision floats (0xF9-0xFA)
+ - break (0xFF)
+
+ @param[in] j JSON value to serialize
+ @return MessagePack serialization as byte vector
+
+ @complexity Linear in the size of the JSON value @a j.
+
+ @liveexample{The example shows the serialization of a JSON value to a byte
+ vector in CBOR format.,to_cbor}
+
+ @sa http://cbor.io
+ @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool) for the
+ analogous deserialization
+ @sa @ref to_msgpack(const basic_json&) for the related MessagePack format
+ @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the
+ related UBJSON format
+
+ @since version 2.0.9
+ */
+ static std::vector<uint8_t> to_cbor(const basic_json& j)
+ {
+ std::vector<uint8_t> result;
+ to_cbor(j, result);
+ return result;
+ }
+
+ static void to_cbor(const basic_json& j, detail::output_adapter<uint8_t> o)
+ {
+ binary_writer<uint8_t>(o).write_cbor(j);
+ }
+
+ static void to_cbor(const basic_json& j, detail::output_adapter<char> o)
+ {
+ binary_writer<char>(o).write_cbor(j);
+ }
+
+ /*!
+ @brief create a MessagePack serialization of a given JSON value
+
+ Serializes a given JSON value @a j to a byte vector using the MessagePack
+ serialization format. MessagePack is a binary serialization format which
+ aims to be more compact than JSON itself, yet more efficient to parse.
+
+ The library uses the following mapping from JSON values types to
+ MessagePack types according to the MessagePack specification:
+
+ JSON value type | value/range | MessagePack type | first byte
+ --------------- | --------------------------------- | ---------------- | ----------
+ null | `null` | nil | 0xC0
+ boolean | `true` | true | 0xC3
+ boolean | `false` | false | 0xC2
+ number_integer | -9223372036854775808..-2147483649 | int64 | 0xD3
+ number_integer | -2147483648..-32769 | int32 | 0xD2
+ number_integer | -32768..-129 | int16 | 0xD1
+ number_integer | -128..-33 | int8 | 0xD0
+ number_integer | -32..-1 | negative fixint | 0xE0..0xFF
+ number_integer | 0..127 | positive fixint | 0x00..0x7F
+ number_integer | 128..255 | uint 8 | 0xCC
+ number_integer | 256..65535 | uint 16 | 0xCD
+ number_integer | 65536..4294967295 | uint 32 | 0xCE
+ number_integer | 4294967296..18446744073709551615 | uint 64 | 0xCF
+ number_unsigned | 0..127 | positive fixint | 0x00..0x7F
+ number_unsigned | 128..255 | uint 8 | 0xCC
+ number_unsigned | 256..65535 | uint 16 | 0xCD
+ number_unsigned | 65536..4294967295 | uint 32 | 0xCE
+ number_unsigned | 4294967296..18446744073709551615 | uint 64 | 0xCF
+ number_float | *any value* | float 64 | 0xCB
+ string | *length*: 0..31 | fixstr | 0xA0..0xBF
+ string | *length*: 32..255 | str 8 | 0xD9
+ string | *length*: 256..65535 | str 16 | 0xDA
+ string | *length*: 65536..4294967295 | str 32 | 0xDB
+ array | *size*: 0..15 | fixarray | 0x90..0x9F
+ array | *size*: 16..65535 | array 16 | 0xDC
+ array | *size*: 65536..4294967295 | array 32 | 0xDD
+ object | *size*: 0..15 | fix map | 0x80..0x8F
+ object | *size*: 16..65535 | map 16 | 0xDE
+ object | *size*: 65536..4294967295 | map 32 | 0xDF
+
+ @note The mapping is **complete** in the sense that any JSON value type
+ can be converted to a MessagePack value.
+
+ @note The following values can **not** be converted to a MessagePack value:
+ - strings with more than 4294967295 bytes
+ - arrays with more than 4294967295 elements
+ - objects with more than 4294967295 elements
+
+ @note The following MessagePack types are not used in the conversion:
+ - bin 8 - bin 32 (0xC4..0xC6)
+ - ext 8 - ext 32 (0xC7..0xC9)
+ - float 32 (0xCA)
+ - fixext 1 - fixext 16 (0xD4..0xD8)
+
+ @note Any MessagePack output created @ref to_msgpack can be successfully
+ parsed by @ref from_msgpack.
+
+ @note If NaN or Infinity are stored inside a JSON number, they are
+ serialized properly. This behavior differs from the @ref dump()
+ function which serializes NaN or Infinity to `null`.
+
+ @param[in] j JSON value to serialize
+ @return MessagePack serialization as byte vector
+
+ @complexity Linear in the size of the JSON value @a j.
+
+ @liveexample{The example shows the serialization of a JSON value to a byte
+ vector in MessagePack format.,to_msgpack}
+
+ @sa http://msgpack.org
+ @sa @ref from_msgpack for the analogous deserialization
+ @sa @ref to_cbor(const basic_json& for the related CBOR format
+ @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the
+ related UBJSON format
+
+ @since version 2.0.9
+ */
+ static std::vector<uint8_t> to_msgpack(const basic_json& j)
+ {
+ std::vector<uint8_t> result;
+ to_msgpack(j, result);
+ return result;
+ }
+
+ static void to_msgpack(const basic_json& j, detail::output_adapter<uint8_t> o)
+ {
+ binary_writer<uint8_t>(o).write_msgpack(j);
+ }
+
+ static void to_msgpack(const basic_json& j, detail::output_adapter<char> o)
+ {
+ binary_writer<char>(o).write_msgpack(j);
+ }
+
+ /*!
+ @brief create a UBJSON serialization of a given JSON value
+
+ Serializes a given JSON value @a j to a byte vector using the UBJSON
+ (Universal Binary JSON) serialization format. UBJSON aims to be more compact
+ than JSON itself, yet more efficient to parse.
+
+ The library uses the following mapping from JSON values types to
+ UBJSON types according to the UBJSON specification:
+
+ JSON value type | value/range | UBJSON type | marker
+ --------------- | --------------------------------- | ----------- | ------
+ null | `null` | null | `Z`
+ boolean | `true` | true | `T`
+ boolean | `false` | false | `F`
+ number_integer | -9223372036854775808..-2147483649 | int64 | `L`
+ number_integer | -2147483648..-32769 | int32 | `l`
+ number_integer | -32768..-129 | int16 | `I`
+ number_integer | -128..127 | int8 | `i`
+ number_integer | 128..255 | uint8 | `U`
+ number_integer | 256..32767 | int16 | `I`
+ number_integer | 32768..2147483647 | int32 | `l`
+ number_integer | 2147483648..9223372036854775807 | int64 | `L`
+ number_unsigned | 0..127 | int8 | `i`
+ number_unsigned | 128..255 | uint8 | `U`
+ number_unsigned | 256..32767 | int16 | `I`
+ number_unsigned | 32768..2147483647 | int32 | `l`
+ number_unsigned | 2147483648..9223372036854775807 | int64 | `L`
+ number_float | *any value* | float64 | `D`
+ string | *with shortest length indicator* | string | `S`
+ array | *see notes on optimized format* | array | `[`
+ object | *see notes on optimized format* | map | `{`
+
+ @note The mapping is **complete** in the sense that any JSON value type
+ can be converted to a UBJSON value.
+
+ @note The following values can **not** be converted to a UBJSON value:
+ - strings with more than 9223372036854775807 bytes (theoretical)
+ - unsigned integer numbers above 9223372036854775807
+
+ @note The following markers are not used in the conversion:
+ - `Z`: no-op values are not created.
+ - `C`: single-byte strings are serialized with `S` markers.
+
+ @note Any UBJSON output created @ref to_ubjson can be successfully parsed
+ by @ref from_ubjson.
+
+ @note If NaN or Infinity are stored inside a JSON number, they are
+ serialized properly. This behavior differs from the @ref dump()
+ function which serializes NaN or Infinity to `null`.
+
+ @note The optimized formats for containers are supported: Parameter
+ @a use_size adds size information to the beginning of a container and
+ removes the closing marker. Parameter @a use_type further checks
+ whether all elements of a container have the same type and adds the
+ type marker to the beginning of the container. The @a use_type
+ parameter must only be used together with @a use_size = true. Note
+ that @a use_size = true alone may result in larger representations -
+ the benefit of this parameter is that the receiving side is
+ immediately informed on the number of elements of the container.
+
+ @param[in] j JSON value to serialize
+ @param[in] use_size whether to add size annotations to container types
+ @param[in] use_type whether to add type annotations to container types
+ (must be combined with @a use_size = true)
+ @return UBJSON serialization as byte vector
+
+ @complexity Linear in the size of the JSON value @a j.
+
+ @liveexample{The example shows the serialization of a JSON value to a byte
+ vector in UBJSON format.,to_ubjson}
+
+ @sa http://ubjson.org
+ @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the
+ analogous deserialization
+ @sa @ref to_cbor(const basic_json& for the related CBOR format
+ @sa @ref to_msgpack(const basic_json&) for the related MessagePack format
+
+ @since version 3.1.0
+ */
+ static std::vector<uint8_t> to_ubjson(const basic_json& j,
+ const bool use_size = false,
+ const bool use_type = false)
+ {
+ std::vector<uint8_t> result;
+ to_ubjson(j, result, use_size, use_type);
+ return result;
+ }
+
+ static void to_ubjson(const basic_json& j, detail::output_adapter<uint8_t> o,
+ const bool use_size = false, const bool use_type = false)
+ {
+ binary_writer<uint8_t>(o).write_ubjson(j, use_size, use_type);
+ }
+
+ static void to_ubjson(const basic_json& j, detail::output_adapter<char> o,
+ const bool use_size = false, const bool use_type = false)
+ {
+ binary_writer<char>(o).write_ubjson(j, use_size, use_type);
+ }
+
+
+ /*!
+ @brief Serializes the given JSON object `j` to BSON and returns a vector
+ containing the corresponding BSON-representation.
+
+ BSON (Binary JSON) is a binary format in which zero or more ordered key/value pairs are
+ stored as a single entity (a so-called document).
+
+ The library uses the following mapping from JSON values types to BSON types:
+
+ JSON value type | value/range | BSON type | marker
+ --------------- | --------------------------------- | ----------- | ------
+ null | `null` | null | 0x0A
+ boolean | `true`, `false` | boolean | 0x08
+ number_integer | -9223372036854775808..-2147483649 | int64 | 0x12
+ number_integer | -2147483648..2147483647 | int32 | 0x10
+ number_integer | 2147483648..9223372036854775807 | int64 | 0x12
+ number_unsigned | 0..2147483647 | int32 | 0x10
+ number_unsigned | 2147483648..9223372036854775807 | int64 | 0x12
+ number_unsigned | 9223372036854775808..18446744073709551615| -- | --
+ number_float | *any value* | double | 0x01
+ string | *any value* | string | 0x02
+ array | *any value* | document | 0x04
+ object | *any value* | document | 0x03
+
+ @warning The mapping is **incomplete**, since only JSON-objects (and things
+ contained therein) can be serialized to BSON.
+ Also, integers larger than 9223372036854775807 cannot be serialized to BSON,
+ and the keys may not contain U+0000, since they are serialized a
+ zero-terminated c-strings.
+
+ @throw out_of_range.407 if `j.is_number_unsigned() && j.get<std::uint64_t>() > 9223372036854775807`
+ @throw out_of_range.409 if a key in `j` contains a NULL (U+0000)
+ @throw type_error.317 if `!j.is_object()`
+
+ @pre The input `j` is required to be an object: `j.is_object() == true`.
+
+ @note Any BSON output created via @ref to_bson can be successfully parsed
+ by @ref from_bson.
+
+ @param[in] j JSON value to serialize
+ @return BSON serialization as byte vector
+
+ @complexity Linear in the size of the JSON value @a j.
+
+ @liveexample{The example shows the serialization of a JSON value to a byte
+ vector in BSON format.,to_bson}
+
+ @sa http://bsonspec.org/spec.html
+ @sa @ref from_bson(detail::input_adapter&&, const bool strict) for the
+ analogous deserialization
+ @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the
+ related UBJSON format
+ @sa @ref to_cbor(const basic_json&) for the related CBOR format
+ @sa @ref to_msgpack(const basic_json&) for the related MessagePack format
+ */
+ static std::vector<uint8_t> to_bson(const basic_json& j)
+ {
+ std::vector<uint8_t> result;
+ to_bson(j, result);
+ return result;
+ }
+
+ /*!
+ @brief Serializes the given JSON object `j` to BSON and forwards the
+ corresponding BSON-representation to the given output_adapter `o`.
+ @param j The JSON object to convert to BSON.
+ @param o The output adapter that receives the binary BSON representation.
+ @pre The input `j` shall be an object: `j.is_object() == true`
+ @sa @ref to_bson(const basic_json&)
+ */
+ static void to_bson(const basic_json& j, detail::output_adapter<uint8_t> o)
+ {
+ binary_writer<uint8_t>(o).write_bson(j);
+ }
+
+ /*!
+ @copydoc to_bson(const basic_json&, detail::output_adapter<uint8_t>)
+ */
+ static void to_bson(const basic_json& j, detail::output_adapter<char> o)
+ {
+ binary_writer<char>(o).write_bson(j);
+ }
+
+
+ /*!
+ @brief create a JSON value from an input in CBOR format
+
+ Deserializes a given input @a i to a JSON value using the CBOR (Concise
+ Binary Object Representation) serialization format.
+
+ The library maps CBOR types to JSON value types as follows:
+
+ CBOR type | JSON value type | first byte
+ ---------------------- | --------------- | ----------
+ Integer | number_unsigned | 0x00..0x17
+ Unsigned integer | number_unsigned | 0x18
+ Unsigned integer | number_unsigned | 0x19
+ Unsigned integer | number_unsigned | 0x1A
+ Unsigned integer | number_unsigned | 0x1B
+ Negative integer | number_integer | 0x20..0x37
+ Negative integer | number_integer | 0x38
+ Negative integer | number_integer | 0x39
+ Negative integer | number_integer | 0x3A
+ Negative integer | number_integer | 0x3B
+ Negative integer | number_integer | 0x40..0x57
+ UTF-8 string | string | 0x60..0x77
+ UTF-8 string | string | 0x78
+ UTF-8 string | string | 0x79
+ UTF-8 string | string | 0x7A
+ UTF-8 string | string | 0x7B
+ UTF-8 string | string | 0x7F
+ array | array | 0x80..0x97
+ array | array | 0x98
+ array | array | 0x99
+ array | array | 0x9A
+ array | array | 0x9B
+ array | array | 0x9F
+ map | object | 0xA0..0xB7
+ map | object | 0xB8
+ map | object | 0xB9
+ map | object | 0xBA
+ map | object | 0xBB
+ map | object | 0xBF
+ False | `false` | 0xF4
+ True | `true` | 0xF5
+ Null | `null` | 0xF6
+ Half-Precision Float | number_float | 0xF9
+ Single-Precision Float | number_float | 0xFA
+ Double-Precision Float | number_float | 0xFB
+
+ @warning The mapping is **incomplete** in the sense that not all CBOR
+ types can be converted to a JSON value. The following CBOR types
+ are not supported and will yield parse errors (parse_error.112):
+ - byte strings (0x40..0x5F)
+ - date/time (0xC0..0xC1)
+ - bignum (0xC2..0xC3)
+ - decimal fraction (0xC4)
+ - bigfloat (0xC5)
+ - tagged items (0xC6..0xD4, 0xD8..0xDB)
+ - expected conversions (0xD5..0xD7)
+ - simple values (0xE0..0xF3, 0xF8)
+ - undefined (0xF7)
+
+ @warning CBOR allows map keys of any type, whereas JSON only allows
+ strings as keys in object values. Therefore, CBOR maps with keys
+ other than UTF-8 strings are rejected (parse_error.113).
+
+ @note Any CBOR output created @ref to_cbor can be successfully parsed by
+ @ref from_cbor.
+
+ @param[in] i an input in CBOR format convertible to an input adapter
+ @param[in] strict whether to expect the input to be consumed until EOF
+ (true by default)
+ @param[in] allow_exceptions whether to throw exceptions in case of a
+ parse error (optional, true by default)
+
+ @return deserialized JSON value
+
+ @throw parse_error.110 if the given input ends prematurely or the end of
+ file was not reached when @a strict was set to true
+ @throw parse_error.112 if unsupported features from CBOR were
+ used in the given input @a v or if the input is not valid CBOR
+ @throw parse_error.113 if a string was expected as map key, but not found
+
+ @complexity Linear in the size of the input @a i.
+
+ @liveexample{The example shows the deserialization of a byte vector in CBOR
+ format to a JSON value.,from_cbor}
+
+ @sa http://cbor.io
+ @sa @ref to_cbor(const basic_json&) for the analogous serialization
+ @sa @ref from_msgpack(detail::input_adapter&&, const bool, const bool) for the
+ related MessagePack format
+ @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the
+ related UBJSON format
+
+ @since version 2.0.9; parameter @a start_index since 2.1.1; changed to
+ consume input adapters, removed start_index parameter, and added
+ @a strict parameter since 3.0.0; added @a allow_exceptions parameter
+ since 3.2.0
+ */
+ static basic_json from_cbor(detail::input_adapter&& i,
+ const bool strict = true,
+ const bool allow_exceptions = true)
+ {
+ basic_json result;
+ detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+ const bool res = binary_reader(detail::input_adapter(i)).sax_parse(input_format_t::cbor, &sdp, strict);
+ return res ? result : basic_json(value_t::discarded);
+ }
+
+ /*!
+ @copydoc from_cbor(detail::input_adapter&&, const bool, const bool)
+ */
+ template<typename A1, typename A2,
+ detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0>
+ static basic_json from_cbor(A1 && a1, A2 && a2,
+ const bool strict = true,
+ const bool allow_exceptions = true)
+ {
+ basic_json result;
+ detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+ const bool res = binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).sax_parse(input_format_t::cbor, &sdp, strict);
+ return res ? result : basic_json(value_t::discarded);
+ }
+
+ /*!
+ @brief create a JSON value from an input in MessagePack format
+
+ Deserializes a given input @a i to a JSON value using the MessagePack
+ serialization format.
+
+ The library maps MessagePack types to JSON value types as follows:
+
+ MessagePack type | JSON value type | first byte
+ ---------------- | --------------- | ----------
+ positive fixint | number_unsigned | 0x00..0x7F
+ fixmap | object | 0x80..0x8F
+ fixarray | array | 0x90..0x9F
+ fixstr | string | 0xA0..0xBF
+ nil | `null` | 0xC0
+ false | `false` | 0xC2
+ true | `true` | 0xC3
+ float 32 | number_float | 0xCA
+ float 64 | number_float | 0xCB
+ uint 8 | number_unsigned | 0xCC
+ uint 16 | number_unsigned | 0xCD
+ uint 32 | number_unsigned | 0xCE
+ uint 64 | number_unsigned | 0xCF
+ int 8 | number_integer | 0xD0
+ int 16 | number_integer | 0xD1
+ int 32 | number_integer | 0xD2
+ int 64 | number_integer | 0xD3
+ str 8 | string | 0xD9
+ str 16 | string | 0xDA
+ str 32 | string | 0xDB
+ array 16 | array | 0xDC
+ array 32 | array | 0xDD
+ map 16 | object | 0xDE
+ map 32 | object | 0xDF
+ negative fixint | number_integer | 0xE0-0xFF
+
+ @warning The mapping is **incomplete** in the sense that not all
+ MessagePack types can be converted to a JSON value. The following
+ MessagePack types are not supported and will yield parse errors:
+ - bin 8 - bin 32 (0xC4..0xC6)
+ - ext 8 - ext 32 (0xC7..0xC9)
+ - fixext 1 - fixext 16 (0xD4..0xD8)
+
+ @note Any MessagePack output created @ref to_msgpack can be successfully
+ parsed by @ref from_msgpack.
+
+ @param[in] i an input in MessagePack format convertible to an input
+ adapter
+ @param[in] strict whether to expect the input to be consumed until EOF
+ (true by default)
+ @param[in] allow_exceptions whether to throw exceptions in case of a
+ parse error (optional, true by default)
+
+ @return deserialized JSON value
+
+ @throw parse_error.110 if the given input ends prematurely or the end of
+ file was not reached when @a strict was set to true
+ @throw parse_error.112 if unsupported features from MessagePack were
+ used in the given input @a i or if the input is not valid MessagePack
+ @throw parse_error.113 if a string was expected as map key, but not found
+
+ @complexity Linear in the size of the input @a i.
+
+ @liveexample{The example shows the deserialization of a byte vector in
+ MessagePack format to a JSON value.,from_msgpack}
+
+ @sa http://msgpack.org
+ @sa @ref to_msgpack(const basic_json&) for the analogous serialization
+ @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool) for the
+ related CBOR format
+ @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for
+ the related UBJSON format
+ @sa @ref from_bson(detail::input_adapter&&, const bool, const bool) for
+ the related BSON format
+
+ @since version 2.0.9; parameter @a start_index since 2.1.1; changed to
+ consume input adapters, removed start_index parameter, and added
+ @a strict parameter since 3.0.0; added @a allow_exceptions parameter
+ since 3.2.0
+ */
+ static basic_json from_msgpack(detail::input_adapter&& i,
+ const bool strict = true,
+ const bool allow_exceptions = true)
+ {
+ basic_json result;
+ detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+ const bool res = binary_reader(detail::input_adapter(i)).sax_parse(input_format_t::msgpack, &sdp, strict);
+ return res ? result : basic_json(value_t::discarded);
+ }
+
+ /*!
+ @copydoc from_msgpack(detail::input_adapter&&, const bool, const bool)
+ */
+ template<typename A1, typename A2,
+ detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0>
+ static basic_json from_msgpack(A1 && a1, A2 && a2,
+ const bool strict = true,
+ const bool allow_exceptions = true)
+ {
+ basic_json result;
+ detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+ const bool res = binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).sax_parse(input_format_t::msgpack, &sdp, strict);
+ return res ? result : basic_json(value_t::discarded);
+ }
+
+ /*!
+ @brief create a JSON value from an input in UBJSON format
+
+ Deserializes a given input @a i to a JSON value using the UBJSON (Universal
+ Binary JSON) serialization format.
+
+ The library maps UBJSON types to JSON value types as follows:
+
+ UBJSON type | JSON value type | marker
+ ----------- | --------------------------------------- | ------
+ no-op | *no value, next value is read* | `N`
+ null | `null` | `Z`
+ false | `false` | `F`
+ true | `true` | `T`
+ float32 | number_float | `d`
+ float64 | number_float | `D`
+ uint8 | number_unsigned | `U`
+ int8 | number_integer | `i`
+ int16 | number_integer | `I`
+ int32 | number_integer | `l`
+ int64 | number_integer | `L`
+ string | string | `S`
+ char | string | `C`
+ array | array (optimized values are supported) | `[`
+ object | object (optimized values are supported) | `{`
+
+ @note The mapping is **complete** in the sense that any UBJSON value can
+ be converted to a JSON value.
+
+ @param[in] i an input in UBJSON format convertible to an input adapter
+ @param[in] strict whether to expect the input to be consumed until EOF
+ (true by default)
+ @param[in] allow_exceptions whether to throw exceptions in case of a
+ parse error (optional, true by default)
+
+ @return deserialized JSON value
+
+ @throw parse_error.110 if the given input ends prematurely or the end of
+ file was not reached when @a strict was set to true
+ @throw parse_error.112 if a parse error occurs
+ @throw parse_error.113 if a string could not be parsed successfully
+
+ @complexity Linear in the size of the input @a i.
+
+ @liveexample{The example shows the deserialization of a byte vector in
+ UBJSON format to a JSON value.,from_ubjson}
+
+ @sa http://ubjson.org
+ @sa @ref to_ubjson(const basic_json&, const bool, const bool) for the
+ analogous serialization
+ @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool) for the
+ related CBOR format
+ @sa @ref from_msgpack(detail::input_adapter&&, const bool, const bool) for
+ the related MessagePack format
+ @sa @ref from_bson(detail::input_adapter&&, const bool, const bool) for
+ the related BSON format
+
+ @since version 3.1.0; added @a allow_exceptions parameter since 3.2.0
+ */
+ static basic_json from_ubjson(detail::input_adapter&& i,
+ const bool strict = true,
+ const bool allow_exceptions = true)
+ {
+ basic_json result;
+ detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+ const bool res = binary_reader(detail::input_adapter(i)).sax_parse(input_format_t::ubjson, &sdp, strict);
+ return res ? result : basic_json(value_t::discarded);
+ }
+
+ /*!
+ @copydoc from_ubjson(detail::input_adapter&&, const bool, const bool)
+ */
+ template<typename A1, typename A2,
+ detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0>
+ static basic_json from_ubjson(A1 && a1, A2 && a2,
+ const bool strict = true,
+ const bool allow_exceptions = true)
+ {
+ basic_json result;
+ detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+ const bool res = binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).sax_parse(input_format_t::ubjson, &sdp, strict);
+ return res ? result : basic_json(value_t::discarded);
+ }
+
+ /*!
+ @brief Create a JSON value from an input in BSON format
+
+ Deserializes a given input @a i to a JSON value using the BSON (Binary JSON)
+ serialization format.
+
+ The library maps BSON record types to JSON value types as follows:
+
+ BSON type | BSON marker byte | JSON value type
+ --------------- | ---------------- | ---------------------------
+ double | 0x01 | number_float
+ string | 0x02 | string
+ document | 0x03 | object
+ array | 0x04 | array
+ binary | 0x05 | still unsupported
+ undefined | 0x06 | still unsupported
+ ObjectId | 0x07 | still unsupported
+ boolean | 0x08 | boolean
+ UTC Date-Time | 0x09 | still unsupported
+ null | 0x0A | null
+ Regular Expr. | 0x0B | still unsupported
+ DB Pointer | 0x0C | still unsupported
+ JavaScript Code | 0x0D | still unsupported
+ Symbol | 0x0E | still unsupported
+ JavaScript Code | 0x0F | still unsupported
+ int32 | 0x10 | number_integer
+ Timestamp | 0x11 | still unsupported
+ 128-bit decimal float | 0x13 | still unsupported
+ Max Key | 0x7F | still unsupported
+ Min Key | 0xFF | still unsupported
+
+ @warning The mapping is **incomplete**. The unsupported mappings
+ are indicated in the table above.
+
+ @param[in] i an input in BSON format convertible to an input adapter
+ @param[in] strict whether to expect the input to be consumed until EOF
+ (true by default)
+ @param[in] allow_exceptions whether to throw exceptions in case of a
+ parse error (optional, true by default)
+
+ @return deserialized JSON value
+
+ @throw parse_error.114 if an unsupported BSON record type is encountered
+
+ @complexity Linear in the size of the input @a i.
+
+ @liveexample{The example shows the deserialization of a byte vector in
+ BSON format to a JSON value.,from_bson}
+
+ @sa http://bsonspec.org/spec.html
+ @sa @ref to_bson(const basic_json&) for the analogous serialization
+ @sa @ref from_cbor(detail::input_adapter&&, const bool, const bool) for the
+ related CBOR format
+ @sa @ref from_msgpack(detail::input_adapter&&, const bool, const bool) for
+ the related MessagePack format
+ @sa @ref from_ubjson(detail::input_adapter&&, const bool, const bool) for the
+ related UBJSON format
+ */
+ static basic_json from_bson(detail::input_adapter&& i,
+ const bool strict = true,
+ const bool allow_exceptions = true)
+ {
+ basic_json result;
+ detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+ const bool res = binary_reader(detail::input_adapter(i)).sax_parse(input_format_t::bson, &sdp, strict);
+ return res ? result : basic_json(value_t::discarded);
+ }
+
+ /*!
+ @copydoc from_bson(detail::input_adapter&&, const bool, const bool)
+ */
+ template<typename A1, typename A2,
+ detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0>
+ static basic_json from_bson(A1 && a1, A2 && a2,
+ const bool strict = true,
+ const bool allow_exceptions = true)
+ {
+ basic_json result;
+ detail::json_sax_dom_parser<basic_json> sdp(result, allow_exceptions);
+ const bool res = binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).sax_parse(input_format_t::bson, &sdp, strict);
+ return res ? result : basic_json(value_t::discarded);
+ }
+
+
+
+ /// @}
+
+ //////////////////////////
+ // JSON Pointer support //
+ //////////////////////////
+
+ /// @name JSON Pointer functions
+ /// @{
+
+ /*!
+ @brief access specified element via JSON Pointer
+
+ Uses a JSON pointer to retrieve a reference to the respective JSON value.
+ No bound checking is performed. Similar to @ref operator[](const typename
+ object_t::key_type&), `null` values are created in arrays and objects if
+ necessary.
+
+ In particular:
+ - If the JSON pointer points to an object key that does not exist, it
+ is created an filled with a `null` value before a reference to it
+ is returned.
+ - If the JSON pointer points to an array index that does not exist, it
+ is created an filled with a `null` value before a reference to it
+ is returned. All indices between the current maximum and the given
+ index are also filled with `null`.
+ - The special value `-` is treated as a synonym for the index past the
+ end.
+
+ @param[in] ptr a JSON pointer
+
+ @return reference to the element pointed to by @a ptr
+
+ @complexity Constant.
+
+ @throw parse_error.106 if an array index begins with '0'
+ @throw parse_error.109 if an array index was not a number
+ @throw out_of_range.404 if the JSON pointer can not be resolved
+
+ @liveexample{The behavior is shown in the example.,operatorjson_pointer}
+
+ @since version 2.0.0
+ */
+ reference operator[](const json_pointer& ptr)
+ {
+ return ptr.get_unchecked(this);
+ }
+
+ /*!
+ @brief access specified element via JSON Pointer
+
+ Uses a JSON pointer to retrieve a reference to the respective JSON value.
+ No bound checking is performed. The function does not change the JSON
+ value; no `null` values are created. In particular, the the special value
+ `-` yields an exception.
+
+ @param[in] ptr JSON pointer to the desired element
+
+ @return const reference to the element pointed to by @a ptr
+
+ @complexity Constant.
+
+ @throw parse_error.106 if an array index begins with '0'
+ @throw parse_error.109 if an array index was not a number
+ @throw out_of_range.402 if the array index '-' is used
+ @throw out_of_range.404 if the JSON pointer can not be resolved
+
+ @liveexample{The behavior is shown in the example.,operatorjson_pointer_const}
+
+ @since version 2.0.0
+ */
+ const_reference operator[](const json_pointer& ptr) const
+ {
+ return ptr.get_unchecked(this);
+ }
+
+ /*!
+ @brief access specified element via JSON Pointer
+
+ Returns a reference to the element at with specified JSON pointer @a ptr,
+ with bounds checking.
+
+ @param[in] ptr JSON pointer to the desired element
+
+ @return reference to the element pointed to by @a ptr
+
+ @throw parse_error.106 if an array index in the passed JSON pointer @a ptr
+ begins with '0'. See example below.
+
+ @throw parse_error.109 if an array index in the passed JSON pointer @a ptr
+ is not a number. See example below.
+
+ @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr
+ is out of range. See example below.
+
+ @throw out_of_range.402 if the array index '-' is used in the passed JSON
+ pointer @a ptr. As `at` provides checked access (and no elements are
+ implicitly inserted), the index '-' is always invalid. See example below.
+
+ @throw out_of_range.403 if the JSON pointer describes a key of an object
+ which cannot be found. See example below.
+
+ @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved.
+ See example below.
+
+ @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+ changes in the JSON value.
+
+ @complexity Constant.
+
+ @since version 2.0.0
+
+ @liveexample{The behavior is shown in the example.,at_json_pointer}
+ */
+ reference at(const json_pointer& ptr)
+ {
+ return ptr.get_checked(this);
+ }
+
+ /*!
+ @brief access specified element via JSON Pointer
+
+ Returns a const reference to the element at with specified JSON pointer @a
+ ptr, with bounds checking.
+
+ @param[in] ptr JSON pointer to the desired element
+
+ @return reference to the element pointed to by @a ptr
+
+ @throw parse_error.106 if an array index in the passed JSON pointer @a ptr
+ begins with '0'. See example below.
+
+ @throw parse_error.109 if an array index in the passed JSON pointer @a ptr
+ is not a number. See example below.
+
+ @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr
+ is out of range. See example below.
+
+ @throw out_of_range.402 if the array index '-' is used in the passed JSON
+ pointer @a ptr. As `at` provides checked access (and no elements are
+ implicitly inserted), the index '-' is always invalid. See example below.
+
+ @throw out_of_range.403 if the JSON pointer describes a key of an object
+ which cannot be found. See example below.
+
+ @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved.
+ See example below.
+
+ @exceptionsafety Strong guarantee: if an exception is thrown, there are no
+ changes in the JSON value.
+
+ @complexity Constant.
+
+ @since version 2.0.0
+
+ @liveexample{The behavior is shown in the example.,at_json_pointer_const}
+ */
+ const_reference at(const json_pointer& ptr) const
+ {
+ return ptr.get_checked(this);
+ }
+
+ /*!
+ @brief return flattened JSON value
+
+ The function creates a JSON object whose keys are JSON pointers (see [RFC
+ 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all
+ primitive. The original JSON value can be restored using the @ref
+ unflatten() function.
+
+ @return an object that maps JSON pointers to primitive values
+
+ @note Empty objects and arrays are flattened to `null` and will not be
+ reconstructed correctly by the @ref unflatten() function.
+
+ @complexity Linear in the size the JSON value.
+
+ @liveexample{The following code shows how a JSON object is flattened to an
+ object whose keys consist of JSON pointers.,flatten}
+
+ @sa @ref unflatten() for the reverse function
+
+ @since version 2.0.0
+ */
+ basic_json flatten() const
+ {
+ basic_json result(value_t::object);
+ json_pointer::flatten("", *this, result);
+ return result;
+ }
+
+ /*!
+ @brief unflatten a previously flattened JSON value
+
+ The function restores the arbitrary nesting of a JSON value that has been
+ flattened before using the @ref flatten() function. The JSON value must
+ meet certain constraints:
+ 1. The value must be an object.
+ 2. The keys must be JSON pointers (see
+ [RFC 6901](https://tools.ietf.org/html/rfc6901))
+ 3. The mapped values must be primitive JSON types.
+
+ @return the original JSON from a flattened version
+
+ @note Empty objects and arrays are flattened by @ref flatten() to `null`
+ values and can not unflattened to their original type. Apart from
+ this example, for a JSON value `j`, the following is always true:
+ `j == j.flatten().unflatten()`.
+
+ @complexity Linear in the size the JSON value.
+
+ @throw type_error.314 if value is not an object
+ @throw type_error.315 if object values are not primitive
+
+ @liveexample{The following code shows how a flattened JSON object is
+ unflattened into the original nested JSON object.,unflatten}
+
+ @sa @ref flatten() for the reverse function
+
+ @since version 2.0.0
+ */
+ basic_json unflatten() const
+ {
+ return json_pointer::unflatten(*this);
+ }
+
+ /// @}
+
+ //////////////////////////
+ // JSON Patch functions //
+ //////////////////////////
+
+ /// @name JSON Patch functions
+ /// @{
+
+ /*!
+ @brief applies a JSON patch
+
+ [JSON Patch](http://jsonpatch.com) defines a JSON document structure for
+ expressing a sequence of operations to apply to a JSON) document. With
+ this function, a JSON Patch is applied to the current JSON value by
+ executing all operations from the patch.
+
+ @param[in] json_patch JSON patch document
+ @return patched document
+
+ @note The application of a patch is atomic: Either all operations succeed
+ and the patched document is returned or an exception is thrown. In
+ any case, the original value is not changed: the patch is applied
+ to a copy of the value.
+
+ @throw parse_error.104 if the JSON patch does not consist of an array of
+ objects
+
+ @throw parse_error.105 if the JSON patch is malformed (e.g., mandatory
+ attributes are missing); example: `"operation add must have member path"`
+
+ @throw out_of_range.401 if an array index is out of range.
+
+ @throw out_of_range.403 if a JSON pointer inside the patch could not be
+ resolved successfully in the current JSON value; example: `"key baz not
+ found"`
+
+ @throw out_of_range.405 if JSON pointer has no parent ("add", "remove",
+ "move")
+
+ @throw other_error.501 if "test" operation was unsuccessful
+
+ @complexity Linear in the size of the JSON value and the length of the
+ JSON patch. As usually only a fraction of the JSON value is affected by
+ the patch, the complexity can usually be neglected.
+
+ @liveexample{The following code shows how a JSON patch is applied to a
+ value.,patch}
+
+ @sa @ref diff -- create a JSON patch by comparing two JSON values
+
+ @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902)
+ @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901)
+
+ @since version 2.0.0
+ */
+ basic_json patch(const basic_json& json_patch) const
+ {
+ // make a working copy to apply the patch to
+ basic_json result = *this;
+
+ // the valid JSON Patch operations
+ enum class patch_operations {add, remove, replace, move, copy, test, invalid};
+
+ const auto get_op = [](const std::string & op)
+ {
+ if (op == "add")
+ {
+ return patch_operations::add;
+ }
+ if (op == "remove")
+ {
+ return patch_operations::remove;
+ }
+ if (op == "replace")
+ {
+ return patch_operations::replace;
+ }
+ if (op == "move")
+ {
+ return patch_operations::move;
+ }
+ if (op == "copy")
+ {
+ return patch_operations::copy;
+ }
+ if (op == "test")
+ {
+ return patch_operations::test;
+ }
+
+ return patch_operations::invalid;
+ };
+
+ // wrapper for "add" operation; add value at ptr
+ const auto operation_add = [&result](json_pointer & ptr, basic_json val)
+ {
+ // adding to the root of the target document means replacing it
+ if (ptr.is_root())
+ {
+ result = val;
+ }
+ else
+ {
+ // make sure the top element of the pointer exists
+ json_pointer top_pointer = ptr.top();
+ if (top_pointer != ptr)
+ {
+ result.at(top_pointer);
+ }
+
+ // get reference to parent of JSON pointer ptr
+ const auto last_path = ptr.pop_back();
+ basic_json& parent = result[ptr];
+
+ switch (parent.m_type)
+ {
+ case value_t::null:
+ case value_t::object:
+ {
+ // use operator[] to add value
+ parent[last_path] = val;
+ break;
+ }
+
+ case value_t::array:
+ {
+ if (last_path == "-")
+ {
+ // special case: append to back
+ parent.push_back(val);
+ }
+ else
+ {
+ const auto idx = json_pointer::array_index(last_path);
+ if (JSON_UNLIKELY(static_cast<size_type>(idx) > parent.size()))
+ {
+ // avoid undefined behavior
+ JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range"));
+ }
+
+ // default case: insert add offset
+ parent.insert(parent.begin() + static_cast<difference_type>(idx), val);
+ }
+ break;
+ }
+
+ // LCOV_EXCL_START
+ default:
+ {
+ // if there exists a parent it cannot be primitive
+ assert(false);
+ }
+ // LCOV_EXCL_STOP
+ }
+ }
+ };
+
+ // wrapper for "remove" operation; remove value at ptr
+ const auto operation_remove = [&result](json_pointer & ptr)
+ {
+ // get reference to parent of JSON pointer ptr
+ const auto last_path = ptr.pop_back();
+ basic_json& parent = result.at(ptr);
+
+ // remove child
+ if (parent.is_object())
+ {
+ // perform range check
+ auto it = parent.find(last_path);
+ if (JSON_LIKELY(it != parent.end()))
+ {
+ parent.erase(it);
+ }
+ else
+ {
+ JSON_THROW(out_of_range::create(403, "key '" + last_path + "' not found"));
+ }
+ }
+ else if (parent.is_array())
+ {
+ // note erase performs range check
+ parent.erase(static_cast<size_type>(json_pointer::array_index(last_path)));
+ }
+ };
+
+ // type check: top level value must be an array
+ if (JSON_UNLIKELY(not json_patch.is_array()))
+ {
+ JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects"));
+ }
+
+ // iterate and apply the operations
+ for (const auto& val : json_patch)
+ {
+ // wrapper to get a value for an operation
+ const auto get_value = [&val](const std::string & op,
+ const std::string & member,
+ bool string_type) -> basic_json &
+ {
+ // find value
+ auto it = val.m_value.object->find(member);
+
+ // context-sensitive error message
+ const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'";
+
+ // check if desired value is present
+ if (JSON_UNLIKELY(it == val.m_value.object->end()))
+ {
+ JSON_THROW(parse_error::create(105, 0, error_msg + " must have member '" + member + "'"));
+ }
+
+ // check if result is of type string
+ if (JSON_UNLIKELY(string_type and not it->second.is_string()))
+ {
+ JSON_THROW(parse_error::create(105, 0, error_msg + " must have string member '" + member + "'"));
+ }
+
+ // no error: return value
+ return it->second;
+ };
+
+ // type check: every element of the array must be an object
+ if (JSON_UNLIKELY(not val.is_object()))
+ {
+ JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects"));
+ }
+
+ // collect mandatory members
+ const std::string op = get_value("op", "op", true);
+ const std::string path = get_value(op, "path", true);
+ json_pointer ptr(path);
+
+ switch (get_op(op))
+ {
+ case patch_operations::add:
+ {
+ operation_add(ptr, get_value("add", "value", false));
+ break;
+ }
+
+ case patch_operations::remove:
+ {
+ operation_remove(ptr);
+ break;
+ }
+
+ case patch_operations::replace:
+ {
+ // the "path" location must exist - use at()
+ result.at(ptr) = get_value("replace", "value", false);
+ break;
+ }
+
+ case patch_operations::move:
+ {
+ const std::string from_path = get_value("move", "from", true);
+ json_pointer from_ptr(from_path);
+
+ // the "from" location must exist - use at()
+ basic_json v = result.at(from_ptr);
+
+ // The move operation is functionally identical to a
+ // "remove" operation on the "from" location, followed
+ // immediately by an "add" operation at the target
+ // location with the value that was just removed.
+ operation_remove(from_ptr);
+ operation_add(ptr, v);
+ break;
+ }
+
+ case patch_operations::copy:
+ {
+ const std::string from_path = get_value("copy", "from", true);
+ const json_pointer from_ptr(from_path);
+
+ // the "from" location must exist - use at()
+ basic_json v = result.at(from_ptr);
+
+ // The copy is functionally identical to an "add"
+ // operation at the target location using the value
+ // specified in the "from" member.
+ operation_add(ptr, v);
+ break;
+ }
+
+ case patch_operations::test:
+ {
+ bool success = false;
+ JSON_TRY
+ {
+ // check if "value" matches the one at "path"
+ // the "path" location must exist - use at()
+ success = (result.at(ptr) == get_value("test", "value", false));
+ }
+ JSON_INTERNAL_CATCH (out_of_range&)
+ {
+ // ignore out of range errors: success remains false
+ }
+
+ // throw an exception if test fails
+ if (JSON_UNLIKELY(not success))
+ {
+ JSON_THROW(other_error::create(501, "unsuccessful: " + val.dump()));
+ }
+
+ break;
+ }
+
+ case patch_operations::invalid:
+ {
+ // op must be "add", "remove", "replace", "move", "copy", or
+ // "test"
+ JSON_THROW(parse_error::create(105, 0, "operation value '" + op + "' is invalid"));
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /*!
+ @brief creates a diff as a JSON patch
+
+ Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can
+ be changed into the value @a target by calling @ref patch function.
+
+ @invariant For two JSON values @a source and @a target, the following code
+ yields always `true`:
+ @code {.cpp}
+ source.patch(diff(source, target)) == target;
+ @endcode
+
+ @note Currently, only `remove`, `add`, and `replace` operations are
+ generated.
+
+ @param[in] source JSON value to compare from
+ @param[in] target JSON value to compare against
+ @param[in] path helper value to create JSON pointers
+
+ @return a JSON patch to convert the @a source to @a target
+
+ @complexity Linear in the lengths of @a source and @a target.
+
+ @liveexample{The following code shows how a JSON patch is created as a
+ diff for two JSON values.,diff}
+
+ @sa @ref patch -- apply a JSON patch
+ @sa @ref merge_patch -- apply a JSON Merge Patch
+
+ @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902)
+
+ @since version 2.0.0
+ */
+ static basic_json diff(const basic_json& source, const basic_json& target,
+ const std::string& path = "")
+ {
+ // the patch
+ basic_json result(value_t::array);
+
+ // if the values are the same, return empty patch
+ if (source == target)
+ {
+ return result;
+ }
+
+ if (source.type() != target.type())
+ {
+ // different types: replace value
+ result.push_back(
+ {
+ {"op", "replace"}, {"path", path}, {"value", target}
+ });
+ }
+ else
+ {
+ switch (source.type())
+ {
+ case value_t::array:
+ {
+ // first pass: traverse common elements
+ std::size_t i = 0;
+ while (i < source.size() and i < target.size())
+ {
+ // recursive call to compare array values at index i
+ auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i));
+ result.insert(result.end(), temp_diff.begin(), temp_diff.end());
+ ++i;
+ }
+
+ // i now reached the end of at least one array
+ // in a second pass, traverse the remaining elements
+
+ // remove my remaining elements
+ const auto end_index = static_cast<difference_type>(result.size());
+ while (i < source.size())
+ {
+ // add operations in reverse order to avoid invalid
+ // indices
+ result.insert(result.begin() + end_index, object(
+ {
+ {"op", "remove"},
+ {"path", path + "/" + std::to_string(i)}
+ }));
+ ++i;
+ }
+
+ // add other remaining elements
+ while (i < target.size())
+ {
+ result.push_back(
+ {
+ {"op", "add"},
+ {"path", path + "/" + std::to_string(i)},
+ {"value", target[i]}
+ });
+ ++i;
+ }
+
+ break;
+ }
+
+ case value_t::object:
+ {
+ // first pass: traverse this object's elements
+ for (auto it = source.cbegin(); it != source.cend(); ++it)
+ {
+ // escape the key name to be used in a JSON patch
+ const auto key = json_pointer::escape(it.key());
+
+ if (target.find(it.key()) != target.end())
+ {
+ // recursive call to compare object values at key it
+ auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key);
+ result.insert(result.end(), temp_diff.begin(), temp_diff.end());
+ }
+ else
+ {
+ // found a key that is not in o -> remove it
+ result.push_back(object(
+ {
+ {"op", "remove"}, {"path", path + "/" + key}
+ }));
+ }
+ }
+
+ // second pass: traverse other object's elements
+ for (auto it = target.cbegin(); it != target.cend(); ++it)
+ {
+ if (source.find(it.key()) == source.end())
+ {
+ // found a key that is not in this -> add it
+ const auto key = json_pointer::escape(it.key());
+ result.push_back(
+ {
+ {"op", "add"}, {"path", path + "/" + key},
+ {"value", it.value()}
+ });
+ }
+ }
+
+ break;
+ }
+
+ default:
+ {
+ // both primitive type: replace value
+ result.push_back(
+ {
+ {"op", "replace"}, {"path", path}, {"value", target}
+ });
+ break;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /// @}
+
+ ////////////////////////////////
+ // JSON Merge Patch functions //
+ ////////////////////////////////
+
+ /// @name JSON Merge Patch functions
+ /// @{
+
+ /*!
+ @brief applies a JSON Merge Patch
+
+ The merge patch format is primarily intended for use with the HTTP PATCH
+ method as a means of describing a set of modifications to a target
+ resource's content. This function applies a merge patch to the current
+ JSON value.
+
+ The function implements the following algorithm from Section 2 of
+ [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396):
+
+ ```
+ define MergePatch(Target, Patch):
+ if Patch is an Object:
+ if Target is not an Object:
+ Target = {} // Ignore the contents and set it to an empty Object
+ for each Name/Value pair in Patch:
+ if Value is null:
+ if Name exists in Target:
+ remove the Name/Value pair from Target
+ else:
+ Target[Name] = MergePatch(Target[Name], Value)
+ return Target
+ else:
+ return Patch
+ ```
+
+ Thereby, `Target` is the current object; that is, the patch is applied to
+ the current value.
+
+ @param[in] apply_patch the patch to apply
+
+ @complexity Linear in the lengths of @a patch.
+
+ @liveexample{The following code shows how a JSON Merge Patch is applied to
+ a JSON document.,merge_patch}
+
+ @sa @ref patch -- apply a JSON patch
+ @sa [RFC 7396 (JSON Merge Patch)](https://tools.ietf.org/html/rfc7396)
+
+ @since version 3.0.0
+ */
+ void merge_patch(const basic_json& apply_patch)
+ {
+ if (apply_patch.is_object())
+ {
+ if (not is_object())
+ {
+ *this = object();
+ }
+ for (auto it = apply_patch.begin(); it != apply_patch.end(); ++it)
+ {
+ if (it.value().is_null())
+ {
+ erase(it.key());
+ }
+ else
+ {
+ operator[](it.key()).merge_patch(it.value());
+ }
+ }
+ }
+ else
+ {
+ *this = apply_patch;
+ }
+ }
+
+ /// @}
+};
+} // namespace nlohmann
+
+///////////////////////
+// nonmember support //
+///////////////////////
+
+// specialization of std::swap, and std::hash
+namespace std
+{
+
+/// hash value for JSON objects
+template<>
+struct hash<nlohmann::json>
+{
+ /*!
+ @brief return a hash value for a JSON object
+
+ @since version 1.0.0
+ */
+ std::size_t operator()(const nlohmann::json& j) const
+ {
+ // a naive hashing via the string representation
+ const auto& h = hash<nlohmann::json::string_t>();
+ return h(j.dump());
+ }
+};
+
+/// specialization for std::less<value_t>
+/// @note: do not remove the space after '<',
+/// see https://github.com/nlohmann/json/pull/679
+template<>
+struct less< ::nlohmann::detail::value_t>
+{
+ /*!
+ @brief compare two value_t enum values
+ @since version 3.0.0
+ */
+ bool operator()(nlohmann::detail::value_t lhs,
+ nlohmann::detail::value_t rhs) const noexcept
+ {
+ return nlohmann::detail::operator<(lhs, rhs);
+ }
+};
+
+/*!
+@brief exchanges the values of two JSON objects
+
+@since version 1.0.0
+*/
+template<>
+inline void swap<nlohmann::json>(nlohmann::json& j1, nlohmann::json& j2) noexcept(
+ is_nothrow_move_constructible<nlohmann::json>::value and
+ is_nothrow_move_assignable<nlohmann::json>::value
+)
+{
+ j1.swap(j2);
+}
+
+} // namespace std
+
+/*!
+@brief user-defined string literal for JSON values
+
+This operator implements a user-defined string literal for JSON objects. It
+can be used by adding `"_json"` to a string literal and returns a JSON object
+if no parse error occurred.
+
+@param[in] s a string representation of a JSON object
+@param[in] n the length of string @a s
+@return a JSON object
+
+@since version 1.0.0
+*/
+inline nlohmann::json operator "" _json(const char* s, std::size_t n)
+{
+ return nlohmann::json::parse(s, s + n);
+}
+
+/*!
+@brief user-defined string literal for JSON pointer
+
+This operator implements a user-defined string literal for JSON Pointers. It
+can be used by adding `"_json_pointer"` to a string literal and returns a JSON pointer
+object if no parse error occurred.
+
+@param[in] s a string representation of a JSON Pointer
+@param[in] n the length of string @a s
+@return a JSON pointer object
+
+@since version 2.0.0
+*/
+inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n)
+{
+ return nlohmann::json::json_pointer(std::string(s, n));
+}
+
+#include <lib/modernjson/detail/macro_unscope.hpp>
+
+#endif
diff --git a/include/lib/modernjson/json_fwd.h b/include/lib/modernjson/json_fwd.h
new file mode 100644
index 0000000..32abba9
--- /dev/null
+++ b/include/lib/modernjson/json_fwd.h
@@ -0,0 +1,64 @@
+#ifndef NLOHMANN_JSON_FWD_HPP
+#define NLOHMANN_JSON_FWD_HPP
+
+#include <cstdint> // int64_t, uint64_t
+#include <map> // map
+#include <memory> // allocator
+#include <string> // string
+#include <vector> // vector
+
+/*!
+@brief namespace for Niels Lohmann
+@see https://github.com/nlohmann
+@since version 1.0.0
+*/
+namespace nlohmann
+{
+/*!
+@brief default JSONSerializer template argument
+
+This serializer ignores the template arguments and uses ADL
+([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
+for serialization.
+*/
+template<typename T = void, typename SFINAE = void>
+struct adl_serializer;
+
+template<template<typename U, typename V, typename... Args> class ObjectType =
+ std::map,
+ template<typename U, typename... Args> class ArrayType = std::vector,
+ class StringType = std::string, class BooleanType = bool,
+ class NumberIntegerType = std::int64_t,
+ class NumberUnsignedType = std::uint64_t,
+ class NumberFloatType = double,
+ template<typename U> class AllocatorType = std::allocator,
+ template<typename T, typename SFINAE = void> class JSONSerializer =
+ adl_serializer>
+class basic_json;
+
+/*!
+@brief JSON Pointer
+
+A JSON pointer defines a string syntax for identifying a specific value
+within a JSON document. It can be used with functions `at` and
+`operator[]`. Furthermore, JSON pointers are the base for JSON patches.
+
+@sa [RFC 6901](https://tools.ietf.org/html/rfc6901)
+
+@since version 2.0.0
+*/
+template<typename BasicJsonType>
+class json_pointer;
+
+/*!
+@brief default JSON class
+
+This type is the default specialization of the @ref basic_json class which
+uses the standard template types.
+
+@since version 1.0.0
+*/
+using json = basic_json<>;
+} // namespace nlohmann
+
+#endif
diff --git a/include/lib/mpack/mpack.h b/include/lib/mpack/mpack.h
new file mode 100644
index 0000000..77a4141
--- /dev/null
+++ b/include/lib/mpack/mpack.h
@@ -0,0 +1,7172 @@
+/**
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015-2018 Nicholas Fraser
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+/*
+ * This is the MPack 1.0 amalgamation package.
+ *
+ * http://github.com/ludocode/mpack
+ */
+
+#ifndef MPACK_H
+#define MPACK_H 1
+
+#define MPACK_AMALGAMATED 1
+#define MPACK_RELEASE_VERSION 1
+
+#if defined(MPACK_HAS_CONFIG) && MPACK_HAS_CONFIG
+#include "mpack-config.h"
+#endif
+
+
+/* mpack/mpack-defaults.h.h */
+
+
+/**
+ * @name Features
+ * @{
+ */
+
+/**
+ * @def MPACK_READER
+ *
+ * Enables compilation of the base Tag Reader.
+ */
+#ifndef MPACK_READER
+#define MPACK_READER 1
+#endif
+
+/**
+ * @def MPACK_EXPECT
+ *
+ * Enables compilation of the static Expect API.
+ */
+#ifndef MPACK_EXPECT
+#define MPACK_EXPECT 1
+#endif
+
+/**
+ * @def MPACK_NODE
+ *
+ * Enables compilation of the dynamic Node API.
+ */
+#ifndef MPACK_NODE
+#define MPACK_NODE 0
+#endif
+
+/**
+ * @def MPACK_WRITER
+ *
+ * Enables compilation of the Writer.
+ */
+#ifndef MPACK_WRITER
+#define MPACK_WRITER 1
+#endif
+
+/**
+ * @def MPACK_COMPATIBILITY
+ *
+ * Enables compatibility features for reading and writing older
+ * versions of MessagePack.
+ *
+ * This is disabled by default. When disabled, the behaviour is equivalent to
+ * using the default version, @ref mpack_version_current.
+ *
+ * Enable this if you need to interoperate with applications or data that do
+ * not support the new (v5) MessagePack spec. See the section on v4
+ * compatibility in @ref docs/protocol.md for more information.
+ */
+#ifndef MPACK_COMPATIBILITY
+#define MPACK_COMPATIBILITY 0
+#endif
+
+/**
+ * @def MPACK_EXTENSIONS
+ *
+ * Enables the use of extension types.
+ *
+ * This is disabled by default. Define it to 1 to enable it. If disabled,
+ * functions to read and write extensions will not exist, and any occurrence of
+ * extension types in parsed messages will flag @ref mpack_error_invalid.
+ *
+ * MPack discourages the use of extension types. See the section on extension
+ * types in @ref docs/protocol.md for more information.
+ */
+#ifndef MPACK_EXTENSIONS
+#define MPACK_EXTENSIONS 0
+#endif
+
+
+/**
+ * @}
+ */
+
+
+/**
+ * @name Dependencies
+ * @{
+ */
+
+/**
+ * @def MPACK_HAS_CONFIG
+ *
+ * Enables the use of an @c mpack-config.h configuration file for MPack.
+ * This file must be in the same folder as @c mpack.h, or it must be
+ * available from your project's include paths.
+ */
+// This goes in your project settings.
+
+/**
+ * @def MPACK_STDLIB
+ *
+ * Enables the use of C stdlib. This allows the library to use malloc
+ * for debugging and in allocation helpers.
+ */
+#ifndef MPACK_STDLIB
+#if defined(MULTIPASS_ARCH_msp430fr5969lp) || defined(MULTIPASS_ARCH_msp430fr5994lp) || \
+ defined(MULTIPASS_ARCH_arduino_nano) || defined(MULTIPASS_ARCH_blinkenrocket) || \
+ defined(MULTIPASS_ARCH_esp8266)
+#define MPACK_STDLIB 0
+#else
+#define MPACK_STDLIB 1
+#endif
+#endif
+
+/**
+ * @def MPACK_STDIO
+ *
+ * Enables the use of C stdio. This adds helpers for easily
+ * reading/writing C files and makes debugging easier.
+ */
+#ifndef MPACK_STDIO
+#if defined(MULTIPASS_ARCH_msp430fr5969lp) || defined(MULTIPASS_ARCH_msp430fr5994lp) || \
+ defined(MULTIPASS_ARCH_arduino_nano) || defined(MULTIPASS_ARCH_blinkenrocket) || \
+ defined(MULTIPASS_ARCH_esp8266)
+#define MPACK_STDIO 0
+#else
+#define MPACK_STDIO 1
+#endif
+#endif
+
+/**
+ * @}
+ */
+
+
+/**
+ * @name System Functions
+ * @{
+ */
+
+/**
+ * @def MPACK_MALLOC
+ *
+ * Defines the memory allocation function used by MPack. This is used by
+ * helpers for automatically allocating data the correct size, and for
+ * debugging functions. If this macro is undefined, the allocation helpers
+ * will not be compiled.
+ *
+ * The default is @c malloc() if @ref MPACK_STDLIB is enabled.
+ */
+/**
+ * @def MPACK_FREE
+ *
+ * Defines the memory free function used by MPack. This is used by helpers
+ * for automatically allocating data the correct size. If this macro is
+ * undefined, the allocation helpers will not be compiled.
+ *
+ * The default is @c free() if @ref MPACK_MALLOC has not been customized and
+ * @ref MPACK_STDLIB is enabled.
+ */
+/**
+ * @def MPACK_REALLOC
+ *
+ * Defines the realloc function used by MPack. It is used by growable
+ * buffers to resize more efficiently.
+ *
+ * The default is @c realloc() if @ref MPACK_MALLOC has not been customized and
+ * @ref MPACK_STDLIB is enabled.
+ *
+ * This is optional, even when @ref MPACK_MALLOC is used. If @ref MPACK_MALLOC is
+ * set and @ref MPACK_REALLOC is not, @ref MPACK_MALLOC is used with a simple copy
+ * to grow buffers.
+ */
+#if defined(MPACK_STDLIB) && MPACK_STDLIB && !defined(MPACK_MALLOC)
+#define MPACK_MALLOC malloc
+#define MPACK_REALLOC realloc
+#define MPACK_FREE free
+#endif
+
+/**
+ * @}
+ */
+
+
+/**
+ * @name Debugging Options
+ */
+
+/**
+ * @def MPACK_DEBUG
+ *
+ * Enables debug features. You may want to wrap this around your
+ * own debug preprocs. By default, this is enabled if @c DEBUG or @c _DEBUG
+ * are defined. (@c NDEBUG is not used since it is allowed to have
+ * different values in different translation units.)
+ */
+#if !defined(MPACK_DEBUG) && (defined(DEBUG) || defined(_DEBUG))
+#define MPACK_DEBUG 1
+#endif
+
+/**
+ * @def MPACK_STRINGS
+ *
+ * Enables descriptive error and type strings.
+ *
+ * This can be turned off (by defining it to 0) to maximize space savings
+ * on embedded devices. If this is disabled, string functions such as
+ * mpack_error_to_string() and mpack_type_to_string() return an empty string.
+ */
+#ifndef MPACK_STRINGS
+#define MPACK_STRINGS 1
+#endif
+
+/**
+ * Set this to 1 to implement a custom @ref mpack_assert_fail() function.
+ * See the documentation on @ref mpack_assert_fail() for details.
+ *
+ * Asserts are only used when @ref MPACK_DEBUG is enabled, and can be
+ * triggered by bugs in MPack or bugs due to incorrect usage of MPack.
+ */
+#ifndef MPACK_CUSTOM_ASSERT
+#define MPACK_CUSTOM_ASSERT 0
+#endif
+
+/**
+ * @def MPACK_READ_TRACKING
+ *
+ * Enables compound type size tracking for readers. This ensures that the
+ * correct number of elements or bytes are read from a compound type.
+ *
+ * This is enabled by default in debug builds (provided a @c malloc() is
+ * available.)
+ */
+#if !defined(MPACK_READ_TRACKING) && \
+ defined(MPACK_DEBUG) && MPACK_DEBUG && \
+ defined(MPACK_READER) && MPACK_READER && \
+ defined(MPACK_MALLOC)
+#define MPACK_READ_TRACKING 1
+#endif
+
+/**
+ * @def MPACK_WRITE_TRACKING
+ *
+ * Enables compound type size tracking for writers. This ensures that the
+ * correct number of elements or bytes are written in a compound type.
+ *
+ * Note that without write tracking enabled, it is possible for buggy code
+ * to emit invalid MessagePack without flagging an error by writing the wrong
+ * number of elements or bytes in a compound type. With tracking enabled,
+ * MPack will catch such errors and break on the offending line of code.
+ *
+ * This is enabled by default in debug builds (provided a @c malloc() is
+ * available.)
+ */
+#if !defined(MPACK_WRITE_TRACKING) && \
+ defined(MPACK_DEBUG) && MPACK_DEBUG && \
+ defined(MPACK_WRITER) && MPACK_WRITER && \
+ defined(MPACK_MALLOC)
+#define MPACK_WRITE_TRACKING 1
+#endif
+
+/**
+ * @}
+ */
+
+
+/**
+ * @name Miscellaneous Options
+ * @{
+ */
+
+/**
+ * Whether to optimize for size or speed.
+ *
+ * Optimizing for size simplifies some parsing and encoding algorithms
+ * at the expense of speed, and saves a few kilobytes of space in the
+ * resulting executable.
+ *
+ * This automatically detects -Os with GCC/Clang. Unfortunately there
+ * doesn't seem to be a macro defined for /Os under MSVC.
+ */
+#ifndef MPACK_OPTIMIZE_FOR_SIZE
+#ifdef __OPTIMIZE_SIZE__
+#define MPACK_OPTIMIZE_FOR_SIZE 1
+#else
+#define MPACK_OPTIMIZE_FOR_SIZE 0
+#endif
+#endif
+
+/**
+ * Stack space in bytes to use when initializing a reader or writer
+ * with a stack-allocated buffer.
+ */
+#ifndef MPACK_STACK_SIZE
+#define MPACK_STACK_SIZE 4096
+#endif
+
+/**
+ * Buffer size to use for allocated buffers (such as for a file writer.)
+ *
+ * Starting with a single page and growing as needed seems to
+ * provide the best performance with minimal memory waste.
+ * Increasing this does not improve performance even when writing
+ * huge messages.
+ */
+#ifndef MPACK_BUFFER_SIZE
+#define MPACK_BUFFER_SIZE 4096
+#endif
+
+/**
+ * Minimum size of an allocated node page in bytes.
+ *
+ * The children for a given compound element must be contiguous, so
+ * larger pages than this may be allocated as needed. (Safety checks
+ * exist to prevent malicious data from causing too large allocations.)
+ *
+ * See @ref mpack_node_data_t for the size of nodes.
+ *
+ * Using as many nodes fit in one memory page seems to provide the
+ * best performance, and has very little waste when parsing small
+ * messages.
+ */
+#ifndef MPACK_NODE_PAGE_SIZE
+#define MPACK_NODE_PAGE_SIZE 4096
+#endif
+
+/**
+ * The initial depth for the node parser. When MPACK_MALLOC is available,
+ * the node parser has no practical depth limit, and it is not recursive
+ * so there is no risk of overflowing the call stack.
+ */
+#ifndef MPACK_NODE_INITIAL_DEPTH
+#define MPACK_NODE_INITIAL_DEPTH 8
+#endif
+
+/**
+ * The maximum depth for the node parser if @ref MPACK_MALLOC is not available.
+ */
+#ifndef MPACK_NODE_MAX_DEPTH_WITHOUT_MALLOC
+#define MPACK_NODE_MAX_DEPTH_WITHOUT_MALLOC 32
+#endif
+
+/**
+ * @}
+ */
+
+
+/**
+ * @}
+ */
+
+
+/* mpack/mpack-platform.h.h */
+
+/**
+ * @file
+ *
+ * Abstracts all platform-specific code from MPack. This contains
+ * implementations of standard C functions when libc is not available,
+ * as well as wrappers to library functions.
+ */
+
+#ifndef MPACK_PLATFORM_H
+#define MPACK_PLATFORM_H 1
+
+
+
+/* Pre-include checks */
+
+#if defined(_MSC_VER) && _MSC_VER < 1800 && !defined(__cplusplus)
+#error "In Visual Studio 2012 and earlier, MPack must be compiled as C++. Enable the /Tp compiler flag."
+#endif
+
+#if defined(WIN32) && defined(MPACK_INTERNAL) && MPACK_INTERNAL
+#define _CRT_SECURE_NO_WARNINGS 1
+#endif
+
+
+
+/* Doxygen preprocs */
+#if defined(MPACK_DOXYGEN) && MPACK_DOXYGEN
+#define MPACK_HAS_CONFIG 0
+// We give these their default values of 0 here even though they are defined to
+// 1 in the doxyfile. Doxygen will show this as the value in the docs, even
+// though it ignores it when parsing the rest of the source. This is what we
+// want, since we want the documentation to show these defaults but still
+// generate documentation for the functions they add when they're on.
+#define MPACK_COMPATIBILITY 0
+#define MPACK_EXTENSIONS 0
+#endif
+
+
+
+/* Include the custom config file if enabled */
+
+#if defined(MPACK_HAS_CONFIG) && MPACK_HAS_CONFIG
+/* #include "mpack-config.h" */
+#endif
+
+/*
+ * Now that the optional config is included, we define the defaults
+ * for any of the configuration options and other switches that aren't
+ * yet defined.
+ */
+#if defined(MPACK_DOXYGEN) && MPACK_DOXYGEN
+/* #include "mpack-defaults-doxygen.h" */
+#else
+/* #include "mpack-defaults.h" */
+#endif
+
+/*
+ * All remaining configuration options that have not yet been set must
+ * be defined here in order to support -Wundef.
+ */
+#ifndef MPACK_DEBUG
+#define MPACK_DEBUG 0
+#endif
+#ifndef MPACK_CUSTOM_BREAK
+#define MPACK_CUSTOM_BREAK 0
+#endif
+#ifndef MPACK_READ_TRACKING
+#define MPACK_READ_TRACKING 0
+#endif
+#ifndef MPACK_WRITE_TRACKING
+#define MPACK_WRITE_TRACKING 0
+#endif
+#ifndef MPACK_EMIT_INLINE_DEFS
+#define MPACK_EMIT_INLINE_DEFS 0
+#endif
+#ifndef MPACK_AMALGAMATED
+#define MPACK_AMALGAMATED 0
+#endif
+#ifndef MPACK_RELEASE_VERSION
+#define MPACK_RELEASE_VERSION 0
+#endif
+#ifndef MPACK_INTERNAL
+#define MPACK_INTERNAL 0
+#endif
+#ifndef MPACK_NO_BUILTINS
+#define MPACK_NO_BUILTINS 0
+#endif
+
+
+
+/* System headers (based on configuration) */
+
+#ifndef __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS 1
+#endif
+#ifndef __STDC_FORMAT_MACROS
+#define __STDC_FORMAT_MACROS 1
+#endif
+#ifndef __STDC_CONSTANT_MACROS
+#define __STDC_CONSTANT_MACROS 1
+#endif
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <limits.h>
+
+#if MPACK_STDLIB
+#include <string.h>
+#include <stdlib.h>
+#endif
+
+#if MPACK_STDIO
+#include <stdio.h>
+#include <errno.h>
+#endif
+
+
+
+/*
+ * Header configuration
+ */
+
+#ifdef __cplusplus
+ #define MPACK_EXTERN_C_START extern "C" {
+ #define MPACK_EXTERN_C_END }
+#else
+ #define MPACK_EXTERN_C_START /* nothing */
+ #define MPACK_EXTERN_C_END /* nothing */
+#endif
+
+/* GCC versions from 4.6 to before 5.1 warn about defining a C99
+ * non-static inline function before declaring it (see issue #20) */
+#ifdef __GNUC__
+ #if (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+ #ifdef __cplusplus
+ #define MPACK_DECLARED_INLINE_WARNING_START \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wmissing-declarations\"")
+ #else
+ #define MPACK_DECLARED_INLINE_WARNING_START \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wmissing-prototypes\"")
+ #endif
+ #define MPACK_DECLARED_INLINE_WARNING_END \
+ _Pragma ("GCC diagnostic pop")
+ #endif
+#endif
+#ifndef MPACK_DECLARED_INLINE_WARNING_START
+ #define MPACK_DECLARED_INLINE_WARNING_START /* nothing */
+ #define MPACK_DECLARED_INLINE_WARNING_END /* nothing */
+#endif
+
+/* GCC versions before 4.8 warn about shadowing a function with a
+ * variable that isn't a function or function pointer (like "index") */
+#ifdef __GNUC__
+ #if (__GNUC__ < 4) || (__GNUC__ == 4 && __GNUC_MINOR__ < 8)
+ #define MPACK_WSHADOW_WARNING_START \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wshadow\"")
+ #define MPACK_WSHADOW_WARNING_END \
+ _Pragma ("GCC diagnostic pop")
+ #endif
+#endif
+#ifndef MPACK_WSHADOW_WARNING_START
+ #define MPACK_WSHADOW_WARNING_START /* nothing */
+ #define MPACK_WSHADOW_WARNING_END /* nothing */
+#endif
+
+#define MPACK_HEADER_START \
+ MPACK_EXTERN_C_START \
+ MPACK_WSHADOW_WARNING_START \
+ MPACK_DECLARED_INLINE_WARNING_START
+
+#define MPACK_HEADER_END \
+ MPACK_DECLARED_INLINE_WARNING_END \
+ MPACK_WSHADOW_WARNING_END \
+ MPACK_EXTERN_C_END
+
+MPACK_HEADER_START
+
+
+
+/* Miscellaneous helper macros */
+
+#define MPACK_UNUSED(var) ((void)(var))
+
+#define MPACK_STRINGIFY_IMPL(arg) #arg
+#define MPACK_STRINGIFY(arg) MPACK_STRINGIFY_IMPL(arg)
+
+// This is a workaround for MSVC's incorrect expansion of __VA_ARGS__. It
+// treats __VA_ARGS__ as a single preprocessor token when passed in the
+// argument list of another macro unless we use an outer wrapper to expand it
+// lexically first. (For safety/consistency we use this in all variadic macros
+// that don't ignore the variadic arguments regardless of whether __VA_ARGS__
+// is passed to another macro.)
+// https://stackoverflow.com/a/32400131
+#define MPACK_EXPAND(x) x
+
+// Extracts the first argument of a variadic macro list, where there might only
+// be one argument.
+#define MPACK_EXTRACT_ARG0_IMPL(first, ...) first
+#define MPACK_EXTRACT_ARG0(...) MPACK_EXPAND(MPACK_EXTRACT_ARG0_IMPL( __VA_ARGS__ , ignored))
+
+// Stringifies the first argument of a variadic macro list, where there might
+// only be one argument.
+#define MPACK_STRINGIFY_ARG0_impl(first, ...) #first
+#define MPACK_STRINGIFY_ARG0(...) MPACK_EXPAND(MPACK_STRINGIFY_ARG0_impl( __VA_ARGS__ , ignored))
+
+
+
+/*
+ * Definition of inline macros.
+ *
+ * MPack does not use static inline in header files; only one non-inline definition
+ * of each function should exist in the final build. This can reduce the binary size
+ * in cases where the compiler cannot or chooses not to inline a function.
+ * The addresses of functions should also compare equal across translation units
+ * regardless of whether they are declared inline.
+ *
+ * The above requirements mean that the declaration and definition of non-trivial
+ * inline functions must be separated so that the definitions will only
+ * appear when necessary. In addition, three different linkage models need
+ * to be supported:
+ *
+ * - The C99 model, where a standalone function is emitted only if there is any
+ * `extern inline` or non-`inline` declaration (including the definition itself)
+ *
+ * - The GNU model, where an `inline` definition emits a standalone function and an
+ * `extern inline` definition does not, regardless of other declarations
+ *
+ * - The C++ model, where `inline` emits a standalone function with special
+ * (COMDAT) linkage
+ *
+ * The macros below wrap up everything above. All inline functions defined in header
+ * files have a single non-inline definition emitted in the compilation of
+ * mpack-platform.c. All inline declarations and definitions use the same MPACK_INLINE
+ * specification to simplify the rules on when standalone functions are emitted.
+ * Inline functions in source files are defined MPACK_STATIC_INLINE.
+ *
+ * Additional reading:
+ * http://www.greenend.org.uk/rjk/tech/inline.html
+ */
+
+#if defined(__cplusplus)
+ // C++ rules
+ // The linker will need COMDAT support to link C++ object files,
+ // so we don't need to worry about emitting definitions from C++
+ // translation units. If mpack-platform.c (or the amalgamation)
+ // is compiled as C, its definition will be used, otherwise a
+ // C++ definition will be used, and no other C files will emit
+ // a defition.
+ #define MPACK_INLINE inline
+
+#elif defined(_MSC_VER)
+ // MSVC 2013 always uses COMDAT linkage, but it doesn't treat 'inline' as a
+ // keyword in C99 mode. (This appears to be fixed in a later version of
+ // MSVC but we don't bother detecting it.)
+ #define MPACK_INLINE __inline
+ #define MPACK_STATIC_INLINE static __inline
+
+#elif defined(__GNUC__) && (defined(__GNUC_GNU_INLINE__) || \
+ !defined(__GNUC_STDC_INLINE__) && !defined(__GNUC_GNU_INLINE__))
+ // GNU rules
+ #if MPACK_EMIT_INLINE_DEFS
+ #define MPACK_INLINE inline
+ #else
+ #define MPACK_INLINE extern inline
+ #endif
+
+#else
+ // C99 rules
+ #if MPACK_EMIT_INLINE_DEFS
+ #define MPACK_INLINE extern inline
+ #else
+ #define MPACK_INLINE inline
+ #endif
+#endif
+
+#ifndef MPACK_STATIC_INLINE
+#define MPACK_STATIC_INLINE static inline
+#endif
+
+#ifdef MPACK_OPTIMIZE_FOR_SPEED
+ #error "You should define MPACK_OPTIMIZE_FOR_SIZE, not MPACK_OPTIMIZE_FOR_SPEED."
+#endif
+
+
+
+/*
+ * Prevent inlining
+ *
+ * When a function is only used once, it is almost always inlined
+ * automatically. This can cause poor instruction cache usage because a
+ * function that should rarely be called (such as buffer exhaustion handling)
+ * will get inlined into the middle of a hot code path.
+ */
+
+#if !MPACK_NO_BUILTINS
+ #if defined(_MSC_VER)
+ #define MPACK_NOINLINE __declspec(noinline)
+ #elif defined(__GNUC__) || defined(__clang__)
+ #define MPACK_NOINLINE __attribute__((noinline))
+ #endif
+#endif
+#ifndef MPACK_NOINLINE
+ #define MPACK_NOINLINE /* nothing */
+#endif
+
+
+
+/* Some compiler-specific keywords and builtins */
+
+#if !MPACK_NO_BUILTINS
+ #if defined(__GNUC__) || defined(__clang__)
+ #define MPACK_UNREACHABLE __builtin_unreachable()
+ #define MPACK_NORETURN(fn) fn __attribute__((noreturn))
+ #define MPACK_RESTRICT __restrict__
+ #elif defined(_MSC_VER)
+ #define MPACK_UNREACHABLE __assume(0)
+ #define MPACK_NORETURN(fn) __declspec(noreturn) fn
+ #define MPACK_RESTRICT __restrict
+ #endif
+#endif
+
+#ifndef MPACK_RESTRICT
+#ifdef __cplusplus
+#define MPACK_RESTRICT /* nothing, unavailable in C++ */
+#else
+#define MPACK_RESTRICT restrict /* required in C99 */
+#endif
+#endif
+
+#ifndef MPACK_UNREACHABLE
+#define MPACK_UNREACHABLE ((void)0)
+#endif
+#ifndef MPACK_NORETURN
+#define MPACK_NORETURN(fn) fn
+#endif
+
+
+
+/*
+ * Likely/unlikely
+ *
+ * These should only really be used when a branch is taken (or not taken) less
+ * than 1/1000th of the time. Buffer flush checks when writing very small
+ * elements are a good example.
+ */
+
+#if !MPACK_NO_BUILTINS
+ #if defined(__GNUC__) || defined(__clang__)
+ #ifndef MPACK_LIKELY
+ #define MPACK_LIKELY(x) __builtin_expect((x),1)
+ #endif
+ #ifndef MPACK_UNLIKELY
+ #define MPACK_UNLIKELY(x) __builtin_expect((x),0)
+ #endif
+ #endif
+#endif
+
+#ifndef MPACK_LIKELY
+ #define MPACK_LIKELY(x) (x)
+#endif
+#ifndef MPACK_UNLIKELY
+ #define MPACK_UNLIKELY(x) (x)
+#endif
+
+
+
+/* Static assert */
+
+#ifndef MPACK_STATIC_ASSERT
+ #if defined(__cplusplus)
+ #if __cplusplus >= 201103L
+ #define MPACK_STATIC_ASSERT static_assert
+ #endif
+ #elif defined(__STDC_VERSION__)
+ #if __STDC_VERSION__ >= 201112L
+ #define MPACK_STATIC_ASSERT _Static_assert
+ #endif
+ #endif
+#endif
+
+#if !MPACK_NO_BUILTINS
+ #ifndef MPACK_STATIC_ASSERT
+ #if defined(__has_feature)
+ #if __has_feature(cxx_static_assert)
+ #define MPACK_STATIC_ASSERT static_assert
+ #elif __has_feature(c_static_assert)
+ #define MPACK_STATIC_ASSERT _Static_assert
+ #endif
+ #endif
+ #endif
+
+ #ifndef MPACK_STATIC_ASSERT
+ #if defined(__GNUC__)
+ #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+ #ifndef __cplusplus
+ #if __GNUC__ >= 5
+ #define MPACK_IGNORE_PEDANTIC "GCC diagnostic ignored \"-Wpedantic\""
+ #else
+ #define MPACK_IGNORE_PEDANTIC "GCC diagnostic ignored \"-pedantic\""
+ #endif
+ #define MPACK_STATIC_ASSERT(expr, str) do { \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma (MPACK_IGNORE_PEDANTIC) \
+ _Pragma ("GCC diagnostic ignored \"-Wc++-compat\"") \
+ _Static_assert(expr, str); \
+ _Pragma ("GCC diagnostic pop") \
+ } while (0)
+ #endif
+ #endif
+ #endif
+ #endif
+
+ #ifndef MPACK_STATIC_ASSERT
+ #ifdef _MSC_VER
+ #if _MSC_VER >= 1600
+ #define MPACK_STATIC_ASSERT static_assert
+ #endif
+ #endif
+ #endif
+#endif
+
+#ifndef MPACK_STATIC_ASSERT
+ #define MPACK_STATIC_ASSERT(expr, str) (MPACK_UNUSED(sizeof(char[1 - 2*!(expr)])))
+#endif
+
+
+
+/* _Generic */
+
+#ifndef MPACK_HAS_GENERIC
+ #if defined(__clang__) && defined(__has_feature)
+ // With Clang we can test for _Generic support directly
+ // and ignore C/C++ version
+ #if __has_feature(c_generic_selections)
+ #define MPACK_HAS_GENERIC 1
+ #else
+ #define MPACK_HAS_GENERIC 0
+ #endif
+ #endif
+#endif
+
+#ifndef MPACK_HAS_GENERIC
+ #if defined(__STDC_VERSION__)
+ #if __STDC_VERSION__ >= 201112L
+ #if defined(__GNUC__) && !defined(__clang__)
+ // GCC does not have full C11 support in GCC 4.7 and 4.8
+ #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)
+ #define MPACK_HAS_GENERIC 1
+ #endif
+ #else
+ // We hope other compilers aren't lying about C11/_Generic support
+ #define MPACK_HAS_GENERIC 1
+ #endif
+ #endif
+ #endif
+#endif
+
+#ifndef MPACK_HAS_GENERIC
+ #define MPACK_HAS_GENERIC 0
+#endif
+
+
+
+/*
+ * Finite Math
+ *
+ * -ffinite-math-only, included in -ffast-math, breaks functions that
+ * that check for non-finite real values such as isnan() and isinf().
+ *
+ * We should use this to trap errors when reading data that contains
+ * non-finite reals. This isn't currently implemented.
+ */
+
+#ifndef MPACK_FINITE_MATH
+#if defined(__FINITE_MATH_ONLY__) && __FINITE_MATH_ONLY__
+#define MPACK_FINITE_MATH 1
+#endif
+#endif
+
+#ifndef MPACK_FINITE_MATH
+#define MPACK_FINITE_MATH 0
+#endif
+
+
+
+/*
+ * Endianness checks
+ *
+ * These define MPACK_NHSWAP*() which swap network<->host byte
+ * order when needed.
+ *
+ * We leave them undefined if we can't determine the endianness
+ * at compile-time, in which case we fall back to bit-shifts.
+ *
+ * See the notes in mpack-common.h.
+ */
+
+#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && defined(__ORDER_BIG_ENDIAN__)
+ #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+ #define MPACK_NHSWAP16(x) (x)
+ #define MPACK_NHSWAP32(x) (x)
+ #define MPACK_NHSWAP64(x) (x)
+ #elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+
+ #if !MPACK_NO_BUILTINS
+ #if defined(__clang__)
+ #ifdef __has_builtin
+ // Unlike the GCC builtins, the bswap builtins in Clang
+ // significantly improve ARM performance.
+ #if __has_builtin(__builtin_bswap16)
+ #define MPACK_NHSWAP16(x) __builtin_bswap16(x)
+ #endif
+ #if __has_builtin(__builtin_bswap32)
+ #define MPACK_NHSWAP32(x) __builtin_bswap32(x)
+ #endif
+ #if __has_builtin(__builtin_bswap64)
+ #define MPACK_NHSWAP64(x) __builtin_bswap64(x)
+ #endif
+ #endif
+
+ #elif defined(__GNUC__)
+
+ // The GCC bswap builtins are apparently poorly optimized on older
+ // versions of GCC, so we set a minimum version here just in case.
+ // http://hardwarebug.org/2010/01/14/beware-the-builtins/
+
+ #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)
+ #define MPACK_NHSWAP64(x) __builtin_bswap64(x)
+ #endif
+
+ // __builtin_bswap16() was not implemented on all platforms
+ // until GCC 4.8.0:
+ // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52624
+ //
+ // The 16- and 32-bit versions in GCC significantly reduce performance
+ // on ARM with little effect on code size so we don't use them.
+
+ #endif
+ #endif
+ #endif
+
+#elif defined(_MSC_VER) && defined(_WIN32) && !MPACK_NO_BUILTINS
+
+ // On Windows, we assume x86 and x86_64 are always little-endian.
+ // We make no assumptions about ARM even though all current
+ // Windows Phone devices are little-endian in case Microsoft's
+ // compiler is ever used with a big-endian ARM device.
+
+ #if defined(_M_IX86) || defined(_M_X64) || defined(_M_AMD64)
+ #define MPACK_NHSWAP16(x) _byteswap_ushort(x)
+ #define MPACK_NHSWAP32(x) _byteswap_ulong(x)
+ #define MPACK_NHSWAP64(x) _byteswap_uint64(x)
+ #endif
+
+#endif
+
+#if defined(__FLOAT_WORD_ORDER__) && defined(__BYTE_ORDER__)
+
+ // We check where possible that the float byte order matches the
+ // integer byte order. This is extremely unlikely to fail, but
+ // we check anyway just in case.
+ //
+ // (The static assert is placed in float/double encoders instead
+ // of here because our static assert fallback doesn't work at
+ // file scope)
+
+ #define MPACK_CHECK_FLOAT_ORDER() \
+ MPACK_STATIC_ASSERT(__FLOAT_WORD_ORDER__ == __BYTE_ORDER__, \
+ "float byte order does not match int byte order! float/double " \
+ "encoding is not properly implemented on this platform.")
+
+#endif
+
+#ifndef MPACK_CHECK_FLOAT_ORDER
+ #define MPACK_CHECK_FLOAT_ORDER() /* nothing */
+#endif
+
+
+/*
+ * Here we define mpack_assert() and mpack_break(). They both work like a normal
+ * assertion function in debug mode, causing a trap or abort. However, on some platforms
+ * you can safely resume execution from mpack_break(), whereas mpack_assert() is
+ * always fatal.
+ *
+ * In release mode, mpack_assert() is converted to an assurance to the compiler
+ * that the expression cannot be false (via e.g. __assume() or __builtin_unreachable())
+ * to improve optimization where supported. There is thus no point in "safely" handling
+ * the case of this being false. Writing mpack_assert(0) rarely makes sense (except
+ * possibly as a default handler in a switch) since the compiler will throw away any
+ * code after it. If at any time an mpack_assert() is not true, the behaviour is
+ * undefined. This also means the expression is evaluated even in release.
+ *
+ * mpack_break() on the other hand is compiled to nothing in release. It is
+ * used in situations where we want to highlight a programming error as early as
+ * possible (in the debugger), but we still handle the situation safely if it
+ * happens in release to avoid producing incorrect results (such as in
+ * MPACK_WRITE_TRACKING.) It does not take an expression to test because it
+ * belongs in a safe-handling block after its failing condition has been tested.
+ *
+ * If stdio is available, we can add a format string describing the error, and
+ * on some compilers we can declare it noreturn to get correct results from static
+ * analysis tools. Note that the format string and arguments are not evaluated unless
+ * the assertion is hit.
+ *
+ * Note that any arguments to mpack_assert() beyond the first are only evaluated
+ * if the expression is false (and are never evaluated in release.)
+ *
+ * mpack_assert_fail() and mpack_break_hit() are defined separately
+ * because assert is noreturn and break isn't. This distinction is very
+ * important for static analysis tools to give correct results.
+ */
+
+#if MPACK_DEBUG
+
+ /**
+ * @addtogroup config
+ * @{
+ */
+ /**
+ * @name Debug Functions
+ */
+ /**
+ * Implement this and define @ref MPACK_CUSTOM_ASSERT to use a custom
+ * assertion function.
+ *
+ * This function should not return. If it does, MPack will @c abort().
+ *
+ * If you use C++, make sure you include @c mpack.h where you define
+ * this to get the correct linkage (or define it <code>extern "C"</code>.)
+ *
+ * Asserts are only used when @ref MPACK_DEBUG is enabled, and can be
+ * triggered by bugs in MPack or bugs due to incorrect usage of MPack.
+ */
+ void mpack_assert_fail(const char* message);
+ /**
+ * @}
+ */
+ /**
+ * @}
+ */
+
+ MPACK_NORETURN(void mpack_assert_fail_wrapper(const char* message));
+ #if MPACK_STDIO
+ MPACK_NORETURN(void mpack_assert_fail_format(const char* format, ...));
+ #define mpack_assert_fail_at(line, file, exprstr, format, ...) \
+ MPACK_EXPAND(mpack_assert_fail_format("mpack assertion failed at " file ":" #line "\n%s\n" format, exprstr, __VA_ARGS__))
+ #else
+ #define mpack_assert_fail_at(line, file, exprstr, format, ...) \
+ mpack_assert_fail_wrapper("mpack assertion failed at " file ":" #line "\n" exprstr "\n")
+ #endif
+
+ #define mpack_assert_fail_pos(line, file, exprstr, expr, ...) \
+ MPACK_EXPAND(mpack_assert_fail_at(line, file, exprstr, __VA_ARGS__))
+
+ // This contains a workaround to the pedantic C99 requirement of having at
+ // least one argument to a variadic macro. The first argument is the
+ // boolean expression, the optional second argument (if provided) must be a
+ // literal format string, and any additional arguments are the format
+ // argument list.
+ //
+ // Unfortunately this means macros are expanded in the expression before it
+ // gets stringified. I haven't found a workaround to this.
+ //
+ // This adds two unused arguments to the format argument list when a
+ // format string is provided, so this would complicate the use of
+ // -Wformat and __attribute__((format)) on mpack_assert_fail_format() if we
+ // ever bothered to implement it.
+ #define mpack_assert(...) \
+ MPACK_EXPAND(((!(MPACK_EXTRACT_ARG0(__VA_ARGS__))) ? \
+ mpack_assert_fail_pos(__LINE__, __FILE__, MPACK_STRINGIFY_ARG0(__VA_ARGS__) , __VA_ARGS__ , "", NULL) : \
+ (void)0))
+
+ void mpack_break_hit(const char* message);
+ #if MPACK_STDIO
+ void mpack_break_hit_format(const char* format, ...);
+ #define mpack_break_hit_at(line, file, ...) \
+ MPACK_EXPAND(mpack_break_hit_format("mpack breakpoint hit at " file ":" #line "\n" __VA_ARGS__))
+ #else
+ #define mpack_break_hit_at(line, file, ...) \
+ mpack_break_hit("mpack breakpoint hit at " file ":" #line )
+ #endif
+ #define mpack_break_hit_pos(line, file, ...) MPACK_EXPAND(mpack_break_hit_at(line, file, __VA_ARGS__))
+ #define mpack_break(...) MPACK_EXPAND(mpack_break_hit_pos(__LINE__, __FILE__, __VA_ARGS__))
+#else
+ #define mpack_assert(...) \
+ (MPACK_EXPAND((!(MPACK_EXTRACT_ARG0(__VA_ARGS__))) ? \
+ (MPACK_UNREACHABLE, (void)0) : \
+ (void)0))
+ #define mpack_break(...) ((void)0)
+#endif
+
+
+
+/* Wrap some needed libc functions */
+
+#if MPACK_STDLIB
+ #define mpack_memcmp memcmp
+ #define mpack_memcpy memcpy
+ #define mpack_memmove memmove
+ #define mpack_memset memset
+ #ifndef mpack_strlen
+ #define mpack_strlen strlen
+ #endif
+
+ #if defined(MPACK_UNIT_TESTS) && MPACK_INTERNAL && defined(__GNUC__)
+ // make sure we don't use the stdlib directly during development
+ #undef memcmp
+ #undef memcpy
+ #undef memmove
+ #undef memset
+ #undef strlen
+ #undef malloc
+ #undef free
+ #pragma GCC poison memcmp
+ #pragma GCC poison memcpy
+ #pragma GCC poison memmove
+ #pragma GCC poison memset
+ #pragma GCC poison strlen
+ #pragma GCC poison malloc
+ #pragma GCC poison free
+ #endif
+
+#elif defined(__GNUC__) && !MPACK_NO_BUILTINS
+ // there's not always a builtin memmove for GCC,
+ // and we don't have a way to test for it
+ #define mpack_memcmp __builtin_memcmp
+ #define mpack_memcpy __builtin_memcpy
+ #define mpack_memset __builtin_memset
+ #define mpack_strlen __builtin_strlen
+
+#elif defined(__clang__) && defined(__has_builtin) && !MPACK_NO_BUILTINS
+ #if __has_builtin(__builtin_memcmp)
+ #define mpack_memcmp __builtin_memcmp
+ #endif
+ #if __has_builtin(__builtin_memcpy)
+ #define mpack_memcpy __builtin_memcpy
+ #endif
+ #if __has_builtin(__builtin_memmove)
+ #define mpack_memmove __builtin_memmove
+ #endif
+ #if __has_builtin(__builtin_memset)
+ #define mpack_memset __builtin_memset
+ #endif
+ #if __has_builtin(__builtin_strlen)
+ #define mpack_strlen __builtin_strlen
+ #endif
+#endif
+
+#ifndef mpack_memcmp
+int mpack_memcmp(const void* s1, const void* s2, size_t n);
+#endif
+#ifndef mpack_memcpy
+void* mpack_memcpy(void* MPACK_RESTRICT s1, const void* MPACK_RESTRICT s2, size_t n);
+#endif
+#ifndef mpack_memmove
+void* mpack_memmove(void* s1, const void* s2, size_t n);
+#endif
+#ifndef mpack_memset
+void* mpack_memset(void* s, int c, size_t n);
+#endif
+#ifndef mpack_strlen
+size_t mpack_strlen(const char* s);
+#endif
+
+#if MPACK_STDIO
+ #if defined(WIN32)
+ #define mpack_snprintf _snprintf
+ #else
+ #define mpack_snprintf snprintf
+ #endif
+#endif
+
+
+
+/* Debug logging */
+#if 0
+ #include <stdio.h>
+ #define mpack_log(...) (MPACK_EXPAND(printf(__VA_ARGS__), fflush(stdout)))
+#else
+ #define mpack_log(...) ((void)0)
+#endif
+
+
+
+/* Make sure our configuration makes sense */
+#if defined(MPACK_MALLOC) && !defined(MPACK_FREE)
+ #error "MPACK_MALLOC requires MPACK_FREE."
+#endif
+#if !defined(MPACK_MALLOC) && defined(MPACK_FREE)
+ #error "MPACK_FREE requires MPACK_MALLOC."
+#endif
+#if MPACK_READ_TRACKING && !defined(MPACK_READER)
+ #error "MPACK_READ_TRACKING requires MPACK_READER."
+#endif
+#if MPACK_WRITE_TRACKING && !defined(MPACK_WRITER)
+ #error "MPACK_WRITE_TRACKING requires MPACK_WRITER."
+#endif
+#ifndef MPACK_MALLOC
+ #if MPACK_STDIO
+ #error "MPACK_STDIO requires preprocessor definitions for MPACK_MALLOC and MPACK_FREE."
+ #endif
+ #if MPACK_READ_TRACKING
+ #error "MPACK_READ_TRACKING requires preprocessor definitions for MPACK_MALLOC and MPACK_FREE."
+ #endif
+ #if MPACK_WRITE_TRACKING
+ #error "MPACK_WRITE_TRACKING requires preprocessor definitions for MPACK_MALLOC and MPACK_FREE."
+ #endif
+#endif
+
+
+
+/* Implement realloc if unavailable */
+#ifdef MPACK_MALLOC
+ #ifdef MPACK_REALLOC
+ MPACK_INLINE void* mpack_realloc(void* old_ptr, size_t used_size, size_t new_size) {
+ MPACK_UNUSED(used_size);
+ return MPACK_REALLOC(old_ptr, new_size);
+ }
+ #else
+ void* mpack_realloc(void* old_ptr, size_t used_size, size_t new_size);
+ #endif
+#endif
+
+
+
+/**
+ * @}
+ */
+
+MPACK_HEADER_END
+
+#endif
+
+
+/* mpack/mpack-common.h.h */
+
+/**
+ * @file
+ *
+ * Defines types and functions shared by the MPack reader and writer.
+ */
+
+#ifndef MPACK_COMMON_H
+#define MPACK_COMMON_H 1
+
+/* #include "mpack-platform.h" */
+
+#ifndef MPACK_PRINT_BYTE_COUNT
+#define MPACK_PRINT_BYTE_COUNT 12
+#endif
+
+MPACK_HEADER_START
+
+
+
+/**
+ * @defgroup common Tags and Common Elements
+ *
+ * Contains types, constants and functions shared by both the encoding
+ * and decoding portions of MPack.
+ *
+ * @{
+ */
+
+/* Version information */
+
+#define MPACK_VERSION_MAJOR 1 /**< The major version number of MPack. */
+#define MPACK_VERSION_MINOR 0 /**< The minor version number of MPack. */
+#define MPACK_VERSION_PATCH 0 /**< The patch version number of MPack. */
+
+/** A number containing the version number of MPack for comparison purposes. */
+#define MPACK_VERSION ((MPACK_VERSION_MAJOR * 10000) + \
+ (MPACK_VERSION_MINOR * 100) + MPACK_VERSION_PATCH)
+
+/** A macro to test for a minimum version of MPack. */
+#define MPACK_VERSION_AT_LEAST(major, minor, patch) \
+ (MPACK_VERSION >= (((major) * 10000) + ((minor) * 100) + (patch)))
+
+/** @cond */
+#if (MPACK_VERSION_PATCH > 0)
+#define MPACK_VERSION_STRING_BASE \
+ MPACK_STRINGIFY(MPACK_VERSION_MAJOR) "." \
+ MPACK_STRINGIFY(MPACK_VERSION_MINOR) "." \
+ MPACK_STRINGIFY(MPACK_VERSION_PATCH)
+#else
+#define MPACK_VERSION_STRING_BASE \
+ MPACK_STRINGIFY(MPACK_VERSION_MAJOR) "." \
+ MPACK_STRINGIFY(MPACK_VERSION_MINOR)
+#endif
+/** @endcond */
+
+/**
+ * @def MPACK_VERSION_STRING
+ * @hideinitializer
+ *
+ * A string containing the MPack version.
+ */
+#if MPACK_RELEASE_VERSION
+#define MPACK_VERSION_STRING MPACK_VERSION_STRING_BASE
+#else
+#define MPACK_VERSION_STRING MPACK_VERSION_STRING_BASE "dev"
+#endif
+
+/**
+ * @def MPACK_LIBRARY_STRING
+ * @hideinitializer
+ *
+ * A string describing MPack, containing the library name, version and debug mode.
+ */
+#if MPACK_DEBUG
+#define MPACK_LIBRARY_STRING "MPack " MPACK_VERSION_STRING "-debug"
+#else
+#define MPACK_LIBRARY_STRING "MPack " MPACK_VERSION_STRING
+#endif
+
+/** @cond */
+/**
+ * @def MPACK_MAXIMUM_TAG_SIZE
+ *
+ * The maximum encoded size of a tag in bytes.
+ */
+#define MPACK_MAXIMUM_TAG_SIZE 9
+/** @endcond */
+
+#if MPACK_EXTENSIONS
+/**
+ * @def MPACK_TIMESTAMP_NANOSECONDS_MAX
+ *
+ * The maximum value of nanoseconds for a timestamp.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+#define MPACK_TIMESTAMP_NANOSECONDS_MAX 999999999
+#endif
+
+
+
+#if MPACK_COMPATIBILITY
+/**
+ * Versions of the MessagePack format.
+ *
+ * A reader, writer, or tree can be configured to serialize in an older
+ * version of the MessagePack spec. This is necessary to interface with
+ * older MessagePack libraries that do not support new MessagePack features.
+ *
+ * @note This requires @ref MPACK_COMPATIBILITY.
+ */
+typedef enum mpack_version_t {
+
+ /**
+ * Version 1.0/v4, supporting only the @c raw type without @c str8.
+ */
+ mpack_version_v4 = 4,
+
+ /**
+ * Version 2.0/v5, supporting the @c str8, @c bin and @c ext types.
+ */
+ mpack_version_v5 = 5,
+
+ /**
+ * The most recent supported version of MessagePack. This is the default.
+ */
+ mpack_version_current = mpack_version_v5,
+
+} mpack_version_t;
+#endif
+
+/**
+ * Error states for MPack objects.
+ *
+ * When a reader, writer, or tree is in an error state, all subsequent calls
+ * are ignored and their return values are nil/zero. You should check whether
+ * the source is in an error state before using such values.
+ */
+typedef enum mpack_error_t {
+ mpack_ok = 0, /**< No error. */
+ mpack_error_io = 2, /**< The reader or writer failed to fill or flush, or some other file or socket error occurred. */
+ mpack_error_invalid, /**< The data read is not valid MessagePack. */
+ mpack_error_unsupported, /**< The data read is not supported by this configuration of MPack. (See @ref MPACK_EXTENSIONS.) */
+ mpack_error_type, /**< The type or value range did not match what was expected by the caller. */
+ mpack_error_too_big, /**< A read or write was bigger than the maximum size allowed for that operation. */
+ mpack_error_memory, /**< An allocation failure occurred. */
+ mpack_error_bug, /**< The MPack API was used incorrectly. (This will always assert in debug mode.) */
+ mpack_error_data, /**< The contained data is not valid. */
+ mpack_error_eof, /**< The reader failed to read because of file or socket EOF */
+} mpack_error_t;
+
+/**
+ * Converts an MPack error to a string. This function returns an empty
+ * string when MPACK_DEBUG is not set.
+ */
+const char* mpack_error_to_string(mpack_error_t error);
+
+/**
+ * Defines the type of a MessagePack tag.
+ *
+ * Note that extension types, both user defined and built-in, are represented
+ * in tags as @ref mpack_type_ext. The value for an extension type is stored
+ * separately.
+ */
+typedef enum mpack_type_t {
+ mpack_type_missing = 0, /**< Special type indicating a missing optional value. */
+ mpack_type_nil, /**< A null value. */
+ mpack_type_bool, /**< A boolean (true or false.) */
+ mpack_type_int, /**< A 64-bit signed integer. */
+ mpack_type_uint, /**< A 64-bit unsigned integer. */
+ mpack_type_float, /**< A 32-bit IEEE 754 floating point number. */
+ mpack_type_double, /**< A 64-bit IEEE 754 floating point number. */
+ mpack_type_str, /**< A string. */
+ mpack_type_bin, /**< A chunk of binary data. */
+ mpack_type_array, /**< An array of MessagePack objects. */
+ mpack_type_map, /**< An ordered map of key/value pairs of MessagePack objects. */
+
+ #if MPACK_EXTENSIONS
+ /**
+ * A typed MessagePack extension object containing a chunk of binary data.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+ mpack_type_ext,
+ #endif
+} mpack_type_t;
+
+/**
+ * Converts an MPack type to a string. This function returns an empty
+ * string when MPACK_DEBUG is not set.
+ */
+const char* mpack_type_to_string(mpack_type_t type);
+
+#if MPACK_EXTENSIONS
+/**
+ * A timestamp.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+typedef struct mpack_timestamp_t {
+ int64_t seconds; /*< The number of seconds (signed) since 1970-01-01T00:00:00Z. */
+ uint32_t nanoseconds; /*< The number of additional nanoseconds, between 0 and 999,999,999. */
+} mpack_timestamp_t;
+#endif
+
+/**
+ * An MPack tag is a MessagePack object header. It is a variant type
+ * representing any kind of object, and includes the length of compound types
+ * (e.g. map, array, string) or the value of non-compound types (e.g. boolean,
+ * integer, float.)
+ *
+ * If the type is compound (str, bin, ext, array or map), the contained
+ * elements or bytes are stored separately.
+ *
+ * This structure is opaque; its fields should not be accessed outside
+ * of MPack.
+ */
+typedef struct mpack_tag_t mpack_tag_t;
+
+/* Hide internals from documentation */
+/** @cond */
+struct mpack_tag_t {
+ mpack_type_t type; /*< The type of value. */
+
+ #if MPACK_EXTENSIONS
+ int8_t exttype; /*< The extension type if the type is @ref mpack_type_ext. */
+ #endif
+
+ /* The value for non-compound types. */
+ union {
+ uint64_t u; /*< The value if the type is unsigned int. */
+ int64_t i; /*< The value if the type is signed int. */
+ double d; /*< The value if the type is double. */
+ float f; /*< The value if the type is float. */
+ bool b; /*< The value if the type is bool. */
+
+ /* The number of bytes if the type is str, bin or ext. */
+ uint32_t l;
+
+ /* The element count if the type is an array, or the number of
+ key/value pairs if the type is map. */
+ uint32_t n;
+ } v;
+};
+/** @endcond */
+
+/**
+ * @name Tag Generators
+ * @{
+ */
+
+/**
+ * @def MPACK_TAG_ZERO
+ *
+ * An @ref mpack_tag_t initializer that zeroes the given tag.
+ *
+ * @warning This does not make the tag nil! The tag's type is invalid when
+ * initialized this way. Use @ref mpack_tag_make_nil() to generate a nil tag.
+ */
+#if MPACK_EXTENSIONS
+#define MPACK_TAG_ZERO {(mpack_type_t)0, 0, {0}}
+#else
+#define MPACK_TAG_ZERO {(mpack_type_t)0, {0}}
+#endif
+
+/** Generates a nil tag. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_nil(void) {
+ mpack_tag_t ret = MPACK_TAG_ZERO;
+ ret.type = mpack_type_nil;
+ return ret;
+}
+
+/** Generates a bool tag. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_bool(bool value) {
+ mpack_tag_t ret = MPACK_TAG_ZERO;
+ ret.type = mpack_type_bool;
+ ret.v.b = value;
+ return ret;
+}
+
+/** Generates a bool tag with value true. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_true(void) {
+ mpack_tag_t ret = MPACK_TAG_ZERO;
+ ret.type = mpack_type_bool;
+ ret.v.b = true;
+ return ret;
+}
+
+/** Generates a bool tag with value false. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_false(void) {
+ mpack_tag_t ret = MPACK_TAG_ZERO;
+ ret.type = mpack_type_bool;
+ ret.v.b = false;
+ return ret;
+}
+
+/** Generates a signed int tag. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_int(int64_t value) {
+ mpack_tag_t ret = MPACK_TAG_ZERO;
+ ret.type = mpack_type_int;
+ ret.v.i = value;
+ return ret;
+}
+
+/** Generates an unsigned int tag. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_uint(uint64_t value) {
+ mpack_tag_t ret = MPACK_TAG_ZERO;
+ ret.type = mpack_type_uint;
+ ret.v.u = value;
+ return ret;
+}
+
+/** Generates a float tag. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_float(float value) {
+ mpack_tag_t ret = MPACK_TAG_ZERO;
+ ret.type = mpack_type_float;
+ ret.v.f = value;
+ return ret;
+}
+
+/** Generates a double tag. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_double(double value) {
+ mpack_tag_t ret = MPACK_TAG_ZERO;
+ ret.type = mpack_type_double;
+ ret.v.d = value;
+ return ret;
+}
+
+/** Generates an array tag. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_array(uint32_t count) {
+ mpack_tag_t ret = MPACK_TAG_ZERO;
+ ret.type = mpack_type_array;
+ ret.v.n = count;
+ return ret;
+}
+
+/** Generates a map tag. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_map(uint32_t count) {
+ mpack_tag_t ret = MPACK_TAG_ZERO;
+ ret.type = mpack_type_map;
+ ret.v.n = count;
+ return ret;
+}
+
+/** Generates a str tag. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_str(uint32_t length) {
+ mpack_tag_t ret = MPACK_TAG_ZERO;
+ ret.type = mpack_type_str;
+ ret.v.l = length;
+ return ret;
+}
+
+/** Generates a bin tag. */
+MPACK_INLINE mpack_tag_t mpack_tag_make_bin(uint32_t length) {
+ mpack_tag_t ret = MPACK_TAG_ZERO;
+ ret.type = mpack_type_bin;
+ ret.v.l = length;
+ return ret;
+}
+
+#if MPACK_EXTENSIONS
+/**
+ * Generates an ext tag.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+MPACK_INLINE mpack_tag_t mpack_tag_make_ext(int8_t exttype, uint32_t length) {
+ mpack_tag_t ret = MPACK_TAG_ZERO;
+ ret.type = mpack_type_ext;
+ ret.exttype = exttype;
+ ret.v.l = length;
+ return ret;
+}
+#endif
+
+/**
+ * @}
+ */
+
+/**
+ * @name Tag Querying Functions
+ * @{
+ */
+
+/**
+ * Gets the type of a tag.
+ */
+MPACK_INLINE mpack_type_t mpack_tag_type(mpack_tag_t* tag) {
+ return tag->type;
+}
+
+/**
+ * Gets the boolean value of a bool-type tag. The tag must be of type @ref
+ * mpack_type_bool.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_bool. (No check is
+ * performed if MPACK_DEBUG is not set.)
+ */
+MPACK_INLINE bool mpack_tag_bool_value(mpack_tag_t* tag) {
+ mpack_assert(tag->type == mpack_type_bool, "tag is not a bool!");
+ return tag->v.b;
+}
+
+/**
+ * Gets the signed integer value of an int-type tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_int. (No check is
+ * performed if MPACK_DEBUG is not set.)
+ *
+ * @warning This does not convert between signed and unsigned tags! A positive
+ * integer may be stored in a tag as either @ref mpack_type_int or @ref
+ * mpack_type_uint. You must check the type first; this can only be used if the
+ * type is @ref mpack_type_int.
+ *
+ * @see mpack_type_int
+ */
+MPACK_INLINE int64_t mpack_tag_int_value(mpack_tag_t* tag) {
+ mpack_assert(tag->type == mpack_type_int, "tag is not an int!");
+ return tag->v.i;
+}
+
+/**
+ * Gets the unsigned integer value of a uint-type tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_uint. (No check is
+ * performed if MPACK_DEBUG is not set.)
+ *
+ * @warning This does not convert between signed and unsigned tags! A positive
+ * integer may be stored in a tag as either @ref mpack_type_int or @ref
+ * mpack_type_uint. You must check the type first; this can only be used if the
+ * type is @ref mpack_type_uint.
+ *
+ * @see mpack_type_uint
+ */
+MPACK_INLINE uint64_t mpack_tag_uint_value(mpack_tag_t* tag) {
+ mpack_assert(tag->type == mpack_type_uint, "tag is not a uint!");
+ return tag->v.u;
+}
+
+/**
+ * Gets the float value of a float-type tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_float. (No check is
+ * performed if MPACK_DEBUG is not set.)
+ *
+ * @warning This does not convert between float and double tags! This can only
+ * be used if the type is @ref mpack_type_float.
+ *
+ * @see mpack_type_float
+ */
+MPACK_INLINE float mpack_tag_float_value(mpack_tag_t* tag) {
+ mpack_assert(tag->type == mpack_type_float, "tag is not a float!");
+ return tag->v.f;
+}
+
+/**
+ * Gets the double value of a double-type tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_double. (No check
+ * is performed if MPACK_DEBUG is not set.)
+ *
+ * @warning This does not convert between float and double tags! This can only
+ * be used if the type is @ref mpack_type_double.
+ *
+ * @see mpack_type_double
+ */
+MPACK_INLINE double mpack_tag_double_value(mpack_tag_t* tag) {
+ mpack_assert(tag->type == mpack_type_double, "tag is not a double!");
+ return tag->v.d;
+}
+
+/**
+ * Gets the number of elements in an array tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_array. (No check is
+ * performed if MPACK_DEBUG is not set.)
+ *
+ * @see mpack_type_array
+ */
+MPACK_INLINE uint32_t mpack_tag_array_count(mpack_tag_t* tag) {
+ mpack_assert(tag->type == mpack_type_array, "tag is not an array!");
+ return tag->v.n;
+}
+
+/**
+ * Gets the number of key-value pairs in a map tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_map. (No check is
+ * performed if MPACK_DEBUG is not set.)
+ *
+ * @see mpack_type_map
+ */
+MPACK_INLINE uint32_t mpack_tag_map_count(mpack_tag_t* tag) {
+ mpack_assert(tag->type == mpack_type_map, "tag is not a map!");
+ return tag->v.n;
+}
+
+/**
+ * Gets the length in bytes of a str-type tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_str. (No check is
+ * performed if MPACK_DEBUG is not set.)
+ *
+ * @see mpack_type_str
+ */
+MPACK_INLINE uint32_t mpack_tag_str_length(mpack_tag_t* tag) {
+ mpack_assert(tag->type == mpack_type_str, "tag is not a str!");
+ return tag->v.l;
+}
+
+/**
+ * Gets the length in bytes of a bin-type tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_bin. (No check is
+ * performed if MPACK_DEBUG is not set.)
+ *
+ * @see mpack_type_bin
+ */
+MPACK_INLINE uint32_t mpack_tag_bin_length(mpack_tag_t* tag) {
+ mpack_assert(tag->type == mpack_type_bin, "tag is not a bin!");
+ return tag->v.l;
+}
+
+#if MPACK_EXTENSIONS
+/**
+ * Gets the length in bytes of an ext-type tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_ext. (No check is
+ * performed if MPACK_DEBUG is not set.)
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @see mpack_type_ext
+ */
+MPACK_INLINE uint32_t mpack_tag_ext_length(mpack_tag_t* tag) {
+ mpack_assert(tag->type == mpack_type_ext, "tag is not an ext!");
+ return tag->v.l;
+}
+
+/**
+ * Gets the extension type (exttype) of an ext-type tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_ext. (No check is
+ * performed if MPACK_DEBUG is not set.)
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @see mpack_type_ext
+ */
+MPACK_INLINE int8_t mpack_tag_ext_exttype(mpack_tag_t* tag) {
+ mpack_assert(tag->type == mpack_type_ext, "tag is not an ext!");
+ return tag->exttype;
+}
+#endif
+
+/**
+ * Gets the length in bytes of a str-, bin- or ext-type tag.
+ *
+ * This asserts that the type in the tag is @ref mpack_type_str, @ref
+ * mpack_type_bin or @ref mpack_type_ext. (No check is performed if MPACK_DEBUG
+ * is not set.)
+ *
+ * @see mpack_type_str
+ * @see mpack_type_bin
+ * @see mpack_type_ext
+ */
+MPACK_INLINE uint32_t mpack_tag_bytes(mpack_tag_t* tag) {
+ #if MPACK_EXTENSIONS
+ mpack_assert(tag->type == mpack_type_str || tag->type == mpack_type_bin
+ || tag->type == mpack_type_ext, "tag is not a str, bin or ext!");
+ #else
+ mpack_assert(tag->type == mpack_type_str || tag->type == mpack_type_bin,
+ "tag is not a str or bin!");
+ #endif
+ return tag->v.l;
+}
+
+/**
+ * @}
+ */
+
+/**
+ * @name Other tag functions
+ * @{
+ */
+
+#if MPACK_EXTENSIONS
+/**
+ * The extension type for a timestamp.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+#define MPACK_EXTTYPE_TIMESTAMP ((int8_t)(-1))
+#endif
+
+/**
+ * Compares two tags with an arbitrary fixed ordering. Returns 0 if the tags are
+ * equal, a negative integer if left comes before right, or a positive integer
+ * otherwise.
+ *
+ * \warning The ordering is not guaranteed to be preserved across MPack versions; do
+ * not rely on it in persistent data.
+ *
+ * \warning Floating point numbers are compared bit-for-bit, not using the language's
+ * operator==. This means that NaNs with matching representation will compare equal.
+ * This behaviour is up for debate; see comments in the definition of mpack_tag_cmp().
+ *
+ * See mpack_tag_equal() for more information on when tags are considered equal.
+ */
+int mpack_tag_cmp(mpack_tag_t left, mpack_tag_t right);
+
+/**
+ * Compares two tags for equality. Tags are considered equal if the types are compatible
+ * and the values (for non-compound types) are equal.
+ *
+ * The field width of variable-width fields is ignored (and in fact is not stored
+ * in a tag), and positive numbers in signed integers are considered equal to their
+ * unsigned counterparts. So for example the value 1 stored as a positive fixint
+ * is equal to the value 1 stored in a 64-bit unsigned integer field.
+ *
+ * The "extension type" of an extension object is considered part of the value
+ * and must match exactly.
+ *
+ * \warning Floating point numbers are compared bit-for-bit, not using the language's
+ * operator==. This means that NaNs with matching representation will compare equal.
+ * This behaviour is up for debate; see comments in the definition of mpack_tag_cmp().
+ */
+MPACK_INLINE bool mpack_tag_equal(mpack_tag_t left, mpack_tag_t right) {
+ return mpack_tag_cmp(left, right) == 0;
+}
+
+#if MPACK_DEBUG && MPACK_STDIO
+/**
+ * Generates a json-like debug description of the given tag into the given buffer.
+ *
+ * This is only available in debug mode, and only if stdio is available (since
+ * it uses snprintf().) It's strictly for debugging purposes.
+ *
+ * The prefix is used to print the first few hexadecimal bytes of a bin or ext
+ * type. Pass NULL if not a bin or ext.
+ */
+void mpack_tag_debug_pseudo_json(mpack_tag_t tag, char* buffer, size_t buffer_size,
+ const char* prefix, size_t prefix_size);
+
+/**
+ * Generates a debug string description of the given tag into the given buffer.
+ *
+ * This is only available in debug mode, and only if stdio is available (since
+ * it uses snprintf().) It's strictly for debugging purposes.
+ */
+void mpack_tag_debug_describe(mpack_tag_t tag, char* buffer, size_t buffer_size);
+
+/** @cond */
+
+/*
+ * A callback function for printing pseudo-JSON for debugging purposes.
+ *
+ * @see mpack_node_print_callback
+ */
+typedef void (*mpack_print_callback_t)(void* context, const char* data, size_t count);
+
+// helpers for printing debug output
+// i feel a bit like i'm re-implementing a buffered writer again...
+typedef struct mpack_print_t {
+ char* buffer;
+ size_t size;
+ size_t count;
+ mpack_print_callback_t callback;
+ void* context;
+} mpack_print_t;
+
+void mpack_print_append(mpack_print_t* print, const char* data, size_t count);
+
+MPACK_INLINE void mpack_print_append_cstr(mpack_print_t* print, const char* cstr) {
+ mpack_print_append(print, cstr, mpack_strlen(cstr));
+}
+
+void mpack_print_flush(mpack_print_t* print);
+
+void mpack_print_file_callback(void* context, const char* data, size_t count);
+
+/** @endcond */
+
+#endif
+
+/**
+ * @}
+ */
+
+/**
+ * @name Deprecated Tag Generators
+ * @{
+ */
+
+/*
+ * "make" has been added to their names to disambiguate them from the
+ * value-fetching functions (e.g. mpack_tag_make_bool() vs
+ * mpack_tag_bool_value().)
+ *
+ * The length and count for all compound types was the wrong sign (int32_t
+ * instead of uint32_t.) These preserve the old behaviour; the new "make"
+ * functions have the correct sign.
+ */
+
+/** \deprecated Renamed to mpack_tag_make_nil(). */
+MPACK_INLINE mpack_tag_t mpack_tag_nil(void) {
+ return mpack_tag_make_nil();
+}
+
+/** \deprecated Renamed to mpack_tag_make_bool(). */
+MPACK_INLINE mpack_tag_t mpack_tag_bool(bool value) {
+ return mpack_tag_make_bool(value);
+}
+
+/** \deprecated Renamed to mpack_tag_make_true(). */
+MPACK_INLINE mpack_tag_t mpack_tag_true(void) {
+ return mpack_tag_make_true();
+}
+
+/** \deprecated Renamed to mpack_tag_make_false(). */
+MPACK_INLINE mpack_tag_t mpack_tag_false(void) {
+ return mpack_tag_make_false();
+}
+
+/** \deprecated Renamed to mpack_tag_make_int(). */
+MPACK_INLINE mpack_tag_t mpack_tag_int(int64_t value) {
+ return mpack_tag_make_int(value);
+}
+
+/** \deprecated Renamed to mpack_tag_make_uint(). */
+MPACK_INLINE mpack_tag_t mpack_tag_uint(uint64_t value) {
+ return mpack_tag_make_uint(value);
+}
+
+/** \deprecated Renamed to mpack_tag_make_float(). */
+MPACK_INLINE mpack_tag_t mpack_tag_float(float value) {
+ return mpack_tag_make_float(value);
+}
+
+/** \deprecated Renamed to mpack_tag_make_double(). */
+MPACK_INLINE mpack_tag_t mpack_tag_double(double value) {
+ return mpack_tag_make_double(value);
+}
+
+/** \deprecated Renamed to mpack_tag_make_array(). */
+MPACK_INLINE mpack_tag_t mpack_tag_array(int32_t count) {
+ return mpack_tag_make_array((uint32_t)count);
+}
+
+/** \deprecated Renamed to mpack_tag_make_map(). */
+MPACK_INLINE mpack_tag_t mpack_tag_map(int32_t count) {
+ return mpack_tag_make_map((uint32_t)count);
+}
+
+/** \deprecated Renamed to mpack_tag_make_str(). */
+MPACK_INLINE mpack_tag_t mpack_tag_str(int32_t length) {
+ return mpack_tag_make_str((uint32_t)length);
+}
+
+/** \deprecated Renamed to mpack_tag_make_bin(). */
+MPACK_INLINE mpack_tag_t mpack_tag_bin(int32_t length) {
+ return mpack_tag_make_bin((uint32_t)length);
+}
+
+#if MPACK_EXTENSIONS
+/** \deprecated Renamed to mpack_tag_make_ext(). */
+MPACK_INLINE mpack_tag_t mpack_tag_ext(int8_t exttype, int32_t length) {
+ return mpack_tag_make_ext(exttype, (uint32_t)length);
+}
+#endif
+
+/**
+ * @}
+ */
+
+/** @cond */
+
+/*
+ * Helpers to perform unaligned network-endian loads and stores
+ * at arbitrary addresses. Byte-swapping builtins are used if they
+ * are available and if they improve performance.
+ *
+ * These will remain available in the public API so feel free to
+ * use them for other purposes, but they are undocumented.
+ */
+
+MPACK_INLINE uint8_t mpack_load_u8(const char* p) {
+ return (uint8_t)p[0];
+}
+
+MPACK_INLINE uint16_t mpack_load_u16(const char* p) {
+ #ifdef MPACK_NHSWAP16
+ uint16_t val;
+ mpack_memcpy(&val, p, sizeof(val));
+ return MPACK_NHSWAP16(val);
+ #else
+ return (uint16_t)((((uint16_t)(uint8_t)p[0]) << 8) |
+ ((uint16_t)(uint8_t)p[1]));
+ #endif
+}
+
+MPACK_INLINE uint32_t mpack_load_u32(const char* p) {
+ #ifdef MPACK_NHSWAP32
+ uint32_t val;
+ mpack_memcpy(&val, p, sizeof(val));
+ return MPACK_NHSWAP32(val);
+ #else
+ return (((uint32_t)(uint8_t)p[0]) << 24) |
+ (((uint32_t)(uint8_t)p[1]) << 16) |
+ (((uint32_t)(uint8_t)p[2]) << 8) |
+ ((uint32_t)(uint8_t)p[3]);
+ #endif
+}
+
+MPACK_INLINE uint64_t mpack_load_u64(const char* p) {
+ #ifdef MPACK_NHSWAP64
+ uint64_t val;
+ mpack_memcpy(&val, p, sizeof(val));
+ return MPACK_NHSWAP64(val);
+ #else
+ return (((uint64_t)(uint8_t)p[0]) << 56) |
+ (((uint64_t)(uint8_t)p[1]) << 48) |
+ (((uint64_t)(uint8_t)p[2]) << 40) |
+ (((uint64_t)(uint8_t)p[3]) << 32) |
+ (((uint64_t)(uint8_t)p[4]) << 24) |
+ (((uint64_t)(uint8_t)p[5]) << 16) |
+ (((uint64_t)(uint8_t)p[6]) << 8) |
+ ((uint64_t)(uint8_t)p[7]);
+ #endif
+}
+
+MPACK_INLINE void mpack_store_u8(char* p, uint8_t val) {
+ uint8_t* u = (uint8_t*)p;
+ u[0] = val;
+}
+
+MPACK_INLINE void mpack_store_u16(char* p, uint16_t val) {
+ #ifdef MPACK_NHSWAP16
+ val = MPACK_NHSWAP16(val);
+ mpack_memcpy(p, &val, sizeof(val));
+ #else
+ uint8_t* u = (uint8_t*)p;
+ u[0] = (uint8_t)((val >> 8) & 0xFF);
+ u[1] = (uint8_t)( val & 0xFF);
+ #endif
+}
+
+MPACK_INLINE void mpack_store_u32(char* p, uint32_t val) {
+ #ifdef MPACK_NHSWAP32
+ val = MPACK_NHSWAP32(val);
+ mpack_memcpy(p, &val, sizeof(val));
+ #else
+ uint8_t* u = (uint8_t*)p;
+ u[0] = (uint8_t)((val >> 24) & 0xFF);
+ u[1] = (uint8_t)((val >> 16) & 0xFF);
+ u[2] = (uint8_t)((val >> 8) & 0xFF);
+ u[3] = (uint8_t)( val & 0xFF);
+ #endif
+}
+
+MPACK_INLINE void mpack_store_u64(char* p, uint64_t val) {
+ #ifdef MPACK_NHSWAP64
+ val = MPACK_NHSWAP64(val);
+ mpack_memcpy(p, &val, sizeof(val));
+ #else
+ uint8_t* u = (uint8_t*)p;
+ u[0] = (uint8_t)((val >> 56) & 0xFF);
+ u[1] = (uint8_t)((val >> 48) & 0xFF);
+ u[2] = (uint8_t)((val >> 40) & 0xFF);
+ u[3] = (uint8_t)((val >> 32) & 0xFF);
+ u[4] = (uint8_t)((val >> 24) & 0xFF);
+ u[5] = (uint8_t)((val >> 16) & 0xFF);
+ u[6] = (uint8_t)((val >> 8) & 0xFF);
+ u[7] = (uint8_t)( val & 0xFF);
+ #endif
+}
+
+MPACK_INLINE int8_t mpack_load_i8 (const char* p) {return (int8_t) mpack_load_u8 (p);}
+MPACK_INLINE int16_t mpack_load_i16(const char* p) {return (int16_t)mpack_load_u16(p);}
+MPACK_INLINE int32_t mpack_load_i32(const char* p) {return (int32_t)mpack_load_u32(p);}
+MPACK_INLINE int64_t mpack_load_i64(const char* p) {return (int64_t)mpack_load_u64(p);}
+MPACK_INLINE void mpack_store_i8 (char* p, int8_t val) {mpack_store_u8 (p, (uint8_t) val);}
+MPACK_INLINE void mpack_store_i16(char* p, int16_t val) {mpack_store_u16(p, (uint16_t)val);}
+MPACK_INLINE void mpack_store_i32(char* p, int32_t val) {mpack_store_u32(p, (uint32_t)val);}
+MPACK_INLINE void mpack_store_i64(char* p, int64_t val) {mpack_store_u64(p, (uint64_t)val);}
+
+MPACK_INLINE float mpack_load_float(const char* p) {
+ MPACK_CHECK_FLOAT_ORDER();
+ MPACK_STATIC_ASSERT(sizeof(float) == sizeof(uint32_t), "float is wrong size??");
+ union {
+ float f;
+ uint32_t u;
+ } v;
+ v.u = mpack_load_u32(p);
+ return v.f;
+}
+
+MPACK_INLINE double mpack_load_double(const char* p) {
+ MPACK_CHECK_FLOAT_ORDER();
+#ifdef MULTIPASS_ARCH_arduino_nano
+ MPACK_STATIC_ASSERT(sizeof(double) == sizeof(uint32_t), "double is wrong size??");
+ union {
+ double d;
+ uint32_t u;
+ } v;
+ v.u = mpack_load_u32(p);
+#else
+ MPACK_STATIC_ASSERT(sizeof(double) == sizeof(uint64_t), "double is wrong size??");
+ union {
+ double d;
+ uint64_t u;
+ } v;
+ v.u = mpack_load_u64(p);
+#endif
+ return v.d;
+}
+
+MPACK_INLINE void mpack_store_float(char* p, float value) {
+ MPACK_CHECK_FLOAT_ORDER();
+ union {
+ float f;
+ uint32_t u;
+ } v;
+ v.f = value;
+ mpack_store_u32(p, v.u);
+}
+
+MPACK_INLINE void mpack_store_double(char* p, double value) {
+ MPACK_CHECK_FLOAT_ORDER();
+ union {
+ double d;
+ uint64_t u;
+ } v;
+ v.d = value;
+ mpack_store_u64(p, v.u);
+}
+
+/** @endcond */
+
+
+
+/** @cond */
+
+// Sizes in bytes for the various possible tags
+#define MPACK_TAG_SIZE_FIXUINT 1
+#define MPACK_TAG_SIZE_U8 2
+#define MPACK_TAG_SIZE_U16 3
+#define MPACK_TAG_SIZE_U32 5
+#define MPACK_TAG_SIZE_U64 9
+#define MPACK_TAG_SIZE_FIXINT 1
+#define MPACK_TAG_SIZE_I8 2
+#define MPACK_TAG_SIZE_I16 3
+#define MPACK_TAG_SIZE_I32 5
+#define MPACK_TAG_SIZE_I64 9
+#define MPACK_TAG_SIZE_FLOAT 5
+#define MPACK_TAG_SIZE_DOUBLE 9
+#define MPACK_TAG_SIZE_FIXARRAY 1
+#define MPACK_TAG_SIZE_ARRAY16 3
+#define MPACK_TAG_SIZE_ARRAY32 5
+#define MPACK_TAG_SIZE_FIXMAP 1
+#define MPACK_TAG_SIZE_MAP16 3
+#define MPACK_TAG_SIZE_MAP32 5
+#define MPACK_TAG_SIZE_FIXSTR 1
+#define MPACK_TAG_SIZE_STR8 2
+#define MPACK_TAG_SIZE_STR16 3
+#define MPACK_TAG_SIZE_STR32 5
+#define MPACK_TAG_SIZE_BIN8 2
+#define MPACK_TAG_SIZE_BIN16 3
+#define MPACK_TAG_SIZE_BIN32 5
+#define MPACK_TAG_SIZE_FIXEXT1 2
+#define MPACK_TAG_SIZE_FIXEXT2 2
+#define MPACK_TAG_SIZE_FIXEXT4 2
+#define MPACK_TAG_SIZE_FIXEXT8 2
+#define MPACK_TAG_SIZE_FIXEXT16 2
+#define MPACK_TAG_SIZE_EXT8 3
+#define MPACK_TAG_SIZE_EXT16 4
+#define MPACK_TAG_SIZE_EXT32 6
+
+// size in bytes for complete ext types
+#define MPACK_EXT_SIZE_TIMESTAMP4 (MPACK_TAG_SIZE_FIXEXT4 + 4)
+#define MPACK_EXT_SIZE_TIMESTAMP8 (MPACK_TAG_SIZE_FIXEXT8 + 8)
+#define MPACK_EXT_SIZE_TIMESTAMP12 (MPACK_TAG_SIZE_EXT8 + 12)
+
+/** @endcond */
+
+
+
+#if MPACK_READ_TRACKING || MPACK_WRITE_TRACKING
+/* Tracks the write state of compound elements (maps, arrays, */
+/* strings, binary blobs and extension types) */
+/** @cond */
+
+typedef struct mpack_track_element_t {
+ mpack_type_t type;
+ uint64_t left; // we need 64-bit because (2 * INT32_MAX) elements can be stored in a map
+} mpack_track_element_t;
+
+typedef struct mpack_track_t {
+ size_t count;
+ size_t capacity;
+ mpack_track_element_t* elements;
+} mpack_track_t;
+
+#if MPACK_INTERNAL
+mpack_error_t mpack_track_init(mpack_track_t* track);
+mpack_error_t mpack_track_grow(mpack_track_t* track);
+mpack_error_t mpack_track_push(mpack_track_t* track, mpack_type_t type, uint64_t count);
+mpack_error_t mpack_track_pop(mpack_track_t* track, mpack_type_t type);
+mpack_error_t mpack_track_element(mpack_track_t* track, bool read);
+mpack_error_t mpack_track_peek_element(mpack_track_t* track, bool read);
+mpack_error_t mpack_track_bytes(mpack_track_t* track, bool read, uint64_t count);
+mpack_error_t mpack_track_str_bytes_all(mpack_track_t* track, bool read, uint64_t count);
+mpack_error_t mpack_track_check_empty(mpack_track_t* track);
+mpack_error_t mpack_track_destroy(mpack_track_t* track, bool cancel);
+#endif
+
+/** @endcond */
+#endif
+
+
+
+#if MPACK_INTERNAL
+/** @cond */
+
+
+
+/* Miscellaneous string functions */
+
+/**
+ * Returns true if the given UTF-8 string is valid.
+ */
+bool mpack_utf8_check(const char* str, size_t bytes);
+
+/**
+ * Returns true if the given UTF-8 string is valid and contains no null characters.
+ */
+bool mpack_utf8_check_no_null(const char* str, size_t bytes);
+
+/**
+ * Returns true if the given string has no null bytes.
+ */
+bool mpack_str_check_no_null(const char* str, size_t bytes);
+
+
+
+/** @endcond */
+#endif
+
+
+
+/**
+ * @}
+ */
+
+/**
+ * @}
+ */
+
+MPACK_HEADER_END
+
+#endif
+
+
+/* mpack/mpack-writer.h.h */
+
+/**
+ * @file
+ *
+ * Declares the MPack Writer.
+ */
+
+#ifndef MPACK_WRITER_H
+#define MPACK_WRITER_H 1
+
+/* #include "mpack-common.h" */
+
+MPACK_HEADER_START
+
+#if MPACK_WRITER
+
+#if MPACK_WRITE_TRACKING
+struct mpack_track_t;
+#endif
+
+/**
+ * @defgroup writer Write API
+ *
+ * The MPack Write API encodes structured data of a fixed (hardcoded) schema to MessagePack.
+ *
+ * @{
+ */
+
+/**
+ * @def MPACK_WRITER_MINIMUM_BUFFER_SIZE
+ *
+ * The minimum buffer size for a writer with a flush function.
+ */
+#define MPACK_WRITER_MINIMUM_BUFFER_SIZE 32
+
+/**
+ * A buffered MessagePack encoder.
+ *
+ * The encoder wraps an existing buffer and, optionally, a flush function.
+ * This allows efficiently encoding to an in-memory buffer or to a stream.
+ *
+ * All write operations are synchronous; they will block until the
+ * data is fully written, or an error occurs.
+ */
+typedef struct mpack_writer_t mpack_writer_t;
+
+/**
+ * The MPack writer's flush function to flush the buffer to the output stream.
+ * It should flag an appropriate error on the writer if flushing fails (usually
+ * mpack_error_io or mpack_error_memory.)
+ *
+ * The specified context for callbacks is at writer->context.
+ */
+typedef void (*mpack_writer_flush_t)(mpack_writer_t* writer, const char* buffer, size_t count);
+
+/**
+ * An error handler function to be called when an error is flagged on
+ * the writer.
+ *
+ * The error handler will only be called once on the first error flagged;
+ * any subsequent writes and errors are ignored, and the writer is
+ * permanently in that error state.
+ *
+ * MPack is safe against non-local jumps out of error handler callbacks.
+ * This means you are allowed to longjmp or throw an exception (in C++,
+ * Objective-C, or with SEH) out of this callback.
+ *
+ * Bear in mind when using longjmp that local non-volatile variables that
+ * have changed are undefined when setjmp() returns, so you can't put the
+ * writer on the stack in the same activation frame as the setjmp without
+ * declaring it volatile.
+ *
+ * You must still eventually destroy the writer. It is not destroyed
+ * automatically when an error is flagged. It is safe to destroy the
+ * writer within this error callback, but you will either need to perform
+ * a non-local jump, or store something in your context to identify
+ * that the writer is destroyed since any future accesses to it cause
+ * undefined behavior.
+ */
+typedef void (*mpack_writer_error_t)(mpack_writer_t* writer, mpack_error_t error);
+
+/**
+ * A teardown function to be called when the writer is destroyed.
+ */
+typedef void (*mpack_writer_teardown_t)(mpack_writer_t* writer);
+
+/* Hide internals from documentation */
+/** @cond */
+
+struct mpack_writer_t {
+ #if MPACK_COMPATIBILITY
+ mpack_version_t version; /* Version of the MessagePack spec to write */
+ #endif
+ mpack_writer_flush_t flush; /* Function to write bytes to the output stream */
+ mpack_writer_error_t error_fn; /* Function to call on error */
+ mpack_writer_teardown_t teardown; /* Function to teardown the context on destroy */
+ void* context; /* Context for writer callbacks */
+
+ char* buffer; /* Byte buffer */
+ char* current; /* Current position within the buffer */
+ char* end; /* The end of the buffer */
+ mpack_error_t error; /* Error state */
+
+ #if MPACK_WRITE_TRACKING
+ mpack_track_t track; /* Stack of map/array/str/bin/ext writes */
+ #endif
+
+ #ifdef MPACK_MALLOC
+ /* Reserved. You can use this space to allocate a custom
+ * context in order to reduce heap allocations. */
+ void* reserved[2];
+ #endif
+};
+
+#if MPACK_WRITE_TRACKING
+void mpack_writer_track_push(mpack_writer_t* writer, mpack_type_t type, uint64_t count);
+void mpack_writer_track_pop(mpack_writer_t* writer, mpack_type_t type);
+void mpack_writer_track_element(mpack_writer_t* writer);
+void mpack_writer_track_bytes(mpack_writer_t* writer, size_t count);
+#else
+MPACK_INLINE void mpack_writer_track_push(mpack_writer_t* writer, mpack_type_t type, uint64_t count) {
+ MPACK_UNUSED(writer);
+ MPACK_UNUSED(type);
+ MPACK_UNUSED(count);
+}
+MPACK_INLINE void mpack_writer_track_pop(mpack_writer_t* writer, mpack_type_t type) {
+ MPACK_UNUSED(writer);
+ MPACK_UNUSED(type);
+}
+MPACK_INLINE void mpack_writer_track_element(mpack_writer_t* writer) {
+ MPACK_UNUSED(writer);
+}
+MPACK_INLINE void mpack_writer_track_bytes(mpack_writer_t* writer, size_t count) {
+ MPACK_UNUSED(writer);
+ MPACK_UNUSED(count);
+}
+#endif
+
+/** @endcond */
+
+/**
+ * @name Lifecycle Functions
+ * @{
+ */
+
+/**
+ * Initializes an MPack writer with the given buffer. The writer
+ * does not assume ownership of the buffer.
+ *
+ * Trying to write past the end of the buffer will result in mpack_error_too_big
+ * unless a flush function is set with mpack_writer_set_flush(). To use the data
+ * without flushing, call mpack_writer_buffer_used() to determine the number of
+ * bytes written.
+ *
+ * @param writer The MPack writer.
+ * @param buffer The buffer into which to write MessagePack data.
+ * @param size The size of the buffer.
+ */
+void mpack_writer_init(mpack_writer_t* writer, char* buffer, size_t size);
+
+#ifdef MPACK_MALLOC
+/**
+ * Initializes an MPack writer using a growable buffer.
+ *
+ * The data is placed in the given data pointer if and when the writer
+ * is destroyed without error. The data pointer is NULL during writing,
+ * and will remain NULL if an error occurs.
+ *
+ * The allocated data must be freed with MPACK_FREE() (or simply free()
+ * if MPack's allocator hasn't been customized.)
+ *
+ * @throws mpack_error_memory if the buffer fails to grow when
+ * flushing.
+ *
+ * @param writer The MPack writer.
+ * @param data Where to place the allocated data.
+ * @param size Where to write the size of the data.
+ */
+void mpack_writer_init_growable(mpack_writer_t* writer, char** data, size_t* size);
+#endif
+
+/**
+ * Initializes an MPack writer directly into an error state. Use this if you
+ * are writing a wrapper to mpack_writer_init() which can fail its setup.
+ */
+void mpack_writer_init_error(mpack_writer_t* writer, mpack_error_t error);
+
+#if MPACK_STDIO
+/**
+ * Initializes an MPack writer that writes to a file.
+ *
+ * @throws mpack_error_memory if allocation fails
+ * @throws mpack_error_io if the file cannot be opened
+ */
+void mpack_writer_init_filename(mpack_writer_t* writer, const char* filename);
+
+/**
+ * Deprecated.
+ *
+ * \deprecated Renamed to mpack_writer_init_filename().
+ */
+MPACK_INLINE void mpack_writer_init_file(mpack_writer_t* writer, const char* filename) {
+ mpack_writer_init_filename(writer, filename);
+}
+
+/**
+ * Initializes an MPack writer that writes to a libc FILE. This can be used to
+ * write to stdout or stderr, or to a file opened separately.
+ *
+ * @param writer The MPack writer.
+ * @param stdfile The FILE.
+ * @param close_when_done If true, fclose() will be called on the FILE when it
+ * is no longer needed. If false, the file will not be flushed or
+ * closed when writing is done.
+ *
+ * @note The writer is buffered. If you want to write other data to the FILE in
+ * between messages, you must flush it first.
+ *
+ * @see mpack_writer_flush_message
+ */
+void mpack_writer_init_stdfile(mpack_writer_t* writer, FILE* stdfile, bool close_when_done);
+#endif
+
+/** @cond */
+
+#define mpack_writer_init_stack_line_ex(line, writer) \
+ char mpack_buf_##line[MPACK_STACK_SIZE]; \
+ mpack_writer_init(writer, mpack_buf_##line, sizeof(mpack_buf_##line))
+
+#define mpack_writer_init_stack_line(line, writer) \
+ mpack_writer_init_stack_line_ex(line, writer)
+
+/*
+ * Initializes an MPack writer using stack space as a buffer. A flush function
+ * should be added to the writer to flush the buffer.
+ *
+ * This is currently undocumented since it's not entirely useful on its own.
+ */
+
+#define mpack_writer_init_stack(writer) \
+ mpack_writer_init_stack_line(__LINE__, (writer))
+
+/** @endcond */
+
+/**
+ * Cleans up the MPack writer, flushing and closing the underlying stream,
+ * if any. Returns the final error state of the writer.
+ *
+ * No flushing is performed if the writer is in an error state. The attached
+ * teardown function is called whether or not the writer is in an error state.
+ *
+ * This will assert in tracking mode if the writer is not in an error
+ * state and has any unclosed compound types. If you want to cancel
+ * writing in the middle of a document, you need to flag an error on
+ * the writer before destroying it (such as mpack_error_data).
+ *
+ * Note that a writer may raise an error and call your error handler during
+ * the final flush. It is safe to longjmp or throw out of this error handler,
+ * but if you do, the writer will not be destroyed, and the teardown function
+ * will not be called. You can still get the writer's error state, and you
+ * must call @ref mpack_writer_destroy() again. (The second call is guaranteed
+ * not to call your error handler again since the writer is already in an error
+ * state.)
+ *
+ * @see mpack_writer_set_error_handler
+ * @see mpack_writer_set_flush
+ * @see mpack_writer_set_teardown
+ * @see mpack_writer_flag_error
+ * @see mpack_error_data
+ */
+mpack_error_t mpack_writer_destroy(mpack_writer_t* writer);
+
+/**
+ * @}
+ */
+
+/**
+ * @name Configuration
+ * @{
+ */
+
+#if MPACK_COMPATIBILITY
+/**
+ * Sets the version of the MessagePack spec that will be generated.
+ *
+ * This can be used to interface with older libraries that do not support
+ * the newest MessagePack features (such as the @c str8 type.)
+ *
+ * @note This requires @ref MPACK_COMPATIBILITY.
+ */
+MPACK_INLINE void mpack_writer_set_version(mpack_writer_t* writer, mpack_version_t version) {
+ writer->version = version;
+}
+#endif
+
+/**
+ * Sets the custom pointer to pass to the writer callbacks, such as flush
+ * or teardown.
+ *
+ * @param writer The MPack writer.
+ * @param context User data to pass to the writer callbacks.
+ *
+ * @see mpack_writer_context()
+ */
+MPACK_INLINE void mpack_writer_set_context(mpack_writer_t* writer, void* context) {
+ writer->context = context;
+}
+
+/**
+ * Returns the custom context for writer callbacks.
+ *
+ * @see mpack_writer_set_context
+ * @see mpack_writer_set_flush
+ */
+MPACK_INLINE void* mpack_writer_context(mpack_writer_t* writer) {
+ return writer->context;
+}
+
+/**
+ * Sets the flush function to write out the data when the buffer is full.
+ *
+ * If no flush function is used, trying to write past the end of the
+ * buffer will result in mpack_error_too_big.
+ *
+ * This should normally be used with mpack_writer_set_context() to register
+ * a custom pointer to pass to the flush function.
+ *
+ * @param writer The MPack writer.
+ * @param flush The function to write out data from the buffer.
+ *
+ * @see mpack_writer_context()
+ */
+void mpack_writer_set_flush(mpack_writer_t* writer, mpack_writer_flush_t flush);
+
+/**
+ * Sets the error function to call when an error is flagged on the writer.
+ *
+ * This should normally be used with mpack_writer_set_context() to register
+ * a custom pointer to pass to the error function.
+ *
+ * See the definition of mpack_writer_error_t for more information about
+ * what you can do from an error callback.
+ *
+ * @see mpack_writer_error_t
+ * @param writer The MPack writer.
+ * @param error_fn The function to call when an error is flagged on the writer.
+ */
+MPACK_INLINE void mpack_writer_set_error_handler(mpack_writer_t* writer, mpack_writer_error_t error_fn) {
+ writer->error_fn = error_fn;
+}
+
+/**
+ * Sets the teardown function to call when the writer is destroyed.
+ *
+ * This should normally be used with mpack_writer_set_context() to register
+ * a custom pointer to pass to the teardown function.
+ *
+ * @param writer The MPack writer.
+ * @param teardown The function to call when the writer is destroyed.
+ */
+MPACK_INLINE void mpack_writer_set_teardown(mpack_writer_t* writer, mpack_writer_teardown_t teardown) {
+ writer->teardown = teardown;
+}
+
+/**
+ * @}
+ */
+
+/**
+ * @name Core Writer Functions
+ * @{
+ */
+
+/**
+ * Flushes any buffered data to the underlying stream.
+ *
+ * If write tracking is enabled, this will break and flag @ref
+ * mpack_error_bug if the writer has any open compound types, ensuring
+ * that no compound types are still open. This prevents a "missing
+ * finish" bug from causing a never-ending message.
+ *
+ * If the writer is connected to a socket and you are keeping it open,
+ * you will want to call this after writing a message (or set of
+ * messages) so that the data is actually sent.
+ *
+ * It is not necessary to call this if you are not keeping the writer
+ * open afterwards. You can just call `mpack_writer_destroy()`, and it
+ * will flush before cleaning up.
+ *
+ * This will assert if no flush function is assigned to the writer.
+ */
+void mpack_writer_flush_message(mpack_writer_t* writer);
+
+/**
+ * Returns the number of bytes currently stored in the buffer. This
+ * may be less than the total number of bytes written if bytes have
+ * been flushed to an underlying stream.
+ */
+MPACK_INLINE size_t mpack_writer_buffer_used(mpack_writer_t* writer) {
+ return (size_t)(writer->current - writer->buffer);
+}
+
+/**
+ * Returns the amount of space left in the buffer. This may be reset
+ * after a write if bytes are flushed to an underlying stream.
+ */
+MPACK_INLINE size_t mpack_writer_buffer_left(mpack_writer_t* writer) {
+ return (size_t)(writer->end - writer->current);
+}
+
+/**
+ * Returns the (current) size of the buffer. This may change after a write if
+ * the flush callback changes the buffer.
+ */
+MPACK_INLINE size_t mpack_writer_buffer_size(mpack_writer_t* writer) {
+ return (size_t)(writer->end - writer->buffer);
+}
+
+/**
+ * Places the writer in the given error state, calling the error callback if one
+ * is set.
+ *
+ * This allows you to externally flag errors, for example if you are validating
+ * data as you write it, or if you want to cancel writing in the middle of a
+ * document. (The writer will assert if you try to destroy it without error and
+ * with unclosed compound types. In this case you should flag mpack_error_data
+ * before destroying it.)
+ *
+ * If the writer is already in an error state, this call is ignored and no
+ * error callback is called.
+ *
+ * @see mpack_writer_destroy
+ * @see mpack_error_data
+ */
+void mpack_writer_flag_error(mpack_writer_t* writer, mpack_error_t error);
+
+/**
+ * Queries the error state of the MPack writer.
+ *
+ * If a writer is in an error state, you should discard all data since the
+ * last time the error flag was checked. The error flag cannot be cleared.
+ */
+MPACK_INLINE mpack_error_t mpack_writer_error(mpack_writer_t* writer) {
+ return writer->error;
+}
+
+/**
+ * Writes a MessagePack object header (an MPack Tag.)
+ *
+ * If the value is a map, array, string, binary or extension type, the
+ * containing elements or bytes must be written separately and the
+ * appropriate finish function must be called (as though one of the
+ * mpack_start_*() functions was called.)
+ *
+ * @see mpack_write_bytes()
+ * @see mpack_finish_map()
+ * @see mpack_finish_array()
+ * @see mpack_finish_str()
+ * @see mpack_finish_bin()
+ * @see mpack_finish_ext()
+ * @see mpack_finish_type()
+ */
+void mpack_write_tag(mpack_writer_t* writer, mpack_tag_t tag);
+
+/**
+ * @}
+ */
+
+/**
+ * @name Integers
+ * @{
+ */
+
+/** Writes an 8-bit integer in the most efficient packing available. */
+void mpack_write_i8(mpack_writer_t* writer, int8_t value);
+
+/** Writes a 16-bit integer in the most efficient packing available. */
+void mpack_write_i16(mpack_writer_t* writer, int16_t value);
+
+/** Writes a 32-bit integer in the most efficient packing available. */
+void mpack_write_i32(mpack_writer_t* writer, int32_t value);
+
+/** Writes a 64-bit integer in the most efficient packing available. */
+void mpack_write_i64(mpack_writer_t* writer, int64_t value);
+
+/** Writes an integer in the most efficient packing available. */
+MPACK_INLINE void mpack_write_int(mpack_writer_t* writer, int64_t value) {
+ mpack_write_i64(writer, value);
+}
+
+/** Writes an 8-bit unsigned integer in the most efficient packing available. */
+void mpack_write_u8(mpack_writer_t* writer, uint8_t value);
+
+/** Writes an 16-bit unsigned integer in the most efficient packing available. */
+void mpack_write_u16(mpack_writer_t* writer, uint16_t value);
+
+/** Writes an 32-bit unsigned integer in the most efficient packing available. */
+void mpack_write_u32(mpack_writer_t* writer, uint32_t value);
+
+/** Writes an 64-bit unsigned integer in the most efficient packing available. */
+void mpack_write_u64(mpack_writer_t* writer, uint64_t value);
+
+/** Writes an unsigned integer in the most efficient packing available. */
+MPACK_INLINE void mpack_write_uint(mpack_writer_t* writer, uint64_t value) {
+ mpack_write_u64(writer, value);
+}
+
+/**
+ * @}
+ */
+
+/**
+ * @name Other Basic Types
+ * @{
+ */
+
+/** Writes a float. */
+void mpack_write_float(mpack_writer_t* writer, float value);
+
+/** Writes a double. */
+void mpack_write_double(mpack_writer_t* writer, double value);
+
+/** Writes a boolean. */
+void mpack_write_bool(mpack_writer_t* writer, bool value);
+
+/** Writes a boolean with value true. */
+void mpack_write_true(mpack_writer_t* writer);
+
+/** Writes a boolean with value false. */
+void mpack_write_false(mpack_writer_t* writer);
+
+/** Writes a nil. */
+void mpack_write_nil(mpack_writer_t* writer);
+
+/** Write a pre-encoded messagepack object */
+void mpack_write_object_bytes(mpack_writer_t* writer, const char* data, size_t bytes);
+
+#if MPACK_EXTENSIONS
+/**
+ * Writes a timestamp.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @param writer The writer
+ * @param seconds The (signed) number of seconds since 1970-01-01T00:00:00Z.
+ * @param nanoseconds The additional number of nanoseconds from 0 to 999,999,999 inclusive.
+ */
+void mpack_write_timestamp(mpack_writer_t* writer, int64_t seconds, uint32_t nanoseconds);
+
+/**
+ * Writes a timestamp with the given number of seconds (and zero nanoseconds).
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @param writer The writer
+ * @param seconds The (signed) number of seconds since 1970-01-01T00:00:00Z.
+ */
+MPACK_INLINE void mpack_write_timestamp_seconds(mpack_writer_t* writer, int64_t seconds) {
+ mpack_write_timestamp(writer, seconds, 0);
+}
+
+/**
+ * Writes a timestamp.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+MPACK_INLINE void mpack_write_timestamp_struct(mpack_writer_t* writer, mpack_timestamp_t timestamp) {
+ mpack_write_timestamp(writer, timestamp.seconds, timestamp.nanoseconds);
+}
+#endif
+
+/**
+ * @}
+ */
+
+/**
+ * @name Map and Array Functions
+ * @{
+ */
+
+/**
+ * Opens an array.
+ *
+ * `count` elements must follow, and mpack_finish_array() must be called
+ * when done.
+ *
+ * @see mpack_finish_array()
+ */
+void mpack_start_array(mpack_writer_t* writer, uint32_t count);
+
+/**
+ * Opens a map.
+ *
+ * `count * 2` elements must follow, and mpack_finish_map() must be called
+ * when done.
+ *
+ * Remember that while map elements in MessagePack are implicitly ordered,
+ * they are not ordered in JSON. If you need elements to be read back
+ * in the order they are written, consider use an array instead.
+ *
+ * @see mpack_finish_map()
+ */
+void mpack_start_map(mpack_writer_t* writer, uint32_t count);
+
+/**
+ * Finishes writing an array.
+ *
+ * This should be called only after a corresponding call to mpack_start_array()
+ * and after the array contents are written.
+ *
+ * This will track writes to ensure that the correct number of elements are written.
+ *
+ * @see mpack_start_array()
+ */
+MPACK_INLINE void mpack_finish_array(mpack_writer_t* writer) {
+ mpack_writer_track_pop(writer, mpack_type_array);
+}
+
+/**
+ * Finishes writing a map.
+ *
+ * This should be called only after a corresponding call to mpack_start_map()
+ * and after the map contents are written.
+ *
+ * This will track writes to ensure that the correct number of elements are written.
+ *
+ * @see mpack_start_map()
+ */
+MPACK_INLINE void mpack_finish_map(mpack_writer_t* writer) {
+ mpack_writer_track_pop(writer, mpack_type_map);
+}
+
+/**
+ * @}
+ */
+
+/**
+ * @name Data Helpers
+ * @{
+ */
+
+/**
+ * Writes a string.
+ *
+ * To stream a string in chunks, use mpack_start_str() instead.
+ *
+ * MPack does not care about the underlying encoding, but UTF-8 is highly
+ * recommended, especially for compatibility with JSON. You should consider
+ * calling mpack_write_utf8() instead, especially if you will be reading
+ * it back as UTF-8.
+ *
+ * You should not call mpack_finish_str() after calling this; this
+ * performs both start and finish.
+ */
+void mpack_write_str(mpack_writer_t* writer, const char* str, uint32_t length);
+
+/**
+ * Writes a string, ensuring that it is valid UTF-8.
+ *
+ * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or
+ * WTF-8. Only pure UTF-8 is allowed.
+ *
+ * You should not call mpack_finish_str() after calling this; this
+ * performs both start and finish.
+ *
+ * @throws mpack_error_invalid if the string is not valid UTF-8
+ */
+void mpack_write_utf8(mpack_writer_t* writer, const char* str, uint32_t length);
+
+/**
+ * Writes a null-terminated string. (The null-terminator is not written.)
+ *
+ * MPack does not care about the underlying encoding, but UTF-8 is highly
+ * recommended, especially for compatibility with JSON. You should consider
+ * calling mpack_write_utf8_cstr() instead, especially if you will be reading
+ * it back as UTF-8.
+ *
+ * You should not call mpack_finish_str() after calling this; this
+ * performs both start and finish.
+ */
+void mpack_write_cstr(mpack_writer_t* writer, const char* cstr);
+
+/**
+ * Writes a null-terminated string, or a nil node if the given cstr pointer
+ * is NULL. (The null-terminator is not written.)
+ *
+ * MPack does not care about the underlying encoding, but UTF-8 is highly
+ * recommended, especially for compatibility with JSON. You should consider
+ * calling mpack_write_utf8_cstr_or_nil() instead, especially if you will
+ * be reading it back as UTF-8.
+ *
+ * You should not call mpack_finish_str() after calling this; this
+ * performs both start and finish.
+ */
+void mpack_write_cstr_or_nil(mpack_writer_t* writer, const char* cstr);
+
+/**
+ * Writes a null-terminated string, ensuring that it is valid UTF-8. (The
+ * null-terminator is not written.)
+ *
+ * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or
+ * WTF-8. Only pure UTF-8 is allowed.
+ *
+ * You should not call mpack_finish_str() after calling this; this
+ * performs both start and finish.
+ *
+ * @throws mpack_error_invalid if the string is not valid UTF-8
+ */
+void mpack_write_utf8_cstr(mpack_writer_t* writer, const char* cstr);
+
+/**
+ * Writes a null-terminated string ensuring that it is valid UTF-8, or
+ * writes nil if the given cstr pointer is NULL. (The null-terminator
+ * is not written.)
+ *
+ * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or
+ * WTF-8. Only pure UTF-8 is allowed.
+ *
+ * You should not call mpack_finish_str() after calling this; this
+ * performs both start and finish.
+ *
+ * @throws mpack_error_invalid if the string is not valid UTF-8
+ */
+void mpack_write_utf8_cstr_or_nil(mpack_writer_t* writer, const char* cstr);
+
+/**
+ * Writes a binary blob.
+ *
+ * To stream a binary blob in chunks, use mpack_start_bin() instead.
+ *
+ * You should not call mpack_finish_bin() after calling this; this
+ * performs both start and finish.
+ */
+void mpack_write_bin(mpack_writer_t* writer, const char* data, uint32_t count);
+
+#if MPACK_EXTENSIONS
+/**
+ * Writes an extension type.
+ *
+ * To stream an extension blob in chunks, use mpack_start_ext() instead.
+ *
+ * Extension types [0, 127] are available for application-specific types. Extension
+ * types [-128, -1] are reserved for future extensions of MessagePack.
+ *
+ * You should not call mpack_finish_ext() after calling this; this
+ * performs both start and finish.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+void mpack_write_ext(mpack_writer_t* writer, int8_t exttype, const char* data, uint32_t count);
+#endif
+
+/**
+ * @}
+ */
+
+/**
+ * @name Chunked Data Functions
+ * @{
+ */
+
+/**
+ * Opens a string. `count` bytes should be written with calls to
+ * mpack_write_bytes(), and mpack_finish_str() should be called
+ * when done.
+ *
+ * To write an entire string at once, use mpack_write_str() or
+ * mpack_write_cstr() instead.
+ *
+ * MPack does not care about the underlying encoding, but UTF-8 is highly
+ * recommended, especially for compatibility with JSON.
+ */
+void mpack_start_str(mpack_writer_t* writer, uint32_t count);
+
+/**
+ * Opens a binary blob. `count` bytes should be written with calls to
+ * mpack_write_bytes(), and mpack_finish_bin() should be called
+ * when done.
+ */
+void mpack_start_bin(mpack_writer_t* writer, uint32_t count);
+
+#if MPACK_EXTENSIONS
+/**
+ * Opens an extension type. `count` bytes should be written with calls
+ * to mpack_write_bytes(), and mpack_finish_ext() should be called
+ * when done.
+ *
+ * Extension types [0, 127] are available for application-specific types. Extension
+ * types [-128, -1] are reserved for future extensions of MessagePack.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+void mpack_start_ext(mpack_writer_t* writer, int8_t exttype, uint32_t count);
+#endif
+
+/**
+ * Writes a portion of bytes for a string, binary blob or extension type which
+ * was opened by mpack_write_tag() or one of the mpack_start_*() functions.
+ *
+ * This can be called multiple times to write the data in chunks, as long as
+ * the total amount of bytes written matches the count given when the compound
+ * type was started.
+ *
+ * The corresponding mpack_finish_*() function must be called when done.
+ *
+ * To write an entire string, binary blob or extension type at
+ * once, use one of the mpack_write_*() functions instead.
+ *
+ * @see mpack_write_tag()
+ * @see mpack_start_str()
+ * @see mpack_start_bin()
+ * @see mpack_start_ext()
+ * @see mpack_finish_str()
+ * @see mpack_finish_bin()
+ * @see mpack_finish_ext()
+ * @see mpack_finish_type()
+ */
+void mpack_write_bytes(mpack_writer_t* writer, const char* data, size_t count);
+
+/**
+ * Finishes writing a string.
+ *
+ * This should be called only after a corresponding call to mpack_start_str()
+ * and after the string bytes are written with mpack_write_bytes().
+ *
+ * This will track writes to ensure that the correct number of elements are written.
+ *
+ * @see mpack_start_str()
+ * @see mpack_write_bytes()
+ */
+MPACK_INLINE void mpack_finish_str(mpack_writer_t* writer) {
+ mpack_writer_track_pop(writer, mpack_type_str);
+}
+
+/**
+ * Finishes writing a binary blob.
+ *
+ * This should be called only after a corresponding call to mpack_start_bin()
+ * and after the binary bytes are written with mpack_write_bytes().
+ *
+ * This will track writes to ensure that the correct number of bytes are written.
+ *
+ * @see mpack_start_bin()
+ * @see mpack_write_bytes()
+ */
+MPACK_INLINE void mpack_finish_bin(mpack_writer_t* writer) {
+ mpack_writer_track_pop(writer, mpack_type_bin);
+}
+
+#if MPACK_EXTENSIONS
+/**
+ * Finishes writing an extended type binary data blob.
+ *
+ * This should be called only after a corresponding call to mpack_start_bin()
+ * and after the binary bytes are written with mpack_write_bytes().
+ *
+ * This will track writes to ensure that the correct number of bytes are written.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @see mpack_start_ext()
+ * @see mpack_write_bytes()
+ */
+MPACK_INLINE void mpack_finish_ext(mpack_writer_t* writer) {
+ mpack_writer_track_pop(writer, mpack_type_ext);
+}
+#endif
+
+/**
+ * Finishes writing the given compound type.
+ *
+ * This will track writes to ensure that the correct number of elements
+ * or bytes are written.
+ *
+ * This can be called with the appropriate type instead the corresponding
+ * mpack_finish_*() function if you want to finish a dynamic type.
+ */
+MPACK_INLINE void mpack_finish_type(mpack_writer_t* writer, mpack_type_t type) {
+ mpack_writer_track_pop(writer, type);
+}
+
+/**
+ * @}
+ */
+
+#if MPACK_WRITER && MPACK_HAS_GENERIC && !defined(__cplusplus)
+
+/**
+ * @name Type-Generic Writers
+ * @{
+ */
+
+/**
+ * @def mpack_write(writer, value)
+ *
+ * Type-generic writer for primitive types.
+ *
+ * The compiler will dispatch to an appropriate write function based
+ * on the type of the @a value parameter.
+ *
+ * @note This requires C11 `_Generic` support. (A set of inline overloads
+ * are used in C++ to provide the same functionality.)
+ *
+ * @warning In C11, the indentifiers `true`, `false` and `NULL` are
+ * all of type `int`, not `bool` or `void*`! They will emit unexpected
+ * types when passed uncast, so be careful when using them.
+ */
+#define mpack_write(writer, value) \
+ _Generic(((void)0, value), \
+ int8_t: mpack_write_i8, \
+ int16_t: mpack_write_i16, \
+ int32_t: mpack_write_i32, \
+ int64_t: mpack_write_i64, \
+ uint8_t: mpack_write_u8, \
+ uint16_t: mpack_write_u16, \
+ uint32_t: mpack_write_u32, \
+ uint64_t: mpack_write_u64, \
+ bool: mpack_write_bool, \
+ float: mpack_write_float, \
+ double: mpack_write_double, \
+ char *: mpack_write_cstr_or_nil, \
+ const char *: mpack_write_cstr_or_nil \
+ )(writer, value)
+
+/**
+ * @def mpack_write_kv(writer, key, value)
+ *
+ * Type-generic writer for key-value pairs of null-terminated string
+ * keys and primitive values.
+ *
+ * @warning @a writer may be evaluated multiple times.
+ *
+ * @warning In C11, the indentifiers `true`, `false` and `NULL` are
+ * all of type `int`, not `bool` or `void*`! They will emit unexpected
+ * types when passed uncast, so be careful when using them.
+ *
+ * @param writer The writer.
+ * @param key A null-terminated C string.
+ * @param value A primitive type supported by mpack_write().
+ */
+#define mpack_write_kv(writer, key, value) do { \
+ mpack_write_cstr(writer, key); \
+ mpack_write(writer, value); \
+} while (0)
+
+/**
+ * @}
+ */
+
+#endif
+
+/**
+ * @}
+ */
+
+#endif
+
+MPACK_HEADER_END
+
+#if defined(__cplusplus) || defined(MPACK_DOXYGEN)
+
+/*
+ * C++ generic writers for primitive values
+ *
+ * These currently sit outside of MPACK_HEADER_END because it defines
+ * extern "C". They'll be moved to a C++-specific header soon.
+ */
+
+#ifdef MPACK_DOXYGEN
+#undef mpack_write
+#undef mpack_write_kv
+#endif
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, int8_t value) {
+ mpack_write_i8(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, int16_t value) {
+ mpack_write_i16(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, int32_t value) {
+ mpack_write_i32(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, int64_t value) {
+ mpack_write_i64(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, uint8_t value) {
+ mpack_write_u8(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, uint16_t value) {
+ mpack_write_u16(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, uint32_t value) {
+ mpack_write_u32(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, uint64_t value) {
+ mpack_write_u64(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, bool value) {
+ mpack_write_bool(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, float value) {
+ mpack_write_float(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, double value) {
+ mpack_write_double(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, char *value) {
+ mpack_write_cstr_or_nil(writer, value);
+}
+
+MPACK_INLINE void mpack_write(mpack_writer_t* writer, const char *value) {
+ mpack_write_cstr_or_nil(writer, value);
+}
+
+/* C++ generic write for key-value pairs */
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, int8_t value) {
+ mpack_write_cstr(writer, key);
+ mpack_write_i8(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, int16_t value) {
+ mpack_write_cstr(writer, key);
+ mpack_write_i16(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, int32_t value) {
+ mpack_write_cstr(writer, key);
+ mpack_write_i32(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, int64_t value) {
+ mpack_write_cstr(writer, key);
+ mpack_write_i64(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, uint8_t value) {
+ mpack_write_cstr(writer, key);
+ mpack_write_u8(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, uint16_t value) {
+ mpack_write_cstr(writer, key);
+ mpack_write_u16(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, uint32_t value) {
+ mpack_write_cstr(writer, key);
+ mpack_write_u32(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, uint64_t value) {
+ mpack_write_cstr(writer, key);
+ mpack_write_u64(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, bool value) {
+ mpack_write_cstr(writer, key);
+ mpack_write_bool(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, float value) {
+ mpack_write_cstr(writer, key);
+ mpack_write_float(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, double value) {
+ mpack_write_cstr(writer, key);
+ mpack_write_double(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, char *value) {
+ mpack_write_cstr(writer, key);
+ mpack_write_cstr_or_nil(writer, value);
+}
+
+MPACK_INLINE void mpack_write_kv(mpack_writer_t* writer, const char *key, const char *value) {
+ mpack_write_cstr(writer, key);
+ mpack_write_cstr_or_nil(writer, value);
+}
+#endif /* __cplusplus */
+
+#endif
+
+/* mpack/mpack-reader.h.h */
+
+/**
+ * @file
+ *
+ * Declares the core MPack Tag Reader.
+ */
+
+#ifndef MPACK_READER_H
+#define MPACK_READER_H 1
+
+/* #include "mpack-common.h" */
+
+MPACK_HEADER_START
+
+#if MPACK_READER
+
+#if MPACK_READ_TRACKING
+struct mpack_track_t;
+#endif
+
+// The denominator to determine whether a read is a small
+// fraction of the buffer size.
+#define MPACK_READER_SMALL_FRACTION_DENOMINATOR 32
+
+/**
+ * @defgroup reader Reader API
+ *
+ * The MPack Reader API contains functions for imperatively reading dynamically
+ * typed data from a MessagePack stream.
+ *
+ * See @ref docs/reader.md for examples.
+ *
+ * @note If you are not writing code for an embedded device (or otherwise do
+ * not need maximum performance with minimal memory usage), you should not use
+ * this. You probably want to use the @link node Node API@endlink instead.
+ *
+ * This forms the basis of the @link expect Expect API@endlink, which can be
+ * used to interpret the stream of elements in expected types and value ranges.
+ *
+ * @{
+ */
+
+/**
+ * @def MPACK_READER_MINIMUM_BUFFER_SIZE
+ *
+ * The minimum buffer size for a reader with a fill function.
+ */
+#define MPACK_READER_MINIMUM_BUFFER_SIZE 32
+
+/**
+ * A buffered MessagePack decoder.
+ *
+ * The decoder wraps an existing buffer and, optionally, a fill function.
+ * This allows efficiently decoding data from existing memory buffers, files,
+ * streams, etc.
+ *
+ * All read operations are synchronous; they will block until the
+ * requested data is fully read, or an error occurs.
+ *
+ * This structure is opaque; its fields should not be accessed outside
+ * of MPack.
+ */
+typedef struct mpack_reader_t mpack_reader_t;
+
+/**
+ * The MPack reader's fill function. It should fill the buffer with at
+ * least one byte and at most the given @c count, returning the number
+ * of bytes written to the buffer.
+ *
+ * In case of error, it should flag an appropriate error on the reader
+ * (usually @ref mpack_error_io), or simply return zero. If zero is
+ * returned, mpack_error_io is raised.
+ *
+ * @note When reading from a stream, you should only copy and return
+ * the bytes that are immediately available. It is always safe to return
+ * less than the requested count as long as some non-zero number of bytes
+ * are read; if more bytes are needed, the read function will simply be
+ * called again.
+ *
+ * @see mpack_reader_context()
+ */
+typedef size_t (*mpack_reader_fill_t)(mpack_reader_t* reader, char* buffer, size_t count);
+
+/**
+ * The MPack reader's skip function. It should discard the given number
+ * of bytes from the source (for example by seeking forward.)
+ *
+ * In case of error, it should flag an appropriate error on the reader.
+ *
+ * @see mpack_reader_context()
+ */
+typedef void (*mpack_reader_skip_t)(mpack_reader_t* reader, size_t count);
+
+/**
+ * An error handler function to be called when an error is flagged on
+ * the reader.
+ *
+ * The error handler will only be called once on the first error flagged;
+ * any subsequent reads and errors are ignored, and the reader is
+ * permanently in that error state.
+ *
+ * MPack is safe against non-local jumps out of error handler callbacks.
+ * This means you are allowed to longjmp or throw an exception (in C++,
+ * Objective-C, or with SEH) out of this callback.
+ *
+ * Bear in mind when using longjmp that local non-volatile variables that
+ * have changed are undefined when setjmp() returns, so you can't put the
+ * reader on the stack in the same activation frame as the setjmp without
+ * declaring it volatile.
+ *
+ * You must still eventually destroy the reader. It is not destroyed
+ * automatically when an error is flagged. It is safe to destroy the
+ * reader within this error callback, but you will either need to perform
+ * a non-local jump, or store something in your context to identify
+ * that the reader is destroyed since any future accesses to it cause
+ * undefined behavior.
+ */
+typedef void (*mpack_reader_error_t)(mpack_reader_t* reader, mpack_error_t error);
+
+/**
+ * A teardown function to be called when the reader is destroyed.
+ */
+typedef void (*mpack_reader_teardown_t)(mpack_reader_t* reader);
+
+/* Hide internals from documentation */
+/** @cond */
+
+struct mpack_reader_t {
+ void* context; /* Context for reader callbacks */
+ mpack_reader_fill_t fill; /* Function to read bytes into the buffer */
+ mpack_reader_error_t error_fn; /* Function to call on error */
+ mpack_reader_teardown_t teardown; /* Function to teardown the context on destroy */
+ mpack_reader_skip_t skip; /* Function to skip bytes from the source */
+
+ char* buffer; /* Writeable byte buffer */
+ size_t size; /* Size of the buffer */
+
+ const char* data; /* Current data pointer (in the buffer, if it is used) */
+ const char* end; /* The end of available data (in the buffer, if it is used) */
+
+ mpack_error_t error; /* Error state */
+
+ #if MPACK_READ_TRACKING
+ mpack_track_t track; /* Stack of map/array/str/bin/ext reads */
+ #endif
+};
+
+/** @endcond */
+
+/**
+ * @name Lifecycle Functions
+ * @{
+ */
+
+/**
+ * Initializes an MPack reader with the given buffer. The reader does
+ * not assume ownership of the buffer, but the buffer must be writeable
+ * if a fill function will be used to refill it.
+ *
+ * @param reader The MPack reader.
+ * @param buffer The buffer with which to read MessagePack data.
+ * @param size The size of the buffer.
+ * @param count The number of bytes already in the buffer.
+ */
+void mpack_reader_init(mpack_reader_t* reader, char* buffer, size_t size, size_t count);
+
+/**
+ * Initializes an MPack reader directly into an error state. Use this if you
+ * are writing a wrapper to mpack_reader_init() which can fail its setup.
+ */
+void mpack_reader_init_error(mpack_reader_t* reader, mpack_error_t error);
+
+/**
+ * Initializes an MPack reader to parse a pre-loaded contiguous chunk of data. The
+ * reader does not assume ownership of the data.
+ *
+ * @param reader The MPack reader.
+ * @param data The data to parse.
+ * @param count The number of bytes pointed to by data.
+ */
+void mpack_reader_init_data(mpack_reader_t* reader, const char* data, size_t count);
+
+#if MPACK_STDIO
+/**
+ * Initializes an MPack reader that reads from a file.
+ *
+ * The file will be automatically opened and closed by the reader.
+ */
+void mpack_reader_init_filename(mpack_reader_t* reader, const char* filename);
+
+/**
+ * Deprecated.
+ *
+ * \deprecated Renamed to mpack_reader_init_filename().
+ */
+MPACK_INLINE void mpack_reader_init_file(mpack_reader_t* reader, const char* filename) {
+ mpack_reader_init_filename(reader, filename);
+}
+
+/**
+ * Initializes an MPack reader that reads from a libc FILE. This can be used to
+ * read from stdin, or from a file opened separately.
+ *
+ * @param reader The MPack reader.
+ * @param stdfile The FILE.
+ * @param close_when_done If true, fclose() will be called on the FILE when it
+ * is no longer needed. If false, the file will not be closed when
+ * reading is done.
+ *
+ * @warning The reader is buffered. It will read data in advance of parsing it,
+ * and it may read more data than it parsed. See mpack_reader_remaining() to
+ * access the extra data.
+ */
+void mpack_reader_init_stdfile(mpack_reader_t* reader, FILE* stdfile, bool close_when_done);
+#endif
+
+/**
+ * @def mpack_reader_init_stack(reader)
+ * @hideinitializer
+ *
+ * Initializes an MPack reader using stack space as a buffer. A fill function
+ * should be added to the reader to fill the buffer.
+ *
+ * @see mpack_reader_set_fill
+ */
+
+/** @cond */
+#define mpack_reader_init_stack_line_ex(line, reader) \
+ char mpack_buf_##line[MPACK_STACK_SIZE]; \
+ mpack_reader_init((reader), mpack_buf_##line, sizeof(mpack_buf_##line), 0)
+
+#define mpack_reader_init_stack_line(line, reader) \
+ mpack_reader_init_stack_line_ex(line, reader)
+/** @endcond */
+
+#define mpack_reader_init_stack(reader) \
+ mpack_reader_init_stack_line(__LINE__, (reader))
+
+/**
+ * Cleans up the MPack reader, ensuring that all compound elements
+ * have been completely read. Returns the final error state of the
+ * reader.
+ *
+ * This will assert in tracking mode if the reader is not in an error
+ * state and has any incomplete reads. If you want to cancel reading
+ * in the middle of a document, you need to flag an error on the reader
+ * before destroying it (such as mpack_error_data).
+ *
+ * @see mpack_read_tag()
+ * @see mpack_reader_flag_error()
+ * @see mpack_error_data
+ */
+mpack_error_t mpack_reader_destroy(mpack_reader_t* reader);
+
+/**
+ * @}
+ */
+
+/**
+ * @name Callbacks
+ * @{
+ */
+
+/**
+ * Sets the custom pointer to pass to the reader callbacks, such as fill
+ * or teardown.
+ *
+ * @param reader The MPack reader.
+ * @param context User data to pass to the reader callbacks.
+ *
+ * @see mpack_reader_context()
+ */
+MPACK_INLINE void mpack_reader_set_context(mpack_reader_t* reader, void* context) {
+ reader->context = context;
+}
+
+/**
+ * Returns the custom context for reader callbacks.
+ *
+ * @see mpack_reader_set_context
+ * @see mpack_reader_set_fill
+ * @see mpack_reader_set_skip
+ */
+MPACK_INLINE void* mpack_reader_context(mpack_reader_t* reader) {
+ return reader->context;
+}
+
+/**
+ * Sets the fill function to refill the data buffer when it runs out of data.
+ *
+ * If no fill function is used, truncated MessagePack data results in
+ * mpack_error_invalid (since the buffer is assumed to contain a
+ * complete MessagePack object.)
+ *
+ * If a fill function is used, truncated MessagePack data usually
+ * results in mpack_error_io (since the fill function fails to get
+ * the missing data.)
+ *
+ * This should normally be used with mpack_reader_set_context() to register
+ * a custom pointer to pass to the fill function.
+ *
+ * @param reader The MPack reader.
+ * @param fill The function to fetch additional data into the buffer.
+ */
+void mpack_reader_set_fill(mpack_reader_t* reader, mpack_reader_fill_t fill);
+
+/**
+ * Sets the skip function to discard bytes from the source stream.
+ *
+ * It's not necessary to implement this function. If the stream is not
+ * seekable, don't set a skip callback. The reader will fall back to
+ * using the fill function instead.
+ *
+ * This should normally be used with mpack_reader_set_context() to register
+ * a custom pointer to pass to the skip function.
+ *
+ * The skip function is ignored in size-optimized builds to reduce code
+ * size. Data will be skipped with the fill function when necessary.
+ *
+ * @param reader The MPack reader.
+ * @param skip The function to discard bytes from the source stream.
+ */
+void mpack_reader_set_skip(mpack_reader_t* reader, mpack_reader_skip_t skip);
+
+/**
+ * Sets the error function to call when an error is flagged on the reader.
+ *
+ * This should normally be used with mpack_reader_set_context() to register
+ * a custom pointer to pass to the error function.
+ *
+ * See the definition of mpack_reader_error_t for more information about
+ * what you can do from an error callback.
+ *
+ * @see mpack_reader_error_t
+ * @param reader The MPack reader.
+ * @param error_fn The function to call when an error is flagged on the reader.
+ */
+MPACK_INLINE void mpack_reader_set_error_handler(mpack_reader_t* reader, mpack_reader_error_t error_fn) {
+ reader->error_fn = error_fn;
+}
+
+/**
+ * Sets the teardown function to call when the reader is destroyed.
+ *
+ * This should normally be used with mpack_reader_set_context() to register
+ * a custom pointer to pass to the teardown function.
+ *
+ * @param reader The MPack reader.
+ * @param teardown The function to call when the reader is destroyed.
+ */
+MPACK_INLINE void mpack_reader_set_teardown(mpack_reader_t* reader, mpack_reader_teardown_t teardown) {
+ reader->teardown = teardown;
+}
+
+/**
+ * @}
+ */
+
+/**
+ * @name Core Reader Functions
+ * @{
+ */
+
+/**
+ * Queries the error state of the MPack reader.
+ *
+ * If a reader is in an error state, you should discard all data since the
+ * last time the error flag was checked. The error flag cannot be cleared.
+ */
+MPACK_INLINE mpack_error_t mpack_reader_error(mpack_reader_t* reader) {
+ return reader->error;
+}
+
+/**
+ * Places the reader in the given error state, calling the error callback if one
+ * is set.
+ *
+ * This allows you to externally flag errors, for example if you are validating
+ * data as you read it.
+ *
+ * If the reader is already in an error state, this call is ignored and no
+ * error callback is called.
+ */
+void mpack_reader_flag_error(mpack_reader_t* reader, mpack_error_t error);
+
+/**
+ * Places the reader in the given error state if the given error is not mpack_ok,
+ * returning the resulting error state of the reader.
+ *
+ * This allows you to externally flag errors, for example if you are validating
+ * data as you read it.
+ *
+ * If the given error is mpack_ok or if the reader is already in an error state,
+ * this call is ignored and the actual error state of the reader is returned.
+ */
+MPACK_INLINE mpack_error_t mpack_reader_flag_if_error(mpack_reader_t* reader, mpack_error_t error) {
+ if (error != mpack_ok)
+ mpack_reader_flag_error(reader, error);
+ return mpack_reader_error(reader);
+}
+
+/**
+ * Returns bytes left in the reader's buffer.
+ *
+ * If you are done reading MessagePack data but there is other interesting data
+ * following it, the reader may have buffered too much data. The number of bytes
+ * remaining in the buffer and a pointer to the position of those bytes can be
+ * queried here.
+ *
+ * If you know the length of the MPack chunk beforehand, it's better to instead
+ * have your fill function limit the data it reads so that the reader does not
+ * have extra data. In this case you can simply check that this returns zero.
+ *
+ * Returns 0 if the reader is in an error state.
+ *
+ * @param reader The MPack reader from which to query remaining data.
+ * @param data [out] A pointer to the remaining data, or NULL.
+ * @return The number of bytes remaining in the buffer.
+ */
+size_t mpack_reader_remaining(mpack_reader_t* reader, const char** data);
+
+/**
+ * Reads a MessagePack object header (an MPack tag.)
+ *
+ * If an error occurs, the reader is placed in an error state and a
+ * nil tag is returned. If the reader is already in an error state,
+ * a nil tag is returned.
+ *
+ * If the type is compound (i.e. is a map, array, string, binary or
+ * extension type), additional reads are required to get the contained
+ * data, and the corresponding done function must be called when done.
+ *
+ * @note Maps in JSON are unordered, so it is recommended not to expect
+ * a specific ordering for your map values in case your data is converted
+ * to/from JSON.
+ *
+ * @see mpack_read_bytes()
+ * @see mpack_done_array()
+ * @see mpack_done_map()
+ * @see mpack_done_str()
+ * @see mpack_done_bin()
+ * @see mpack_done_ext()
+ */
+mpack_tag_t mpack_read_tag(mpack_reader_t* reader);
+
+/**
+ * Parses the next MessagePack object header (an MPack tag) without
+ * advancing the reader.
+ *
+ * If an error occurs, the reader is placed in an error state and a
+ * nil tag is returned. If the reader is already in an error state,
+ * a nil tag is returned.
+ *
+ * @note Maps in JSON are unordered, so it is recommended not to expect
+ * a specific ordering for your map values in case your data is converted
+ * to/from JSON.
+ *
+ * @see mpack_read_tag()
+ * @see mpack_discard()
+ */
+mpack_tag_t mpack_peek_tag(mpack_reader_t* reader);
+
+/**
+ * @}
+ */
+
+/**
+ * @name String and Data Functions
+ * @{
+ */
+
+/**
+ * Skips bytes from the underlying stream. This is used only to
+ * skip the contents of a string, binary blob or extension object.
+ */
+void mpack_skip_bytes(mpack_reader_t* reader, size_t count);
+
+/**
+ * Reads bytes from a string, binary blob or extension object, copying
+ * them into the given buffer.
+ *
+ * A str, bin or ext must have been opened by a call to mpack_read_tag()
+ * which yielded one of these types, or by a call to an expect function
+ * such as mpack_expect_str() or mpack_expect_bin().
+ *
+ * If an error occurs, the buffer contents are undefined.
+ *
+ * This can be called multiple times for a single str, bin or ext
+ * to read the data in chunks. The total data read must add up
+ * to the size of the object.
+ *
+ * @param reader The MPack reader
+ * @param p The buffer in which to copy the bytes
+ * @param count The number of bytes to read
+ */
+void mpack_read_bytes(mpack_reader_t* reader, char* p, size_t count);
+
+/**
+ * Reads bytes from a string, ensures that the string is valid UTF-8,
+ * and copies the bytes into the given buffer.
+ *
+ * A string must have been opened by a call to mpack_read_tag() which
+ * yielded a string, or by a call to an expect function such as
+ * mpack_expect_str().
+ *
+ * The given byte count must match the complete size of the string as
+ * returned by the tag or expect function. You must ensure that the
+ * buffer fits the data.
+ *
+ * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or
+ * WTF-8. Only pure UTF-8 is allowed.
+ *
+ * If an error occurs, the buffer contents are undefined.
+ *
+ * Unlike mpack_read_bytes(), this cannot be used to read the data in
+ * chunks (since this might split a character's UTF-8 bytes, and the
+ * reader does not keep track of the UTF-8 decoding state between reads.)
+ *
+ * @throws mpack_error_type if the string contains invalid UTF-8.
+ */
+void mpack_read_utf8(mpack_reader_t* reader, char* p, size_t byte_count);
+
+/**
+ * Reads bytes from a string, ensures that the string contains no NUL
+ * bytes, copies the bytes into the given buffer and adds a null-terminator.
+ *
+ * A string must have been opened by a call to mpack_read_tag() which
+ * yielded a string, or by a call to an expect function such as
+ * mpack_expect_str().
+ *
+ * The given byte count must match the size of the string as returned
+ * by the tag or expect function. The string will only be copied if
+ * the buffer is large enough to store it.
+ *
+ * If an error occurs, the buffer will contain an empty string.
+ *
+ * @note If you know the object will be a string before reading it,
+ * it is highly recommended to use mpack_expect_cstr() instead.
+ * Alternatively you could use mpack_peek_tag() and call
+ * mpack_expect_cstr() if it's a string.
+ *
+ * @throws mpack_error_too_big if the string plus null-terminator is larger than the given buffer size
+ * @throws mpack_error_type if the string contains a null byte.
+ *
+ * @see mpack_peek_tag()
+ * @see mpack_expect_cstr()
+ * @see mpack_expect_utf8_cstr()
+ */
+void mpack_read_cstr(mpack_reader_t* reader, char* buf, size_t buffer_size, size_t byte_count);
+
+/**
+ * Reads bytes from a string, ensures that the string is valid UTF-8
+ * with no NUL bytes, copies the bytes into the given buffer and adds a
+ * null-terminator.
+ *
+ * A string must have been opened by a call to mpack_read_tag() which
+ * yielded a string, or by a call to an expect function such as
+ * mpack_expect_str().
+ *
+ * The given byte count must match the size of the string as returned
+ * by the tag or expect function. The string will only be copied if
+ * the buffer is large enough to store it.
+ *
+ * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or
+ * WTF-8. Only pure UTF-8 is allowed, but without the NUL character, since
+ * it cannot be represented in a null-terminated string.
+ *
+ * If an error occurs, the buffer will contain an empty string.
+ *
+ * @note If you know the object will be a string before reading it,
+ * it is highly recommended to use mpack_expect_utf8_cstr() instead.
+ * Alternatively you could use mpack_peek_tag() and call
+ * mpack_expect_utf8_cstr() if it's a string.
+ *
+ * @throws mpack_error_too_big if the string plus null-terminator is larger than the given buffer size
+ * @throws mpack_error_type if the string contains invalid UTF-8 or a null byte.
+ *
+ * @see mpack_peek_tag()
+ * @see mpack_expect_utf8_cstr()
+ */
+void mpack_read_utf8_cstr(mpack_reader_t* reader, char* buf, size_t buffer_size, size_t byte_count);
+
+#ifdef MPACK_MALLOC
+/** @cond */
+// This can optionally add a null-terminator, but it does not check
+// whether the data contains null bytes. This must be done separately
+// in a cstring read function (possibly as part of a UTF-8 check.)
+char* mpack_read_bytes_alloc_impl(mpack_reader_t* reader, size_t count, bool null_terminated);
+/** @endcond */
+
+/**
+ * Reads bytes from a string, binary blob or extension object, allocating
+ * storage for them and returning the allocated pointer.
+ *
+ * The allocated string must be freed with MPACK_FREE() (or simply free()
+ * if MPack's allocator hasn't been customized.)
+ *
+ * Returns NULL if any error occurs, or if count is zero.
+ */
+MPACK_INLINE char* mpack_read_bytes_alloc(mpack_reader_t* reader, size_t count) {
+ return mpack_read_bytes_alloc_impl(reader, count, false);
+}
+#endif
+
+/**
+ * Reads bytes from a string, binary blob or extension object in-place in
+ * the buffer. This can be used to avoid copying the data.
+ *
+ * A str, bin or ext must have been opened by a call to mpack_read_tag()
+ * which yielded one of these types, or by a call to an expect function
+ * such as mpack_expect_str() or mpack_expect_bin().
+ *
+ * If the bytes are from a string, the string is not null-terminated! Use
+ * mpack_read_cstr() to copy the string into a buffer and add a null-terminator.
+ *
+ * The returned pointer is invalidated on the next read, or when the buffer
+ * is destroyed.
+ *
+ * The reader will move data around in the buffer if needed to ensure that
+ * the pointer can always be returned, so this should only be used if
+ * count is very small compared to the buffer size. If you need to check
+ * whether a small size is reasonable (for example you intend to handle small and
+ * large sizes differently), you can call mpack_should_read_bytes_inplace().
+ *
+ * This can be called multiple times for a single str, bin or ext
+ * to read the data in chunks. The total data read must add up
+ * to the size of the object.
+ *
+ * NULL is returned if the reader is in an error state.
+ *
+ * @throws mpack_error_too_big if the requested size is larger than the buffer size
+ *
+ * @see mpack_should_read_bytes_inplace()
+ */
+const char* mpack_read_bytes_inplace(mpack_reader_t* reader, size_t count);
+
+/**
+ * Reads bytes from a string in-place in the buffer and ensures they are
+ * valid UTF-8. This can be used to avoid copying the data.
+ *
+ * A string must have been opened by a call to mpack_read_tag() which
+ * yielded a string, or by a call to an expect function such as
+ * mpack_expect_str().
+ *
+ * The string is not null-terminated! Use mpack_read_utf8_cstr() to
+ * copy the string into a buffer and add a null-terminator.
+ *
+ * The returned pointer is invalidated on the next read, or when the buffer
+ * is destroyed.
+ *
+ * The reader will move data around in the buffer if needed to ensure that
+ * the pointer can always be returned, so this should only be used if
+ * count is very small compared to the buffer size. If you need to check
+ * whether a small size is reasonable (for example you intend to handle small and
+ * large sizes differently), you can call mpack_should_read_bytes_inplace().
+ *
+ * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or
+ * WTF-8. Only pure UTF-8 is allowed.
+ *
+ * Unlike mpack_read_bytes_inplace(), this cannot be used to read the data in
+ * chunks (since this might split a character's UTF-8 bytes, and the
+ * reader does not keep track of the UTF-8 decoding state between reads.)
+ *
+ * NULL is returned if the reader is in an error state.
+ *
+ * @throws mpack_error_type if the string contains invalid UTF-8
+ * @throws mpack_error_too_big if the requested size is larger than the buffer size
+ *
+ * @see mpack_should_read_bytes_inplace()
+ */
+const char* mpack_read_utf8_inplace(mpack_reader_t* reader, size_t count);
+
+/**
+ * Returns true if it's a good idea to read the given number of bytes
+ * in-place.
+ *
+ * If the read will be larger than some small fraction of the buffer size,
+ * this will return false to avoid shuffling too much data back and forth
+ * in the buffer.
+ *
+ * Use this if you're expecting arbitrary size data, and you want to read
+ * in-place for the best performance when possible but will fall back to
+ * a normal read if the data is too large.
+ *
+ * @see mpack_read_bytes_inplace()
+ */
+MPACK_INLINE bool mpack_should_read_bytes_inplace(mpack_reader_t* reader, size_t count) {
+ return (reader->size == 0 || count <= reader->size / MPACK_READER_SMALL_FRACTION_DENOMINATOR);
+}
+
+#if MPACK_EXTENSIONS
+/**
+ * Reads a timestamp contained in an ext object of the given size, closing the
+ * ext type.
+ *
+ * An ext object of exttype @ref MPACK_EXTTYPE_TIMESTAMP must have been opened
+ * by a call to e.g. mpack_read_tag() or mpack_expect_ext().
+ *
+ * You must NOT call mpack_done_ext() after calling this. A timestamp ext
+ * object can only contain a single timestamp value, so this calls
+ * mpack_done_ext() automatically.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @throws mpack_error_invalid if the size is not one of the supported
+ * timestamp sizes, or if the nanoseconds are out of range.
+ */
+mpack_timestamp_t mpack_read_timestamp(mpack_reader_t* reader, size_t size);
+#endif
+
+/**
+ * @}
+ */
+
+/**
+ * @name Core Reader Functions
+ * @{
+ */
+
+#if MPACK_READ_TRACKING
+/**
+ * Finishes reading the given type.
+ *
+ * This will track reads to ensure that the correct number of elements
+ * or bytes are read.
+ */
+void mpack_done_type(mpack_reader_t* reader, mpack_type_t type);
+#else
+MPACK_INLINE void mpack_done_type(mpack_reader_t* reader, mpack_type_t type) {
+ MPACK_UNUSED(reader);
+ MPACK_UNUSED(type);
+}
+#endif
+
+/**
+ * Finishes reading an array.
+ *
+ * This will track reads to ensure that the correct number of elements are read.
+ */
+MPACK_INLINE void mpack_done_array(mpack_reader_t* reader) {
+ mpack_done_type(reader, mpack_type_array);
+}
+
+/**
+ * @fn mpack_done_map(mpack_reader_t* reader)
+ *
+ * Finishes reading a map.
+ *
+ * This will track reads to ensure that the correct number of elements are read.
+ */
+MPACK_INLINE void mpack_done_map(mpack_reader_t* reader) {
+ mpack_done_type(reader, mpack_type_map);
+}
+
+/**
+ * @fn mpack_done_str(mpack_reader_t* reader)
+ *
+ * Finishes reading a string.
+ *
+ * This will track reads to ensure that the correct number of bytes are read.
+ */
+MPACK_INLINE void mpack_done_str(mpack_reader_t* reader) {
+ mpack_done_type(reader, mpack_type_str);
+}
+
+/**
+ * @fn mpack_done_bin(mpack_reader_t* reader)
+ *
+ * Finishes reading a binary data blob.
+ *
+ * This will track reads to ensure that the correct number of bytes are read.
+ */
+MPACK_INLINE void mpack_done_bin(mpack_reader_t* reader) {
+ mpack_done_type(reader, mpack_type_bin);
+}
+
+#if MPACK_EXTENSIONS
+/**
+ * @fn mpack_done_ext(mpack_reader_t* reader)
+ *
+ * Finishes reading an extended type binary data blob.
+ *
+ * This will track reads to ensure that the correct number of bytes are read.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+MPACK_INLINE void mpack_done_ext(mpack_reader_t* reader) {
+ mpack_done_type(reader, mpack_type_ext);
+}
+#endif
+
+/**
+ * Reads and discards the next object. This will read and discard all
+ * contained data as well if it is a compound type.
+ */
+void mpack_discard(mpack_reader_t* reader);
+
+/**
+ * @}
+ */
+
+/** @cond */
+
+#if MPACK_DEBUG && MPACK_STDIO
+/**
+ * @name Debugging Functions
+ * @{
+ */
+/*
+ * Converts a blob of MessagePack to a pseudo-JSON string for debugging
+ * purposes, placing the result in the given buffer with a null-terminator.
+ *
+ * If the buffer does not have enough space, the result will be truncated (but
+ * it is guaranteed to be null-terminated.)
+ *
+ * This is only available in debug mode, and only if stdio is available (since
+ * it uses snprintf().) It's strictly for debugging purposes.
+ */
+void mpack_print_data_to_buffer(const char* data, size_t data_size, char* buffer, size_t buffer_size);
+
+/*
+ * Converts a node to pseudo-JSON for debugging purposes, calling the given
+ * callback as many times as is necessary to output the character data.
+ *
+ * No null-terminator or trailing newline will be written.
+ *
+ * This is only available in debug mode, and only if stdio is available (since
+ * it uses snprintf().) It's strictly for debugging purposes.
+ */
+void mpack_print_data_to_callback(const char* data, size_t size, mpack_print_callback_t callback, void* context);
+
+/*
+ * Converts a blob of MessagePack to pseudo-JSON for debugging purposes
+ * and pretty-prints it to the given file.
+ */
+void mpack_print_data_to_file(const char* data, size_t len, FILE* file);
+
+/*
+ * Converts a blob of MessagePack to pseudo-JSON for debugging purposes
+ * and pretty-prints it to stdout.
+ */
+MPACK_INLINE void mpack_print_data_to_stdout(const char* data, size_t len) {
+ mpack_print_data_to_file(data, len, stdout);
+}
+
+/*
+ * Converts the MessagePack contained in the given `FILE*` to pseudo-JSON for
+ * debugging purposes, calling the given callback as many times as is necessary
+ * to output the character data.
+ */
+void mpack_print_stdfile_to_callback(FILE* file, mpack_print_callback_t callback, void* context);
+
+/*
+ * Deprecated.
+ *
+ * \deprecated Renamed to mpack_print_data_to_stdout().
+ */
+MPACK_INLINE void mpack_print(const char* data, size_t len) {
+ mpack_print_data_to_stdout(data, len);
+}
+
+/**
+ * @}
+ */
+#endif
+
+/** @endcond */
+
+/**
+ * @}
+ */
+
+
+
+#if MPACK_INTERNAL
+
+bool mpack_reader_ensure_straddle(mpack_reader_t* reader, size_t count);
+
+/*
+ * Ensures there are at least @c count bytes left in the
+ * data, raising an error and returning false if more
+ * data cannot be made available.
+ */
+MPACK_INLINE bool mpack_reader_ensure(mpack_reader_t* reader, size_t count) {
+ mpack_assert(count != 0, "cannot ensure zero bytes!");
+ mpack_assert(reader->error == mpack_ok, "reader cannot be in an error state!");
+
+ if (count <= (size_t)(reader->end - reader->data))
+ return true;
+ return mpack_reader_ensure_straddle(reader, count);
+}
+
+void mpack_read_native_straddle(mpack_reader_t* reader, char* p, size_t count);
+
+// Reads count bytes into p, deferring to mpack_read_native_straddle() if more
+// bytes are needed than are available in the buffer.
+MPACK_INLINE void mpack_read_native(mpack_reader_t* reader, char* p, size_t count) {
+ mpack_assert(count == 0 || p != NULL, "data pointer for %i bytes is NULL", (int)count);
+
+ if (count > (size_t)(reader->end - reader->data)) {
+ mpack_read_native_straddle(reader, p, count);
+ } else {
+ mpack_memcpy(p, reader->data, count);
+ reader->data += count;
+ }
+}
+
+#if MPACK_READ_TRACKING
+#define MPACK_READER_TRACK(reader, error_expr) \
+ (((reader)->error == mpack_ok) ? mpack_reader_flag_if_error((reader), (error_expr)) : (reader)->error)
+#else
+#define MPACK_READER_TRACK(reader, error_expr) (MPACK_UNUSED(reader), mpack_ok)
+#endif
+
+MPACK_INLINE mpack_error_t mpack_reader_track_element(mpack_reader_t* reader) {
+ return MPACK_READER_TRACK(reader, mpack_track_element(&reader->track, true));
+}
+
+MPACK_INLINE mpack_error_t mpack_reader_track_peek_element(mpack_reader_t* reader) {
+ return MPACK_READER_TRACK(reader, mpack_track_peek_element(&reader->track, true));
+}
+
+MPACK_INLINE mpack_error_t mpack_reader_track_bytes(mpack_reader_t* reader, uint64_t count) {
+ MPACK_UNUSED(count);
+ return MPACK_READER_TRACK(reader, mpack_track_bytes(&reader->track, true, count));
+}
+
+MPACK_INLINE mpack_error_t mpack_reader_track_str_bytes_all(mpack_reader_t* reader, uint64_t count) {
+ MPACK_UNUSED(count);
+ return MPACK_READER_TRACK(reader, mpack_track_str_bytes_all(&reader->track, true, count));
+}
+
+#endif
+
+
+
+#endif
+
+MPACK_HEADER_END
+
+#endif
+
+
+/* mpack/mpack-expect.h.h */
+
+/**
+ * @file
+ *
+ * Declares the MPack static Expect API.
+ */
+
+#ifndef MPACK_EXPECT_H
+#define MPACK_EXPECT_H 1
+
+/* #include "mpack-reader.h" */
+
+MPACK_HEADER_START
+
+#if MPACK_EXPECT
+
+#if !MPACK_READER
+#error "MPACK_EXPECT requires MPACK_READER."
+#endif
+
+/**
+ * @defgroup expect Expect API
+ *
+ * The MPack Expect API allows you to easily read MessagePack data when you
+ * expect it to follow a predefined schema.
+ *
+ * @note If you are not writing code for an embedded device (or otherwise do
+ * not need maximum performance with minimal memory usage), you should not use
+ * this. You probably want to use the @link node Node API@endlink instead.
+ *
+ * See @ref docs/expect.md for examples.
+ *
+ * The main purpose of the Expect API is convenience, so the API is lax. It
+ * automatically converts between similar types where there is no loss of
+ * precision.
+ *
+ * When using any of the expect functions, if the type or value of what was
+ * read does not match what is expected, @ref mpack_error_type is raised.
+ *
+ * @{
+ */
+
+/**
+ * @name Basic Number Functions
+ * @{
+ */
+
+/**
+ * Reads an 8-bit unsigned integer.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in an 8-bit unsigned int.
+ *
+ * Returns zero if an error occurs.
+ */
+uint8_t mpack_expect_u8(mpack_reader_t* reader);
+
+/**
+ * Reads a 16-bit unsigned integer.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 16-bit unsigned int.
+ *
+ * Returns zero if an error occurs.
+ */
+uint16_t mpack_expect_u16(mpack_reader_t* reader);
+
+/**
+ * Reads a 32-bit unsigned integer.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 32-bit unsigned int.
+ *
+ * Returns zero if an error occurs.
+ */
+uint32_t mpack_expect_u32(mpack_reader_t* reader);
+
+/**
+ * Reads a 64-bit unsigned integer.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 64-bit unsigned int.
+ *
+ * Returns zero if an error occurs.
+ */
+uint64_t mpack_expect_u64(mpack_reader_t* reader);
+
+/**
+ * Reads an 8-bit signed integer.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in an 8-bit signed int.
+ *
+ * Returns zero if an error occurs.
+ */
+int8_t mpack_expect_i8(mpack_reader_t* reader);
+
+/**
+ * Reads a 16-bit signed integer.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 16-bit signed int.
+ *
+ * Returns zero if an error occurs.
+ */
+int16_t mpack_expect_i16(mpack_reader_t* reader);
+
+/**
+ * Reads a 32-bit signed integer.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 32-bit signed int.
+ *
+ * Returns zero if an error occurs.
+ */
+int32_t mpack_expect_i32(mpack_reader_t* reader);
+
+/**
+ * Reads a 64-bit signed integer.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 64-bit signed int.
+ *
+ * Returns zero if an error occurs.
+ */
+int64_t mpack_expect_i64(mpack_reader_t* reader);
+
+/**
+ * Reads a number, returning the value as a float. The underlying value can be an
+ * integer, float or double; the value is converted to a float.
+ *
+ * @note Reading a double or a large integer with this function can incur a
+ * loss of precision.
+ *
+ * @throws mpack_error_type if the underlying value is not a float, double or integer.
+ */
+float mpack_expect_float(mpack_reader_t* reader);
+
+/**
+ * Reads a number, returning the value as a double. The underlying value can be an
+ * integer, float or double; the value is converted to a double.
+ *
+ * @note Reading a very large integer with this function can incur a
+ * loss of precision.
+ *
+ * @throws mpack_error_type if the underlying value is not a float, double or integer.
+ */
+double mpack_expect_double(mpack_reader_t* reader);
+
+/**
+ * Reads a float. The underlying value must be a float, not a double or an integer.
+ * This ensures no loss of precision can occur.
+ *
+ * @throws mpack_error_type if the underlying value is not a float.
+ */
+float mpack_expect_float_strict(mpack_reader_t* reader);
+
+/**
+ * Reads a double. The underlying value must be a float or double, not an integer.
+ * This ensures no loss of precision can occur.
+ *
+ * @throws mpack_error_type if the underlying value is not a float or double.
+ */
+double mpack_expect_double_strict(mpack_reader_t* reader);
+
+/**
+ * @}
+ */
+
+/**
+ * @name Ranged Number Functions
+ * @{
+ */
+
+/**
+ * Reads an 8-bit unsigned integer, ensuring that it falls within the given range.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in an 8-bit unsigned int.
+ *
+ * Returns min_value if an error occurs.
+ */
+uint8_t mpack_expect_u8_range(mpack_reader_t* reader, uint8_t min_value, uint8_t max_value);
+
+/**
+ * Reads a 16-bit unsigned integer, ensuring that it falls within the given range.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 16-bit unsigned int.
+ *
+ * Returns min_value if an error occurs.
+ */
+uint16_t mpack_expect_u16_range(mpack_reader_t* reader, uint16_t min_value, uint16_t max_value);
+
+/**
+ * Reads a 32-bit unsigned integer, ensuring that it falls within the given range.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 32-bit unsigned int.
+ *
+ * Returns min_value if an error occurs.
+ */
+uint32_t mpack_expect_u32_range(mpack_reader_t* reader, uint32_t min_value, uint32_t max_value);
+
+/**
+ * Reads a 64-bit unsigned integer, ensuring that it falls within the given range.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 64-bit unsigned int.
+ *
+ * Returns min_value if an error occurs.
+ */
+uint64_t mpack_expect_u64_range(mpack_reader_t* reader, uint64_t min_value, uint64_t max_value);
+
+/**
+ * Reads an unsigned integer, ensuring that it falls within the given range.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in an unsigned int.
+ *
+ * Returns min_value if an error occurs.
+ */
+MPACK_INLINE unsigned int mpack_expect_uint_range(mpack_reader_t* reader, unsigned int min_value, unsigned int max_value) {
+ // This should be true at compile-time, so this just wraps the 32-bit
+ // function. We fallback to 64-bit if for some reason sizeof(int) isn't 4.
+ if (sizeof(unsigned int) == 4)
+ return (unsigned int)mpack_expect_u32_range(reader, (uint32_t)min_value, (uint32_t)max_value);
+ return (unsigned int)mpack_expect_u64_range(reader, min_value, max_value);
+}
+
+/**
+ * Reads an 8-bit unsigned integer, ensuring that it is at most @a max_value.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in an 8-bit unsigned int.
+ *
+ * Returns 0 if an error occurs.
+ */
+MPACK_INLINE uint8_t mpack_expect_u8_max(mpack_reader_t* reader, uint8_t max_value) {
+ return mpack_expect_u8_range(reader, 0, max_value);
+}
+
+/**
+ * Reads a 16-bit unsigned integer, ensuring that it is at most @a max_value.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 16-bit unsigned int.
+ *
+ * Returns 0 if an error occurs.
+ */
+MPACK_INLINE uint16_t mpack_expect_u16_max(mpack_reader_t* reader, uint16_t max_value) {
+ return mpack_expect_u16_range(reader, 0, max_value);
+}
+
+/**
+ * Reads a 32-bit unsigned integer, ensuring that it is at most @a max_value.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 32-bit unsigned int.
+ *
+ * Returns 0 if an error occurs.
+ */
+MPACK_INLINE uint32_t mpack_expect_u32_max(mpack_reader_t* reader, uint32_t max_value) {
+ return mpack_expect_u32_range(reader, 0, max_value);
+}
+
+/**
+ * Reads a 64-bit unsigned integer, ensuring that it is at most @a max_value.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 64-bit unsigned int.
+ *
+ * Returns 0 if an error occurs.
+ */
+MPACK_INLINE uint64_t mpack_expect_u64_max(mpack_reader_t* reader, uint64_t max_value) {
+ return mpack_expect_u64_range(reader, 0, max_value);
+}
+
+/**
+ * Reads an unsigned integer, ensuring that it is at most @a max_value.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in an unsigned int.
+ *
+ * Returns 0 if an error occurs.
+ */
+MPACK_INLINE unsigned int mpack_expect_uint_max(mpack_reader_t* reader, unsigned int max_value) {
+ return mpack_expect_uint_range(reader, 0, max_value);
+}
+
+/**
+ * Reads an 8-bit signed integer, ensuring that it falls within the given range.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in an 8-bit signed int.
+ *
+ * Returns min_value if an error occurs.
+ */
+int8_t mpack_expect_i8_range(mpack_reader_t* reader, int8_t min_value, int8_t max_value);
+
+/**
+ * Reads a 16-bit signed integer, ensuring that it falls within the given range.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 16-bit signed int.
+ *
+ * Returns min_value if an error occurs.
+ */
+int16_t mpack_expect_i16_range(mpack_reader_t* reader, int16_t min_value, int16_t max_value);
+
+/**
+ * Reads a 32-bit signed integer, ensuring that it falls within the given range.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 32-bit signed int.
+ *
+ * Returns min_value if an error occurs.
+ */
+int32_t mpack_expect_i32_range(mpack_reader_t* reader, int32_t min_value, int32_t max_value);
+
+/**
+ * Reads a 64-bit signed integer, ensuring that it falls within the given range.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 64-bit signed int.
+ *
+ * Returns min_value if an error occurs.
+ */
+int64_t mpack_expect_i64_range(mpack_reader_t* reader, int64_t min_value, int64_t max_value);
+
+/**
+ * Reads a signed integer, ensuring that it falls within the given range.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a signed int.
+ *
+ * Returns min_value if an error occurs.
+ */
+MPACK_INLINE int mpack_expect_int_range(mpack_reader_t* reader, int min_value, int max_value) {
+ // This should be true at compile-time, so this just wraps the 32-bit
+ // function. We fallback to 64-bit if for some reason sizeof(int) isn't 4.
+ if (sizeof(int) == 4)
+ return (int)mpack_expect_i32_range(reader, (int32_t)min_value, (int32_t)max_value);
+ return (int)mpack_expect_i64_range(reader, min_value, max_value);
+}
+
+/**
+ * Reads an 8-bit signed integer, ensuring that it is at least zero and at
+ * most @a max_value.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in an 8-bit signed int.
+ *
+ * Returns 0 if an error occurs.
+ */
+MPACK_INLINE int8_t mpack_expect_i8_max(mpack_reader_t* reader, int8_t max_value) {
+ return mpack_expect_i8_range(reader, 0, max_value);
+}
+
+/**
+ * Reads a 16-bit signed integer, ensuring that it is at least zero and at
+ * most @a max_value.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 16-bit signed int.
+ *
+ * Returns 0 if an error occurs.
+ */
+MPACK_INLINE int16_t mpack_expect_i16_max(mpack_reader_t* reader, int16_t max_value) {
+ return mpack_expect_i16_range(reader, 0, max_value);
+}
+
+/**
+ * Reads a 32-bit signed integer, ensuring that it is at least zero and at
+ * most @a max_value.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 32-bit signed int.
+ *
+ * Returns 0 if an error occurs.
+ */
+MPACK_INLINE int32_t mpack_expect_i32_max(mpack_reader_t* reader, int32_t max_value) {
+ return mpack_expect_i32_range(reader, 0, max_value);
+}
+
+/**
+ * Reads a 64-bit signed integer, ensuring that it is at least zero and at
+ * most @a max_value.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a 64-bit signed int.
+ *
+ * Returns 0 if an error occurs.
+ */
+MPACK_INLINE int64_t mpack_expect_i64_max(mpack_reader_t* reader, int64_t max_value) {
+ return mpack_expect_i64_range(reader, 0, max_value);
+}
+
+/**
+ * Reads an int, ensuring that it is at least zero and at most @a max_value.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a signed int.
+ *
+ * Returns 0 if an error occurs.
+ */
+MPACK_INLINE int mpack_expect_int_max(mpack_reader_t* reader, int max_value) {
+ return mpack_expect_int_range(reader, 0, max_value);
+}
+
+/**
+ * Reads a number, ensuring that it falls within the given range and returning
+ * the value as a float. The underlying value can be an integer, float or
+ * double; the value is converted to a float.
+ *
+ * @note Reading a double or a large integer with this function can incur a
+ * loss of precision.
+ *
+ * @throws mpack_error_type if the underlying value is not a float, double or integer.
+ */
+float mpack_expect_float_range(mpack_reader_t* reader, float min_value, float max_value);
+
+/**
+ * Reads a number, ensuring that it falls within the given range and returning
+ * the value as a double. The underlying value can be an integer, float or
+ * double; the value is converted to a double.
+ *
+ * @note Reading a very large integer with this function can incur a
+ * loss of precision.
+ *
+ * @throws mpack_error_type if the underlying value is not a float, double or integer.
+ */
+double mpack_expect_double_range(mpack_reader_t* reader, double min_value, double max_value);
+
+/**
+ * @}
+ */
+
+
+
+// These are additional Basic Number functions that wrap inline range functions.
+
+/**
+ * @name Basic Number Functions
+ * @{
+ */
+
+/**
+ * Reads an unsigned int.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in an unsigned int.
+ *
+ * Returns zero if an error occurs.
+ */
+MPACK_INLINE unsigned int mpack_expect_uint(mpack_reader_t* reader) {
+
+ // This should be true at compile-time, so this just wraps the 32-bit function.
+ if (sizeof(unsigned int) == 4)
+ return (unsigned int)mpack_expect_u32(reader);
+
+ // Otherwise we wrap the max function to ensure it fits.
+ return (unsigned int)mpack_expect_u64_max(reader, UINT_MAX);
+
+}
+
+/**
+ * Reads a signed int.
+ *
+ * The underlying type may be an integer type of any size and signedness,
+ * as long as the value can be represented in a signed int.
+ *
+ * Returns zero if an error occurs.
+ */
+MPACK_INLINE int mpack_expect_int(mpack_reader_t* reader) {
+
+ // This should be true at compile-time, so this just wraps the 32-bit function.
+ if (sizeof(int) == 4)
+ return (int)mpack_expect_i32(reader);
+
+ // Otherwise we wrap the range function to ensure it fits.
+ return (int)mpack_expect_i64_range(reader, INT_MIN, INT_MAX);
+
+}
+
+/**
+ * @}
+ */
+
+
+
+/**
+ * @name Matching Number Functions
+ * @{
+ */
+
+/**
+ * Reads an unsigned integer, ensuring that it exactly matches the given value.
+ *
+ * mpack_error_type is raised if the value is not representable as an unsigned
+ * integer or if it does not exactly match the given value.
+ */
+void mpack_expect_uint_match(mpack_reader_t* reader, uint64_t value);
+
+/**
+ * Reads a signed integer, ensuring that it exactly matches the given value.
+ *
+ * mpack_error_type is raised if the value is not representable as a signed
+ * integer or if it does not exactly match the given value.
+ */
+void mpack_expect_int_match(mpack_reader_t* reader, int64_t value);
+
+/**
+ * @name Other Basic Types
+ * @{
+ */
+
+/**
+ * Reads a nil, raising @ref mpack_error_type if the value is not nil.
+ */
+void mpack_expect_nil(mpack_reader_t* reader);
+
+/**
+ * Reads a boolean.
+ *
+ * @note Integers will raise mpack_error_type; the value must be strictly a boolean.
+ */
+bool mpack_expect_bool(mpack_reader_t* reader);
+
+/**
+ * Reads a boolean, raising @ref mpack_error_type if its value is not @c true.
+ */
+void mpack_expect_true(mpack_reader_t* reader);
+
+/**
+ * Reads a boolean, raising @ref mpack_error_type if its value is not @c false.
+ */
+void mpack_expect_false(mpack_reader_t* reader);
+
+/**
+ * @}
+ */
+
+/**
+ * @name Extension Functions
+ * @{
+ */
+
+#if MPACK_EXTENSIONS
+/**
+ * Reads a timestamp.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+mpack_timestamp_t mpack_expect_timestamp(mpack_reader_t* reader);
+
+/**
+ * Reads a timestamp in seconds, truncating the nanoseconds (if any).
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+int64_t mpack_expect_timestamp_truncate(mpack_reader_t* reader);
+#endif
+
+/**
+ * @}
+ */
+
+/**
+ * @name Compound Types
+ * @{
+ */
+
+/**
+ * Reads the start of a map, returning its element count.
+ *
+ * A number of values follow equal to twice the element count of the map,
+ * alternating between keys and values. @ref mpack_done_map() must be called
+ * once all elements have been read.
+ *
+ * @note Maps in JSON are unordered, so it is recommended not to expect
+ * a specific ordering for your map values in case your data is converted
+ * to/from JSON.
+ *
+ * @warning This call is dangerous! It does not have a size limit, and it
+ * does not have any way of checking whether there is enough data in the
+ * message (since the data could be coming from a stream.) When looping
+ * through the map's contents, you must check for errors on each iteration
+ * of the loop. Otherwise an attacker could craft a message declaring a map
+ * of a billion elements which would throw your parsing code into an
+ * infinite loop! You should strongly consider using mpack_expect_map_max()
+ * with a safe maximum size instead.
+ *
+ * @throws mpack_error_type if the value is not a map.
+ */
+uint32_t mpack_expect_map(mpack_reader_t* reader);
+
+/**
+ * Reads the start of a map with a number of elements in the given range, returning
+ * its element count.
+ *
+ * A number of values follow equal to twice the element count of the map,
+ * alternating between keys and values. @ref mpack_done_map() must be called
+ * once all elements have been read.
+ *
+ * @note Maps in JSON are unordered, so it is recommended not to expect
+ * a specific ordering for your map values in case your data is converted
+ * to/from JSON.
+ *
+ * min_count is returned if an error occurs.
+ *
+ * @throws mpack_error_type if the value is not a map or if its size does
+ * not fall within the given range.
+ */
+uint32_t mpack_expect_map_range(mpack_reader_t* reader, uint32_t min_count, uint32_t max_count);
+
+/**
+ * Reads the start of a map with a number of elements at most @a max_count,
+ * returning its element count.
+ *
+ * A number of values follow equal to twice the element count of the map,
+ * alternating between keys and values. @ref mpack_done_map() must be called
+ * once all elements have been read.
+ *
+ * @note Maps in JSON are unordered, so it is recommended not to expect
+ * a specific ordering for your map values in case your data is converted
+ * to/from JSON.
+ *
+ * Zero is returned if an error occurs.
+ *
+ * @throws mpack_error_type if the value is not a map or if its size is
+ * greater than max_count.
+ */
+MPACK_INLINE uint32_t mpack_expect_map_max(mpack_reader_t* reader, uint32_t max_count) {
+ return mpack_expect_map_range(reader, 0, max_count);
+}
+
+/**
+ * Reads the start of a map of the exact size given.
+ *
+ * A number of values follow equal to twice the element count of the map,
+ * alternating between keys and values. @ref mpack_done_map() must be called
+ * once all elements have been read.
+ *
+ * @note Maps in JSON are unordered, so it is recommended not to expect
+ * a specific ordering for your map values in case your data is converted
+ * to/from JSON.
+ *
+ * @throws mpack_error_type if the value is not a map or if its size
+ * does not match the given count.
+ */
+void mpack_expect_map_match(mpack_reader_t* reader, uint32_t count);
+
+/**
+ * Reads a nil node or the start of a map, returning whether a map was
+ * read and placing its number of key/value pairs in count.
+ *
+ * If a map was read, a number of values follow equal to twice the element count
+ * of the map, alternating between keys and values. @ref mpack_done_map() should
+ * also be called once all elements have been read (only if a map was read.)
+ *
+ * @note Maps in JSON are unordered, so it is recommended not to expect
+ * a specific ordering for your map values in case your data is converted
+ * to/from JSON.
+ *
+ * @warning This call is dangerous! It does not have a size limit, and it
+ * does not have any way of checking whether there is enough data in the
+ * message (since the data could be coming from a stream.) When looping
+ * through the map's contents, you must check for errors on each iteration
+ * of the loop. Otherwise an attacker could craft a message declaring a map
+ * of a billion elements which would throw your parsing code into an
+ * infinite loop! You should strongly consider using mpack_expect_map_max_or_nil()
+ * with a safe maximum size instead.
+ *
+ * @returns @c true if a map was read successfully; @c false if nil was read
+ * or an error occured.
+ * @throws mpack_error_type if the value is not a nil or map.
+ */
+bool mpack_expect_map_or_nil(mpack_reader_t* reader, uint32_t* count);
+
+/**
+ * Reads a nil node or the start of a map with a number of elements at most
+ * max_count, returning whether a map was read and placing its number of
+ * key/value pairs in count.
+ *
+ * If a map was read, a number of values follow equal to twice the element count
+ * of the map, alternating between keys and values. @ref mpack_done_map() should
+ * anlso be called once all elements have been read (only if a map was read.)
+ *
+ * @note Maps in JSON are unordered, so it is recommended not to expect
+ * a specific ordering for your map values in case your data is converted
+ * to/from JSON. Consider using mpack_expect_key_cstr() or mpack_expect_key_uint()
+ * to switch on the key; see @ref docs/expect.md for examples.
+ *
+ * @returns @c true if a map was read successfully; @c false if nil was read
+ * or an error occured.
+ * @throws mpack_error_type if the value is not a nil or map.
+ */
+bool mpack_expect_map_max_or_nil(mpack_reader_t* reader, uint32_t max_count, uint32_t* count);
+
+/**
+ * Reads the start of an array, returning its element count.
+ *
+ * A number of values follow equal to the element count of the array.
+ * @ref mpack_done_array() must be called once all elements have been read.
+ *
+ * @warning This call is dangerous! It does not have a size limit, and it
+ * does not have any way of checking whether there is enough data in the
+ * message (since the data could be coming from a stream.) When looping
+ * through the array's contents, you must check for errors on each iteration
+ * of the loop. Otherwise an attacker could craft a message declaring an array
+ * of a billion elements which would throw your parsing code into an
+ * infinite loop! You should strongly consider using mpack_expect_array_max()
+ * with a safe maximum size instead.
+ */
+uint32_t mpack_expect_array(mpack_reader_t* reader);
+
+/**
+ * Reads the start of an array with a number of elements in the given range,
+ * returning its element count.
+ *
+ * A number of values follow equal to the element count of the array.
+ * @ref mpack_done_array() must be called once all elements have been read.
+ *
+ * min_count is returned if an error occurs.
+ *
+ * @throws mpack_error_type if the value is not an array or if its size does
+ * not fall within the given range.
+ */
+uint32_t mpack_expect_array_range(mpack_reader_t* reader, uint32_t min_count, uint32_t max_count);
+
+/**
+ * Reads the start of an array with a number of elements at most @a max_count,
+ * returning its element count.
+ *
+ * A number of values follow equal to the element count of the array.
+ * @ref mpack_done_array() must be called once all elements have been read.
+ *
+ * Zero is returned if an error occurs.
+ *
+ * @throws mpack_error_type if the value is not an array or if its size is
+ * greater than max_count.
+ */
+MPACK_INLINE uint32_t mpack_expect_array_max(mpack_reader_t* reader, uint32_t max_count) {
+ return mpack_expect_array_range(reader, 0, max_count);
+}
+
+/**
+ * Reads the start of an array of the exact size given.
+ *
+ * A number of values follow equal to the element count of the array.
+ * @ref mpack_done_array() must be called once all elements have been read.
+ *
+ * @throws mpack_error_type if the value is not an array or if its size does
+ * not match the given count.
+ */
+void mpack_expect_array_match(mpack_reader_t* reader, uint32_t count);
+
+/**
+ * Reads a nil node or the start of an array, returning whether an array was
+ * read and placing its number of elements in count.
+ *
+ * If an array was read, a number of values follow equal to the element count
+ * of the array. @ref mpack_done_array() should also be called once all elements
+ * have been read (only if an array was read.)
+ *
+ * @warning This call is dangerous! It does not have a size limit, and it
+ * does not have any way of checking whether there is enough data in the
+ * message (since the data could be coming from a stream.) When looping
+ * through the array's contents, you must check for errors on each iteration
+ * of the loop. Otherwise an attacker could craft a message declaring an array
+ * of a billion elements which would throw your parsing code into an
+ * infinite loop! You should strongly consider using mpack_expect_array_max_or_nil()
+ * with a safe maximum size instead.
+ *
+ * @returns @c true if an array was read successfully; @c false if nil was read
+ * or an error occured.
+ * @throws mpack_error_type if the value is not a nil or array.
+ */
+bool mpack_expect_array_or_nil(mpack_reader_t* reader, uint32_t* count);
+
+/**
+ * Reads a nil node or the start of an array with a number of elements at most
+ * max_count, returning whether an array was read and placing its number of
+ * key/value pairs in count.
+ *
+ * If an array was read, a number of values follow equal to the element count
+ * of the array. @ref mpack_done_array() should also be called once all elements
+ * have been read (only if an array was read.)
+ *
+ * @returns @c true if an array was read successfully; @c false if nil was read
+ * or an error occured.
+ * @throws mpack_error_type if the value is not a nil or array.
+ */
+bool mpack_expect_array_max_or_nil(mpack_reader_t* reader, uint32_t max_count, uint32_t* count);
+
+#ifdef MPACK_MALLOC
+/**
+ * @hideinitializer
+ *
+ * Reads the start of an array and allocates storage for it, placing its
+ * size in out_count. A number of objects follow equal to the element count
+ * of the array. You must call @ref mpack_done_array() when done (even
+ * if the element count is zero.)
+ *
+ * If an error occurs, NULL is returned and the reader is placed in an
+ * error state.
+ *
+ * If the count is zero, NULL is returned. This does not indicate error.
+ * You should not check the return value for NULL to check for errors; only
+ * check the reader's error state.
+ *
+ * The allocated array must be freed with MPACK_FREE() (or simply free()
+ * if MPack's allocator hasn't been customized.)
+ *
+ * @throws mpack_error_type if the value is not an array or if its size is
+ * greater than max_count.
+ */
+#define mpack_expect_array_alloc(reader, Type, max_count, out_count) \
+ ((Type*)mpack_expect_array_alloc_impl(reader, sizeof(Type), max_count, out_count, false))
+
+/**
+ * @hideinitializer
+ *
+ * Reads a nil node or the start of an array and allocates storage for it,
+ * placing its size in out_count. A number of objects follow equal to the element
+ * count of the array if a non-empty array was read.
+ *
+ * If an error occurs, NULL is returned and the reader is placed in an
+ * error state.
+ *
+ * If a nil node was read, NULL is returned. If an empty array was read,
+ * mpack_done_array() is called automatically and NULL is returned. These
+ * do not indicate error. You should not check the return value for NULL
+ * to check for errors; only check the reader's error state.
+ *
+ * The allocated array must be freed with MPACK_FREE() (or simply free()
+ * if MPack's allocator hasn't been customized.)
+ *
+ * @warning You must call @ref mpack_done_array() if and only if a non-zero
+ * element count is read. This function does not differentiate between nil
+ * and an empty array.
+ *
+ * @throws mpack_error_type if the value is not an array or if its size is
+ * greater than max_count.
+ */
+#define mpack_expect_array_or_nil_alloc(reader, Type, max_count, out_count) \
+ ((Type*)mpack_expect_array_alloc_impl(reader, sizeof(Type), max_count, out_count, true))
+#endif
+
+/**
+ * @}
+ */
+
+/** @cond */
+#ifdef MPACK_MALLOC
+void* mpack_expect_array_alloc_impl(mpack_reader_t* reader,
+ size_t element_size, uint32_t max_count, uint32_t* out_count, bool allow_nil);
+#endif
+/** @endcond */
+
+
+/**
+ * @name String Functions
+ * @{
+ */
+
+/**
+ * Reads the start of a string, returning its size in bytes.
+ *
+ * The bytes follow and must be read separately with mpack_read_bytes()
+ * or mpack_read_bytes_inplace(). mpack_done_str() must be called
+ * once all bytes have been read.
+ *
+ * NUL bytes are allowed in the string, and no encoding checks are done.
+ *
+ * mpack_error_type is raised if the value is not a string.
+ */
+uint32_t mpack_expect_str(mpack_reader_t* reader);
+
+/**
+ * Reads a string of at most the given size, writing it into the
+ * given buffer and returning its size in bytes.
+ *
+ * This does not add a null-terminator! Use mpack_expect_cstr() to
+ * add a null-terminator.
+ *
+ * NUL bytes are allowed in the string, and no encoding checks are done.
+ */
+size_t mpack_expect_str_buf(mpack_reader_t* reader, char* buf, size_t bufsize);
+
+/**
+ * Reads a string into the given buffer, ensuring it is a valid UTF-8 string
+ * and returning its size in bytes.
+ *
+ * This does not add a null-terminator! Use mpack_expect_utf8_cstr() to
+ * add a null-terminator.
+ *
+ * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or
+ * WTF-8. Only pure UTF-8 is allowed.
+ *
+ * NUL bytes are allowed in the string (as they are in UTF-8.)
+ *
+ * Raises mpack_error_too_big if there is not enough room for the string.
+ * Raises mpack_error_type if the value is not a string or is not a valid UTF-8 string.
+ */
+size_t mpack_expect_utf8(mpack_reader_t* reader, char* buf, size_t bufsize);
+
+/**
+ * Reads the start of a string, raising an error if its length is not
+ * at most the given number of bytes (not including any null-terminator.)
+ *
+ * The bytes follow and must be read separately with mpack_read_bytes()
+ * or mpack_read_bytes_inplace(). @ref mpack_done_str() must be called
+ * once all bytes have been read.
+ *
+ * @throws mpack_error_type If the value is not a string.
+ * @throws mpack_error_too_big If the string's length in bytes is larger than the given maximum size.
+ */
+MPACK_INLINE uint32_t mpack_expect_str_max(mpack_reader_t* reader, uint32_t maxsize) {
+ uint32_t length = mpack_expect_str(reader);
+ if (length > maxsize) {
+ mpack_reader_flag_error(reader, mpack_error_too_big);
+ return 0;
+ }
+ return length;
+}
+
+/**
+ * Reads the start of a string, raising an error if its length is not
+ * exactly the given number of bytes (not including any null-terminator.)
+ *
+ * The bytes follow and must be read separately with mpack_read_bytes()
+ * or mpack_read_bytes_inplace(). @ref mpack_done_str() must be called
+ * once all bytes have been read.
+ *
+ * mpack_error_type is raised if the value is not a string or if its
+ * length does not match.
+ */
+MPACK_INLINE void mpack_expect_str_length(mpack_reader_t* reader, uint32_t count) {
+ if (mpack_expect_str(reader) != count)
+ mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+/**
+ * Reads a string, ensuring it exactly matches the given string.
+ *
+ * Remember that maps are unordered in JSON. Don't use this for map keys
+ * unless the map has only a single key!
+ */
+void mpack_expect_str_match(mpack_reader_t* reader, const char* str, size_t length);
+
+/**
+ * Reads a string into the given buffer, ensures it has no null bytes,
+ * and adds a null-terminator at the end.
+ *
+ * Raises mpack_error_too_big if there is not enough room for the string and null-terminator.
+ * Raises mpack_error_type if the value is not a string or contains a null byte.
+ */
+void mpack_expect_cstr(mpack_reader_t* reader, char* buf, size_t size);
+
+/**
+ * Reads a string into the given buffer, ensures it is a valid UTF-8 string
+ * without NUL characters, and adds a null-terminator at the end.
+ *
+ * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or
+ * WTF-8. Only pure UTF-8 is allowed, but without the NUL character, since
+ * it cannot be represented in a null-terminated string.
+ *
+ * Raises mpack_error_too_big if there is not enough room for the string and null-terminator.
+ * Raises mpack_error_type if the value is not a string or is not a valid UTF-8 string.
+ */
+void mpack_expect_utf8_cstr(mpack_reader_t* reader, char* buf, size_t size);
+
+#ifdef MPACK_MALLOC
+/**
+ * Reads a string with the given total maximum size (including space for a
+ * null-terminator), allocates storage for it, ensures it has no null-bytes,
+ * and adds a null-terminator at the end. You assume ownership of the
+ * returned pointer if reading succeeds.
+ *
+ * The allocated string must be freed with MPACK_FREE() (or simply free()
+ * if MPack's allocator hasn't been customized.)
+ *
+ * @throws mpack_error_too_big If the string plus null-terminator is larger than the given maxsize.
+ * @throws mpack_error_type If the value is not a string or contains a null byte.
+ */
+char* mpack_expect_cstr_alloc(mpack_reader_t* reader, size_t maxsize);
+
+/**
+ * Reads a string with the given total maximum size (including space for a
+ * null-terminator), allocates storage for it, ensures it is valid UTF-8
+ * with no null-bytes, and adds a null-terminator at the end. You assume
+ * ownership of the returned pointer if reading succeeds.
+ *
+ * The length in bytes of the string, not including the null-terminator,
+ * will be written to size.
+ *
+ * This does not accept any UTF-8 variant such as Modified UTF-8, CESU-8 or
+ * WTF-8. Only pure UTF-8 is allowed, but without the NUL character, since
+ * it cannot be represented in a null-terminated string.
+ *
+ * The allocated string must be freed with MPACK_FREE() (or simply free()
+ * if MPack's allocator hasn't been customized.)
+ * if you want a null-terminator.
+ *
+ * @throws mpack_error_too_big If the string plus null-terminator is larger
+ * than the given maxsize.
+ * @throws mpack_error_type If the value is not a string or contains
+ * invalid UTF-8 or a null byte.
+ */
+char* mpack_expect_utf8_cstr_alloc(mpack_reader_t* reader, size_t maxsize);
+#endif
+
+/**
+ * Reads a string, ensuring it exactly matches the given null-terminated
+ * string.
+ *
+ * Remember that maps are unordered in JSON. Don't use this for map keys
+ * unless the map has only a single key!
+ */
+MPACK_INLINE void mpack_expect_cstr_match(mpack_reader_t* reader, const char* cstr) {
+ mpack_assert(cstr != NULL, "cstr pointer is NULL");
+ mpack_expect_str_match(reader, cstr, mpack_strlen(cstr));
+}
+
+/**
+ * @}
+ */
+
+/**
+ * @name Binary Data
+ * @{
+ */
+
+/**
+ * Reads the start of a binary blob, returning its size in bytes.
+ *
+ * The bytes follow and must be read separately with mpack_read_bytes()
+ * or mpack_read_bytes_inplace(). @ref mpack_done_bin() must be called
+ * once all bytes have been read.
+ *
+ * mpack_error_type is raised if the value is not a binary blob.
+ */
+uint32_t mpack_expect_bin(mpack_reader_t* reader);
+
+/**
+ * Reads the start of a binary blob, raising an error if its length is not
+ * at most the given number of bytes.
+ *
+ * The bytes follow and must be read separately with mpack_read_bytes()
+ * or mpack_read_bytes_inplace(). @ref mpack_done_bin() must be called
+ * once all bytes have been read.
+ *
+ * mpack_error_type is raised if the value is not a binary blob or if its
+ * length does not match.
+ */
+MPACK_INLINE uint32_t mpack_expect_bin_max(mpack_reader_t* reader, uint32_t maxsize) {
+ uint32_t length = mpack_expect_bin(reader);
+ if (length > maxsize) {
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return 0;
+ }
+ return length;
+}
+
+/**
+ * Reads the start of a binary blob, raising an error if its length is not
+ * exactly the given number of bytes.
+ *
+ * The bytes follow and must be read separately with mpack_read_bytes()
+ * or mpack_read_bytes_inplace(). @ref mpack_done_bin() must be called
+ * once all bytes have been read.
+ *
+ * mpack_error_type is raised if the value is not a binary blob or if its
+ * length does not match.
+ */
+MPACK_INLINE void mpack_expect_bin_size(mpack_reader_t* reader, uint32_t count) {
+ if (mpack_expect_bin(reader) != count)
+ mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+/**
+ * Reads a binary blob into the given buffer, returning its size in bytes.
+ *
+ * For compatibility, this will accept if the underlying type is string or
+ * binary (since in MessagePack 1.0, strings and binary data were combined
+ * under the "raw" type which became string in 1.1.)
+ */
+size_t mpack_expect_bin_buf(mpack_reader_t* reader, char* buf, size_t size);
+
+/**
+ * Reads a binary blob with the given total maximum size, allocating storage for it.
+ */
+char* mpack_expect_bin_alloc(mpack_reader_t* reader, size_t maxsize, size_t* size);
+
+/**
+ * @}
+ */
+
+/**
+ * @name Extension Functions
+ * @{
+ */
+
+#if MPACK_EXTENSIONS
+/**
+ * Reads the start of an extension blob, returning its size in bytes and
+ * placing the type into @p type.
+ *
+ * The bytes follow and must be read separately with mpack_read_bytes()
+ * or mpack_read_bytes_inplace(). @ref mpack_done_ext() must be called
+ * once all bytes have been read.
+ *
+ * @p type will be a user-defined type in the range [0,127] or a reserved type
+ * in the range [-128,-2].
+ *
+ * mpack_error_type is raised if the value is not an extension blob. The @p
+ * type value is zero if an error occurs.
+ *
+ * @note This cannot be used to match a timestamp. @ref mpack_error_type will
+ * be flagged if the value is a timestamp. Use mpack_expect_timestamp() or
+ * mpack_expect_timestamp_truncate() instead.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @warning Be careful when using reserved types. They may no longer be ext
+ * types in the future, and previously valid data containing reserved types may
+ * become invalid in the future.
+ */
+uint32_t mpack_expect_ext(mpack_reader_t* reader, int8_t* type);
+
+/**
+ * Reads the start of an extension blob, raising an error if its length is not
+ * at most the given number of bytes and placing the type into @p type.
+ *
+ * The bytes follow and must be read separately with mpack_read_bytes()
+ * or mpack_read_bytes_inplace(). @ref mpack_done_ext() must be called
+ * once all bytes have been read.
+ *
+ * mpack_error_type is raised if the value is not an extension blob or if its
+ * length does not match. The @p type value is zero if an error is raised.
+ *
+ * @p type will be a user-defined type in the range [0,127] or a reserved type
+ * in the range [-128,-2].
+ *
+ * @note This cannot be used to match a timestamp. @ref mpack_error_type will
+ * be flagged if the value is a timestamp. Use mpack_expect_timestamp() or
+ * mpack_expect_timestamp_truncate() instead.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @warning Be careful when using reserved types. They may no longer be ext
+ * types in the future, and previously valid data containing reserved types may
+ * become invalid in the future.
+ *
+ * @see mpack_expect_ext()
+ */
+MPACK_INLINE uint32_t mpack_expect_ext_max(mpack_reader_t* reader, int8_t* type, uint32_t maxsize) {
+ uint32_t length = mpack_expect_ext(reader, type);
+ if (length > maxsize) {
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return 0;
+ }
+ return length;
+}
+
+/**
+ * Reads the start of an extension blob, raising an error if its length is not
+ * exactly the given number of bytes and placing the type into @p type.
+ *
+ * The bytes follow and must be read separately with mpack_read_bytes()
+ * or mpack_read_bytes_inplace(). @ref mpack_done_ext() must be called
+ * once all bytes have been read.
+ *
+ * mpack_error_type is raised if the value is not an extension blob or if its
+ * length does not match. The @p type value is zero if an error is raised.
+ *
+ * @p type will be a user-defined type in the range [0,127] or a reserved type
+ * in the range [-128,-2].
+ *
+ * @note This cannot be used to match a timestamp. @ref mpack_error_type will
+ * be flagged if the value is a timestamp. Use mpack_expect_timestamp() or
+ * mpack_expect_timestamp_truncate() instead.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @warning Be careful when using reserved types. They may no longer be ext
+ * types in the future, and previously valid data containing reserved types may
+ * become invalid in the future.
+ *
+ * @see mpack_expect_ext()
+ */
+MPACK_INLINE void mpack_expect_ext_size(mpack_reader_t* reader, int8_t* type, uint32_t count) {
+ if (mpack_expect_ext(reader, type) != count) {
+ *type = 0;
+ mpack_reader_flag_error(reader, mpack_error_type);
+ }
+}
+
+/**
+ * Reads an extension blob into the given buffer, returning its size in bytes
+ * and placing the type into @p type.
+ *
+ * mpack_error_type is raised if the value is not an extension blob or if its
+ * length does not match. The @p type value is zero if an error is raised.
+ *
+ * @p type will be a user-defined type in the range [0,127] or a reserved type
+ * in the range [-128,-2].
+ *
+ * @note This cannot be used to match a timestamp. @ref mpack_error_type will
+ * be flagged if the value is a timestamp. Use mpack_expect_timestamp() or
+ * mpack_expect_timestamp_truncate() instead.
+ *
+ * @warning Be careful when using reserved types. They may no longer be ext
+ * types in the future, and previously valid data containing reserved types may
+ * become invalid in the future.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @see mpack_expect_ext()
+ */
+size_t mpack_expect_ext_buf(mpack_reader_t* reader, int8_t* type, char* buf, size_t size);
+#endif
+
+#if MPACK_EXTENSIONS && defined(MPACK_MALLOC)
+/**
+ * Reads an extension blob with the given total maximum size, allocating
+ * storage for it, and placing the type into @p type.
+ *
+ * mpack_error_type is raised if the value is not an extension blob or if its
+ * length does not match. The @p type value is zero if an error is raised.
+ *
+ * @p type will be a user-defined type in the range [0,127] or a reserved type
+ * in the range [-128,-2].
+ *
+ * @note This cannot be used to match a timestamp. @ref mpack_error_type will
+ * be flagged if the value is a timestamp. Use mpack_expect_timestamp() or
+ * mpack_expect_timestamp_truncate() instead.
+ *
+ * @warning Be careful when using reserved types. They may no longer be ext
+ * types in the future, and previously valid data containing reserved types may
+ * become invalid in the future.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS and @ref MPACK_MALLOC.
+ *
+ * @see mpack_expect_ext()
+ */
+char* mpack_expect_ext_alloc(mpack_reader_t* reader, int8_t* type, size_t maxsize, size_t* size);
+#endif
+
+/**
+ * @}
+ */
+
+/**
+ * @name Special Functions
+ * @{
+ */
+
+/**
+ * Reads a MessagePack object header (an MPack tag), expecting it to exactly
+ * match the given tag.
+ *
+ * If the type is compound (i.e. is a map, array, string, binary or
+ * extension type), additional reads are required to get the contained
+ * data, and the corresponding done function must be called when done.
+ *
+ * @throws mpack_error_type if the tag does not match
+ *
+ * @see mpack_read_bytes()
+ * @see mpack_done_array()
+ * @see mpack_done_map()
+ * @see mpack_done_str()
+ * @see mpack_done_bin()
+ * @see mpack_done_ext()
+ */
+void mpack_expect_tag(mpack_reader_t* reader, mpack_tag_t tag);
+
+/**
+ * Expects a string matching one of the strings in the given array,
+ * returning its array index.
+ *
+ * If the value does not match any of the given strings,
+ * @ref mpack_error_type is flagged. Use mpack_expect_enum_optional()
+ * if you want to allow other values than the given strings.
+ *
+ * If any error occurs or the reader is in an error state, @a count
+ * is returned.
+ *
+ * This can be used to quickly parse a string into an enum when the
+ * enum values range from 0 to @a count-1. If the last value in the
+ * enum is a special "count" value, it can be passed as the count,
+ * and the return value can be cast directly to the enum type.
+ *
+ * @code{.c}
+ * typedef enum { APPLE , BANANA , ORANGE , COUNT} fruit_t;
+ * const char* fruits[] = {"apple", "banana", "orange"};
+ *
+ * fruit_t fruit = (fruit_t)mpack_expect_enum(reader, fruits, COUNT);
+ * @endcode
+ *
+ * See @ref docs/expect.md for more examples.
+ *
+ * The maximum string length is the size of the buffer (strings are read in-place.)
+ *
+ * @param reader The reader
+ * @param strings An array of expected strings of length count
+ * @param count The number of strings
+ * @return The index of the matched string, or @a count in case of error
+ */
+size_t mpack_expect_enum(mpack_reader_t* reader, const char* strings[], size_t count);
+
+/**
+ * Expects a string matching one of the strings in the given array
+ * returning its array index, or @a count if no strings match.
+ *
+ * If the value is not a string, or it does not match any of the
+ * given strings, @a count is returned and no error is flagged.
+ *
+ * If any error occurs or the reader is in an error state, @a count
+ * is returned.
+ *
+ * This can be used to quickly parse a string into an enum when the
+ * enum values range from 0 to @a count-1. If the last value in the
+ * enum is a special "count" value, it can be passed as the count,
+ * and the return value can be cast directly to the enum type.
+ *
+ * @code{.c}
+ * typedef enum { APPLE , BANANA , ORANGE , COUNT} fruit_t;
+ * const char* fruits[] = {"apple", "banana", "orange"};
+ *
+ * fruit_t fruit = (fruit_t)mpack_expect_enum_optional(reader, fruits, COUNT);
+ * @endcode
+ *
+ * See @ref docs/expect.md for more examples.
+ *
+ * The maximum string length is the size of the buffer (strings are read in-place.)
+ *
+ * @param reader The reader
+ * @param strings An array of expected strings of length count
+ * @param count The number of strings
+ *
+ * @return The index of the matched string, or @a count if it does not
+ * match or an error occurs
+ */
+size_t mpack_expect_enum_optional(mpack_reader_t* reader, const char* strings[], size_t count);
+
+/**
+ * Expects an unsigned integer map key between 0 and count-1, marking it
+ * as found in the given bool array and returning it.
+ *
+ * This is a helper for switching among int keys in a map. It is
+ * typically used with an enum to define the key values. It should
+ * be called in the expression of a switch() statement. See @ref
+ * docs/expect.md for an example.
+ *
+ * The found array must be cleared before expecting the first key. If the
+ * flag for a given key is already set when found (i.e. the map contains a
+ * duplicate key), mpack_error_invalid is flagged.
+ *
+ * If the key is not a non-negative integer, or if the key is @a count or
+ * larger, @a count is returned and no error is flagged. If you want an error
+ * on unrecognized keys, flag an error in the default case in your switch;
+ * otherwise you must call mpack_discard() to discard its content.
+ *
+ * @param reader The reader
+ * @param found An array of bool flags of length count
+ * @param count The number of values in the found array, and one more than the
+ * maximum allowed key
+ *
+ * @see @ref docs/expect.md
+ */
+size_t mpack_expect_key_uint(mpack_reader_t* reader, bool found[], size_t count);
+
+/**
+ * Expects a string map key matching one of the strings in the given key list,
+ * marking it as found in the given bool array and returning its index.
+ *
+ * This is a helper for switching among string keys in a map. It is
+ * typically used with an enum with names matching the strings in the
+ * array to define the key indices. It should be called in the expression
+ * of a switch() statement. See @ref docs/expect.md for an example.
+ *
+ * The found array must be cleared before expecting the first key. If the
+ * flag for a given key is already set when found (i.e. the map contains a
+ * duplicate key), mpack_error_invalid is flagged.
+ *
+ * If the key is unrecognized, count is returned and no error is flagged. If
+ * you want an error on unrecognized keys, flag an error in the default case
+ * in your switch; otherwise you must call mpack_discard() to discard its content.
+ *
+ * The maximum key length is the size of the buffer (keys are read in-place.)
+ *
+ * @param reader The reader
+ * @param keys An array of expected string keys of length count
+ * @param found An array of bool flags of length count
+ * @param count The number of values in the keys and found arrays
+ *
+ * @see @ref docs/expect.md
+ */
+size_t mpack_expect_key_cstr(mpack_reader_t* reader, const char* keys[],
+ bool found[], size_t count);
+
+/**
+ * @}
+ */
+
+/**
+ * @}
+ */
+
+#endif
+
+MPACK_HEADER_END
+
+#endif
+
+
+
+/* mpack/mpack-node.h.h */
+
+/**
+ * @file
+ *
+ * Declares the MPack dynamic Node API.
+ */
+
+#ifndef MPACK_NODE_H
+#define MPACK_NODE_H 1
+
+/* #include "mpack-reader.h" */
+
+MPACK_HEADER_START
+
+#if MPACK_NODE
+
+/**
+ * @defgroup node Node API
+ *
+ * The MPack Node API allows you to parse a chunk of MessagePack into a
+ * dynamically typed data structure, providing random access to the parsed
+ * data.
+ *
+ * See @ref docs/node.md for examples.
+ *
+ * @{
+ */
+
+/**
+ * A handle to node data in a parsed MPack tree.
+ *
+ * Nodes represent either primitive values or compound types. If a
+ * node is a compound type, it contains a pointer to its child nodes,
+ * or a pointer to its underlying data.
+ *
+ * Nodes are immutable.
+ *
+ * @note @ref mpack_node_t is an opaque reference to the node data, not the
+ * node data itself. (It contains pointers to both the node data and the tree.)
+ * It is passed by value in the Node API.
+ */
+typedef struct mpack_node_t mpack_node_t;
+
+/**
+ * The storage for nodes in an MPack tree.
+ *
+ * You only need to use this if you intend to provide your own storage
+ * for nodes instead of letting the tree allocate it.
+ *
+ * @ref mpack_node_data_t is 16 bytes on most common architectures (32-bit
+ * and 64-bit.)
+ */
+typedef struct mpack_node_data_t mpack_node_data_t;
+
+/**
+ * An MPack tree parser to parse a blob or stream of MessagePack.
+ *
+ * When a message is parsed, the tree contains a single root node which
+ * contains all parsed data. The tree and its nodes are immutable.
+ */
+typedef struct mpack_tree_t mpack_tree_t;
+
+/**
+ * An error handler function to be called when an error is flagged on
+ * the tree.
+ *
+ * The error handler will only be called once on the first error flagged;
+ * any subsequent node reads and errors are ignored, and the tree is
+ * permanently in that error state.
+ *
+ * MPack is safe against non-local jumps out of error handler callbacks.
+ * This means you are allowed to longjmp or throw an exception (in C++,
+ * Objective-C, or with SEH) out of this callback.
+ *
+ * Bear in mind when using longjmp that local non-volatile variables that
+ * have changed are undefined when setjmp() returns, so you can't put the
+ * tree on the stack in the same activation frame as the setjmp without
+ * declaring it volatile.
+ *
+ * You must still eventually destroy the tree. It is not destroyed
+ * automatically when an error is flagged. It is safe to destroy the
+ * tree within this error callback, but you will either need to perform
+ * a non-local jump, or store something in your context to identify
+ * that the tree is destroyed since any future accesses to it cause
+ * undefined behavior.
+ */
+typedef void (*mpack_tree_error_t)(mpack_tree_t* tree, mpack_error_t error);
+
+/**
+ * The MPack tree's read function. It should fill the buffer with as many bytes
+ * as are immediately available up to the given @c count, returning the number
+ * of bytes written to the buffer.
+ *
+ * In case of error, it should flag an appropriate error on the reader
+ * (usually @ref mpack_error_io.)
+ *
+ * The blocking or non-blocking behaviour of the read should match whether you
+ * are using mpack_tree_parse() or mpack_tree_try_parse().
+ *
+ * If you are using mpack_tree_parse(), the read should block until at least
+ * one byte is read. If you return 0, mpack_tree_parse() will raise @ref
+ * mpack_error_io.
+ *
+ * If you are using mpack_tree_try_parse(), the read function can always
+ * return 0, and must never block waiting for data (otherwise
+ * mpack_tree_try_parse() would be equivalent to mpack_tree_parse().)
+ * When you return 0, mpack_tree_try_parse() will return false without flagging
+ * an error.
+ */
+typedef size_t (*mpack_tree_read_t)(mpack_tree_t* tree, char* buffer, size_t count);
+
+/**
+ * A teardown function to be called when the tree is destroyed.
+ */
+typedef void (*mpack_tree_teardown_t)(mpack_tree_t* tree);
+
+
+
+/* Hide internals from documentation */
+/** @cond */
+
+struct mpack_node_t {
+ mpack_node_data_t* data;
+ mpack_tree_t* tree;
+};
+
+struct mpack_node_data_t {
+ mpack_type_t type;
+
+ /*
+ * The element count if the type is an array;
+ * the number of key/value pairs if the type is map;
+ * or the number of bytes if the type is str, bin or ext.
+ */
+ uint32_t len;
+
+ union
+ {
+ bool b; /* The value if the type is bool. */
+ float f; /* The value if the type is float. */
+ double d; /* The value if the type is double. */
+ int64_t i; /* The value if the type is signed int. */
+ uint64_t u; /* The value if the type is unsigned int. */
+ size_t offset; /* The byte offset for str, bin and ext */
+ mpack_node_data_t* children; /* The children for map or array */
+ } value;
+};
+
+typedef struct mpack_tree_page_t {
+ struct mpack_tree_page_t* next;
+ mpack_node_data_t nodes[1]; // variable size
+} mpack_tree_page_t;
+
+typedef enum mpack_tree_parse_state_t {
+ mpack_tree_parse_state_not_started,
+ mpack_tree_parse_state_in_progress,
+ mpack_tree_parse_state_parsed,
+} mpack_tree_parse_state_t;
+
+typedef struct mpack_level_t {
+ mpack_node_data_t* child;
+ size_t left; // children left in level
+} mpack_level_t;
+
+typedef struct mpack_tree_parser_t {
+ mpack_tree_parse_state_t state;
+
+ // We keep track of the number of "possible nodes" left in the data rather
+ // than the number of bytes.
+ //
+ // When a map or array is parsed, we ensure at least one byte for each child
+ // exists and subtract them right away. This ensures that if ever a map or
+ // array declares more elements than could possibly be contained in the data,
+ // we will error out immediately rather than allocating storage for them.
+ //
+ // For example malicious data that repeats 0xDE 0xFF 0xFF (start of a map
+ // with 65536 key-value pairs) would otherwise cause us to run out of
+ // memory. With this, the parser can allocate at most as many nodes as
+ // there are bytes in the data (plus the paging overhead, 12%.) An error
+ // will be flagged immediately if and when there isn't enough data left to
+ // fully read all children of all open compound types on the parsing stack.
+ //
+ // Once an entire message has been parsed (and there are no nodes left to
+ // parse whose bytes have been subtracted), this matches the number of left
+ // over bytes in the data.
+ size_t possible_nodes_left;
+
+ mpack_node_data_t* nodes; // next node in current page/pool
+ size_t nodes_left; // nodes left in current page/pool
+
+ size_t current_node_reserved;
+ size_t level;
+
+ #ifdef MPACK_MALLOC
+ // It's much faster to allocate the initial parsing stack inline within the
+ // parser. We replace it with a heap allocation if we need to grow it.
+ mpack_level_t* stack;
+ size_t stack_capacity;
+ bool stack_owned;
+ mpack_level_t stack_local[MPACK_NODE_INITIAL_DEPTH];
+ #else
+ // Without malloc(), we have to reserve a parsing stack the maximum allowed
+ // parsing depth.
+ mpack_level_t stack[MPACK_NODE_MAX_DEPTH_WITHOUT_MALLOC];
+ #endif
+} mpack_tree_parser_t;
+
+struct mpack_tree_t {
+ mpack_tree_error_t error_fn; /* Function to call on error */
+ mpack_tree_read_t read_fn; /* Function to call to read more data */
+ mpack_tree_teardown_t teardown; /* Function to teardown the context on destroy */
+ void* context; /* Context for tree callbacks */
+
+ mpack_node_data_t nil_node; /* a nil node to be returned in case of error */
+ mpack_node_data_t missing_node; /* a missing node to be returned in optional lookups */
+ mpack_error_t error;
+
+ #ifdef MPACK_MALLOC
+ char* buffer;
+ size_t buffer_capacity;
+ #endif
+
+ const char* data;
+ size_t data_length; // length of data (and content of buffer, if used)
+
+ size_t size; // size in bytes of tree (usually matches data_length, but not if tree has trailing data)
+ size_t node_count; // total number of nodes in tree (across all pages)
+
+ size_t max_size; // maximum message size
+ size_t max_nodes; // maximum nodes in a message
+
+ mpack_tree_parser_t parser;
+ mpack_node_data_t* root;
+
+ mpack_node_data_t* pool; // pool, or NULL if no pool provided
+ size_t pool_count;
+
+ #ifdef MPACK_MALLOC
+ mpack_tree_page_t* next;
+ #endif
+};
+
+// internal functions
+
+MPACK_INLINE mpack_node_t mpack_node(mpack_tree_t* tree, mpack_node_data_t* data) {
+ mpack_node_t node;
+ node.data = data;
+ node.tree = tree;
+ return node;
+}
+
+MPACK_INLINE mpack_node_data_t* mpack_node_child(mpack_node_t node, size_t child) {
+ return node.data->value.children + child;
+}
+
+MPACK_INLINE mpack_node_t mpack_tree_nil_node(mpack_tree_t* tree) {
+ return mpack_node(tree, &tree->nil_node);
+}
+
+MPACK_INLINE mpack_node_t mpack_tree_missing_node(mpack_tree_t* tree) {
+ return mpack_node(tree, &tree->missing_node);
+}
+
+/** @endcond */
+
+
+
+/**
+ * @name Tree Initialization
+ * @{
+ */
+
+#ifdef MPACK_MALLOC
+/**
+ * Initializes a tree parser with the given data.
+ *
+ * Configure the tree if desired, then call mpack_tree_parse() to parse it. The
+ * tree will allocate pages of nodes as needed and will free them when
+ * destroyed.
+ *
+ * The tree must be destroyed with mpack_tree_destroy().
+ *
+ * Any string or blob data types reference the original data, so the given data
+ * pointer must remain valid until after the tree is destroyed.
+ */
+void mpack_tree_init_data(mpack_tree_t* tree, const char* data, size_t length);
+
+/**
+ * Deprecated.
+ *
+ * \deprecated Renamed to mpack_tree_init_data().
+ */
+MPACK_INLINE void mpack_tree_init(mpack_tree_t* tree, const char* data, size_t length) {
+ mpack_tree_init_data(tree, data, length);
+}
+
+/**
+ * Initializes a tree parser from an unbounded stream, or a stream of
+ * unknown length.
+ *
+ * The parser can be used to read a single message from a stream of unknown
+ * length, or multiple messages from an unbounded stream, allowing it to
+ * be used for RPC communication. Call @ref mpack_tree_parse() to parse
+ * a message from a blocking stream, or @ref mpack_tree_try_parse() for a
+ * non-blocking stream.
+ *
+ * The stream will use a growable internal buffer to store the most recent
+ * message, as well as allocated pages of nodes for the parse tree.
+ *
+ * Maximum allowances for message size and node count must be specified in this
+ * function (since the stream is unbounded.) They can be changed later with
+ * @ref mpack_tree_set_limits().
+ *
+ * @param tree The tree parser
+ * @param read_fn The read function
+ * @param context The context for the read function
+ * @param max_message_size The maximum size of a message in bytes
+ * @param max_message_nodes The maximum number of nodes per message. See
+ * @ref mpack_node_data_t for the size of nodes.
+ *
+ * @see mpack_tree_read_t
+ * @see mpack_reader_context()
+ */
+void mpack_tree_init_stream(mpack_tree_t* tree, mpack_tree_read_t read_fn, void* context,
+ size_t max_message_size, size_t max_message_nodes);
+#endif
+
+/**
+ * Initializes a tree parser with the given data, using the given node data
+ * pool to store the results.
+ *
+ * Configure the tree if desired, then call mpack_tree_parse() to parse it.
+ *
+ * If the data does not fit in the pool, @ref mpack_error_too_big will be flagged
+ * on the tree.
+ *
+ * The tree must be destroyed with mpack_tree_destroy(), even if parsing fails.
+ */
+void mpack_tree_init_pool(mpack_tree_t* tree, const char* data, size_t length,
+ mpack_node_data_t* node_pool, size_t node_pool_count);
+
+/**
+ * Initializes an MPack tree directly into an error state. Use this if you
+ * are writing a wrapper to another <tt>mpack_tree_init*()</tt> function which
+ * can fail its setup.
+ */
+void mpack_tree_init_error(mpack_tree_t* tree, mpack_error_t error);
+
+#if MPACK_STDIO
+/**
+ * Initializes a tree to parse the given file. The tree must be destroyed with
+ * mpack_tree_destroy(), even if parsing fails.
+ *
+ * The file is opened, loaded fully into memory, and closed before this call
+ * returns.
+ *
+ * @param tree The tree to initialize
+ * @param filename The filename passed to fopen() to read the file
+ * @param max_bytes The maximum size of file to load, or 0 for unlimited size.
+ */
+void mpack_tree_init_filename(mpack_tree_t* tree, const char* filename, size_t max_bytes);
+
+/**
+ * Deprecated.
+ *
+ * \deprecated Renamed to mpack_tree_init_filename().
+ */
+MPACK_INLINE void mpack_tree_init_file(mpack_tree_t* tree, const char* filename, size_t max_bytes) {
+ mpack_tree_init_filename(tree, filename, max_bytes);
+}
+
+/**
+ * Initializes a tree to parse the given libc FILE. This can be used to
+ * read from stdin, or from a file opened separately.
+ *
+ * The tree must be destroyed with mpack_tree_destroy(), even if parsing fails.
+ *
+ * The FILE is fully loaded fully into memory (and closed if requested) before
+ * this call returns.
+ *
+ * @param tree The tree to initialize.
+ * @param stdfile The FILE.
+ * @param max_bytes The maximum size of file to load, or 0 for unlimited size.
+ * @param close_when_done If true, fclose() will be called on the FILE when it
+ * is no longer needed. If false, the file will not be closed when
+ * reading is done.
+ *
+ * @warning The tree will read all data in the FILE before parsing it. If this
+ * is used on stdin, the parser will block until it is closed, even if
+ * a complete message has been written to it!
+ */
+void mpack_tree_init_stdfile(mpack_tree_t* tree, FILE* stdfile, size_t max_bytes, bool close_when_done);
+#endif
+
+/**
+ * @}
+ */
+
+/**
+ * @name Tree Functions
+ * @{
+ */
+
+/**
+ * Sets the maximum byte size and maximum number of nodes allowed per message.
+ *
+ * The default is SIZE_MAX (no limit) unless @ref mpack_tree_init_stream() is
+ * called (where maximums are required.)
+ *
+ * If a pool of nodes is used, the node limit is the lesser of this limit and
+ * the pool size.
+ *
+ * @param tree The tree parser
+ * @param max_message_size The maximum size of a message in bytes
+ * @param max_message_nodes The maximum number of nodes per message. See
+ * @ref mpack_node_data_t for the size of nodes.
+ */
+void mpack_tree_set_limits(mpack_tree_t* tree, size_t max_message_size,
+ size_t max_message_nodes);
+
+/**
+ * Parses a MessagePack message into a tree of immutable nodes.
+ *
+ * If successful, the root node will be available under @ref mpack_tree_root().
+ * If not, an appropriate error will be flagged.
+ *
+ * This can be called repeatedly to parse a series of messages from a data
+ * source. When this is called, all previous nodes from this tree and their
+ * contents (including the root node) are invalidated.
+ *
+ * If this is called with a stream (see @ref mpack_tree_init_stream()), the
+ * stream must block until data is available. (Otherwise, if this is called on
+ * a non-blocking stream, parsing will fail with @ref mpack_error_io when the
+ * fill function returns 0.)
+ *
+ * There is no way to recover a tree in an error state. It must be destroyed.
+ */
+void mpack_tree_parse(mpack_tree_t* tree);
+
+/**
+ * Attempts to parse a MessagePack message from a non-blocking stream into a
+ * tree of immutable nodes.
+ *
+ * A non-blocking read function must have been passed to the tree in
+ * mpack_tree_init_stream().
+ *
+ * If this returns true, a message is available under
+ * @ref mpack_tree_root(). The tree nodes and data will be valid until
+ * the next time a parse is started.
+ *
+ * If this returns false, no message is available, because either not enough
+ * data is available yet or an error has occurred. You must check the tree for
+ * errors whenever this returns false. If there is no error, you should try
+ * again later when more data is available. (You will want to select()/poll()
+ * on the underlying socket or use some other asynchronous mechanism to
+ * determine when it has data.)
+ *
+ * There is no way to recover a tree in an error state. It must be destroyed.
+ *
+ * @see mpack_tree_init_stream()
+ */
+bool mpack_tree_try_parse(mpack_tree_t* tree);
+
+/**
+ * Returns the root node of the tree, if the tree is not in an error state.
+ * Returns a nil node otherwise.
+ *
+ * @warning You must call mpack_tree_parse() before calling this. If
+ * @ref mpack_tree_parse() was never called, the tree will assert.
+ */
+mpack_node_t mpack_tree_root(mpack_tree_t* tree);
+
+/**
+ * Returns the error state of the tree.
+ */
+MPACK_INLINE mpack_error_t mpack_tree_error(mpack_tree_t* tree) {
+ return tree->error;
+}
+
+/**
+ * Returns the size in bytes of the current parsed message.
+ *
+ * If there is something in the buffer after the MessagePack object, this can
+ * be used to find it.
+ *
+ * This is zero if an error occurred during tree parsing (since the
+ * portion of the data that the first complete object occupies cannot
+ * be determined if the data is invalid or corrupted.)
+ */
+MPACK_INLINE size_t mpack_tree_size(mpack_tree_t* tree) {
+ return tree->size;
+}
+
+/**
+ * Destroys the tree.
+ */
+mpack_error_t mpack_tree_destroy(mpack_tree_t* tree);
+
+/**
+ * Sets the custom pointer to pass to the tree callbacks, such as teardown.
+ *
+ * @param tree The MPack tree.
+ * @param context User data to pass to the tree callbacks.
+ *
+ * @see mpack_reader_context()
+ */
+MPACK_INLINE void mpack_tree_set_context(mpack_tree_t* tree, void* context) {
+ tree->context = context;
+}
+
+/**
+ * Returns the custom context for tree callbacks.
+ *
+ * @see mpack_tree_set_context
+ * @see mpack_tree_init_stream
+ */
+MPACK_INLINE void* mpack_tree_context(mpack_tree_t* tree) {
+ return tree->context;
+}
+
+/**
+ * Sets the error function to call when an error is flagged on the tree.
+ *
+ * This should normally be used with mpack_tree_set_context() to register
+ * a custom pointer to pass to the error function.
+ *
+ * See the definition of mpack_tree_error_t for more information about
+ * what you can do from an error callback.
+ *
+ * @see mpack_tree_error_t
+ * @param tree The MPack tree.
+ * @param error_fn The function to call when an error is flagged on the tree.
+ */
+MPACK_INLINE void mpack_tree_set_error_handler(mpack_tree_t* tree, mpack_tree_error_t error_fn) {
+ tree->error_fn = error_fn;
+}
+
+/**
+ * Sets the teardown function to call when the tree is destroyed.
+ *
+ * This should normally be used with mpack_tree_set_context() to register
+ * a custom pointer to pass to the teardown function.
+ *
+ * @param tree The MPack tree.
+ * @param teardown The function to call when the tree is destroyed.
+ */
+MPACK_INLINE void mpack_tree_set_teardown(mpack_tree_t* tree, mpack_tree_teardown_t teardown) {
+ tree->teardown = teardown;
+}
+
+/**
+ * Places the tree in the given error state, calling the error callback if one
+ * is set.
+ *
+ * This allows you to externally flag errors, for example if you are validating
+ * data as you read it.
+ *
+ * If the tree is already in an error state, this call is ignored and no
+ * error callback is called.
+ */
+void mpack_tree_flag_error(mpack_tree_t* tree, mpack_error_t error);
+
+/**
+ * @}
+ */
+
+/**
+ * @name Node Core Functions
+ * @{
+ */
+
+/**
+ * Places the node's tree in the given error state, calling the error callback
+ * if one is set.
+ *
+ * This allows you to externally flag errors, for example if you are validating
+ * data as you read it.
+ *
+ * If the tree is already in an error state, this call is ignored and no
+ * error callback is called.
+ */
+void mpack_node_flag_error(mpack_node_t node, mpack_error_t error);
+
+/**
+ * Returns the error state of the node's tree.
+ */
+MPACK_INLINE mpack_error_t mpack_node_error(mpack_node_t node) {
+ return mpack_tree_error(node.tree);
+}
+
+/**
+ * Returns a tag describing the given node, or a nil tag if the
+ * tree is in an error state.
+ */
+mpack_tag_t mpack_node_tag(mpack_node_t node);
+
+/** @cond */
+
+#if MPACK_DEBUG && MPACK_STDIO
+/*
+ * Converts a node to a pseudo-JSON string for debugging purposes, placing the
+ * result in the given buffer with a null-terminator.
+ *
+ * If the buffer does not have enough space, the result will be truncated (but
+ * it is guaranteed to be null-terminated.)
+ *
+ * This is only available in debug mode, and only if stdio is available (since
+ * it uses snprintf().) It's strictly for debugging purposes.
+ */
+void mpack_node_print_to_buffer(mpack_node_t node, char* buffer, size_t buffer_size);
+
+/*
+ * Converts a node to pseudo-JSON for debugging purposes, calling the given
+ * callback as many times as is necessary to output the character data.
+ *
+ * No null-terminator or trailing newline will be written.
+ *
+ * This is only available in debug mode, and only if stdio is available (since
+ * it uses snprintf().) It's strictly for debugging purposes.
+ */
+void mpack_node_print_to_callback(mpack_node_t node, mpack_print_callback_t callback, void* context);
+
+/*
+ * Converts a node to pseudo-JSON for debugging purposes
+ * and pretty-prints it to the given file.
+ *
+ * This is only available in debug mode, and only if stdio is available (since
+ * it uses snprintf().) It's strictly for debugging purposes.
+ */
+void mpack_node_print_to_file(mpack_node_t node, FILE* file);
+
+/*
+ * Converts a node to pseudo-JSON for debugging purposes
+ * and pretty-prints it to stdout.
+ *
+ * This is only available in debug mode, and only if stdio is available (since
+ * it uses snprintf().) It's strictly for debugging purposes.
+ */
+MPACK_INLINE void mpack_node_print_to_stdout(mpack_node_t node) {
+ mpack_node_print_to_file(node, stdout);
+}
+
+/*
+ * Deprecated.
+ *
+ * \deprecated Renamed to mpack_node_print_to_stdout().
+ */
+MPACK_INLINE void mpack_node_print(mpack_node_t node) {
+ mpack_node_print_to_stdout(node);
+}
+#endif
+
+/** @endcond */
+
+/**
+ * @}
+ */
+
+/**
+ * @name Node Primitive Value Functions
+ * @{
+ */
+
+/**
+ * Returns the type of the node.
+ */
+mpack_type_t mpack_node_type(mpack_node_t node);
+
+/**
+ * Returns true if the given node is a nil node; false otherwise.
+ *
+ * To ensure that a node is nil and flag an error otherwise, use
+ * mpack_node_nil().
+ */
+bool mpack_node_is_nil(mpack_node_t node);
+
+/**
+ * Returns true if the given node handle indicates a missing node; false otherwise.
+ *
+ * To ensure that a node is missing and flag an error otherwise, use
+ * mpack_node_missing().
+ */
+bool mpack_node_is_missing(mpack_node_t node);
+
+/**
+ * Checks that the given node is of nil type, raising @ref mpack_error_type
+ * otherwise.
+ *
+ * Use mpack_node_is_nil() to return whether the node is nil.
+ */
+void mpack_node_nil(mpack_node_t node);
+
+/**
+ * Checks that the given node indicates a missing node, raising @ref
+ * mpack_error_type otherwise.
+ *
+ * Use mpack_node_is_missing() to return whether the node is missing.
+ */
+void mpack_node_missing(mpack_node_t node);
+
+/**
+ * Returns the bool value of the node. If this node is not of the correct
+ * type, false is returned and mpack_error_type is raised.
+ */
+bool mpack_node_bool(mpack_node_t node);
+
+/**
+ * Checks if the given node is of bool type with value true, raising
+ * mpack_error_type otherwise.
+ */
+void mpack_node_true(mpack_node_t node);
+
+/**
+ * Checks if the given node is of bool type with value false, raising
+ * mpack_error_type otherwise.
+ */
+void mpack_node_false(mpack_node_t node);
+
+/**
+ * Returns the 8-bit unsigned value of the node. If this node is not
+ * of a compatible type, @ref mpack_error_type is raised and zero is returned.
+ */
+uint8_t mpack_node_u8(mpack_node_t node);
+
+/**
+ * Returns the 8-bit signed value of the node. If this node is not
+ * of a compatible type, @ref mpack_error_type is raised and zero is returned.
+ */
+int8_t mpack_node_i8(mpack_node_t node);
+
+/**
+ * Returns the 16-bit unsigned value of the node. If this node is not
+ * of a compatible type, @ref mpack_error_type is raised and zero is returned.
+ */
+uint16_t mpack_node_u16(mpack_node_t node);
+
+/**
+ * Returns the 16-bit signed value of the node. If this node is not
+ * of a compatible type, @ref mpack_error_type is raised and zero is returned.
+ */
+int16_t mpack_node_i16(mpack_node_t node);
+
+/**
+ * Returns the 32-bit unsigned value of the node. If this node is not
+ * of a compatible type, @ref mpack_error_type is raised and zero is returned.
+ */
+uint32_t mpack_node_u32(mpack_node_t node);
+
+/**
+ * Returns the 32-bit signed value of the node. If this node is not
+ * of a compatible type, @ref mpack_error_type is raised and zero is returned.
+ */
+int32_t mpack_node_i32(mpack_node_t node);
+
+/**
+ * Returns the 64-bit unsigned value of the node. If this node is not
+ * of a compatible type, @ref mpack_error_type is raised, and zero is returned.
+ */
+uint64_t mpack_node_u64(mpack_node_t node);
+
+/**
+ * Returns the 64-bit signed value of the node. If this node is not
+ * of a compatible type, @ref mpack_error_type is raised and zero is returned.
+ */
+int64_t mpack_node_i64(mpack_node_t node);
+
+/**
+ * Returns the unsigned int value of the node.
+ *
+ * Returns zero if an error occurs.
+ *
+ * @throws mpack_error_type If the node is not an integer type or does not fit in the range of an unsigned int
+ */
+unsigned int mpack_node_uint(mpack_node_t node);
+
+/**
+ * Returns the int value of the node.
+ *
+ * Returns zero if an error occurs.
+ *
+ * @throws mpack_error_type If the node is not an integer type or does not fit in the range of an int
+ */
+int mpack_node_int(mpack_node_t node);
+
+/**
+ * Returns the float value of the node. The underlying value can be an
+ * integer, float or double; the value is converted to a float.
+ *
+ * @note Reading a double or a large integer with this function can incur a
+ * loss of precision.
+ *
+ * @throws mpack_error_type if the underlying value is not a float, double or integer.
+ */
+float mpack_node_float(mpack_node_t node);
+
+/**
+ * Returns the double value of the node. The underlying value can be an
+ * integer, float or double; the value is converted to a double.
+ *
+ * @note Reading a very large integer with this function can incur a
+ * loss of precision.
+ *
+ * @throws mpack_error_type if the underlying value is not a float, double or integer.
+ */
+double mpack_node_double(mpack_node_t node);
+
+/**
+ * Returns the float value of the node. The underlying value must be a float,
+ * not a double or an integer. This ensures no loss of precision can occur.
+ *
+ * @throws mpack_error_type if the underlying value is not a float.
+ */
+float mpack_node_float_strict(mpack_node_t node);
+
+/**
+ * Returns the double value of the node. The underlying value must be a float
+ * or double, not an integer. This ensures no loss of precision can occur.
+ *
+ * @throws mpack_error_type if the underlying value is not a float or double.
+ */
+double mpack_node_double_strict(mpack_node_t node);
+
+#if MPACK_EXTENSIONS
+/**
+ * Returns a timestamp.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @throws mpack_error_type if the underlying value is not a timestamp.
+ */
+mpack_timestamp_t mpack_node_timestamp(mpack_node_t node);
+
+/**
+ * Returns a timestamp's (signed) seconds since 1970-01-01T00:00:00Z.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @throws mpack_error_type if the underlying value is not a timestamp.
+ */
+int64_t mpack_node_timestamp_seconds(mpack_node_t node);
+
+/**
+ * Returns a timestamp's additional nanoseconds.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ *
+ * @return A nanosecond count between 0 and 999,999,999 inclusive.
+ * @throws mpack_error_type if the underlying value is not a timestamp.
+ */
+uint32_t mpack_node_timestamp_nanoseconds(mpack_node_t node);
+#endif
+
+/**
+ * @}
+ */
+
+/**
+ * @name Node String and Data Functions
+ * @{
+ */
+
+/**
+ * Checks that the given node contains a valid UTF-8 string.
+ *
+ * If the string is invalid, this flags an error, which would cause subsequent calls
+ * to mpack_node_str() to return NULL and mpack_node_strlen() to return zero. So you
+ * can check the node for error immediately after calling this, or you can call those
+ * functions to use the data anyway and check for errors later.
+ *
+ * @throws mpack_error_type If this node is not a string or does not contain valid UTF-8.
+ *
+ * @param node The string node to test
+ *
+ * @see mpack_node_str()
+ * @see mpack_node_strlen()
+ */
+void mpack_node_check_utf8(mpack_node_t node);
+
+/**
+ * Checks that the given node contains a valid UTF-8 string with no NUL bytes.
+ *
+ * This does not check that the string has a null-terminator! It only checks whether
+ * the string could safely be represented as a C-string by appending a null-terminator.
+ * (If the string does already contain a null-terminator, this will flag an error.)
+ *
+ * This is performed automatically by other UTF-8 cstr helper functions. Only
+ * call this if you will do something else with the data directly, but you still
+ * want to ensure it will be valid as a UTF-8 C-string.
+ *
+ * @throws mpack_error_type If this node is not a string, does not contain valid UTF-8,
+ * or contains a NUL byte.
+ *
+ * @param node The string node to test
+ *
+ * @see mpack_node_str()
+ * @see mpack_node_strlen()
+ * @see mpack_node_copy_utf8_cstr()
+ * @see mpack_node_utf8_cstr_alloc()
+ */
+void mpack_node_check_utf8_cstr(mpack_node_t node);
+
+#if MPACK_EXTENSIONS
+/**
+ * Returns the extension type of the given ext node.
+ *
+ * This returns zero if the tree is in an error state.
+ *
+ * @note This requires @ref MPACK_EXTENSIONS.
+ */
+int8_t mpack_node_exttype(mpack_node_t node);
+#endif
+
+/**
+ * Returns the number of bytes in the given bin node.
+ *
+ * This returns zero if the tree is in an error state.
+ *
+ * If this node is not a bin, @ref mpack_error_type is raised and zero is returned.
+ */
+size_t mpack_node_bin_size(mpack_node_t node);
+
+/**
+ * Returns the length of the given str, bin or ext node.
+ *
+ * This returns zero if the tree is in an error state.
+ *
+ * If this node is not a str, bin or map, @ref mpack_error_type is raised and zero
+ * is returned.
+ */
+uint32_t mpack_node_data_len(mpack_node_t node);
+
+/**
+ * Returns the length in bytes of the given string node. This does not
+ * include any null-terminator.
+ *
+ * This returns zero if the tree is in an error state.
+ *
+ * If this node is not a str, @ref mpack_error_type is raised and zero is returned.
+ */
+size_t mpack_node_strlen(mpack_node_t node);
+
+/**
+ * Returns a pointer to the data contained by this node, ensuring the node is a
+ * string.
+ *
+ * @warning Strings are not null-terminated! Use one of the cstr functions
+ * to get a null-terminated string.
+ *
+ * The pointer is valid as long as the data backing the tree is valid.
+ *
+ * If this node is not a string, @ref mpack_error_type is raised and @c NULL is returned.
+ *
+ * @see mpack_node_copy_cstr()
+ * @see mpack_node_cstr_alloc()
+ * @see mpack_node_utf8_cstr_alloc()
+ */
+const char* mpack_node_str(mpack_node_t node);
+
+/**
+ * Returns a pointer to the data contained by this node.
+ *
+ * @note Strings are not null-terminated! Use one of the cstr functions
+ * to get a null-terminated string.
+ *
+ * The pointer is valid as long as the data backing the tree is valid.
+ *
+ * If this node is not of a str, bin or map, @ref mpack_error_type is raised, and
+ * @c NULL is returned.
+ *
+ * @see mpack_node_copy_cstr()
+ * @see mpack_node_cstr_alloc()
+ * @see mpack_node_utf8_cstr_alloc()
+ */
+const char* mpack_node_data(mpack_node_t node);
+
+/**
+ * Returns a pointer to the data contained by this bin node.
+ *
+ * The pointer is valid as long as the data backing the tree is valid.
+ *
+ * If this node is not a bin, @ref mpack_error_type is raised and @c NULL is
+ * returned.
+ */
+const char* mpack_node_bin_data(mpack_node_t node);
+
+/**
+ * Copies the bytes contained by this node into the given buffer, returning the
+ * number of bytes in the node.
+ *
+ * @throws mpack_error_type If this node is not a str, bin or ext type
+ * @throws mpack_error_too_big If the string does not fit in the given buffer
+ *
+ * @param node The string node from which to copy data
+ * @param buffer A buffer in which to copy the node's bytes
+ * @param bufsize The size of the given buffer
+ *
+ * @return The number of bytes in the node, or zero if an error occurs.
+ */
+size_t mpack_node_copy_data(mpack_node_t node, char* buffer, size_t bufsize);
+
+/**
+ * Checks that the given node contains a valid UTF-8 string and copies the
+ * string into the given buffer, returning the number of bytes in the string.
+ *
+ * @throws mpack_error_type If this node is not a string
+ * @throws mpack_error_too_big If the string does not fit in the given buffer
+ *
+ * @param node The string node from which to copy data
+ * @param buffer A buffer in which to copy the node's bytes
+ * @param bufsize The size of the given buffer
+ *
+ * @return The number of bytes in the node, or zero if an error occurs.
+ */
+size_t mpack_node_copy_utf8(mpack_node_t node, char* buffer, size_t bufsize);
+
+/**
+ * Checks that the given node contains a string with no NUL bytes, copies the string
+ * into the given buffer, and adds a null terminator.
+ *
+ * If this node is not of a string type, @ref mpack_error_type is raised. If the string
+ * does not fit, @ref mpack_error_data is raised.
+ *
+ * If any error occurs, the buffer will contain an empty null-terminated string.
+ *
+ * @param node The string node from which to copy data
+ * @param buffer A buffer in which to copy the node's string
+ * @param size The size of the given buffer
+ */
+void mpack_node_copy_cstr(mpack_node_t node, char* buffer, size_t size);
+
+/**
+ * Checks that the given node contains a valid UTF-8 string with no NUL bytes,
+ * copies the string into the given buffer, and adds a null terminator.
+ *
+ * If this node is not of a string type, @ref mpack_error_type is raised. If the string
+ * does not fit, @ref mpack_error_data is raised.
+ *
+ * If any error occurs, the buffer will contain an empty null-terminated string.
+ *
+ * @param node The string node from which to copy data
+ * @param buffer A buffer in which to copy the node's string
+ * @param size The size of the given buffer
+ */
+void mpack_node_copy_utf8_cstr(mpack_node_t node, char* buffer, size_t size);
+
+#ifdef MPACK_MALLOC
+/**
+ * Allocates a new chunk of data using MPACK_MALLOC() with the bytes
+ * contained by this node.
+ *
+ * The allocated data must be freed with MPACK_FREE() (or simply free()
+ * if MPack's allocator hasn't been customized.)
+ *
+ * @throws mpack_error_type If this node is not a str, bin or ext type
+ * @throws mpack_error_too_big If the size of the data is larger than the
+ * given maximum size
+ * @throws mpack_error_memory If an allocation failure occurs
+ *
+ * @param node The node from which to allocate and copy data
+ * @param maxsize The maximum size to allocate
+ *
+ * @return The allocated data, or NULL if any error occurs.
+ */
+char* mpack_node_data_alloc(mpack_node_t node, size_t maxsize);
+
+/**
+ * Allocates a new null-terminated string using MPACK_MALLOC() with the string
+ * contained by this node.
+ *
+ * The allocated string must be freed with MPACK_FREE() (or simply free()
+ * if MPack's allocator hasn't been customized.)
+ *
+ * @throws mpack_error_type If this node is not a string or contains NUL bytes
+ * @throws mpack_error_too_big If the size of the string plus null-terminator
+ * is larger than the given maximum size
+ * @throws mpack_error_memory If an allocation failure occurs
+ *
+ * @param node The node from which to allocate and copy string data
+ * @param maxsize The maximum size to allocate, including the null-terminator
+ *
+ * @return The allocated string, or NULL if any error occurs.
+ */
+char* mpack_node_cstr_alloc(mpack_node_t node, size_t maxsize);
+
+/**
+ * Allocates a new null-terminated string using MPACK_MALLOC() with the UTF-8
+ * string contained by this node.
+ *
+ * The allocated string must be freed with MPACK_FREE() (or simply free()
+ * if MPack's allocator hasn't been customized.)
+ *
+ * @throws mpack_error_type If this node is not a string, is not valid UTF-8,
+ * or contains NUL bytes
+ * @throws mpack_error_too_big If the size of the string plus null-terminator
+ * is larger than the given maximum size
+ * @throws mpack_error_memory If an allocation failure occurs
+ *
+ * @param node The node from which to allocate and copy string data
+ * @param maxsize The maximum size to allocate, including the null-terminator
+ *
+ * @return The allocated string, or NULL if any error occurs.
+ */
+char* mpack_node_utf8_cstr_alloc(mpack_node_t node, size_t maxsize);
+#endif
+
+/**
+ * Searches the given string array for a string matching the given
+ * node and returns its index.
+ *
+ * If the node does not match any of the given strings,
+ * @ref mpack_error_type is flagged. Use mpack_node_enum_optional()
+ * if you want to allow values other than the given strings.
+ *
+ * If any error occurs or if the tree is in an error state, @a count
+ * is returned.
+ *
+ * This can be used to quickly parse a string into an enum when the
+ * enum values range from 0 to @a count-1. If the last value in the
+ * enum is a special "count" value, it can be passed as the count,
+ * and the return value can be cast directly to the enum type.
+ *
+ * @code{.c}
+ * typedef enum { APPLE , BANANA , ORANGE , COUNT} fruit_t;
+ * const char* fruits[] = {"apple", "banana", "orange"};
+ *
+ * fruit_t fruit = (fruit_t)mpack_node_enum(node, fruits, COUNT);
+ * @endcode
+ *
+ * @param node The node
+ * @param strings An array of expected strings of length count
+ * @param count The number of strings
+ * @return The index of the matched string, or @a count in case of error
+ */
+size_t mpack_node_enum(mpack_node_t node, const char* strings[], size_t count);
+
+/**
+ * Searches the given string array for a string matching the given node,
+ * returning its index or @a count if no strings match.
+ *
+ * If the value is not a string, or it does not match any of the
+ * given strings, @a count is returned and no error is flagged.
+ *
+ * If any error occurs or if the tree is in an error state, @a count
+ * is returned.
+ *
+ * This can be used to quickly parse a string into an enum when the
+ * enum values range from 0 to @a count-1. If the last value in the
+ * enum is a special "count" value, it can be passed as the count,
+ * and the return value can be cast directly to the enum type.
+ *
+ * @code{.c}
+ * typedef enum { APPLE , BANANA , ORANGE , COUNT} fruit_t;
+ * const char* fruits[] = {"apple", "banana", "orange"};
+ *
+ * fruit_t fruit = (fruit_t)mpack_node_enum_optional(node, fruits, COUNT);
+ * @endcode
+ *
+ * @param node The node
+ * @param strings An array of expected strings of length count
+ * @param count The number of strings
+ * @return The index of the matched string, or @a count in case of error
+ */
+size_t mpack_node_enum_optional(mpack_node_t node, const char* strings[], size_t count);
+
+/**
+ * @}
+ */
+
+/**
+ * @name Compound Node Functions
+ * @{
+ */
+
+/**
+ * Returns the length of the given array node. Raises mpack_error_type
+ * and returns 0 if the given node is not an array.
+ */
+size_t mpack_node_array_length(mpack_node_t node);
+
+/**
+ * Returns the node in the given array at the given index. If the node
+ * is not an array, @ref mpack_error_type is raised and a nil node is returned.
+ * If the given index is out of bounds, @ref mpack_error_data is raised and
+ * a nil node is returned.
+ */
+mpack_node_t mpack_node_array_at(mpack_node_t node, size_t index);
+
+/**
+ * Returns the number of key/value pairs in the given map node. Raises
+ * mpack_error_type and returns 0 if the given node is not a map.
+ */
+size_t mpack_node_map_count(mpack_node_t node);
+
+/**
+ * Returns the key node in the given map at the given index.
+ *
+ * A nil node is returned in case of error.
+ *
+ * @throws mpack_error_type if the node is not a map
+ * @throws mpack_error_data if the given index is out of bounds
+ */
+mpack_node_t mpack_node_map_key_at(mpack_node_t node, size_t index);
+
+/**
+ * Returns the value node in the given map at the given index.
+ *
+ * A nil node is returned in case of error.
+ *
+ * @throws mpack_error_type if the node is not a map
+ * @throws mpack_error_data if the given index is out of bounds
+ */
+mpack_node_t mpack_node_map_value_at(mpack_node_t node, size_t index);
+
+/**
+ * Returns the value node in the given map for the given integer key.
+ *
+ * The key must exist within the map. Use mpack_node_map_int_optional() to
+ * check for optional keys.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node does not contain exactly one entry with the given key
+ *
+ * @return The value node for the given key, or a nil node in case of error
+ */
+mpack_node_t mpack_node_map_int(mpack_node_t node, int64_t num);
+
+/**
+ * Returns the value node in the given map for the given integer key, or a
+ * missing node if the map does not contain the given key.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node contains more than one entry with the given key
+ *
+ * @return The value node for the given key, or a missing node if the key does
+ * not exist, or a nil node in case of error
+ *
+ * @see mpack_node_is_missing()
+ */
+mpack_node_t mpack_node_map_int_optional(mpack_node_t node, int64_t num);
+
+/**
+ * Returns the value node in the given map for the given unsigned integer key.
+ *
+ * The key must exist within the map. Use mpack_node_map_uint_optional() to
+ * check for optional keys.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node does not contain exactly one entry with the given key
+ *
+ * @return The value node for the given key, or a nil node in case of error
+ */
+mpack_node_t mpack_node_map_uint(mpack_node_t node, uint64_t num);
+
+/**
+ * Returns the value node in the given map for the given unsigned integer
+ * key, or a nil node if the map does not contain the given key.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node contains more than one entry with the given key
+ *
+ * @return The value node for the given key, or a missing node if the key does
+ * not exist, or a nil node in case of error
+ *
+ * @see mpack_node_is_missing()
+ */
+mpack_node_t mpack_node_map_uint_optional(mpack_node_t node, uint64_t num);
+
+/**
+ * Returns the value node in the given map for the given string key.
+ *
+ * The key must exist within the map. Use mpack_node_map_str_optional() to
+ * check for optional keys.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node does not contain exactly one entry with the given key
+ *
+ * @return The value node for the given key, or a nil node in case of error
+ */
+mpack_node_t mpack_node_map_str(mpack_node_t node, const char* str, size_t length);
+
+/**
+ * Returns the value node in the given map for the given string key, or a nil
+ * node if the map does not contain the given key.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node contains more than one entry with the given key
+ *
+ * @return The value node for the given key, or a missing node if the key does
+ * not exist, or a nil node in case of error
+ *
+ * @see mpack_node_is_missing()
+ */
+mpack_node_t mpack_node_map_str_optional(mpack_node_t node, const char* str, size_t length);
+
+/**
+ * Returns the value node in the given map for the given null-terminated
+ * string key.
+ *
+ * The key must exist within the map. Use mpack_node_map_cstr_optional() to
+ * check for optional keys.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node does not contain exactly one entry with the given key
+ *
+ * @return The value node for the given key, or a nil node in case of error
+ */
+mpack_node_t mpack_node_map_cstr(mpack_node_t node, const char* cstr);
+
+/**
+ * Returns the value node in the given map for the given null-terminated
+ * string key, or a nil node if the map does not contain the given key.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node contains more than one entry with the given key
+ *
+ * @return The value node for the given key, or a missing node if the key does
+ * not exist, or a nil node in case of error
+ *
+ * @see mpack_node_is_missing()
+ */
+mpack_node_t mpack_node_map_cstr_optional(mpack_node_t node, const char* cstr);
+
+/**
+ * Returns true if the given node map contains exactly one entry with the
+ * given integer key.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node contains more than one entry with the given key
+ */
+bool mpack_node_map_contains_int(mpack_node_t node, int64_t num);
+
+/**
+ * Returns true if the given node map contains exactly one entry with the
+ * given unsigned integer key.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node contains more than one entry with the given key
+ */
+bool mpack_node_map_contains_uint(mpack_node_t node, uint64_t num);
+
+/**
+ * Returns true if the given node map contains exactly one entry with the
+ * given string key.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node contains more than one entry with the given key
+ */
+bool mpack_node_map_contains_str(mpack_node_t node, const char* str, size_t length);
+
+/**
+ * Returns true if the given node map contains exactly one entry with the
+ * given null-terminated string key.
+ *
+ * The key must be unique. An error is flagged if the node has multiple
+ * entries with the given key.
+ *
+ * @throws mpack_error_type If the node is not a map
+ * @throws mpack_error_data If the node contains more than one entry with the given key
+ */
+bool mpack_node_map_contains_cstr(mpack_node_t node, const char* cstr);
+
+/**
+ * @}
+ */
+
+/**
+ * @}
+ */
+
+#endif
+
+MPACK_HEADER_END
+
+#endif
+
+
+#endif
+
diff --git a/include/lib/mpmalloc.h b/include/lib/mpmalloc.h
new file mode 100644
index 0000000..4f1bb47
--- /dev/null
+++ b/include/lib/mpmalloc.h
@@ -0,0 +1,17 @@
+#ifndef MPMALLOC_H
+#define MPMALLOC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void* mpcalloc(size_t nmemb, size_t size);
+void* mpmalloc(size_t size);
+void* mprealloc(void* addr, size_t size);
+void mpfree(void* addr);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/lib/nanopb/pb.h b/include/lib/nanopb/pb.h
new file mode 100644
index 0000000..3a5c760
--- /dev/null
+++ b/include/lib/nanopb/pb.h
@@ -0,0 +1,594 @@
+/* Common parts of the nanopb library. Most of these are quite low-level
+ * stuff. For the high-level interface, see pb_encode.h and pb_decode.h.
+ */
+
+#ifndef PB_H_INCLUDED
+#define PB_H_INCLUDED
+
+/*****************************************************************
+ * Nanopb compilation time options. You can change these here by *
+ * uncommenting the lines, or on the compiler command line. *
+ *****************************************************************/
+
+/* Enable support for dynamically allocated fields */
+/* #define PB_ENABLE_MALLOC 1 */
+
+/* Define this if your CPU / compiler combination does not support
+ * unaligned memory access to packed structures. */
+/* #define PB_NO_PACKED_STRUCTS 1 */
+
+/* Increase the number of required fields that are tracked.
+ * A compiler warning will tell if you need this. */
+/* #define PB_MAX_REQUIRED_FIELDS 256 */
+
+/* Add support for tag numbers > 255 and fields larger than 255 bytes. */
+/* #define PB_FIELD_16BIT 1 */
+
+/* Add support for tag numbers > 65536 and fields larger than 65536 bytes. */
+/* #define PB_FIELD_32BIT 1 */
+
+/* Disable support for error messages in order to save some code space. */
+/* #define PB_NO_ERRMSG 1 */
+
+/* Disable support for custom streams (support only memory buffers). */
+/* #define PB_BUFFER_ONLY 1 */
+
+/* Switch back to the old-style callback function signature.
+ * This was the default until nanopb-0.2.1. */
+/* #define PB_OLD_CALLBACK_STYLE */
+
+
+/******************************************************************
+ * You usually don't need to change anything below this line. *
+ * Feel free to look around and use the defined macros, though. *
+ ******************************************************************/
+
+
+/* Version of the nanopb library. Just in case you want to check it in
+ * your own program. */
+#define NANOPB_VERSION nanopb-0.4.0-dev
+
+/* Include all the system headers needed by nanopb. You will need the
+ * definitions of the following:
+ * - strlen, memcpy, memset functions
+ * - [u]int_least8_t, uint_fast8_t, [u]int_least16_t, [u]int32_t, [u]int64_t
+ * - size_t
+ * - bool
+ *
+ * If you don't have the standard header files, you can instead provide
+ * a custom header that defines or includes all this. In that case,
+ * define PB_SYSTEM_HEADER to the path of this file.
+ */
+#ifdef PB_SYSTEM_HEADER
+#include PB_SYSTEM_HEADER
+#else
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <string.h>
+#include <limits.h>
+
+#ifdef PB_ENABLE_MALLOC
+#include <stdlib.h>
+#endif
+#endif
+
+/* Macro for defining packed structures (compiler dependent).
+ * This just reduces memory requirements, but is not required.
+ */
+#if defined(PB_NO_PACKED_STRUCTS)
+ /* Disable struct packing */
+# define PB_PACKED_STRUCT_START
+# define PB_PACKED_STRUCT_END
+# define pb_packed
+#elif defined(__GNUC__) || defined(__clang__)
+ /* For GCC and clang */
+# define PB_PACKED_STRUCT_START
+# define PB_PACKED_STRUCT_END
+# define pb_packed __attribute__((packed))
+#elif defined(__ICCARM__) || defined(__CC_ARM)
+ /* For IAR ARM and Keil MDK-ARM compilers */
+# define PB_PACKED_STRUCT_START _Pragma("pack(push, 1)")
+# define PB_PACKED_STRUCT_END _Pragma("pack(pop)")
+# define pb_packed
+#elif defined(_MSC_VER) && (_MSC_VER >= 1500)
+ /* For Microsoft Visual C++ */
+# define PB_PACKED_STRUCT_START __pragma(pack(push, 1))
+# define PB_PACKED_STRUCT_END __pragma(pack(pop))
+# define pb_packed
+#else
+ /* Unknown compiler */
+# define PB_PACKED_STRUCT_START
+# define PB_PACKED_STRUCT_END
+# define pb_packed
+#endif
+
+/* Handly macro for suppressing unreferenced-parameter compiler warnings. */
+#ifndef PB_UNUSED
+#define PB_UNUSED(x) (void)(x)
+#endif
+
+/* Compile-time assertion, used for checking compatible compilation options.
+ * If this does not work properly on your compiler, use
+ * #define PB_NO_STATIC_ASSERT to disable it.
+ *
+ * But before doing that, check carefully the error message / place where it
+ * comes from to see if the error has a real cause. Unfortunately the error
+ * message is not always very clear to read, but you can see the reason better
+ * in the place where the PB_STATIC_ASSERT macro was called.
+ */
+#ifndef PB_NO_STATIC_ASSERT
+#ifndef PB_STATIC_ASSERT
+#define PB_STATIC_ASSERT(COND,MSG) typedef char PB_STATIC_ASSERT_MSG(MSG, __LINE__, __COUNTER__)[(COND)?1:-1];
+#define PB_STATIC_ASSERT_MSG(MSG, LINE, COUNTER) PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER)
+#define PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER) pb_static_assertion_##MSG##LINE##COUNTER
+#endif
+#else
+#define PB_STATIC_ASSERT(COND,MSG)
+#endif
+
+/* Number of required fields to keep track of. */
+#ifndef PB_MAX_REQUIRED_FIELDS
+#define PB_MAX_REQUIRED_FIELDS 64
+#endif
+
+#if PB_MAX_REQUIRED_FIELDS < 64
+#error You should not lower PB_MAX_REQUIRED_FIELDS from the default value (64).
+#endif
+
+/* List of possible field types. These are used in the autogenerated code.
+ * Least-significant 4 bits tell the scalar type
+ * Most-significant 4 bits specify repeated/required/packed etc.
+ */
+
+typedef uint_least8_t pb_type_t;
+
+/**** Field data types ****/
+
+/* Numeric types */
+#define PB_LTYPE_VARINT 0x00 /* int32, int64, enum, bool */
+#define PB_LTYPE_UVARINT 0x01 /* uint32, uint64 */
+#define PB_LTYPE_SVARINT 0x02 /* sint32, sint64 */
+#define PB_LTYPE_FIXED32 0x03 /* fixed32, sfixed32, float */
+#define PB_LTYPE_FIXED64 0x04 /* fixed64, sfixed64, double */
+
+/* Marker for last packable field type. */
+#define PB_LTYPE_LAST_PACKABLE 0x04
+
+/* Byte array with pre-allocated buffer.
+ * data_size is the length of the allocated PB_BYTES_ARRAY structure. */
+#define PB_LTYPE_BYTES 0x05
+
+/* String with pre-allocated buffer.
+ * data_size is the maximum length. */
+#define PB_LTYPE_STRING 0x06
+
+/* Submessage
+ * submsg_fields is pointer to field descriptions */
+#define PB_LTYPE_SUBMESSAGE 0x07
+
+/* Extension pseudo-field
+ * The field contains a pointer to pb_extension_t */
+#define PB_LTYPE_EXTENSION 0x08
+
+/* Byte array with inline, pre-allocated byffer.
+ * data_size is the length of the inline, allocated buffer.
+ * This differs from PB_LTYPE_BYTES by defining the element as
+ * pb_byte_t[data_size] rather than pb_bytes_array_t. */
+#define PB_LTYPE_FIXED_LENGTH_BYTES 0x09
+
+/* Number of declared LTYPES */
+#define PB_LTYPES_COUNT 0x0A
+#define PB_LTYPE_MASK 0x0F
+
+/**** Field repetition rules ****/
+
+#define PB_HTYPE_REQUIRED 0x00
+#define PB_HTYPE_OPTIONAL 0x10
+#define PB_HTYPE_REPEATED 0x20
+#define PB_HTYPE_ONEOF 0x30
+#define PB_HTYPE_MASK 0x30
+
+/**** Field allocation types ****/
+
+#define PB_ATYPE_STATIC 0x00
+#define PB_ATYPE_POINTER 0x80
+#define PB_ATYPE_CALLBACK 0x40
+#define PB_ATYPE_MASK 0xC0
+
+#define PB_ATYPE(x) ((x) & PB_ATYPE_MASK)
+#define PB_HTYPE(x) ((x) & PB_HTYPE_MASK)
+#define PB_LTYPE(x) ((x) & PB_LTYPE_MASK)
+
+/* Data type used for storing sizes of struct fields
+ * and array counts.
+ */
+#if defined(PB_FIELD_32BIT)
+ typedef uint32_t pb_size_t;
+ typedef int32_t pb_ssize_t;
+#elif defined(PB_FIELD_16BIT)
+ typedef uint_least16_t pb_size_t;
+ typedef int_least16_t pb_ssize_t;
+#else
+ typedef uint_least8_t pb_size_t;
+ typedef int_least8_t pb_ssize_t;
+#endif
+#define PB_SIZE_MAX ((pb_size_t)-1)
+
+/* Data type for storing encoded data and other byte streams.
+ * This typedef exists to support platforms where uint8_t does not exist.
+ * You can regard it as equivalent on uint8_t on other platforms.
+ */
+typedef uint_least8_t pb_byte_t;
+
+/* This structure is used in auto-generated constants
+ * to specify struct fields.
+ * You can change field sizes if you need structures
+ * larger than 256 bytes or field tags larger than 256.
+ * The compiler should complain if your .proto has such
+ * structures. Fix that by defining PB_FIELD_16BIT or
+ * PB_FIELD_32BIT.
+ */
+PB_PACKED_STRUCT_START
+typedef struct pb_field_s pb_field_t;
+struct pb_field_s {
+ pb_size_t tag;
+ pb_type_t type;
+ pb_size_t data_offset; /* Offset of field data, relative to previous field. */
+ pb_ssize_t size_offset; /* Offset of array size or has-boolean, relative to data */
+ pb_size_t data_size; /* Data size in bytes for a single item */
+ pb_size_t array_size; /* Maximum number of entries in array */
+
+ /* Field definitions for submessage
+ * OR default value for all other non-array, non-callback types
+ * If null, then field will zeroed. */
+ const void *ptr;
+} pb_packed;
+PB_PACKED_STRUCT_END
+
+/* Make sure that the standard integer types are of the expected sizes.
+ * Otherwise fixed32/fixed64 fields can break.
+ *
+ * If you get errors here, it probably means that your stdint.h is not
+ * correct for your platform.
+ */
+#ifndef PB_WITHOUT_64BIT
+PB_STATIC_ASSERT(sizeof(int64_t) == 2 * sizeof(int32_t), INT64_T_WRONG_SIZE)
+PB_STATIC_ASSERT(sizeof(uint64_t) == 2 * sizeof(uint32_t), UINT64_T_WRONG_SIZE)
+#endif
+
+/* This structure is used for 'bytes' arrays.
+ * It has the number of bytes in the beginning, and after that an array.
+ * Note that actual structs used will have a different length of bytes array.
+ */
+#define PB_BYTES_ARRAY_T(n) struct { pb_size_t size; pb_byte_t bytes[n]; }
+#define PB_BYTES_ARRAY_T_ALLOCSIZE(n) ((size_t)n + offsetof(pb_bytes_array_t, bytes))
+
+struct pb_bytes_array_s {
+ pb_size_t size;
+ pb_byte_t bytes[1];
+};
+typedef struct pb_bytes_array_s pb_bytes_array_t;
+
+/* This structure is used for giving the callback function.
+ * It is stored in the message structure and filled in by the method that
+ * calls pb_decode.
+ *
+ * The decoding callback will be given a limited-length stream
+ * If the wire type was string, the length is the length of the string.
+ * If the wire type was a varint/fixed32/fixed64, the length is the length
+ * of the actual value.
+ * The function may be called multiple times (especially for repeated types,
+ * but also otherwise if the message happens to contain the field multiple
+ * times.)
+ *
+ * The encoding callback will receive the actual output stream.
+ * It should write all the data in one call, including the field tag and
+ * wire type. It can write multiple fields.
+ *
+ * The callback can be null if you want to skip a field.
+ */
+typedef struct pb_istream_s pb_istream_t;
+typedef struct pb_ostream_s pb_ostream_t;
+typedef struct pb_callback_s pb_callback_t;
+struct pb_callback_s {
+#ifdef PB_OLD_CALLBACK_STYLE
+ /* Deprecated since nanopb-0.2.1 */
+ union {
+ bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void *arg);
+ bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, const void *arg);
+ } funcs;
+#else
+ /* New function signature, which allows modifying arg contents in callback. */
+ union {
+ bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void **arg);
+ bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, void * const *arg);
+ } funcs;
+#endif
+
+ /* Free arg for use by callback */
+ void *arg;
+};
+
+/* Wire types. Library user needs these only in encoder callbacks. */
+typedef enum {
+ PB_WT_VARINT = 0,
+ PB_WT_64BIT = 1,
+ PB_WT_STRING = 2,
+ PB_WT_32BIT = 5
+} pb_wire_type_t;
+
+/* Structure for defining the handling of unknown/extension fields.
+ * Usually the pb_extension_type_t structure is automatically generated,
+ * while the pb_extension_t structure is created by the user. However,
+ * if you want to catch all unknown fields, you can also create a custom
+ * pb_extension_type_t with your own callback.
+ */
+typedef struct pb_extension_type_s pb_extension_type_t;
+typedef struct pb_extension_s pb_extension_t;
+struct pb_extension_type_s {
+ /* Called for each unknown field in the message.
+ * If you handle the field, read off all of its data and return true.
+ * If you do not handle the field, do not read anything and return true.
+ * If you run into an error, return false.
+ * Set to NULL for default handler.
+ */
+ bool (*decode)(pb_istream_t *stream, pb_extension_t *extension,
+ uint32_t tag, pb_wire_type_t wire_type);
+
+ /* Called once after all regular fields have been encoded.
+ * If you have something to write, do so and return true.
+ * If you do not have anything to write, just return true.
+ * If you run into an error, return false.
+ * Set to NULL for default handler.
+ */
+ bool (*encode)(pb_ostream_t *stream, const pb_extension_t *extension);
+
+ /* Free field for use by the callback. */
+ const void *arg;
+};
+
+struct pb_extension_s {
+ /* Type describing the extension field. Usually you'll initialize
+ * this to a pointer to the automatically generated structure. */
+ const pb_extension_type_t *type;
+
+ /* Destination for the decoded data. This must match the datatype
+ * of the extension field. */
+ void *dest;
+
+ /* Pointer to the next extension handler, or NULL.
+ * If this extension does not match a field, the next handler is
+ * automatically called. */
+ pb_extension_t *next;
+
+ /* The decoder sets this to true if the extension was found.
+ * Ignored for encoding. */
+ bool found;
+};
+
+/* Memory allocation functions to use. You can define pb_realloc and
+ * pb_free to custom functions if you want. */
+#ifdef PB_ENABLE_MALLOC
+# ifndef pb_realloc
+# define pb_realloc(ptr, size) realloc(ptr, size)
+# endif
+# ifndef pb_free
+# define pb_free(ptr) free(ptr)
+# endif
+#endif
+
+/* This is used to inform about need to regenerate .pb.h/.pb.c files. */
+#define PB_PROTO_HEADER_VERSION 30
+
+/* These macros are used to declare pb_field_t's in the constant array. */
+/* Size of a structure member, in bytes. */
+#define pb_membersize(st, m) (sizeof ((st*)0)->m)
+/* Number of entries in an array. */
+#define pb_arraysize(st, m) (pb_membersize(st, m) / pb_membersize(st, m[0]))
+/* Delta from start of one member to the start of another member. */
+#define pb_delta(st, m1, m2) ((int)offsetof(st, m1) - (int)offsetof(st, m2))
+/* Marks the end of the field list */
+#define PB_LAST_FIELD {0,(pb_type_t) 0,0,0,0,0,0}
+
+/* Macros for filling in the data_offset field */
+/* data_offset for first field in a message */
+#define PB_DATAOFFSET_FIRST(st, m1, m2) (offsetof(st, m1))
+/* data_offset for subsequent fields */
+#define PB_DATAOFFSET_OTHER(st, m1, m2) (offsetof(st, m1) - offsetof(st, m2) - pb_membersize(st, m2))
+/* data offset for subsequent fields inside an union (oneof) */
+#define PB_DATAOFFSET_UNION(st, m1, m2) (PB_SIZE_MAX)
+/* Choose first/other based on m1 == m2 (deprecated, remains for backwards compatibility) */
+#define PB_DATAOFFSET_CHOOSE(st, m1, m2) (int)(offsetof(st, m1) == offsetof(st, m2) \
+ ? PB_DATAOFFSET_FIRST(st, m1, m2) \
+ : PB_DATAOFFSET_OTHER(st, m1, m2))
+
+/* Required fields are the simplest. They just have delta (padding) from
+ * previous field end, and the size of the field. Pointer is used for
+ * submessages and default values.
+ */
+#define PB_REQUIRED_STATIC(tag, st, m, fd, ltype, ptr) \
+ {tag, PB_ATYPE_STATIC | PB_HTYPE_REQUIRED | ltype, \
+ fd, 0, pb_membersize(st, m), 0, ptr}
+
+/* Optional fields add the delta to the has_ variable. */
+#define PB_OPTIONAL_STATIC(tag, st, m, fd, ltype, ptr) \
+ {tag, PB_ATYPE_STATIC | PB_HTYPE_OPTIONAL | ltype, \
+ fd, \
+ pb_delta(st, has_ ## m, m), \
+ pb_membersize(st, m), 0, ptr}
+
+#define PB_SINGULAR_STATIC(tag, st, m, fd, ltype, ptr) \
+ {tag, PB_ATYPE_STATIC | PB_HTYPE_OPTIONAL | ltype, \
+ fd, 0, pb_membersize(st, m), 0, ptr}
+
+/* Repeated fields have a _count field and also the maximum number of entries. */
+#define PB_REPEATED_STATIC(tag, st, m, fd, ltype, ptr) \
+ {tag, PB_ATYPE_STATIC | PB_HTYPE_REPEATED | ltype, \
+ fd, \
+ pb_delta(st, m ## _count, m), \
+ pb_membersize(st, m[0]), \
+ pb_arraysize(st, m), ptr}
+
+/* Allocated fields carry the size of the actual data, not the pointer */
+#define PB_REQUIRED_POINTER(tag, st, m, fd, ltype, ptr) \
+ {tag, PB_ATYPE_POINTER | PB_HTYPE_REQUIRED | ltype, \
+ fd, 0, pb_membersize(st, m[0]), 0, ptr}
+
+/* Optional fields don't need a has_ variable, as information would be redundant */
+#define PB_OPTIONAL_POINTER(tag, st, m, fd, ltype, ptr) \
+ {tag, PB_ATYPE_POINTER | PB_HTYPE_OPTIONAL | ltype, \
+ fd, 0, pb_membersize(st, m[0]), 0, ptr}
+
+/* Same as optional fields*/
+#define PB_SINGULAR_POINTER(tag, st, m, fd, ltype, ptr) \
+ {tag, PB_ATYPE_POINTER | PB_HTYPE_OPTIONAL | ltype, \
+ fd, 0, pb_membersize(st, m[0]), 0, ptr}
+
+/* Repeated fields have a _count field and a pointer to array of pointers */
+#define PB_REPEATED_POINTER(tag, st, m, fd, ltype, ptr) \
+ {tag, PB_ATYPE_POINTER | PB_HTYPE_REPEATED | ltype, \
+ fd, pb_delta(st, m ## _count, m), \
+ pb_membersize(st, m[0]), 0, ptr}
+
+/* Callbacks are much like required fields except with special datatype. */
+#define PB_REQUIRED_CALLBACK(tag, st, m, fd, ltype, ptr) \
+ {tag, PB_ATYPE_CALLBACK | PB_HTYPE_REQUIRED | ltype, \
+ fd, 0, pb_membersize(st, m), 0, ptr}
+
+#define PB_OPTIONAL_CALLBACK(tag, st, m, fd, ltype, ptr) \
+ {tag, PB_ATYPE_CALLBACK | PB_HTYPE_OPTIONAL | ltype, \
+ fd, 0, pb_membersize(st, m), 0, ptr}
+
+#define PB_SINGULAR_CALLBACK(tag, st, m, fd, ltype, ptr) \
+ {tag, PB_ATYPE_CALLBACK | PB_HTYPE_OPTIONAL | ltype, \
+ fd, 0, pb_membersize(st, m), 0, ptr}
+
+#define PB_REPEATED_CALLBACK(tag, st, m, fd, ltype, ptr) \
+ {tag, PB_ATYPE_CALLBACK | PB_HTYPE_REPEATED | ltype, \
+ fd, 0, pb_membersize(st, m), 0, ptr}
+
+/* Optional extensions don't have the has_ field, as that would be redundant.
+ * Furthermore, the combination of OPTIONAL without has_ field is used
+ * for indicating proto3 style fields. Extensions exist in proto2 mode only,
+ * so they should be encoded according to proto2 rules. To avoid the conflict,
+ * extensions are marked as REQUIRED instead.
+ */
+#define PB_OPTEXT_STATIC(tag, st, m, fd, ltype, ptr) \
+ {tag, PB_ATYPE_STATIC | PB_HTYPE_REQUIRED | ltype, \
+ 0, \
+ 0, \
+ pb_membersize(st, m), 0, ptr}
+
+#define PB_OPTEXT_POINTER(tag, st, m, fd, ltype, ptr) \
+ PB_OPTIONAL_POINTER(tag, st, m, fd, ltype, ptr)
+
+#define PB_OPTEXT_CALLBACK(tag, st, m, fd, ltype, ptr) \
+ PB_OPTIONAL_CALLBACK(tag, st, m, fd, ltype, ptr)
+
+/* The mapping from protobuf types to LTYPEs is done using these macros. */
+#define PB_LTYPE_MAP_BOOL PB_LTYPE_VARINT
+#define PB_LTYPE_MAP_BYTES PB_LTYPE_BYTES
+#define PB_LTYPE_MAP_DOUBLE PB_LTYPE_FIXED64
+#define PB_LTYPE_MAP_ENUM PB_LTYPE_VARINT
+#define PB_LTYPE_MAP_UENUM PB_LTYPE_UVARINT
+#define PB_LTYPE_MAP_FIXED32 PB_LTYPE_FIXED32
+#define PB_LTYPE_MAP_FIXED64 PB_LTYPE_FIXED64
+#define PB_LTYPE_MAP_FLOAT PB_LTYPE_FIXED32
+#define PB_LTYPE_MAP_INT32 PB_LTYPE_VARINT
+#define PB_LTYPE_MAP_INT64 PB_LTYPE_VARINT
+#define PB_LTYPE_MAP_MESSAGE PB_LTYPE_SUBMESSAGE
+#define PB_LTYPE_MAP_SFIXED32 PB_LTYPE_FIXED32
+#define PB_LTYPE_MAP_SFIXED64 PB_LTYPE_FIXED64
+#define PB_LTYPE_MAP_SINT32 PB_LTYPE_SVARINT
+#define PB_LTYPE_MAP_SINT64 PB_LTYPE_SVARINT
+#define PB_LTYPE_MAP_STRING PB_LTYPE_STRING
+#define PB_LTYPE_MAP_UINT32 PB_LTYPE_UVARINT
+#define PB_LTYPE_MAP_UINT64 PB_LTYPE_UVARINT
+#define PB_LTYPE_MAP_EXTENSION PB_LTYPE_EXTENSION
+#define PB_LTYPE_MAP_FIXED_LENGTH_BYTES PB_LTYPE_FIXED_LENGTH_BYTES
+
+/* This is the actual macro used in field descriptions.
+ * It takes these arguments:
+ * - Field tag number
+ * - Field type: BOOL, BYTES, DOUBLE, ENUM, UENUM, FIXED32, FIXED64,
+ * FLOAT, INT32, INT64, MESSAGE, SFIXED32, SFIXED64
+ * SINT32, SINT64, STRING, UINT32, UINT64 or EXTENSION
+ * - Field rules: REQUIRED, OPTIONAL or REPEATED
+ * - Allocation: STATIC, CALLBACK or POINTER
+ * - Placement: FIRST or OTHER, depending on if this is the first field in structure.
+ * - Message name
+ * - Field name
+ * - Previous field name (or field name again for first field)
+ * - Pointer to default value or submsg fields.
+ */
+
+#define PB_FIELD(tag, type, rules, allocation, placement, message, field, prevfield, ptr) \
+ PB_ ## rules ## _ ## allocation(tag, message, field, \
+ PB_DATAOFFSET_ ## placement(message, field, prevfield), \
+ PB_LTYPE_MAP_ ## type, ptr)
+
+/* Field description for repeated static fixed count fields.*/
+#define PB_REPEATED_FIXED_COUNT(tag, type, placement, message, field, prevfield, ptr) \
+ {tag, PB_ATYPE_STATIC | PB_HTYPE_REPEATED | PB_LTYPE_MAP_ ## type, \
+ PB_DATAOFFSET_ ## placement(message, field, prevfield), \
+ 0, \
+ pb_membersize(message, field[0]), \
+ pb_arraysize(message, field), ptr}
+
+/* Field description for oneof fields. This requires taking into account the
+ * union name also, that's why a separate set of macros is needed.
+ */
+#define PB_ONEOF_STATIC(u, tag, st, m, fd, ltype, ptr) \
+ {tag, PB_ATYPE_STATIC | PB_HTYPE_ONEOF | ltype, \
+ fd, pb_delta(st, which_ ## u, u.m), \
+ pb_membersize(st, u.m), 0, ptr}
+
+#define PB_ONEOF_POINTER(u, tag, st, m, fd, ltype, ptr) \
+ {tag, PB_ATYPE_POINTER | PB_HTYPE_ONEOF | ltype, \
+ fd, pb_delta(st, which_ ## u, u.m), \
+ pb_membersize(st, u.m[0]), 0, ptr}
+
+#define PB_ONEOF_FIELD(union_name, tag, type, rules, allocation, placement, message, field, prevfield, ptr) \
+ PB_ONEOF_ ## allocation(union_name, tag, message, field, \
+ PB_DATAOFFSET_ ## placement(message, union_name.field, prevfield), \
+ PB_LTYPE_MAP_ ## type, ptr)
+
+#define PB_ANONYMOUS_ONEOF_STATIC(u, tag, st, m, fd, ltype, ptr) \
+ {tag, PB_ATYPE_STATIC | PB_HTYPE_ONEOF | ltype, \
+ fd, pb_delta(st, which_ ## u, m), \
+ pb_membersize(st, m), 0, ptr}
+
+#define PB_ANONYMOUS_ONEOF_POINTER(u, tag, st, m, fd, ltype, ptr) \
+ {tag, PB_ATYPE_POINTER | PB_HTYPE_ONEOF | ltype, \
+ fd, pb_delta(st, which_ ## u, m), \
+ pb_membersize(st, m[0]), 0, ptr}
+
+#define PB_ANONYMOUS_ONEOF_FIELD(union_name, tag, type, rules, allocation, placement, message, field, prevfield, ptr) \
+ PB_ANONYMOUS_ONEOF_ ## allocation(union_name, tag, message, field, \
+ PB_DATAOFFSET_ ## placement(message, field, prevfield), \
+ PB_LTYPE_MAP_ ## type, ptr)
+
+/* These macros are used for giving out error messages.
+ * They are mostly a debugging aid; the main error information
+ * is the true/false return value from functions.
+ * Some code space can be saved by disabling the error
+ * messages if not used.
+ *
+ * PB_SET_ERROR() sets the error message if none has been set yet.
+ * msg must be a constant string literal.
+ * PB_GET_ERROR() always returns a pointer to a string.
+ * PB_RETURN_ERROR() sets the error and returns false from current
+ * function.
+ */
+#ifdef PB_NO_ERRMSG
+#define PB_SET_ERROR(stream, msg) PB_UNUSED(stream)
+#define PB_GET_ERROR(stream) "(errmsg disabled)"
+#else
+#define PB_SET_ERROR(stream, msg) (stream->errmsg = (stream)->errmsg ? (stream)->errmsg : (msg))
+#define PB_GET_ERROR(stream) ((stream)->errmsg ? (stream)->errmsg : "(none)")
+#endif
+
+#define PB_RETURN_ERROR(stream, msg) return PB_SET_ERROR(stream, msg), false
+
+#endif
diff --git a/include/lib/nanopb/pb_common.h b/include/lib/nanopb/pb_common.h
new file mode 100644
index 0000000..60b3d37
--- /dev/null
+++ b/include/lib/nanopb/pb_common.h
@@ -0,0 +1,42 @@
+/* pb_common.h: Common support functions for pb_encode.c and pb_decode.c.
+ * These functions are rarely needed by applications directly.
+ */
+
+#ifndef PB_COMMON_H_INCLUDED
+#define PB_COMMON_H_INCLUDED
+
+#include "pb.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Iterator for pb_field_t list */
+struct pb_field_iter_s {
+ const pb_field_t *start; /* Start of the pb_field_t array */
+ const pb_field_t *pos; /* Current position of the iterator */
+ unsigned required_field_index; /* Zero-based index that counts only the required fields */
+ void *dest_struct; /* Pointer to start of the structure */
+ void *pData; /* Pointer to current field value */
+ void *pSize; /* Pointer to count/has field */
+};
+typedef struct pb_field_iter_s pb_field_iter_t;
+
+/* Initialize the field iterator structure to beginning.
+ * Returns false if the message type is empty. */
+bool pb_field_iter_begin(pb_field_iter_t *iter, const pb_field_t *fields, void *dest_struct);
+
+/* Advance the iterator to the next field.
+ * Returns false when the iterator wraps back to the first field. */
+bool pb_field_iter_next(pb_field_iter_t *iter);
+
+/* Advance the iterator until it points at a field with the given tag.
+ * Returns false if no such field exists. */
+bool pb_field_iter_find(pb_field_iter_t *iter, uint32_t tag);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif
+
diff --git a/include/lib/nanopb/pb_decode.h b/include/lib/nanopb/pb_decode.h
new file mode 100644
index 0000000..398b24a
--- /dev/null
+++ b/include/lib/nanopb/pb_decode.h
@@ -0,0 +1,175 @@
+/* pb_decode.h: Functions to decode protocol buffers. Depends on pb_decode.c.
+ * The main function is pb_decode. You also need an input stream, and the
+ * field descriptions created by nanopb_generator.py.
+ */
+
+#ifndef PB_DECODE_H_INCLUDED
+#define PB_DECODE_H_INCLUDED
+
+#include "pb.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Structure for defining custom input streams. You will need to provide
+ * a callback function to read the bytes from your storage, which can be
+ * for example a file or a network socket.
+ *
+ * The callback must conform to these rules:
+ *
+ * 1) Return false on IO errors. This will cause decoding to abort.
+ * 2) You can use state to store your own data (e.g. buffer pointer),
+ * and rely on pb_read to verify that no-body reads past bytes_left.
+ * 3) Your callback may be used with substreams, in which case bytes_left
+ * is different than from the main stream. Don't use bytes_left to compute
+ * any pointers.
+ */
+struct pb_istream_s
+{
+#ifdef PB_BUFFER_ONLY
+ /* Callback pointer is not used in buffer-only configuration.
+ * Having an int pointer here allows binary compatibility but
+ * gives an error if someone tries to assign callback function.
+ */
+ int *callback;
+#else
+ bool (*callback)(pb_istream_t *stream, pb_byte_t *buf, size_t count);
+#endif
+
+ void *state; /* Free field for use by callback implementation */
+ size_t bytes_left;
+
+#ifndef PB_NO_ERRMSG
+ const char *errmsg;
+#endif
+};
+
+/***************************
+ * Main decoding functions *
+ ***************************/
+
+/* Decode a single protocol buffers message from input stream into a C structure.
+ * Returns true on success, false on any failure.
+ * The actual struct pointed to by dest must match the description in fields.
+ * Callback fields of the destination structure must be initialized by caller.
+ * All other fields will be initialized by this function.
+ *
+ * Example usage:
+ * MyMessage msg = {};
+ * uint8_t buffer[64];
+ * pb_istream_t stream;
+ *
+ * // ... read some data into buffer ...
+ *
+ * stream = pb_istream_from_buffer(buffer, count);
+ * pb_decode(&stream, MyMessage_fields, &msg);
+ */
+bool pb_decode(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct);
+
+/* Same as pb_decode, except does not initialize the destination structure
+ * to default values. This is slightly faster if you need no default values
+ * and just do memset(struct, 0, sizeof(struct)) yourself.
+ *
+ * This can also be used for 'merging' two messages, i.e. update only the
+ * fields that exist in the new message.
+ *
+ * Note: If this function returns with an error, it will not release any
+ * dynamically allocated fields. You will need to call pb_release() yourself.
+ */
+bool pb_decode_noinit(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct);
+
+/* Same as pb_decode, except expects the stream to start with the message size
+ * encoded as varint. Corresponds to parseDelimitedFrom() in Google's
+ * protobuf API.
+ */
+bool pb_decode_delimited(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct);
+
+/* Same as pb_decode_delimited, except that it does not initialize the destination structure.
+ * See pb_decode_noinit
+ */
+bool pb_decode_delimited_noinit(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct);
+
+/* Same as pb_decode, except allows the message to be terminated with a null byte.
+ * NOTE: Until nanopb-0.4.0, pb_decode() also allows null-termination. This behaviour
+ * is not supported in most other protobuf implementations, so pb_decode_delimited()
+ * is a better option for compatibility.
+ */
+bool pb_decode_nullterminated(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct);
+
+#ifdef PB_ENABLE_MALLOC
+/* Release any allocated pointer fields. If you use dynamic allocation, you should
+ * call this for any successfully decoded message when you are done with it. If
+ * pb_decode() returns with an error, the message is already released.
+ */
+void pb_release(const pb_field_t fields[], void *dest_struct);
+#endif
+
+
+/**************************************
+ * Functions for manipulating streams *
+ **************************************/
+
+/* Create an input stream for reading from a memory buffer.
+ *
+ * Alternatively, you can use a custom stream that reads directly from e.g.
+ * a file or a network socket.
+ */
+pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t bufsize);
+
+/* Function to read from a pb_istream_t. You can use this if you need to
+ * read some custom header data, or to read data in field callbacks.
+ */
+bool pb_read(pb_istream_t *stream, pb_byte_t *buf, size_t count);
+
+
+/************************************************
+ * Helper functions for writing field callbacks *
+ ************************************************/
+
+/* Decode the tag for the next field in the stream. Gives the wire type and
+ * field tag. At end of the message, returns false and sets eof to true. */
+bool pb_decode_tag(pb_istream_t *stream, pb_wire_type_t *wire_type, uint32_t *tag, bool *eof);
+
+/* Skip the field payload data, given the wire type. */
+bool pb_skip_field(pb_istream_t *stream, pb_wire_type_t wire_type);
+
+/* Decode an integer in the varint format. This works for bool, enum, int32,
+ * int64, uint32 and uint64 field types. */
+#ifndef PB_WITHOUT_64BIT
+bool pb_decode_varint(pb_istream_t *stream, uint64_t *dest);
+#else
+#define pb_decode_varint pb_decode_varint32
+#endif
+
+/* Decode an integer in the varint format. This works for bool, enum, int32,
+ * and uint32 field types. */
+bool pb_decode_varint32(pb_istream_t *stream, uint32_t *dest);
+
+/* Decode an integer in the zig-zagged svarint format. This works for sint32
+ * and sint64. */
+#ifndef PB_WITHOUT_64BIT
+bool pb_decode_svarint(pb_istream_t *stream, int64_t *dest);
+#else
+bool pb_decode_svarint(pb_istream_t *stream, int32_t *dest);
+#endif
+
+/* Decode a fixed32, sfixed32 or float value. You need to pass a pointer to
+ * a 4-byte wide C variable. */
+bool pb_decode_fixed32(pb_istream_t *stream, void *dest);
+
+#ifndef PB_WITHOUT_64BIT
+/* Decode a fixed64, sfixed64 or double value. You need to pass a pointer to
+ * a 8-byte wide C variable. */
+bool pb_decode_fixed64(pb_istream_t *stream, void *dest);
+#endif
+
+/* Make a limited-length substream for reading a PB_WT_STRING field. */
+bool pb_make_string_substream(pb_istream_t *stream, pb_istream_t *substream);
+bool pb_close_string_substream(pb_istream_t *stream, pb_istream_t *substream);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif
diff --git a/include/lib/nanopb/pb_encode.h b/include/lib/nanopb/pb_encode.h
new file mode 100644
index 0000000..8bf78dd
--- /dev/null
+++ b/include/lib/nanopb/pb_encode.h
@@ -0,0 +1,170 @@
+/* pb_encode.h: Functions to encode protocol buffers. Depends on pb_encode.c.
+ * The main function is pb_encode. You also need an output stream, and the
+ * field descriptions created by nanopb_generator.py.
+ */
+
+#ifndef PB_ENCODE_H_INCLUDED
+#define PB_ENCODE_H_INCLUDED
+
+#include "pb.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Structure for defining custom output streams. You will need to provide
+ * a callback function to write the bytes to your storage, which can be
+ * for example a file or a network socket.
+ *
+ * The callback must conform to these rules:
+ *
+ * 1) Return false on IO errors. This will cause encoding to abort.
+ * 2) You can use state to store your own data (e.g. buffer pointer).
+ * 3) pb_write will update bytes_written after your callback runs.
+ * 4) Substreams will modify max_size and bytes_written. Don't use them
+ * to calculate any pointers.
+ */
+struct pb_ostream_s
+{
+#ifdef PB_BUFFER_ONLY
+ /* Callback pointer is not used in buffer-only configuration.
+ * Having an int pointer here allows binary compatibility but
+ * gives an error if someone tries to assign callback function.
+ * Also, NULL pointer marks a 'sizing stream' that does not
+ * write anything.
+ */
+ int *callback;
+#else
+ bool (*callback)(pb_ostream_t *stream, const pb_byte_t *buf, size_t count);
+#endif
+ void *state; /* Free field for use by callback implementation. */
+ size_t max_size; /* Limit number of output bytes written (or use SIZE_MAX). */
+ size_t bytes_written; /* Number of bytes written so far. */
+
+#ifndef PB_NO_ERRMSG
+ const char *errmsg;
+#endif
+};
+
+/***************************
+ * Main encoding functions *
+ ***************************/
+
+/* Encode a single protocol buffers message from C structure into a stream.
+ * Returns true on success, false on any failure.
+ * The actual struct pointed to by src_struct must match the description in fields.
+ * All required fields in the struct are assumed to have been filled in.
+ *
+ * Example usage:
+ * MyMessage msg = {};
+ * uint8_t buffer[64];
+ * pb_ostream_t stream;
+ *
+ * msg.field1 = 42;
+ * stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
+ * pb_encode(&stream, MyMessage_fields, &msg);
+ */
+bool pb_encode(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct);
+
+/* Same as pb_encode, but prepends the length of the message as a varint.
+ * Corresponds to writeDelimitedTo() in Google's protobuf API.
+ */
+bool pb_encode_delimited(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct);
+
+/* Same as pb_encode, but appends a null byte to the message for termination.
+ * NOTE: This behaviour is not supported in most other protobuf implementations, so pb_encode_delimited()
+ * is a better option for compatibility.
+ */
+bool pb_encode_nullterminated(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct);
+
+/* Encode the message to get the size of the encoded data, but do not store
+ * the data. */
+bool pb_get_encoded_size(size_t *size, const pb_field_t fields[], const void *src_struct);
+
+/**************************************
+ * Functions for manipulating streams *
+ **************************************/
+
+/* Create an output stream for writing into a memory buffer.
+ * The number of bytes written can be found in stream.bytes_written after
+ * encoding the message.
+ *
+ * Alternatively, you can use a custom stream that writes directly to e.g.
+ * a file or a network socket.
+ */
+pb_ostream_t pb_ostream_from_buffer(pb_byte_t *buf, size_t bufsize);
+
+/* Pseudo-stream for measuring the size of a message without actually storing
+ * the encoded data.
+ *
+ * Example usage:
+ * MyMessage msg = {};
+ * pb_ostream_t stream = PB_OSTREAM_SIZING;
+ * pb_encode(&stream, MyMessage_fields, &msg);
+ * printf("Message size is %d\n", stream.bytes_written);
+ */
+#ifndef PB_NO_ERRMSG
+#define PB_OSTREAM_SIZING {0,0,0,0,0}
+#else
+#define PB_OSTREAM_SIZING {0,0,0,0}
+#endif
+
+/* Function to write into a pb_ostream_t stream. You can use this if you need
+ * to append or prepend some custom headers to the message.
+ */
+bool pb_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count);
+
+
+/************************************************
+ * Helper functions for writing field callbacks *
+ ************************************************/
+
+/* Encode field header based on type and field number defined in the field
+ * structure. Call this from the callback before writing out field contents. */
+bool pb_encode_tag_for_field(pb_ostream_t *stream, const pb_field_t *field);
+
+/* Encode field header by manually specifing wire type. You need to use this
+ * if you want to write out packed arrays from a callback field. */
+bool pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, uint32_t field_number);
+
+/* Encode an integer in the varint format.
+ * This works for bool, enum, int32, int64, uint32 and uint64 field types. */
+#ifndef PB_WITHOUT_64BIT
+bool pb_encode_varint(pb_ostream_t *stream, uint64_t value);
+#else
+bool pb_encode_varint(pb_ostream_t *stream, uint32_t value);
+#endif
+
+/* Encode an integer in the zig-zagged svarint format.
+ * This works for sint32 and sint64. */
+#ifndef PB_WITHOUT_64BIT
+bool pb_encode_svarint(pb_ostream_t *stream, int64_t value);
+#else
+bool pb_encode_svarint(pb_ostream_t *stream, int32_t value);
+#endif
+
+/* Encode a string or bytes type field. For strings, pass strlen(s) as size. */
+bool pb_encode_string(pb_ostream_t *stream, const pb_byte_t *buffer, size_t size);
+
+/* Encode a fixed32, sfixed32 or float value.
+ * You need to pass a pointer to a 4-byte wide C variable. */
+bool pb_encode_fixed32(pb_ostream_t *stream, const void *value);
+
+#ifndef PB_WITHOUT_64BIT
+/* Encode a fixed64, sfixed64 or double value.
+ * You need to pass a pointer to a 8-byte wide C variable. */
+bool pb_encode_fixed64(pb_ostream_t *stream, const void *value);
+#endif
+
+/* Encode a submessage field.
+ * You need to pass the pb_field_t array and pointer to struct, just like
+ * with pb_encode(). This internally encodes the submessage twice, first to
+ * calculate message size and then to actually write it out.
+ */
+bool pb_encode_submessage(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif
diff --git a/include/lib/ubjson/ubj.h b/include/lib/ubjson/ubj.h
new file mode 100644
index 0000000..f3d5ac8
--- /dev/null
+++ b/include/lib/ubjson/ubj.h
@@ -0,0 +1,231 @@
+#ifndef UBJ_H
+#define UBJ_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include<inttypes.h>
+#include<stdio.h>
+
+typedef enum
+{
+ UBJ_MIXED=0, //NOT a type...or the type is mixed
+
+ UBJ_NULLTYPE,
+ UBJ_NOOP,
+ UBJ_BOOL_TRUE,
+ UBJ_BOOL_FALSE,
+
+ UBJ_CHAR,
+ UBJ_STRING,
+ UBJ_HIGH_PRECISION,
+
+ UBJ_INT8,
+ UBJ_UINT8 ,
+ UBJ_INT16,
+ UBJ_INT32,
+ UBJ_INT64,
+ UBJ_FLOAT32 ,
+ UBJ_FLOAT64,
+
+ UBJ_ARRAY,
+ UBJ_OBJECT,
+
+ UBJ_NUM_TYPES //this is the size of how many types there are (chris' trick)
+} UBJ_TYPE;
+
+
+
+//////////here is the declarations for the writer API////////////////////////////////////
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+struct ubjw_context_t_s;
+typedef struct ubjw_context_t_s ubjw_context_t;
+
+ubjw_context_t* ubjw_open_callback(void* userdata,
+ size_t(*write_cb)(const void* data, size_t size, size_t count, void* userdata),
+ int(*close_cb)(void* userdata),
+ void(*error_cb)(const char* error_msg)
+ );
+ubjw_context_t* ubjw_open_file(FILE*);
+ubjw_context_t* ubjw_open_memory(uint8_t* dst_b, uint8_t* dst_e);
+
+size_t ubjw_close_context(ubjw_context_t* ctx);
+
+void ubjw_write_string(ubjw_context_t* dst, const char* out);
+void ubjw_write_char(ubjw_context_t* dst, char out);
+
+void ubjw_write_uint8(ubjw_context_t* dst, uint8_t out);
+void ubjw_write_int8(ubjw_context_t* dst, int8_t out);
+void ubjw_write_int16(ubjw_context_t* dst, int16_t out);
+void ubjw_write_int32(ubjw_context_t* dst, int32_t out);
+void ubjw_write_int64(ubjw_context_t* dst, int64_t out);
+void ubjw_write_high_precision(ubjw_context_t* dst, const char* hp);
+
+void ubjw_write_integer(ubjw_context_t* dst, int64_t out);
+UBJ_TYPE ubjw_min_integer_type(int64_t in);
+
+void ubjw_write_float32(ubjw_context_t* dst, float out);
+void ubjw_write_float64(ubjw_context_t* dst, double out);
+
+void ubjw_write_floating_point(ubjw_context_t* dst, double out);
+
+void ubjw_write_noop(ubjw_context_t* dst);
+void ubjw_write_null(ubjw_context_t* dst);
+void ubjw_write_bool(ubjw_context_t* dst, uint8_t out);
+
+void ubjw_begin_array(ubjw_context_t* dst, UBJ_TYPE type, size_t count);
+
+void ubjw_begin_object(ubjw_context_t* dst, UBJ_TYPE type, size_t count);
+void ubjw_write_key(ubjw_context_t* dst, const char* key);
+void ubjw_end(ubjw_context_t* dst);
+
+//output an efficient buffer of types
+void ubjw_write_buffer(ubjw_context_t* dst, const uint8_t* data, UBJ_TYPE type, size_t count);
+
+//Proposal for N-D arrays
+void ubjw_begin_ndarray(ubjw_context_t* dst, UBJ_TYPE type, const size_t* dims, uint8_t ndims);
+void ubjw_write_ndbuffer(ubjw_context_t* dst,const uint8_t* data, UBJ_TYPE type, const size_t* dims, uint8_t ndims);
+
+
+//////////here is the declarations for the reader API////////////////////////////////////
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+struct ubjr_context_t_s;
+typedef struct ubjr_context_t_s ubjr_context_t;
+
+//Open up a reader context for reading using a custom calllback
+ubjr_context_t* ubjr_open_callback(void* userdata,
+ size_t(*read_cb)(void* data, size_t size, size_t count, void* userdata),
+ int(*peek_cb)(void* userdata),
+ int(*close_cb)(void* userdata),
+ void(*error_cb)(const char* error_msg)
+ );
+
+//Open a context initialized to a UBJ file
+ubjr_context_t* ubjr_open_file(FILE*);
+
+//Open up a context initialized to a memory dump of a UBJ file (or a segment of a UBJ file)
+ubjr_context_t* ubjr_open_memory(const uint8_t* dst_b, const uint8_t* dst_e);
+
+//Close a reader context
+size_t ubjr_close_context(ubjr_context_t* ctx);
+
+typedef char* ubjr_string_t;
+
+//An array that you read from the stream
+typedef struct ubjr_array_t_s
+{
+ uint8_t originally_sized;
+ UBJ_TYPE type;
+ size_t size; //total number of elements
+ void* values;
+ uint8_t num_dims;
+ size_t* dims; //this could be faster if it was constant size, but would also make the size of the dynamic object a LOT bigger
+
+} ubjr_array_t;
+
+//a map that you read from the stream
+typedef struct ubjr_object_t_s
+{
+ uint8_t originally_sized;
+ UBJ_TYPE type;
+ size_t size;
+ void* values;
+ ubjr_string_t* keys;
+ void* metatable; //don't use this..only useful for computing object_lookup
+} ubjr_object_t;
+
+//a dynamic type that you parsed.
+typedef struct ubjr_dynamic_t_s
+{
+ UBJ_TYPE type;
+ union
+ {
+ uint8_t boolean;
+ double real;
+ int64_t integer;
+ ubjr_string_t string;
+ ubjr_array_t container_array;
+ ubjr_object_t container_object;
+ };
+} ubjr_dynamic_t;
+
+//Parse a dynamic object from the stream
+ubjr_dynamic_t ubjr_read_dynamic(ubjr_context_t* ctx);
+void ubjr_cleanup_dynamic(ubjr_dynamic_t* dyn);
+
+ubjr_dynamic_t ubjr_object_lookup(ubjr_object_t* obj, const char* key);
+size_t ubjr_local_type_size(UBJ_TYPE typ);//should be equivalent to sizeof()
+size_t ubjr_ndarray_index(const ubjr_array_t* arr, const size_t* indices);
+
+
+//output an efficient buffer of types
+///void ubjr_read_buffer(struct ubjr_context_t* dst, const uint8_t* data, UBJ_TYPE type, size_t count);
+
+void ubjr_cleanup_dynamic(ubjr_dynamic_t* dyn);
+void ubjr_cleanup_array(ubjr_array_t* arr);
+void ubjr_cleanup_object(ubjr_object_t* obj);
+
+
+
+///////UBJ_RW api
+
+void ubjrw_write_dynamic(ubjw_context_t* ctx, ubjr_dynamic_t dobj,uint8_t optimize);
+//ubjrw_append_object(ubjw_context_t* ctx, ubjr_dynamic_t dobj);
+//ubjrw_append_array(ubjw_context_t* ctx, ubjr_dynamic_t dobj);
+
+#ifdef __cplusplus
+}
+
+/*
+#include<iostream>
+
+static size_t write_os(const void* data, size_t size, size_t count, void* userdata)
+{
+ size_t n = size*count;
+ reinterpret_cast<std::ostream*>(userdata)->write(data, n);
+ return n;
+}
+static void close_os(void* userdata)
+{
+ reinterpret_cast<std::ostream*>(userdata)->close();
+}
+
+static size_t read_is(void* data, size_t size, size_t count, void* userdata)
+{
+ size_t n = size*count;
+ reinterpret_cast<std::istream*>(userdata)->read(data, n);
+ return n;
+}
+static int peek_is(void* userdata)
+{
+ return reinterpret_cast<std::istream*>(userdata)->peek();
+}
+static void close_is(void* userdata)
+{
+ reinterpret_cast<std::istream*>(userdata)->close();
+}
+
+static ubjw_context_t* ubjw_open_stream(std::ostream& outstream)
+{
+ return ubjw_open_callback((void*)&outstream, write_os, close_os, NULL);
+}
+
+static ubjr_context_t* ubjr_open_stream(std::istream& instream)
+{
+ return ubjr_open_callback((void*)&instream, read_is, peek_is, close_is, NULL);
+}
+*/
+
+
+#endif
+
+#endif
diff --git a/include/lib/ubjson/ubj_internal.h b/include/lib/ubjson/ubj_internal.h
new file mode 100644
index 0000000..fc61697
--- /dev/null
+++ b/include/lib/ubjson/ubj_internal.h
@@ -0,0 +1,163 @@
+#ifndef UBJ_INTERNAL_H
+#define UBJ_INTERNAL_H
+
+#include "ubj.h"
+#include <stdlib.h>
+#include <string.h>
+
+#if _MSC_VER
+#define inline __inline
+#endif
+
+
+static const uint8_t UBJI_TYPEC_convert[UBJ_NUM_TYPES] = "\x00ZNTFCSHiUIlLdD[{";
+
+static const int UBJI_TYPE_size[UBJ_NUM_TYPES] =
+ { -1, //MIXED
+ 0, //NULLTYPE
+ 0, //NOOP
+ 0, //BOOL_TRUE
+ 0, //BOOL_FALSE
+ 1, //CHAR
+ sizeof(const char*), //STRING
+ sizeof(const char*), //high-precision
+ 1, //INT8
+ 1, //UINT8
+ 2, //int16
+ 4, //int32
+ 8, //int64
+ 4, //float32
+ 8, //float64
+ -1, //array
+ -1 //object
+ };
+
+static const size_t UBJR_TYPE_localsize[UBJ_NUM_TYPES] =
+{
+ sizeof(ubjr_dynamic_t), //MIXED
+ 0, //NULLTYPE
+ 0, //NOOP
+ 0, //BOOL_TRUE
+ 0, //BOOL_FALSE
+ sizeof(ubjr_string_t), //CHAR
+ sizeof(ubjr_string_t), //STRING
+ sizeof(ubjr_string_t), //high-precision
+ sizeof(int8_t), //INT8
+ sizeof(uint8_t), //UINT8
+ sizeof(int16_t), //int16
+ sizeof(int32_t), //int32
+ sizeof(int64_t), //int64
+ sizeof(float), //float32
+ sizeof(double), //float64
+ sizeof(ubjr_array_t), //array
+ sizeof(ubjr_object_t) //object
+};
+
+static inline void _to_bigendian16(uint8_t* outbuffer, uint16_t input)
+{
+ *outbuffer++ = (input >> 8); // Get top order byte (guaranteed endian-independent since machine registers)
+ *outbuffer++ = input & 0xFF; // Get bottom order byte
+}
+static inline void _to_bigendian32(uint8_t* outbuffer, uint32_t input)
+{
+ _to_bigendian16(outbuffer, (uint16_t)(input >> 16)); // Get top order 2 bytes
+ _to_bigendian16(outbuffer + 2, (uint16_t)(input & 0xFFFF)); // Get bottom order 2 bytes
+}
+static inline void _to_bigendian64(uint8_t* outbuffer, uint64_t input)
+{
+ _to_bigendian32(outbuffer, (uint32_t)(input >> 32));
+ _to_bigendian32(outbuffer + 4, (uint32_t)(input & 0xFFFFFFFF));
+}
+
+static inline uint8_t _is_bigendian()
+{
+ int i = 1;
+ char *low = (char*)&i;
+ return *low ? 0 : 1;
+}
+
+#define BUF_BIG_ENDIAN_SWAP(type,func,ptr,num) \
+ { \
+ size_t i;type* d = (type*)ptr; \
+ for (i = 0; i < num; i++) \
+ { \
+ func((uint8_t*)&d[i], d[i]); \
+ } \
+ } \
+
+static inline void buf_endian_swap(uint8_t* buf, size_t sz, size_t n)
+{
+ if (!_is_bigendian())
+ {
+ switch (sz)
+ {
+ case 1:
+ case 0:
+ break;
+ case 2:
+ BUF_BIG_ENDIAN_SWAP(uint16_t, _to_bigendian16,buf,n);
+ break;
+ case 4:
+ BUF_BIG_ENDIAN_SWAP(uint32_t, _to_bigendian32,buf,n);
+ break;
+ case 8:
+ BUF_BIG_ENDIAN_SWAP(uint64_t, _to_bigendian64,buf,n);
+ break;
+ };
+ }
+}
+
+//warning...null-terminated strings are assumed...when this is not necessarily valid. FIXED: we don't use null-terminated strings in the reader (NOT FIXED...string type is awkward)
+static inline ubjr_dynamic_t priv_ubjr_pointer_to_dynamic(UBJ_TYPE typ, const void* dat)
+{
+ ubjr_dynamic_t outdyn;
+ outdyn.type = typ;
+ size_t n = 1;
+ switch (typ)
+ {
+ case UBJ_NULLTYPE:
+ case UBJ_NOOP:
+ break;
+ case UBJ_BOOL_TRUE:
+ case UBJ_BOOL_FALSE:
+ outdyn.boolean = (typ == UBJ_BOOL_TRUE ? 1 : 0);
+ break;
+ case UBJ_HIGH_PRECISION:
+ case UBJ_STRING:
+ case UBJ_CHAR://possibly if char allocate, otherwise don't
+ outdyn.string = *(const ubjr_string_t*)dat;
+ break;
+ case UBJ_INT8:
+ outdyn.integer = *(const int8_t*)dat;
+ break;
+ case UBJ_UINT8:
+ outdyn.integer = *(const uint8_t*)dat;
+ break;
+ case UBJ_INT16:
+ outdyn.integer = *(const int16_t*)dat;
+ break;
+ case UBJ_INT32:
+ outdyn.integer = *(const int32_t*)dat;
+ break;
+ case UBJ_INT64:
+ outdyn.integer = *(const int64_t*)dat;
+ break;
+ case UBJ_FLOAT32:
+ outdyn.real = *(const float*)dat;
+ break;
+ case UBJ_FLOAT64:
+ outdyn.real = *(const double*)dat;
+ break;
+ case UBJ_ARRAY:
+ outdyn.container_array = *(const ubjr_array_t*)dat;
+ break;
+ case UBJ_OBJECT:
+ outdyn.container_object = *(const ubjr_object_t*)dat;
+ break;
+ case UBJ_MIXED:
+ outdyn = *(const ubjr_dynamic_t*)dat;
+ };
+ return outdyn;
+}
+
+#endif \ No newline at end of file
diff --git a/include/lib/xdr.h b/include/lib/xdr.h
new file mode 100644
index 0000000..c451a2f
--- /dev/null
+++ b/include/lib/xdr.h
@@ -0,0 +1,66 @@
+#ifndef XDR_H
+#define XDR_H
+
+#include <stdint.h>
+
+class XDRWriter
+{
+private:
+ XDRWriter(const XDRWriter &copy);
+ char *buffer;
+ uint32_t next_array_len;
+ uint32_t pos;
+ bool is_fixed_length;
+
+public:
+ XDRWriter(char *output_buf) : buffer(output_buf), next_array_len(0), pos(0) {}
+
+ inline void setNextArrayLen(uint32_t len) { next_array_len = len; }
+ inline void setFixedLength() { is_fixed_length = true; }
+ inline void setVariableLength() { is_fixed_length = false; }
+ inline void startList()
+ {
+ if (!is_fixed_length)
+ {
+ put(next_array_len);
+ }
+ }
+
+ inline void put(char c) { put((int32_t)c); }
+ inline void put(unsigned char c) { put((uint32_t)c); }
+ inline void put(uint16_t number) { put((int32_t)number); }
+ inline void put(int16_t number) { put((uint32_t)number); }
+ void put(uint32_t number);
+ void put(int32_t number);
+ void put(uint64_t number);
+ void put(int64_t number);
+ void put(float number);
+ void put(double number);
+ void put(char const *text);
+ template <uint32_t TSize>
+ void put(char const (&text)[TSize]);
+
+ inline uint32_t size() { return pos; }
+};
+
+class XDRReader
+{
+private:
+ XDRReader(const XDRReader &copy);
+ char *data;
+ uint32_t pos;
+
+public:
+ XDRReader(char *d) : pos(0) { data = d; }
+ uint32_t get_uint32();
+ int32_t get_int32();
+ uint64_t get_uint64();
+ int64_t get_int64();
+ float get_float();
+ double get_double();
+ uint32_t get_opaque_length();
+ char *get_opaque(uint32_t length);
+ void get_string(char *target);
+};
+
+#endif
diff --git a/include/lib/xdr16.h b/include/lib/xdr16.h
new file mode 100644
index 0000000..483efe4
--- /dev/null
+++ b/include/lib/xdr16.h
@@ -0,0 +1,68 @@
+#ifndef XDR16_H
+#define XDR16_H
+
+#include <stdint.h>
+
+class XDRWriter
+{
+private:
+ XDRWriter(const XDRWriter &copy);
+ char *buffer;
+ uint16_t next_array_len;
+ uint16_t pos;
+ bool is_fixed_length;
+
+public:
+ XDRWriter(char *output_buf) : buffer(output_buf), next_array_len(0), pos(0) {}
+
+ inline void setNextArrayLen(uint16_t len) { next_array_len = len; }
+ inline void setFixedLength() { is_fixed_length = true; }
+ inline void setVariableLength() { is_fixed_length = false; }
+ inline void startList()
+ {
+ if (!is_fixed_length)
+ {
+ put(next_array_len);
+ }
+ }
+
+ inline void put(char c) { put((int16_t)c); }
+ inline void put(unsigned char c) { put((uint16_t)c); }
+ void put(uint16_t number);
+ void put(int16_t number);
+ void put(uint32_t number);
+ void put(int32_t number);
+ void put(uint64_t number);
+ void put(int64_t number);
+ void put(float number);
+ void put(double number);
+ void put(char const *text);
+ template <uint16_t TSize>
+ void put(char const (&text)[TSize]);
+
+ inline uint16_t size() { return pos; }
+};
+
+class XDRReader
+{
+private:
+ XDRReader(const XDRReader &copy);
+ char *data;
+ uint32_t pos;
+
+public:
+ XDRReader(char *d) : pos(0) { data = d; }
+ int16_t get_int16();
+ uint16_t get_uint16();
+ uint32_t get_uint32();
+ int32_t get_int32();
+ uint64_t get_uint64();
+ int64_t get_int64();
+ float get_float();
+ double get_double();
+ uint32_t get_opaque_length();
+ char *get_opaque(uint32_t length);
+ void get_string(char *target);
+};
+
+#endif
diff --git a/src/app/prototest/Makefile.inc b/src/app/prototest/Makefile.inc
new file mode 100644
index 0000000..c088f6b
--- /dev/null
+++ b/src/app/prototest/Makefile.inc
@@ -0,0 +1,96 @@
+ifeq (${aspectc}, 1)
+ CXX_FLAGS += --Xweaver -asrc/app/prototest/prototest.ah --Xcompiler
+endif
+
+ifeq (${prototest_bench_energy}, 1)
+ loop ?= 1
+ COMMON_FLAGS += -DPROTOTEST_BENCH_ENERGY
+endif
+
+ifeq (${prototest_bench_cycles}, 1)
+ loop ?= 0
+ arch_drivers += ,counter
+ COMMON_FLAGS += -DPROTOTEST_BENCH_CYCLES
+endif
+
+ifneq (${prototest_include_global}, )
+ COMMON_FLAGS += -DPROTOTEST_INCLUDE_GLOBAL=${prototest_include_global}
+endif
+
+ifneq (${prototest_include_local}, )
+ COMMON_FLAGS += -DPROTOTEST_INCLUDE_LOCAL=${prototest_include_local}
+endif
+
+ifeq (${prototest_arduinojson}, 1)
+ COMMON_FLAGS += -DPROTOTEST_ARDUINOJSON
+endif
+
+ifeq (${prototest_binn}, 1)
+ COMMON_FLAGS += -DPROTOTEST_BINN
+ CXX_TARGETS += src/lib/binn.cc
+endif
+
+ifeq (${prototest_capnproto_c}, 1)
+ COMMON_FLAGS += -DPROTOTEST_CAPNPROTO_C
+ CXX_TARGETS += src/app/prototest/capnp_c_bench.capnp.cc
+ CXX_TARGETS += src/lib/capnp-c/capn.cc
+ CXX_TARGETS += src/lib/capnp-c/capn-malloc.cc
+ CXX_TARGETS += src/lib/capnp-c/capn-stream.cc
+ INCLUDES += -Iinclude/lib/capnp-c
+endif
+
+ifeq (${prototest_manualjson}, 1)
+ COMMON_FLAGS += -DPROTOTEST_MANUALJSON
+endif
+
+ifeq (${prototest_modernjson}, 1)
+ COMMON_FLAGS += -DPROTOTEST_MODERNJSON
+ ostream = 1
+endif
+
+ifeq (${prototest_mpack}, 1)
+ COMMON_FLAGS += -DPROTOTEST_MPACK
+ CXX_TARGETS += src/lib/mpack/mpack.cc
+ INCLUDES += -Iinclude/lib/mpack
+endif
+
+ifeq (${prototest_nanopb}, 1)
+ COMMON_FLAGS += -DPROTOTEST_NANOPB
+ CXX_TARGETS += src/app/prototest/nanopbbench.pb.cc src/lib/nanopb/pb_common.cc
+ CXX_TARGETS += src/lib/nanopb/pb_decode.cc src/lib/nanopb/pb_encode.cc
+ INCLUDES += -Iinclude/lib/nanopb
+endif
+
+ifeq (${prototest_ubjson}, 1)
+ COMMON_FLAGS += -DPROTOTEST_UBJSON
+ C_TARGETS += src/lib/ubjson/ubjr.c src/lib/ubjson/ubjw.c
+ INCLUDES += -Iinclude/lib/ubjson
+endif
+
+ifeq (${prototest_xdr}, 1)
+ COMMON_FLAGS += -DPROTOTEST_XDR
+ CXX_TARGETS += src/lib/xdr.cc
+endif
+
+ifeq (${prototest_xdr16}, 1)
+ COMMON_FLAGS += -DPROTOTEST_XDR16
+ CXX_TARGETS += src/lib/xdr16.cc
+endif
+
+# Don't try to make .capnp from .capnp.c
+%.capnp: ;
+
+# Don't try to make .proto from .proto.c
+%.proto: ;
+
+%.pb.cc: %.proto
+ ${QUIET}protoc --plugin=protoc-gen-nanopb=${HOME}/var/ess/protocol-modeling/nanopb/generator/protoc-gen-nanopb --nanopb_out=. src/app/prototest/nanopbbench.proto
+ ${QUIET}mv src/app/prototest/nanopbbench.pb.c src/app/prototest/nanopbbench.pb.cc
+ ${QUIET}cp src/app/prototest/nanopbbench.pb.cc /tmp
+ ${QUIET}sed -i 's!src/app/prototest/!!' src/app/prototest/nanopbbench.pb.cc
+ ${QUIET}sed -i 's!\#include "src/app/prototest/nanopb.pb.h"!!' src/app/prototest/nanopbbench.pb.h
+
+%.capnp.cc: %.capnp
+ ${QUIET}capnp compile -oc $<
+ ${QUIET}mv $<.c $<.cc
+ ${QUIET}cp $<.cc /tmp
diff --git a/src/app/prototest/main.cc b/src/app/prototest/main.cc
new file mode 100644
index 0000000..7fc897e
--- /dev/null
+++ b/src/app/prototest/main.cc
@@ -0,0 +1,79 @@
+#include "arch.h"
+#include "driver/gpio.h"
+#include "driver/stdout.h"
+
+#ifdef PROTOTEST_BENCH_CYCLES
+#include "driver/counter.h"
+#endif
+
+#ifdef PROTOTEST_ARDUINOJSON
+#include "lib/ArduinoJson.h"
+#endif
+#ifdef PROTOTEST_BINN
+#include "lib/binn.h"
+#endif
+#ifdef PROTOTEST_CAPNPROTO_C
+#include <capnp_c.h>
+#include "capnp_c_bench.capnp.h"
+#endif
+#ifdef PROTOTEST_MANUALJSON
+#include "object/stdbuf.h"
+#endif
+#ifdef PROTOTEST_MODERNJSON
+#include "lib/modernjson/json.h"
+#endif
+#ifdef PROTOTEST_MPACK
+#include "mpack.h"
+#endif
+#ifdef PROTOTEST_NANOPB
+#include <pb.h>
+#include "nanopbbench.pb.h"
+#include <pb_encode.h>
+#include <pb_decode.h>
+#endif
+#ifdef PROTOTEST_UBJSON
+#include "ubj.h"
+#endif
+#ifdef PROTOTEST_XDR
+#include "lib/xdr.h"
+#endif
+#ifdef PROTOTEST_XDR16
+#include "lib/xdr16.h"
+#endif
+
+#include <stdint.h>
+
+#ifdef PROTOTEST_INCLUDE_GLOBAL
+#include "prototest_global.cc.inc"
+#endif
+
+void loop(void)
+{
+ //static uint16_t ts = 0;
+ //ts++;
+
+#ifdef PROTOTEST_INCLUDE_LOCAL
+#include "prototest_local.cc.inc"
+#endif
+
+}
+
+int main(void)
+{
+ arch.setup();
+ gpio.setup();
+ kout.setup();
+
+ //gpio.led_on(0);
+ kout << "Hello, World!" << endl;
+
+#if defined(PROTOTEST_BENCH_CYCLES) and !defined(PROTOTEST_ARCH_esp8266)
+ while (1) {
+ loop();
+ }
+#else
+ arch.idle_loop();
+#endif
+
+ return 0;
+}
diff --git a/src/lib/binn.cc b/src/lib/binn.cc
new file mode 100644
index 0000000..b7e4839
--- /dev/null
+++ b/src/lib/binn.cc
@@ -0,0 +1,3372 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <memory.h>
+#include "lib/binn.h"
+
+#ifdef MULTIPASS_TRACE_MALLOC
+#include "lib/mpmalloc.h"
+#else
+#define mpmalloc malloc
+#define mprealloc realloc
+#define mpfree free
+#endif
+
+#define UNUSED(x) (void)(x)
+#define round(dbl) dbl >= 0.0 ? (int)(dbl + 0.5) : ((dbl - (double)(int)dbl) <= -0.5 ? (int)dbl : (int)(dbl - 0.5))
+
+// magic number: 0x1F 0xb1 0x22 0x1F => 0x1FB1221F or 0x1F22B11F
+// because the BINN_STORAGE_NOBYTES (binary 000) may not have so many sub-types (BINN_STORAGE_HAS_MORE = 0x10)
+#define BINN_MAGIC 0x1F22B11F
+
+#define MAX_BINN_HEADER 9 // [1:type][4:size][4:count]
+#define MIN_BINN_SIZE 3 // [1:type][1:size][1:count]
+#define CHUNK_SIZE 256 // 1024
+
+#define BINN_STRUCT 1
+#define BINN_BUFFER 2
+
+void* (*malloc_fn)(size_t len) = 0;
+void* (*realloc_fn)(void *ptr, size_t len) = 0;
+void (*free_fn)(void *ptr) = 0;
+
+/***************************************************************************/
+
+#if defined(__alpha__) || defined(__hppa__) || defined(__mips__) || defined(__powerpc__) || defined(__sparc__)
+#define BINN_ONLY_ALIGNED_ACCESS
+#elif ( defined(__arm__) || defined(__aarch64__) ) && !defined(__ARM_FEATURE_UNALIGNED)
+#define BINN_ONLY_ALIGNED_ACCESS
+#endif
+
+#if defined(_WIN32)
+#define BIG_ENDIAN 0x1000
+#define LITTLE_ENDIAN 0x0001
+#define BYTE_ORDER LITTLE_ENDIAN
+#elif defined(__APPLE__)
+/* macros already defined */
+#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__)
+#include <sys/endian.h>
+#elif defined(_AIX)
+#include <sys/machine.h>
+#else
+#include <endian.h>
+#endif
+
+#ifndef BYTE_ORDER
+#error "BYTE_ORDER not defined"
+#endif
+#ifndef BIG_ENDIAN
+#error "BIG_ENDIAN not defined"
+#endif
+#ifndef LITTLE_ENDIAN
+#error "LITTLE_ENDIAN not defined"
+#endif
+#if BIG_ENDIAN == LITTLE_ENDIAN
+#error "BIG_ENDIAN == LITTLE_ENDIAN"
+#endif
+#if BYTE_ORDER!=BIG_ENDIAN && BYTE_ORDER!=LITTLE_ENDIAN
+#error "BYTE_ORDER not supported"
+#endif
+
+typedef unsigned short int u16;
+typedef unsigned int u32;
+typedef unsigned long long int u64;
+
+BINN_PRIVATE void copy_be16(u16 *pdest, u16 *psource) {
+#if BYTE_ORDER == LITTLE_ENDIAN
+ unsigned char *source = (unsigned char *) psource;
+ unsigned char *dest = (unsigned char *) pdest;
+ dest[0] = source[1];
+ dest[1] = source[0];
+#else // if BYTE_ORDER == BIG_ENDIAN
+#ifdef BINN_ONLY_ALIGNED_ACCESS
+ if (psource % 2 == 0){ // address aligned to 16 bit
+ *pdest = *psource;
+ } else {
+ unsigned char *source = (unsigned char *) psource;
+ unsigned char *dest = (unsigned char *) pdest;
+ dest[0] = source[0]; // indexes are the same
+ dest[1] = source[1];
+ }
+#else
+ *pdest = *psource;
+#endif
+#endif
+}
+
+BINN_PRIVATE void copy_be32(u32 *pdest, u32 *psource) {
+#if BYTE_ORDER == LITTLE_ENDIAN
+ unsigned char *source = (unsigned char *) psource;
+ unsigned char *dest = (unsigned char *) pdest;
+ dest[0] = source[3];
+ dest[1] = source[2];
+ dest[2] = source[1];
+ dest[3] = source[0];
+#else // if BYTE_ORDER == BIG_ENDIAN
+#ifdef BINN_ONLY_ALIGNED_ACCESS
+ if (psource % 4 == 0){ // address aligned to 32 bit
+ *pdest = *psource;
+ } else {
+ unsigned char *source = (unsigned char *) psource;
+ unsigned char *dest = (unsigned char *) pdest;
+ dest[0] = source[0]; // indexes are the same
+ dest[1] = source[1];
+ dest[2] = source[2];
+ dest[3] = source[3];
+ }
+#else
+ *pdest = *psource;
+#endif
+#endif
+}
+
+BINN_PRIVATE void copy_be64(u64 *pdest, u64 *psource) {
+#if BYTE_ORDER == LITTLE_ENDIAN
+ unsigned char *source = (unsigned char *) psource;
+ unsigned char *dest = (unsigned char *) pdest;
+ int i;
+ for (i=0; i < 8; i++) {
+ dest[i] = source[7-i];
+ }
+#else // if BYTE_ORDER == BIG_ENDIAN
+#ifdef BINN_ONLY_ALIGNED_ACCESS
+ if (psource % 8 == 0){ // address aligned to 64 bit
+ *pdest = *psource;
+ } else {
+ unsigned char *source = (unsigned char *) psource;
+ unsigned char *dest = (unsigned char *) pdest;
+ int i;
+ for (i=0; i < 8; i++) {
+ dest[i] = source[i]; // indexes are the same
+ }
+ }
+#else
+ *pdest = *psource;
+#endif
+#endif
+}
+
+/***************************************************************************/
+
+#ifndef WIN32
+#define stricmp strcasecmp
+#define strnicmp strncasecmp
+#endif
+
+/***************************************************************************/
+
+void APIENTRY binn_set_alloc_functions(void* (*new_malloc)(size_t), void* (*new_realloc)(void*,size_t), void (*new_free)(void*)) {
+
+ malloc_fn = new_malloc;
+ realloc_fn = new_realloc;
+ free_fn = new_free;
+
+}
+
+/***************************************************************************/
+
+BINN_PRIVATE void check_alloc_functions() {
+
+ if (malloc_fn == 0) malloc_fn = &mpmalloc;
+ if (realloc_fn == 0) realloc_fn = &mprealloc;
+ if (free_fn == 0) free_fn = &mpfree;
+
+}
+
+/***************************************************************************/
+
+BINN_PRIVATE void * binn_malloc(int size) {
+ check_alloc_functions();
+ return malloc_fn(size);
+}
+
+/***************************************************************************/
+
+BINN_PRIVATE void * binn_memdup(void *src, int size) {
+ void *dest;
+
+ if (src == NULL || size <= 0) return NULL;
+ dest = binn_malloc(size);
+ if (dest == NULL) return NULL;
+ memcpy(dest, src, size);
+ return dest;
+
+}
+
+/***************************************************************************/
+
+BINN_PRIVATE size_t strlen2(char *str) {
+
+ if (str == NULL) return 0;
+ return strlen(str);
+
+}
+
+/***************************************************************************/
+
+int APIENTRY binn_create_type(int storage_type, int data_type_index) {
+ if (data_type_index < 0) return -1;
+ if ((storage_type < BINN_STORAGE_MIN) || (storage_type > BINN_STORAGE_MAX)) return -1;
+ if (data_type_index < 16)
+ return storage_type | data_type_index;
+ else if (data_type_index < 4096) {
+ storage_type |= BINN_STORAGE_HAS_MORE;
+ storage_type <<= 8;
+ data_type_index >>= 4;
+ return storage_type | data_type_index;
+ } else
+ return -1;
+}
+
+/***************************************************************************/
+
+BOOL APIENTRY binn_get_type_info(int long_type, int *pstorage_type, int *pextra_type) {
+ int storage_type, extra_type;
+ BOOL retval=TRUE;
+
+again:
+
+ if (long_type < 0) {
+ goto loc_invalid;
+ } else if (long_type <= 0xff) {
+ storage_type = long_type & BINN_STORAGE_MASK;
+ extra_type = long_type & BINN_TYPE_MASK;
+ } else if (long_type <= 0xffff) {
+ storage_type = long_type & BINN_STORAGE_MASK16;
+ storage_type >>= 8;
+ extra_type = long_type & BINN_TYPE_MASK16;
+ extra_type >>= 4;
+ } else if (long_type & BINN_STORAGE_VIRTUAL) {
+ //storage_type = BINN_STORAGE_VIRTUAL;
+ //extra_type = xxx;
+ long_type &= 0xffff;
+ goto again;
+ } else {
+loc_invalid:
+ storage_type = -1;
+ extra_type = -1;
+ retval = FALSE;
+ }
+
+ if (pstorage_type) *pstorage_type = storage_type;
+ if (pextra_type) *pextra_type = extra_type;
+
+ return retval;
+
+}
+
+/***************************************************************************/
+
+BOOL APIENTRY binn_create(binn *item, int type, int size, void *pointer) {
+ BOOL retval=FALSE;
+
+ switch (type) {
+ case BINN_LIST:
+ case BINN_MAP:
+ case BINN_OBJECT:
+ break;
+ default:
+ goto loc_exit;
+ }
+
+ if ((item == NULL) || (size < 0)) goto loc_exit;
+ if (size < MIN_BINN_SIZE) {
+ if (pointer) goto loc_exit;
+ else size = 0;
+ }
+
+ memset(item, 0, sizeof(binn));
+
+ if (pointer) {
+ item->pre_allocated = TRUE;
+ item->pbuf = pointer;
+ item->alloc_size = size;
+ } else {
+ item->pre_allocated = FALSE;
+ if (size == 0) size = CHUNK_SIZE;
+ pointer = binn_malloc(size);
+ if (pointer == 0) return INVALID_BINN;
+ item->pbuf = pointer;
+ item->alloc_size = size;
+ }
+
+ item->header = BINN_MAGIC;
+ //item->allocated = FALSE; -- already zeroed
+ item->writable = TRUE;
+ item->used_size = MAX_BINN_HEADER; // save space for the header
+ item->type = type;
+ //item->count = 0; -- already zeroed
+ item->dirty = TRUE; // the header is not written to the buffer
+
+ retval = TRUE;
+
+loc_exit:
+ return retval;
+
+}
+
+/***************************************************************************/
+
+binn * APIENTRY binn_new(int type, int size, void *pointer) {
+ binn *item;
+
+ item = (binn*) binn_malloc(sizeof(binn));
+
+ if (binn_create(item, type, size, pointer) == FALSE) {
+ free_fn(item);
+ return NULL;
+ }
+
+ item->allocated = TRUE;
+ return item;
+
+}
+
+/*************************************************************************************/
+
+BOOL APIENTRY binn_create_list(binn *list) {
+
+ return binn_create(list, BINN_LIST, 0, NULL);
+
+}
+
+/*************************************************************************************/
+
+BOOL APIENTRY binn_create_map(binn *map) {
+
+ return binn_create(map, BINN_MAP, 0, NULL);
+
+}
+
+/*************************************************************************************/
+
+BOOL APIENTRY binn_create_object(binn *object) {
+
+ return binn_create(object, BINN_OBJECT, 0, NULL);
+
+}
+
+/***************************************************************************/
+
+binn * APIENTRY binn_list() {
+ return binn_new(BINN_LIST, 0, 0);
+}
+
+/***************************************************************************/
+
+binn * APIENTRY binn_map() {
+ return binn_new(BINN_MAP, 0, 0);
+}
+
+/***************************************************************************/
+
+binn * APIENTRY binn_object() {
+ return binn_new(BINN_OBJECT, 0, 0);
+}
+
+/*************************************************************************************/
+
+BOOL APIENTRY binn_load(void *data, binn *value) {
+
+ if ((data == NULL) || (value == NULL)) return FALSE;
+ memset(value, 0, sizeof(binn));
+ value->header = BINN_MAGIC;
+ //value->allocated = FALSE; -- already zeroed
+ //value->writable = FALSE;
+
+ if (binn_is_valid(data, &value->type, &value->count, &value->size) == FALSE) return FALSE;
+ value->ptr = data;
+ return TRUE;
+
+}
+
+/*************************************************************************************/
+
+binn * APIENTRY binn_open(void *data) {
+ binn *item;
+
+ item = (binn*) binn_malloc(sizeof(binn));
+
+ if (binn_load(data, item) == FALSE) {
+ free_fn(item);
+ return NULL;
+ }
+
+ item->allocated = TRUE;
+ return item;
+
+}
+
+/***************************************************************************/
+
+BINN_PRIVATE int binn_get_ptr_type(void *ptr) {
+
+ if (ptr == NULL) return 0;
+
+ switch (*(unsigned int *)ptr) {
+ case BINN_MAGIC:
+ return BINN_STRUCT;
+ default:
+ return BINN_BUFFER;
+ }
+
+}
+
+/***************************************************************************/
+
+BOOL APIENTRY binn_is_struct(void *ptr) {
+
+ if (ptr == NULL) return FALSE;
+
+ if ((*(unsigned int *)ptr) == BINN_MAGIC) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+
+}
+
+/***************************************************************************/
+
+BINN_PRIVATE int CalcAllocation(int needed_size, int alloc_size) {
+ int calc_size;
+
+ calc_size = alloc_size;
+ while (calc_size < needed_size) {
+ calc_size <<= 1; // same as *= 2
+ //calc_size += CHUNK_SIZE; -- this is slower than the above line, because there are more reallocations
+ }
+ return calc_size;
+
+}
+
+/***************************************************************************/
+
+BINN_PRIVATE BOOL CheckAllocation(binn *item, int add_size) {
+ int alloc_size;
+ void *ptr;
+
+ if (item->used_size + add_size > item->alloc_size) {
+ if (item->pre_allocated) return FALSE;
+ alloc_size = CalcAllocation(item->used_size + add_size, item->alloc_size);
+ ptr = realloc_fn(item->pbuf, alloc_size);
+ if (ptr == NULL) return FALSE;
+ item->pbuf = ptr;
+ item->alloc_size = alloc_size;
+ }
+
+ return TRUE;
+
+}
+
+/***************************************************************************/
+
+#if BYTE_ORDER == BIG_ENDIAN
+
+BINN_PRIVATE int get_storage_size(int storage_type) {
+
+ switch (storage_type) {
+ case BINN_STORAGE_NOBYTES:
+ return 0;
+ case BINN_STORAGE_BYTE:
+ return 1;
+ case BINN_STORAGE_WORD:
+ return 2;
+ case BINN_STORAGE_DWORD:
+ return 4;
+ case BINN_STORAGE_QWORD:
+ return 8;
+ default:
+ return 0;
+ }
+
+}
+
+#endif
+
+/***************************************************************************/
+
+BINN_PRIVATE unsigned char * AdvanceDataPos(unsigned char *p, unsigned char *plimit) {
+ unsigned char byte;
+ int storage_type, DataSize;
+
+ if (p > plimit) return 0;
+
+ byte = *p; p++;
+ storage_type = byte & BINN_STORAGE_MASK;
+ if (byte & BINN_STORAGE_HAS_MORE) p++;
+
+ switch (storage_type) {
+ case BINN_STORAGE_NOBYTES:
+ //p += 0;
+ break;
+ case BINN_STORAGE_BYTE:
+ p ++;
+ break;
+ case BINN_STORAGE_WORD:
+ p += 2;
+ break;
+ case BINN_STORAGE_DWORD:
+ p += 4;
+ break;
+ case BINN_STORAGE_QWORD:
+ p += 8;
+ break;
+ case BINN_STORAGE_BLOB:
+ if (p + sizeof(int) - 1 > plimit) return 0;
+ copy_be32((u32*)&DataSize, (u32*)p);
+ p += 4 + DataSize;
+ break;
+ case BINN_STORAGE_CONTAINER:
+ if (p > plimit) return 0;
+ DataSize = *((unsigned char*)p);
+ if (DataSize & 0x80) {
+ if (p + sizeof(int) - 1 > plimit) return 0;
+ copy_be32((u32*)&DataSize, (u32*)p);
+ DataSize &= 0x7FFFFFFF;
+ }
+ DataSize--; // remove the type byte already added before
+ p += DataSize;
+ break;
+ case BINN_STORAGE_STRING:
+ if (p > plimit) return 0;
+ DataSize = *((unsigned char*)p);
+ if (DataSize & 0x80) {
+ if (p + sizeof(int) - 1 > plimit) return 0;
+ copy_be32((u32*)&DataSize, (u32*)p);
+ DataSize &= 0x7FFFFFFF;
+ p+=4;
+ } else {
+ p++;
+ }
+ p += DataSize;
+ p++; // null terminator.
+ break;
+ default:
+ return 0;
+ }
+
+ if (p > plimit) return 0;
+
+ return p;
+
+}
+
+/***************************************************************************/
+
+BINN_PRIVATE unsigned char * SearchForID(unsigned char *p, int header_size, int size, int numitems, int id) {
+ unsigned char *plimit, *base;
+ int i, int32;
+
+ base = p;
+ plimit = p + size - 1;
+ p += header_size;
+
+ // search for the ID in all the arguments.
+ for (i = 0; i < numitems; i++) {
+ copy_be32((u32*)&int32, (u32*)p);
+ p += 4;
+ if (p > plimit) break;
+ // Compare if the IDs are equal.
+ if (int32 == id) return p;
+ // xxx
+ p = AdvanceDataPos(p, plimit);
+ if ((p == 0) || (p < base)) break;
+ }
+
+ return NULL;
+
+}
+
+/***************************************************************************/
+
+BINN_PRIVATE unsigned char * SearchForKey(unsigned char *p, int header_size, int size, int numitems, char *key) {
+ unsigned char len, *plimit, *base;
+ int i, keylen;
+
+ base = p;
+ plimit = p + size - 1;
+ p += header_size;
+
+ keylen = strlen(key);
+
+ // search for the key in all the arguments.
+ for (i = 0; i < numitems; i++) {
+ len = *((unsigned char *)p);
+ p++;
+ if (p > plimit) break;
+ // Compare if the strings are equal.
+ if (len > 0) {
+ if (strnicmp((char*)p, key, len) == 0) { // note that there is no null terminator here
+ if (keylen == len) {
+ p += len;
+ return p;
+ }
+ }
+ p += len;
+ if (p > plimit) break;
+ } else if (len == keylen) { // in the case of empty string: ""
+ return p;
+ }
+ // xxx
+ p = AdvanceDataPos(p, plimit);
+ if ((p == 0) || (p < base)) break;
+ }
+
+ return NULL;
+
+}
+
+/***************************************************************************/
+
+BINN_PRIVATE BOOL AddValue(binn *item, int type, void *pvalue, int size);
+
+/***************************************************************************/
+
+BINN_PRIVATE BOOL binn_list_add_raw(binn *item, int type, void *pvalue, int size) {
+
+ if ((item == NULL) || (item->type != BINN_LIST) || (item->writable == FALSE)) return FALSE;
+
+ //if (CheckAllocation(item, 4) == FALSE) return FALSE; // 4 bytes used for data_store and data_format.
+
+ if (AddValue(item, type, pvalue, size) == FALSE) return FALSE;
+
+ item->count++;
+
+ return TRUE;
+
+}
+
+/***************************************************************************/
+
+BINN_PRIVATE BOOL binn_object_set_raw(binn *item, char *key, int type, void *pvalue, int size) {
+ unsigned char *p, len;
+ int int32;
+
+ if ((item == NULL) || (item->type != BINN_OBJECT) || (item->writable == FALSE)) return FALSE;
+
+ if (key == NULL) return FALSE;
+ int32 = strlen(key);
+ if (int32 > 255) return FALSE;
+
+ // is the key already in it?
+ p = SearchForKey((unsigned char*)item->pbuf, MAX_BINN_HEADER, item->used_size, item->count, key);
+ if (p) return FALSE;
+
+ // start adding it
+
+ if (CheckAllocation(item, 1 + int32) == FALSE) return FALSE; // bytes used for the key size and the key itself.
+
+ p = ((unsigned char *) item->pbuf) + item->used_size;
+ len = int32;
+ *p = len;
+ p++;
+ memcpy(p, key, int32);
+ int32++; // now contains the strlen + 1 byte for the len
+ item->used_size += int32;
+
+ if (AddValue(item, type, pvalue, size) == FALSE) {
+ item->used_size -= int32;
+ return FALSE;
+ }
+
+ item->count++;
+
+ return TRUE;
+
+}
+
+/***************************************************************************/
+
+BINN_PRIVATE BOOL binn_map_set_raw(binn *item, int id, int type, void *pvalue, int size) {
+ unsigned char *p;
+
+ if ((item == NULL) || (item->type != BINN_MAP) || (item->writable == FALSE)) return FALSE;
+
+ // is the ID already in it?
+ p = SearchForID((unsigned char*)item->pbuf, MAX_BINN_HEADER, item->used_size, item->count, id);
+ if (p) return FALSE;
+
+ // start adding it
+
+ if (CheckAllocation(item, 4) == FALSE) return FALSE; // 4 bytes used for the id.
+
+ p = ((unsigned char *) item->pbuf) + item->used_size;
+ copy_be32((u32*)p, (u32*)&id);
+ item->used_size += 4;
+
+ if (AddValue(item, type, pvalue, size) == FALSE) {
+ item->used_size -= 4;
+ return FALSE;
+ }
+
+ item->count++;
+
+ return TRUE;
+
+}
+
+/***************************************************************************/
+
+BINN_PRIVATE void * compress_int(int *pstorage_type, int *ptype, void *psource) {
+ int storage_type, storage_type2, type, type2=0;
+ int64 vint;
+ uint64 vuint;
+ char *pvalue;
+#if BYTE_ORDER == BIG_ENDIAN
+ int size1, size2;
+#endif
+
+ storage_type = *pstorage_type;
+ if (storage_type == BINN_STORAGE_BYTE) return psource;
+
+ type = *ptype;
+
+ switch (type) {
+ case BINN_INT64:
+ vint = *(int64*)psource;
+ goto loc_signed;
+ case BINN_INT32:
+ vint = *(int*)psource;
+ goto loc_signed;
+ case BINN_INT16:
+ vint = *(short*)psource;
+ goto loc_signed;
+ case BINN_UINT64:
+ vuint = *(uint64*)psource;
+ goto loc_positive;
+ case BINN_UINT32:
+ vuint = *(unsigned int*)psource;
+ goto loc_positive;
+ case BINN_UINT16:
+ vuint = *(unsigned short*)psource;
+ goto loc_positive;
+ }
+
+loc_signed:
+
+ if (vint >= 0) {
+ vuint = vint;
+ goto loc_positive;
+ }
+
+//loc_negative:
+
+ if (vint >= INT8_MIN) {
+ type2 = BINN_INT8;
+ } else
+ if (vint >= INT16_MIN) {
+ type2 = BINN_INT16;
+ } else
+ if (vint >= INT32_MIN) {
+ type2 = BINN_INT32;
+ }
+ goto loc_exit;
+
+loc_positive:
+
+ if (vuint <= UINT8_MAX) {
+ type2 = BINN_UINT8;
+ } else
+ if (vuint <= UINT16_MAX) {
+ type2 = BINN_UINT16;
+ } else
+ if (vuint <= UINT32_MAX) {
+ type2 = BINN_UINT32;
+ }
+
+loc_exit:
+
+ pvalue = (char *) psource;
+
+ if ((type2) && (type2 != type)) {
+ *ptype = type2;
+ storage_type2 = binn_get_write_storage(type2);
+ *pstorage_type = storage_type2;
+#if BYTE_ORDER == BIG_ENDIAN
+ size1 = get_storage_size(storage_type);
+ size2 = get_storage_size(storage_type2);
+ pvalue += (size1 - size2);
+#endif
+ }
+
+ return pvalue;
+
+}
+
+/***************************************************************************/
+
+BINN_PRIVATE int type_family(int type);
+
+BINN_PRIVATE BOOL AddValue(binn *item, int type, void *pvalue, int size) {
+ int int32, ArgSize, storage_type, extra_type;
+ unsigned char *p;
+
+ binn_get_type_info(type, &storage_type, &extra_type);
+
+ if (pvalue == NULL) {
+ switch (storage_type) {
+ case BINN_STORAGE_NOBYTES:
+ break;
+ case BINN_STORAGE_BLOB:
+ case BINN_STORAGE_STRING:
+ if (size == 0) break; // the 2 above are allowed to have 0 length
+ default:
+ return FALSE;
+ }
+ }
+
+ if ((type_family(type) == BINN_FAMILY_INT) && (item->disable_int_compression == FALSE))
+ pvalue = compress_int(&storage_type, &type, pvalue);
+
+ switch (storage_type) {
+ case BINN_STORAGE_NOBYTES:
+ size = 0;
+ ArgSize = size;
+ break;
+ case BINN_STORAGE_BYTE:
+ size = 1;
+ ArgSize = size;
+ break;
+ case BINN_STORAGE_WORD:
+ size = 2;
+ ArgSize = size;
+ break;
+ case BINN_STORAGE_DWORD:
+ size = 4;
+ ArgSize = size;
+ break;
+ case BINN_STORAGE_QWORD:
+ size = 8;
+ ArgSize = size;
+ break;
+ case BINN_STORAGE_BLOB:
+ if (size < 0) return FALSE;
+ //if (size == 0) ...
+ ArgSize = size + 4;
+ break;
+ case BINN_STORAGE_STRING:
+ if (size < 0) return FALSE;
+ if (size == 0) size = strlen2( (char *) pvalue);
+ ArgSize = size + 5; // at least this size
+ break;
+ case BINN_STORAGE_CONTAINER:
+ if (size <= 0) return FALSE;
+ ArgSize = size;
+ break;
+ default:
+ return FALSE;
+ }
+
+ ArgSize += 2; // at least 2 bytes used for data_type.
+ if (CheckAllocation(item, ArgSize) == FALSE) return FALSE;
+
+ // Gets the pointer to the next place in buffer
+ p = ((unsigned char *) item->pbuf) + item->used_size;
+
+ // If the data is not a container, store the data type
+ if (storage_type != BINN_STORAGE_CONTAINER) {
+ if (type > 255) {
+ u16 type16 = type;
+ copy_be16((u16*)p, (u16*)&type16);
+ p += 2;
+ item->used_size += 2;
+ } else {
+ *p = type;
+ p++;
+ item->used_size++;
+ }
+ }
+
+ switch (storage_type) {
+ case BINN_STORAGE_NOBYTES:
+ // Nothing to do.
+ break;
+ case BINN_STORAGE_BYTE:
+ *((char *) p) = *((char *) pvalue);
+ item->used_size += 1;
+ break;
+ case BINN_STORAGE_WORD:
+ copy_be16((u16*)p, (u16*)pvalue);
+ item->used_size += 2;
+ break;
+ case BINN_STORAGE_DWORD:
+ copy_be32((u32*)p, (u32*)pvalue);
+ item->used_size += 4;
+ break;
+ case BINN_STORAGE_QWORD:
+ copy_be64((u64*)p, (u64*)pvalue);
+ item->used_size += 8;
+ break;
+ case BINN_STORAGE_BLOB:
+ copy_be32((u32*)p, (u32*)&size);
+ p += 4;
+ memcpy(p, pvalue, size);
+ item->used_size += 4 + size;
+ break;
+ case BINN_STORAGE_STRING:
+ if (size > 127) {
+ int32 = size | 0x80000000;
+ copy_be32((u32*)p, (u32*)&int32);
+ p += 4;
+ item->used_size += 4;
+ } else {
+ *((unsigned char *) p) = size;
+ p++;
+ item->used_size++;
+ }
+ memcpy(p, pvalue, size);
+ p += size;
+ *((char *) p) = (char) 0;
+ size++; // null terminator
+ item->used_size += size;
+ break;
+ case BINN_STORAGE_CONTAINER:
+ memcpy(p, pvalue, size);
+ item->used_size += size;
+ break;
+ }
+
+ item->dirty = TRUE;
+
+ return TRUE;
+}
+
+/***************************************************************************/
+
+BINN_PRIVATE BOOL binn_save_header(binn *item) {
+ unsigned char byte, *p;
+ int int32, size;
+
+ if (item == NULL) return FALSE;
+
+#ifndef BINN_DISABLE_SMALL_HEADER
+
+ p = ((unsigned char *) item->pbuf) + MAX_BINN_HEADER;
+ size = item->used_size - MAX_BINN_HEADER + 3; // at least 3 bytes for the header
+
+ // write the count
+ if (item->count > 127) {
+ p -= 4;
+ size += 3;
+ int32 = item->count | 0x80000000;
+ copy_be32((u32*)p, (u32*)&int32);
+ } else {
+ p--;
+ *p = (unsigned char) item->count;
+ }
+
+ // write the size
+ if (size > 127) {
+ p -= 4;
+ size += 3;
+ int32 = size | 0x80000000;
+ copy_be32((u32*)p, (u32*)&int32);
+ } else {
+ p--;
+ *p = (unsigned char) size;
+ }
+
+ // write the type.
+ p--;
+ *p = (unsigned char) item->type;
+
+ // set the values
+ item->ptr = p;
+ item->size = size;
+
+ UNUSED(byte);
+
+#else
+
+ p = (unsigned char *) item->pbuf;
+
+ // write the type.
+ byte = item->type;
+ *p = byte; p++;
+ // write the size
+ int32 = item->used_size | 0x80000000;
+ copy_be32((u32*)p, (u32*)&int32);
+ p+=4;
+ // write the count
+ int32 = item->count | 0x80000000;
+ copy_be32((u32*)p, (u32*)&int32);
+
+ item->ptr = item->pbuf;
+ item->size = item->used_size;
+
+#endif
+
+ item->dirty = FALSE;
+
+ return TRUE;
+
+}
+
+/***************************************************************************/
+
+void APIENTRY binn_free(binn *item) {
+
+ if (item == NULL) return;
+
+ if ((item->writable) && (item->pre_allocated == FALSE)) {
+ free_fn(item->pbuf);
+ }
+
+ if (item->freefn) item->freefn(item->ptr);
+
+ if (item->allocated) {
+ free_fn(item);
+ } else {
+ memset(item, 0, sizeof(binn));
+ item->header = BINN_MAGIC;
+ }
+
+}
+
+/***************************************************************************/
+// free the binn structure but keeps the binn buffer allocated, returning a pointer to it. use the free function to release the buffer later
+void * APIENTRY binn_release(binn *item) {
+ void *data;
+
+ if (item == NULL) return NULL;
+
+ data = binn_ptr(item);
+
+ if (data > item->pbuf) {
+ memmove(item->pbuf, data, item->size);
+ data = item->pbuf;
+ }
+
+ if (item->allocated) {
+ free_fn(item);
+ } else {
+ memset(item, 0, sizeof(binn));
+ item->header = BINN_MAGIC;
+ }
+
+ return data;
+
+}
+
+/***************************************************************************/
+
+BINN_PRIVATE BOOL IsValidBinnHeader(void *pbuf, int *ptype, int *pcount, int *psize, int *pheadersize) {
+ unsigned char byte, *p, *plimit=0;
+ int int32, type, size, count;
+
+ if (pbuf == NULL) return FALSE;
+
+ p = (unsigned char *) pbuf;
+
+ if (psize && *psize > 0) {
+ plimit = p + *psize - 1;
+ }
+
+ // get the type
+ byte = *p; p++;
+ if ((byte & BINN_STORAGE_MASK) != BINN_STORAGE_CONTAINER) return FALSE;
+ if (byte & BINN_STORAGE_HAS_MORE) return FALSE;
+ type = byte;
+
+ switch (type) {
+ case BINN_LIST:
+ case BINN_MAP:
+ case BINN_OBJECT:
+ break;
+ default:
+ return FALSE;
+ }
+
+ // get the size
+ if (plimit && p > plimit) return FALSE;
+ int32 = *((unsigned char*)p);
+ if (int32 & 0x80) {
+ if (plimit && p + sizeof(int) - 1 > plimit) return FALSE;
+ copy_be32((u32*)&int32, (u32*)p);
+ int32 &= 0x7FFFFFFF;
+ p+=4;
+ } else {
+ p++;
+ }
+ size = int32;
+
+ // get the count
+ if (plimit && p > plimit) return FALSE;
+ int32 = *((unsigned char*)p);
+ if (int32 & 0x80) {
+ if (plimit && p + sizeof(int) - 1 > plimit) return FALSE;
+ copy_be32((u32*)&int32, (u32*)p);
+ int32 &= 0x7FFFFFFF;
+ p+=4;
+ } else {
+ p++;
+ }
+ count = int32;
+
+#if 0
+ // get the size
+ copy_be32((u32*)&size, (u32*)p);
+ size &= 0x7FFFFFFF;
+ p+=4;
+
+ // get the count
+ copy_be32((u32*)&count, (u32*)p);
+ count &= 0x7FFFFFFF;
+ p+=4;
+#endif
+
+ if ((size < MIN_BINN_SIZE) || (count < 0)) return FALSE;
+
+ // return the values
+ if (ptype) *ptype = type;
+ if (pcount) *pcount = count;
+ if (psize && *psize==0) *psize = size;
+ if (pheadersize) *pheadersize = (int) (p - (unsigned char*)pbuf);
+ return TRUE;
+}
+
+/***************************************************************************/
+
+BINN_PRIVATE int binn_buf_type(void *pbuf) {
+ int type;
+
+ if (!IsValidBinnHeader(pbuf, &type, NULL, NULL, NULL)) return INVALID_BINN;
+
+ return type;
+
+}
+
+/***************************************************************************/
+
+BINN_PRIVATE int binn_buf_count(void *pbuf) {
+ int nitems;
+
+ if (!IsValidBinnHeader(pbuf, NULL, &nitems, NULL, NULL)) return 0;
+
+ return nitems;
+
+}
+
+/***************************************************************************/
+
+BINN_PRIVATE int binn_buf_size(void *pbuf) {
+ int size=0;
+
+ if (!IsValidBinnHeader(pbuf, NULL, NULL, &size, NULL)) return 0;
+
+ return size;
+
+}
+
+/***************************************************************************/
+
+void * APIENTRY binn_ptr(void *ptr) {
+ binn *item;
+
+ switch (binn_get_ptr_type(ptr)) {
+ case BINN_STRUCT:
+ item = (binn*) ptr;
+ if (item->writable && item->dirty) {
+ binn_save_header(item);
+ }
+ return item->ptr;
+ case BINN_BUFFER:
+ return ptr;
+ default:
+ return NULL;
+ }
+
+}
+
+/***************************************************************************/
+
+int APIENTRY binn_size(void *ptr) {
+ binn *item;
+
+ switch (binn_get_ptr_type(ptr)) {
+ case BINN_STRUCT:
+ item = (binn*) ptr;
+ if (item->writable && item->dirty) {
+ binn_save_header(item);
+ }
+ return item->size;
+ case BINN_BUFFER:
+ return binn_buf_size(ptr);
+ default:
+ return 0;
+ }
+
+}
+
+/***************************************************************************/
+
+int APIENTRY binn_type(void *ptr) {
+ binn *item;
+
+ switch (binn_get_ptr_type(ptr)) {
+ case BINN_STRUCT:
+ item = (binn*) ptr;
+ return item->type;
+ case BINN_BUFFER:
+ return binn_buf_type(ptr);
+ default:
+ return -1;
+ }
+
+}
+
+/***************************************************************************/
+
+int APIENTRY binn_count(void *ptr) {
+ binn *item;
+
+ switch (binn_get_ptr_type(ptr)) {
+ case BINN_STRUCT:
+ item = (binn*) ptr;
+ return item->count;
+ case BINN_BUFFER:
+ return binn_buf_count(ptr);
+ default:
+ return -1;
+ }
+
+}
+
+/***************************************************************************/
+
+BOOL APIENTRY binn_is_valid_ex(void *ptr, int *ptype, int *pcount, int *psize) {
+ int i, type, count, size, header_size;
+ unsigned char *p, *plimit, *base, len;
+ void *pbuf;
+
+ pbuf = binn_ptr(ptr);
+ if (pbuf == NULL) return FALSE;
+
+ // is there an informed size?
+ if (psize && *psize > 0) {
+ size = *psize;
+ } else {
+ size = 0;
+ }
+
+ if (!IsValidBinnHeader(pbuf, &type, &count, &size, &header_size)) return FALSE;
+
+ // is there an informed size?
+ if (psize && *psize > 0) {
+ // is it the same as the one in the buffer?
+ if (size != *psize) return FALSE;
+ }
+ // is there an informed count?
+ if (pcount && *pcount > 0) {
+ // is it the same as the one in the buffer?
+ if (count != *pcount) return FALSE;
+ }
+ // is there an informed type?
+ if (ptype && *ptype != 0) {
+ // is it the same as the one in the buffer?
+ if (type != *ptype) return FALSE;
+ }
+
+ // it could compare the content size with the size informed on the header
+
+ p = (unsigned char *)pbuf;
+ base = p;
+ plimit = p + size;
+
+ p += header_size;
+
+ // process all the arguments.
+ for (i = 0; i < count; i++) {
+ switch (type) {
+ case BINN_OBJECT:
+ // gets the string size (argument name)
+ len = *p;
+ p++;
+ //if (len == 0) goto Invalid;
+ // increment the used space
+ p += len;
+ break;
+ case BINN_MAP:
+ // increment the used space
+ p += 4;
+ break;
+ //case BINN_LIST:
+ // break;
+ }
+ // xxx
+ p = AdvanceDataPos(p, plimit);
+ if ((p == 0) || (p < base)) goto Invalid;
+ }
+
+ if (ptype && *ptype==0) *ptype = type;
+ if (pcount && *pcount==0) *pcount = count;
+ if (psize && *psize==0) *psize = size;
+ return TRUE;
+
+Invalid:
+ return FALSE;
+
+}
+
+/***************************************************************************/
+
+BOOL APIENTRY binn_is_valid(void *ptr, int *ptype, int *pcount, int *psize) {
+
+ if (ptype) *ptype = 0;
+ if (pcount) *pcount = 0;
+ if (psize) *psize = 0;
+
+ return binn_is_valid_ex(ptr, ptype, pcount, psize);
+
+}
+
+/***************************************************************************/
+/*** INTERNAL FUNCTIONS ****************************************************/
+/***************************************************************************/
+
+BINN_PRIVATE BOOL GetValue(unsigned char *p, binn *value) {
+ unsigned char byte;
+ int data_type, storage_type; //, extra_type;
+ int DataSize;
+ void *p2;
+
+ if (value == NULL) return FALSE;
+ memset(value, 0, sizeof(binn));
+ value->header = BINN_MAGIC;
+ //value->allocated = FALSE; -- already zeroed
+ //value->writable = FALSE;
+
+ // saves for use with BINN_STORAGE_CONTAINER
+ p2 = p;
+
+ // read the data type
+ byte = *p; p++;
+ storage_type = byte & BINN_STORAGE_MASK;
+ if (byte & BINN_STORAGE_HAS_MORE) {
+ data_type = byte << 8;
+ byte = *p; p++;
+ data_type |= byte;
+ //extra_type = data_type & BINN_TYPE_MASK16;
+ } else {
+ data_type = byte;
+ //extra_type = byte & BINN_TYPE_MASK;
+ }
+
+ //value->storage_type = storage_type;
+ value->type = data_type;
+
+ switch (storage_type) {
+ case BINN_STORAGE_NOBYTES:
+ break;
+ case BINN_STORAGE_BYTE:
+ value->vuint8 = *((unsigned char *) p);
+ value->ptr = p; //value->ptr = &value->vuint8;
+ break;
+ case BINN_STORAGE_WORD:
+ copy_be16((u16*)&value->vint16, (u16*)p);
+ value->ptr = &value->vint16;
+ break;
+ case BINN_STORAGE_DWORD:
+ copy_be32((u32*)&value->vint32, (u32*)p);
+ value->ptr = &value->vint32;
+ break;
+ case BINN_STORAGE_QWORD:
+ copy_be64((u64*)&value->vint64, (u64*)p);
+ value->ptr = &value->vint64;
+ break;
+ case BINN_STORAGE_BLOB:
+ copy_be32((u32*)&value->size, (u32*)p);
+ p+=4;
+ value->ptr = p;
+ break;
+ case BINN_STORAGE_CONTAINER:
+ value->ptr = p2; // <-- it returns the pointer to the container, not the data
+ if (IsValidBinnHeader(p2, NULL, &value->count, &value->size, NULL) == FALSE) return FALSE;
+ break;
+ case BINN_STORAGE_STRING:
+ DataSize = *((unsigned char*)p);
+ if (DataSize & 0x80) {
+ copy_be32((u32*)&DataSize, (u32*)p);
+ DataSize &= 0x7FFFFFFF;
+ p+=4;
+ } else {
+ p++;
+ }
+ value->size = DataSize;
+ value->ptr = p;
+ break;
+ default:
+ return FALSE;
+ }
+
+ // convert the returned value, if needed
+
+ switch (value->type) {
+ case BINN_TRUE:
+ value->type = BINN_BOOL;
+ value->vbool = TRUE;
+ value->ptr = &value->vbool;
+ break;
+ case BINN_FALSE:
+ value->type = BINN_BOOL;
+ value->vbool = FALSE;
+ value->ptr = &value->vbool;
+ break;
+#ifdef BINN_EXTENDED
+ case BINN_SINGLE_STR:
+ value->type = BINN_SINGLE;
+ value->vfloat = (float) atof((const char*)value->ptr); // converts from string to double, and then to float
+ value->ptr = &value->vfloat;
+ break;
+ case BINN_DOUBLE_STR:
+ value->type = BINN_DOUBLE;
+ value->vdouble = atof((const char*)value->ptr); // converts from string to double
+ value->ptr = &value->vdouble;
+ break;
+#endif
+ /*
+ case BINN_DECIMAL:
+ case BINN_CURRENCYSTR:
+ case BINN_DATE:
+ case BINN_DATETIME:
+ case BINN_TIME:
+ */
+ }
+
+ return TRUE;
+
+}
+
+/***************************************************************************/
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+
+// on little-endian devices we store the value so we can return a pointer to integers.
+// it's valid only for single-threaded apps. multi-threaded apps must use the _get_ functions instead.
+
+binn local_value;
+
+BINN_PRIVATE void * store_value(binn *value) {
+
+ memcpy(&local_value, value, sizeof(binn));
+
+ switch (binn_get_read_storage(value->type)) {
+ case BINN_STORAGE_NOBYTES:
+ // return a valid pointer
+ case BINN_STORAGE_WORD:
+ case BINN_STORAGE_DWORD:
+ case BINN_STORAGE_QWORD:
+ return &local_value.vint32; // returns the pointer to the converted value, from big-endian to little-endian
+ }
+
+ return value->ptr; // returns from the on stack value to be thread-safe (for list, map, object, string and blob)
+
+}
+
+#endif
+
+/***************************************************************************/
+/*** READ FUNCTIONS ********************************************************/
+/***************************************************************************/
+
+BOOL APIENTRY binn_object_get_value(void *ptr, char *key, binn *value) {
+ int type, count, size=0, header_size;
+ unsigned char *p;
+
+ ptr = binn_ptr(ptr);
+ if ((ptr == 0) || (key == 0) || (value == 0)) return FALSE;
+
+ // check the header
+ if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) return FALSE;
+
+ if (type != BINN_OBJECT) return FALSE;
+ if (count == 0) return FALSE;
+
+ p = (unsigned char *) ptr;
+ p = SearchForKey(p, header_size, size, count, key);
+ if (p == FALSE) return FALSE;
+
+ return GetValue(p, value);
+
+}
+
+/***************************************************************************/
+
+BOOL APIENTRY binn_map_get_value(void* ptr, int id, binn *value) {
+ int type, count, size=0, header_size;
+ unsigned char *p;
+
+ ptr = binn_ptr(ptr);
+ if ((ptr == 0) || (value == 0)) return FALSE;
+
+ // check the header
+ if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) return FALSE;
+
+ if (type != BINN_MAP) return FALSE;
+ if (count == 0) return FALSE;
+
+ p = (unsigned char *) ptr;
+ p = SearchForID(p, header_size, size, count, id);
+ if (p == FALSE) return FALSE;
+
+ return GetValue(p, value);
+
+}
+
+/***************************************************************************/
+
+BOOL APIENTRY binn_list_get_value(void* ptr, int pos, binn *value) {
+ int i, type, count, size=0, header_size;
+ unsigned char *p, *plimit, *base;
+
+ ptr = binn_ptr(ptr);
+ if ((ptr == 0) || (value == 0)) return FALSE;
+
+ // check the header
+ if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) return FALSE;
+
+ if (type != BINN_LIST) return FALSE;
+ if (count == 0) return FALSE;
+ if ((pos <= 0) | (pos > count)) return FALSE;
+ pos--; // convert from base 1 to base 0
+
+ p = (unsigned char *) ptr;
+ base = p;
+ plimit = p + size;
+ p += header_size;
+
+ for (i = 0; i < pos; i++) {
+ p = AdvanceDataPos(p, plimit);
+ if ((p == 0) || (p < base)) return FALSE;
+ }
+
+ return GetValue(p, value);
+
+}
+
+/***************************************************************************/
+/*** READ PAIR BY POSITION *************************************************/
+/***************************************************************************/
+
+BINN_PRIVATE BOOL binn_read_pair(int expected_type, void *ptr, int pos, int *pid, char *pkey, binn *value) {
+ int type, count, size=0, header_size;
+ int i, int32, id, counter=0;
+ unsigned char *p, *plimit, *base, *key, len;
+
+ ptr = binn_ptr(ptr);
+
+ // check the header
+ if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) return FALSE;
+
+ if ((type != expected_type) || (count == 0) || (pos < 1) || (pos > count)) return FALSE;
+
+ p = (unsigned char *) ptr;
+ base = p;
+ plimit = p + size - 1;
+ p += header_size;
+
+ for (i = 0; i < count; i++) {
+ switch (type) {
+ case BINN_MAP:
+ copy_be32((u32*)&int32, (u32*)p);
+ p += 4;
+ if (p > plimit) return FALSE;
+ id = int32;
+ break;
+ case BINN_OBJECT:
+ len = *((unsigned char *)p); p++;
+ if (p > plimit) return FALSE;
+ key = p;
+ p += len;
+ if (p > plimit) return FALSE;
+ break;
+ }
+ counter++;
+ if (counter == pos) goto found;
+ //
+ p = AdvanceDataPos(p, plimit);
+ if ((p == 0) || (p < base)) return FALSE;
+ }
+
+ return FALSE;
+
+found:
+
+ switch (type) {
+ case BINN_MAP:
+ if (pid) *pid = id;
+ break;
+ case BINN_OBJECT:
+ if (pkey) {
+ memcpy(pkey, key, len);
+ pkey[len] = 0;
+ }
+ break;
+ }
+
+ return GetValue(p, value);
+
+}
+
+/***************************************************************************/
+
+BOOL APIENTRY binn_map_get_pair(void *ptr, int pos, int *pid, binn *value) {
+
+ return binn_read_pair(BINN_MAP, ptr, pos, pid, NULL, value);
+
+}
+
+/***************************************************************************/
+
+BOOL APIENTRY binn_object_get_pair(void *ptr, int pos, char *pkey, binn *value) {
+
+ return binn_read_pair(BINN_OBJECT, ptr, pos, NULL, pkey, value);
+
+}
+
+/***************************************************************************/
+
+binn * APIENTRY binn_map_pair(void *map, int pos, int *pid) {
+ binn *value;
+
+ value = (binn *) binn_malloc(sizeof(binn));
+
+ if (binn_read_pair(BINN_MAP, map, pos, pid, NULL, value) == FALSE) {
+ free_fn(value);
+ return NULL;
+ }
+
+ value->allocated = TRUE;
+ return value;
+
+}
+
+/***************************************************************************/
+
+binn * APIENTRY binn_object_pair(void *obj, int pos, char *pkey) {
+ binn *value;
+
+ value = (binn *) binn_malloc(sizeof(binn));
+
+ if (binn_read_pair(BINN_OBJECT, obj, pos, NULL, pkey, value) == FALSE) {
+ free_fn(value);
+ return NULL;
+ }
+
+ value->allocated = TRUE;
+ return value;
+
+}
+
+/***************************************************************************/
+/***************************************************************************/
+
+void * APIENTRY binn_map_read_pair(void *ptr, int pos, int *pid, int *ptype, int *psize) {
+ binn value;
+
+ if (binn_map_get_pair(ptr, pos, pid, &value) == FALSE) return NULL;
+ if (ptype) *ptype = value.type;
+ if (psize) *psize = value.size;
+#if BYTE_ORDER == LITTLE_ENDIAN
+ return store_value(&value);
+#else
+ return value.ptr;
+#endif
+
+}
+
+/***************************************************************************/
+
+void * APIENTRY binn_object_read_pair(void *ptr, int pos, char *pkey, int *ptype, int *psize) {
+ binn value;
+
+ if (binn_object_get_pair(ptr, pos, pkey, &value) == FALSE) return NULL;
+ if (ptype) *ptype = value.type;
+ if (psize) *psize = value.size;
+#if BYTE_ORDER == LITTLE_ENDIAN
+ return store_value(&value);
+#else
+ return value.ptr;
+#endif
+
+}
+
+/***************************************************************************/
+/*** SEQUENTIAL READ FUNCTIONS *********************************************/
+/***************************************************************************/
+
+BOOL APIENTRY binn_iter_init(binn_iter *iter, void *ptr, int expected_type) {
+ int type, count, size=0, header_size;
+
+ ptr = binn_ptr(ptr);
+ if ((ptr == 0) || (iter == 0)) return FALSE;
+ memset(iter, 0, sizeof(binn_iter));
+
+ // check the header
+ if (IsValidBinnHeader(ptr, &type, &count, &size, &header_size) == FALSE) return FALSE;
+
+ if (type != expected_type) return FALSE;
+ //if (count == 0) return FALSE; -- should not be used
+
+ iter->plimit = (unsigned char *)ptr + size - 1;
+ iter->pnext = (unsigned char *)ptr + header_size;
+ iter->count = count;
+ iter->current = 0;
+ iter->type = type;
+
+ return TRUE;
+}
+
+/***************************************************************************/
+
+BOOL APIENTRY binn_list_next(binn_iter *iter, binn *value) {
+ unsigned char *pnow;
+
+ if ((iter == 0) || (iter->pnext == 0) || (iter->pnext > iter->plimit) || (iter->current > iter->count) || (iter->type != BINN_LIST)) return FALSE;
+
+ iter->current++;
+ if (iter->current > iter->count) return FALSE;
+
+ pnow = iter->pnext;
+ iter->pnext = AdvanceDataPos(pnow, iter->plimit);
+ if (iter->pnext != 0 && iter->pnext < pnow) return FALSE;
+
+ return GetValue(pnow, value);
+
+}
+
+/***************************************************************************/
+
+BINN_PRIVATE BOOL binn_read_next_pair(int expected_type, binn_iter *iter, int *pid, char *pkey, binn *value) {
+ int int32, id;
+ unsigned char *p, *key;
+ unsigned short len;
+
+ if ((iter == 0) || (iter->pnext == 0) || (iter->pnext > iter->plimit) || (iter->current > iter->count) || (iter->type != expected_type)) return FALSE;
+
+ iter->current++;
+ if (iter->current > iter->count) return FALSE;
+
+ p = iter->pnext;
+
+ switch (expected_type) {
+ case BINN_MAP:
+ copy_be32((u32*)&int32, (u32*)p);
+ p += 4;
+ if (p > iter->plimit) return FALSE;
+ id = int32;
+ if (pid) *pid = id;
+ break;
+ case BINN_OBJECT:
+ len = *((unsigned char *)p); p++;
+ key = p;
+ p += len;
+ if (p > iter->plimit) return FALSE;
+ if (pkey) {
+ memcpy(pkey, key, len);
+ pkey[len] = 0;
+ }
+ break;
+ }
+
+ iter->pnext = AdvanceDataPos(p, iter->plimit);
+ if (iter->pnext != 0 && iter->pnext < p) return FALSE;
+
+ return GetValue(p, value);
+
+}
+
+/***************************************************************************/
+
+BOOL APIENTRY binn_map_next(binn_iter *iter, int *pid, binn *value) {
+
+ return binn_read_next_pair(BINN_MAP, iter, pid, NULL, value);
+
+}
+
+/***************************************************************************/
+
+BOOL APIENTRY binn_object_next(binn_iter *iter, char *pkey, binn *value) {
+
+ return binn_read_next_pair(BINN_OBJECT, iter, NULL, pkey, value);
+
+}
+
+/***************************************************************************/
+/***************************************************************************/
+
+binn * APIENTRY binn_list_next_value(binn_iter *iter) {
+ binn *value;
+
+ value = (binn *) binn_malloc(sizeof(binn));
+
+ if (binn_list_next(iter, value) == FALSE) {
+ free_fn(value);
+ return NULL;
+ }
+
+ value->allocated = TRUE;
+ return value;
+
+}
+
+/***************************************************************************/
+
+binn * APIENTRY binn_map_next_value(binn_iter *iter, int *pid) {
+ binn *value;
+
+ value = (binn *) binn_malloc(sizeof(binn));
+
+ if (binn_map_next(iter, pid, value) == FALSE) {
+ free_fn(value);
+ return NULL;
+ }
+
+ value->allocated = TRUE;
+ return value;
+
+}
+
+/***************************************************************************/
+
+binn * APIENTRY binn_object_next_value(binn_iter *iter, char *pkey) {
+ binn *value;
+
+ value = (binn *) binn_malloc(sizeof(binn));
+
+ if (binn_object_next(iter, pkey, value) == FALSE) {
+ free_fn(value);
+ return NULL;
+ }
+
+ value->allocated = TRUE;
+ return value;
+
+}
+
+/***************************************************************************/
+/***************************************************************************/
+
+void * APIENTRY binn_list_read_next(binn_iter *iter, int *ptype, int *psize) {
+ binn value;
+
+ if (binn_list_next(iter, &value) == FALSE) return NULL;
+ if (ptype) *ptype = value.type;
+ if (psize) *psize = value.size;
+#if BYTE_ORDER == LITTLE_ENDIAN
+ return store_value(&value);
+#else
+ return value.ptr;
+#endif
+
+}
+
+/***************************************************************************/
+
+void * APIENTRY binn_map_read_next(binn_iter *iter, int *pid, int *ptype, int *psize) {
+ binn value;
+
+ if (binn_map_next(iter, pid, &value) == FALSE) return NULL;
+ if (ptype) *ptype = value.type;
+ if (psize) *psize = value.size;
+#if BYTE_ORDER == LITTLE_ENDIAN
+ return store_value(&value);
+#else
+ return value.ptr;
+#endif
+
+}
+
+/***************************************************************************/
+
+void * APIENTRY binn_object_read_next(binn_iter *iter, char *pkey, int *ptype, int *psize) {
+ binn value;
+
+ if (binn_object_next(iter, pkey, &value) == FALSE) return NULL;
+ if (ptype) *ptype = value.type;
+ if (psize) *psize = value.size;
+#if BYTE_ORDER == LITTLE_ENDIAN
+ return store_value(&value);
+#else
+ return value.ptr;
+#endif
+
+}
+
+/*************************************************************************************/
+/****** EXTENDED INTERFACE ***********************************************************/
+/****** none of the functions above call the functions below *************************/
+/*************************************************************************************/
+
+int APIENTRY binn_get_write_storage(int type) {
+ int storage_type;
+
+ switch (type) {
+ case BINN_SINGLE_STR:
+ case BINN_DOUBLE_STR:
+ return BINN_STORAGE_STRING;
+
+ case BINN_BOOL:
+ return BINN_STORAGE_NOBYTES;
+
+ default:
+ binn_get_type_info(type, &storage_type, NULL);
+ return storage_type;
+ }
+
+}
+
+/*************************************************************************************/
+
+int APIENTRY binn_get_read_storage(int type) {
+ int storage_type;
+
+ switch (type) {
+#ifdef BINN_EXTENDED
+ case BINN_SINGLE_STR:
+ return BINN_STORAGE_DWORD;
+ case BINN_DOUBLE_STR:
+ return BINN_STORAGE_QWORD;
+#endif
+ case BINN_BOOL:
+ case BINN_TRUE:
+ case BINN_FALSE:
+ return BINN_STORAGE_DWORD;
+ default:
+ binn_get_type_info(type, &storage_type, NULL);
+ return storage_type;
+ }
+
+}
+
+/*************************************************************************************/
+
+BINN_PRIVATE BOOL GetWriteConvertedData(int *ptype, void **ppvalue, int *psize) {
+ int type;
+ float f1;
+ double d1;
+ char pstr[128];
+
+ UNUSED(pstr);
+ UNUSED(d1);
+ UNUSED(f1);
+
+ type = *ptype;
+
+ if (*ppvalue == NULL) {
+ switch (type) {
+ case BINN_NULL:
+ case BINN_TRUE:
+ case BINN_FALSE:
+ break;
+ case BINN_STRING:
+ case BINN_BLOB:
+ if (*psize == 0) break;
+ default:
+ return FALSE;
+ }
+ }
+
+ switch (type) {
+#ifdef BINN_EXTENDED
+ case BINN_SINGLE:
+ f1 = **(float**)ppvalue;
+ d1 = f1; // convert from float (32bits) to double (64bits)
+ type = BINN_SINGLE_STR;
+ goto conv_double;
+ case BINN_DOUBLE:
+ d1 = **(double**)ppvalue;
+ type = BINN_DOUBLE_STR;
+conv_double:
+ // the '%.17e' is more precise than the '%g'
+ snprintf(pstr, 127, "%.17e", d1);
+ *ppvalue = pstr;
+ *ptype = type;
+ break;
+#endif
+ case BINN_DECIMAL:
+ case BINN_CURRENCYSTR:
+ /*
+ if (binn_malloc_extptr(128) == NULL) return FALSE;
+ snprintf(sptr, 127, "%E", **ppvalue);
+ *ppvalue = sptr;
+ */
+ return TRUE; //! temporary
+ break;
+
+ case BINN_DATE:
+ case BINN_DATETIME:
+ case BINN_TIME:
+ return TRUE; //! temporary
+ break;
+
+ case BINN_BOOL:
+ if (**((BOOL**)ppvalue) == FALSE) {
+ type = BINN_FALSE;
+ } else {
+ type = BINN_TRUE;
+ }
+ *ptype = type;
+ break;
+
+ }
+
+ return TRUE;
+
+}
+
+/*************************************************************************************/
+
+BINN_PRIVATE int type_family(int type) {
+
+ switch (type) {
+ case BINN_LIST:
+ case BINN_MAP:
+ case BINN_OBJECT:
+ return BINN_FAMILY_BINN;
+
+ case BINN_INT8:
+ case BINN_INT16:
+ case BINN_INT32:
+ case BINN_INT64:
+ case BINN_UINT8:
+ case BINN_UINT16:
+ case BINN_UINT32:
+ case BINN_UINT64:
+ return BINN_FAMILY_INT;
+
+ case BINN_FLOAT32:
+ case BINN_FLOAT64:
+ //case BINN_SINGLE:
+ case BINN_SINGLE_STR:
+ //case BINN_DOUBLE:
+ case BINN_DOUBLE_STR:
+ return BINN_FAMILY_FLOAT;
+
+ case BINN_STRING:
+ case BINN_HTML:
+ case BINN_CSS:
+ case BINN_XML:
+ case BINN_JSON:
+ case BINN_JAVASCRIPT:
+ return BINN_FAMILY_STRING;
+
+ case BINN_BLOB:
+ case BINN_JPEG:
+ case BINN_GIF:
+ case BINN_PNG:
+ case BINN_BMP:
+ return BINN_FAMILY_BLOB;
+
+ case BINN_DECIMAL:
+ case BINN_CURRENCY:
+ case BINN_DATE:
+ case BINN_TIME:
+ case BINN_DATETIME:
+ return BINN_FAMILY_STRING;
+
+ case BINN_BOOL:
+ return BINN_FAMILY_BOOL;
+
+ case BINN_NULL:
+ return BINN_FAMILY_NULL;
+
+ default:
+ // if it wasn't found
+ return BINN_FAMILY_NONE;
+ }
+
+}
+
+/*************************************************************************************/
+
+BINN_PRIVATE int int_type(int type) {
+
+ switch (type) {
+ case BINN_INT8:
+ case BINN_INT16:
+ case BINN_INT32:
+ case BINN_INT64:
+ return BINN_SIGNED_INT;
+
+ case BINN_UINT8:
+ case BINN_UINT16:
+ case BINN_UINT32:
+ case BINN_UINT64:
+ return BINN_UNSIGNED_INT;
+
+ default:
+ return 0;
+ }
+
+}
+
+/*************************************************************************************/
+
+BINN_PRIVATE BOOL copy_raw_value(void *psource, void *pdest, int data_store) {
+
+ switch (data_store) {
+ case BINN_STORAGE_NOBYTES:
+ break;
+ case BINN_STORAGE_BYTE:
+ *((char *) pdest) = *(char *)psource;
+ break;
+ case BINN_STORAGE_WORD:
+ *((short *) pdest) = *(short *)psource;
+ break;
+ case BINN_STORAGE_DWORD:
+ *((int *) pdest) = *(int *)psource;
+ break;
+ case BINN_STORAGE_QWORD:
+ *((uint64 *) pdest) = *(uint64 *)psource;
+ break;
+ case BINN_STORAGE_BLOB:
+ case BINN_STORAGE_STRING:
+ case BINN_STORAGE_CONTAINER:
+ *((char **) pdest) = (char *)psource;
+ break;
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+
+}
+
+/*************************************************************************************/
+
+BINN_PRIVATE BOOL copy_int_value(void *psource, void *pdest, int source_type, int dest_type) {
+ uint64 vuint64; int64 vint64;
+
+ switch (source_type) {
+ case BINN_INT8:
+ vint64 = *(signed char *)psource;
+ break;
+ case BINN_INT16:
+ vint64 = *(short *)psource;
+ break;
+ case BINN_INT32:
+ vint64 = *(int *)psource;
+ break;
+ case BINN_INT64:
+ vint64 = *(int64 *)psource;
+ break;
+
+ case BINN_UINT8:
+ vuint64 = *(unsigned char *)psource;
+ break;
+ case BINN_UINT16:
+ vuint64 = *(unsigned short *)psource;
+ break;
+ case BINN_UINT32:
+ vuint64 = *(unsigned int *)psource;
+ break;
+ case BINN_UINT64:
+ vuint64 = *(uint64 *)psource;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+
+ // copy from int64 to uint64, if possible
+
+ if ((int_type(source_type) == BINN_UNSIGNED_INT) && (int_type(dest_type) == BINN_SIGNED_INT)) {
+ if (vuint64 > INT64_MAX) return FALSE;
+ vint64 = vuint64;
+ } else if ((int_type(source_type) == BINN_SIGNED_INT) && (int_type(dest_type) == BINN_UNSIGNED_INT)) {
+ if (vint64 < 0) return FALSE;
+ vuint64 = vint64;
+ }
+
+
+ switch (dest_type) {
+ case BINN_INT8:
+ if ((vint64 < INT8_MIN) || (vint64 > INT8_MAX)) return FALSE;
+ *(signed char *)pdest = (signed char) vint64;
+ break;
+ case BINN_INT16:
+ if ((vint64 < INT16_MIN) || (vint64 > INT16_MAX)) return FALSE;
+ *(short *)pdest = (short) vint64;
+ break;
+ case BINN_INT32:
+ if ((vint64 < INT32_MIN) || (vint64 > INT32_MAX)) return FALSE;
+ *(int *)pdest = (int) vint64;
+ break;
+ case BINN_INT64:
+ *(int64 *)pdest = vint64;
+ break;
+
+ case BINN_UINT8:
+ if (vuint64 > UINT8_MAX) return FALSE;
+ *(unsigned char *)pdest = (unsigned char) vuint64;
+ break;
+ case BINN_UINT16:
+ if (vuint64 > UINT16_MAX) return FALSE;
+ *(unsigned short *)pdest = (unsigned short) vuint64;
+ break;
+ case BINN_UINT32:
+ if (vuint64 > UINT32_MAX) return FALSE;
+ *(unsigned int *)pdest = (unsigned int) vuint64;
+ break;
+ case BINN_UINT64:
+ *(uint64 *)pdest = vuint64;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+
+}
+
+/*************************************************************************************/
+
+BINN_PRIVATE BOOL copy_float_value(void *psource, void *pdest, int source_type, int dest_type) {
+
+ switch (source_type) {
+ case BINN_FLOAT32:
+ *(double *)pdest = *(float *)psource;
+ break;
+ case BINN_FLOAT64:
+ *(float *)pdest = (float) *(double *)psource;
+ break;
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+
+}
+
+/*************************************************************************************/
+
+BINN_PRIVATE void zero_value(void *pvalue, int type) {
+ //int size=0;
+
+ switch (binn_get_read_storage(type)) {
+ case BINN_STORAGE_NOBYTES:
+ break;
+ case BINN_STORAGE_BYTE:
+ *((char *) pvalue) = 0;
+ //size=1;
+ break;
+ case BINN_STORAGE_WORD:
+ *((short *) pvalue) = 0;
+ //size=2;
+ break;
+ case BINN_STORAGE_DWORD:
+ *((int *) pvalue) = 0;
+ //size=4;
+ break;
+ case BINN_STORAGE_QWORD:
+ *((uint64 *) pvalue) = 0;
+ //size=8;
+ break;
+ case BINN_STORAGE_BLOB:
+ case BINN_STORAGE_STRING:
+ case BINN_STORAGE_CONTAINER:
+ *(char **)pvalue = NULL;
+ break;
+ }
+
+ //if (size>0) memset(pvalue, 0, size);
+
+}
+
+/*************************************************************************************/
+
+BINN_PRIVATE BOOL copy_value(void *psource, void *pdest, int source_type, int dest_type, int data_store) {
+
+ if (type_family(source_type) != type_family(dest_type)) return FALSE;
+
+ if ((type_family(source_type) == BINN_FAMILY_INT) && (source_type != dest_type)) {
+ return copy_int_value(psource, pdest, source_type, dest_type);
+ } else if ((type_family(source_type) == BINN_FAMILY_FLOAT) && (source_type != dest_type)) {
+ return copy_float_value(psource, pdest, source_type, dest_type);
+ } else {
+ return copy_raw_value(psource, pdest, data_store);
+ }
+
+}
+
+/*************************************************************************************/
+/*** WRITE FUNCTIONS *****************************************************************/
+/*************************************************************************************/
+
+BOOL APIENTRY binn_list_add(binn *list, int type, void *pvalue, int size) {
+
+ if (GetWriteConvertedData(&type, &pvalue, &size) == FALSE) return FALSE;
+
+ return binn_list_add_raw(list, type, pvalue, size);
+
+}
+
+/*************************************************************************************/
+
+BOOL APIENTRY binn_map_set(binn *map, int id, int type, void *pvalue, int size) {
+
+ if (GetWriteConvertedData(&type, &pvalue, &size) == FALSE) return FALSE;
+
+ return binn_map_set_raw(map, id, type, pvalue, size);
+
+}
+
+/*************************************************************************************/
+
+BOOL APIENTRY binn_object_set(binn *obj, char *key, int type, void *pvalue, int size) {
+
+ if (GetWriteConvertedData(&type, &pvalue, &size) == FALSE) return FALSE;
+
+ return binn_object_set_raw(obj, key, type, pvalue, size);
+
+}
+
+/*************************************************************************************/
+
+// this function is used by the wrappers
+BOOL APIENTRY binn_add_value(binn *item, int binn_type, int id, char *name, int type, void *pvalue, int size) {
+
+ switch (binn_type) {
+ case BINN_LIST:
+ return binn_list_add(item, type, pvalue, size);
+ case BINN_MAP:
+ return binn_map_set(item, id, type, pvalue, size);
+ case BINN_OBJECT:
+ return binn_object_set(item, name, type, pvalue, size);
+ default:
+ return FALSE;
+ }
+
+}
+
+/*************************************************************************************/
+/*************************************************************************************/
+
+BOOL APIENTRY binn_list_add_new(binn *list, binn *value) {
+ BOOL retval;
+
+ retval = binn_list_add_value(list, value);
+ if (value) free_fn(value);
+ return retval;
+
+}
+
+/*************************************************************************************/
+
+BOOL APIENTRY binn_map_set_new(binn *map, int id, binn *value) {
+ BOOL retval;
+
+ retval = binn_map_set_value(map, id, value);
+ if (value) free_fn(value);
+ return retval;
+
+}
+
+/*************************************************************************************/
+
+BOOL APIENTRY binn_object_set_new(binn *obj, char *key, binn *value) {
+ BOOL retval;
+
+ retval = binn_object_set_value(obj, key, value);
+ if (value) free_fn(value);
+ return retval;
+
+}
+
+/*************************************************************************************/
+/*** READ FUNCTIONS ******************************************************************/
+/*************************************************************************************/
+
+binn * APIENTRY binn_list_value(void *ptr, int pos) {
+ binn *value;
+
+ value = (binn *) binn_malloc(sizeof(binn));
+
+ if (binn_list_get_value(ptr, pos, value) == FALSE) {
+ free_fn(value);
+ return NULL;
+ }
+
+ value->allocated = TRUE;
+ return value;
+
+}
+
+/*************************************************************************************/
+
+binn * APIENTRY binn_map_value(void *ptr, int id) {
+ binn *value;
+
+ value = (binn *) binn_malloc(sizeof(binn));
+
+ if (binn_map_get_value(ptr, id, value) == FALSE) {
+ free_fn(value);
+ return NULL;
+ }
+
+ value->allocated = TRUE;
+ return value;
+
+}
+
+/*************************************************************************************/
+
+binn * APIENTRY binn_object_value(void *ptr, char *key) {
+ binn *value;
+
+ value = (binn *) binn_malloc(sizeof(binn));
+
+ if (binn_object_get_value(ptr, key, value) == FALSE) {
+ free_fn(value);
+ return NULL;
+ }
+
+ value->allocated = TRUE;
+ return value;
+
+}
+
+/***************************************************************************/
+/***************************************************************************/
+
+void * APIENTRY binn_list_read(void *list, int pos, int *ptype, int *psize) {
+ binn value;
+
+ if (binn_list_get_value(list, pos, &value) == FALSE) return NULL;
+ if (ptype) *ptype = value.type;
+ if (psize) *psize = value.size;
+#if BYTE_ORDER == LITTLE_ENDIAN
+ return store_value(&value);
+#else
+ return value.ptr;
+#endif
+
+}
+
+/***************************************************************************/
+
+void * APIENTRY binn_map_read(void *map, int id, int *ptype, int *psize) {
+ binn value;
+
+ if (binn_map_get_value(map, id, &value) == FALSE) return NULL;
+ if (ptype) *ptype = value.type;
+ if (psize) *psize = value.size;
+#if BYTE_ORDER == LITTLE_ENDIAN
+ return store_value(&value);
+#else
+ return value.ptr;
+#endif
+
+}
+
+/***************************************************************************/
+
+void * APIENTRY binn_object_read(void *obj, char *key, int *ptype, int *psize) {
+ binn value;
+
+ if (binn_object_get_value(obj, key, &value) == FALSE) return NULL;
+ if (ptype) *ptype = value.type;
+ if (psize) *psize = value.size;
+#if BYTE_ORDER == LITTLE_ENDIAN
+ return store_value(&value);
+#else
+ return value.ptr;
+#endif
+
+}
+
+/***************************************************************************/
+/***************************************************************************/
+
+BOOL APIENTRY binn_list_get(void *ptr, int pos, int type, void *pvalue, int *psize) {
+ binn value;
+ int storage_type;
+
+ storage_type = binn_get_read_storage(type);
+ if ((storage_type != BINN_STORAGE_NOBYTES) && (pvalue == NULL)) return FALSE;
+
+ zero_value(pvalue, type);
+
+ if (binn_list_get_value(ptr, pos, &value) == FALSE) return FALSE;
+
+ if (copy_value(value.ptr, pvalue, value.type, type, storage_type) == FALSE) return FALSE;
+
+ if (psize) *psize = value.size;
+
+ return TRUE;
+
+}
+
+/***************************************************************************/
+
+BOOL APIENTRY binn_map_get(void *ptr, int id, int type, void *pvalue, int *psize) {
+ binn value;
+ int storage_type;
+
+ storage_type = binn_get_read_storage(type);
+ if ((storage_type != BINN_STORAGE_NOBYTES) && (pvalue == NULL)) return FALSE;
+
+ zero_value(pvalue, type);
+
+ if (binn_map_get_value(ptr, id, &value) == FALSE) return FALSE;
+
+ if (copy_value(value.ptr, pvalue, value.type, type, storage_type) == FALSE) return FALSE;
+
+ if (psize) *psize = value.size;
+
+ return TRUE;
+
+}
+
+/***************************************************************************/
+
+// if (binn_object_get(obj, "multiplier", BINN_INT32, &multiplier, NULL) == FALSE) xxx;
+
+BOOL APIENTRY binn_object_get(void *ptr, char *key, int type, void *pvalue, int *psize) {
+ binn value;
+ int storage_type;
+
+ storage_type = binn_get_read_storage(type);
+ if ((storage_type != BINN_STORAGE_NOBYTES) && (pvalue == NULL)) return FALSE;
+
+ zero_value(pvalue, type);
+
+ if (binn_object_get_value(ptr, key, &value) == FALSE) return FALSE;
+
+ if (copy_value(value.ptr, pvalue, value.type, type, storage_type) == FALSE) return FALSE;
+
+ if (psize) *psize = value.size;
+
+ return TRUE;
+
+}
+
+/***************************************************************************/
+/***************************************************************************/
+
+// these functions below may not be implemented as inline functions, because
+// they use a lot of space, even for the variable. so they will be exported.
+
+// but what about using as static?
+// is there any problem with wrappers? can these wrappers implement these functions using the header?
+// if as static, will they be present even on modules that don't use the functions?
+
+signed char APIENTRY binn_list_int8(void *list, int pos) {
+ signed char value;
+
+ binn_list_get(list, pos, BINN_INT8, &value, NULL);
+
+ return value;
+}
+
+short APIENTRY binn_list_int16(void *list, int pos) {
+ short value;
+
+ binn_list_get(list, pos, BINN_INT16, &value, NULL);
+
+ return value;
+}
+
+int APIENTRY binn_list_int32(void *list, int pos) {
+ int value;
+
+ binn_list_get(list, pos, BINN_INT32, &value, NULL);
+
+ return value;
+}
+
+int64 APIENTRY binn_list_int64(void *list, int pos) {
+ int64 value;
+
+ binn_list_get(list, pos, BINN_INT64, &value, NULL);
+
+ return value;
+}
+
+unsigned char APIENTRY binn_list_uint8(void *list, int pos) {
+ unsigned char value;
+
+ binn_list_get(list, pos, BINN_UINT8, &value, NULL);
+
+ return value;
+}
+
+unsigned short APIENTRY binn_list_uint16(void *list, int pos) {
+ unsigned short value;
+
+ binn_list_get(list, pos, BINN_UINT16, &value, NULL);
+
+ return value;
+}
+
+unsigned int APIENTRY binn_list_uint32(void *list, int pos) {
+ unsigned int value;
+
+ binn_list_get(list, pos, BINN_UINT32, &value, NULL);
+
+ return value;
+}
+
+uint64 APIENTRY binn_list_uint64(void *list, int pos) {
+ uint64 value;
+
+ binn_list_get(list, pos, BINN_UINT64, &value, NULL);
+
+ return value;
+}
+
+float APIENTRY binn_list_float(void *list, int pos) {
+ float value;
+
+ binn_list_get(list, pos, BINN_FLOAT32, &value, NULL);
+
+ return value;
+}
+
+double APIENTRY binn_list_double(void *list, int pos) {
+ double value;
+
+ binn_list_get(list, pos, BINN_FLOAT64, &value, NULL);
+
+ return value;
+}
+
+BOOL APIENTRY binn_list_bool(void *list, int pos) {
+ BOOL value;
+
+ binn_list_get(list, pos, BINN_BOOL, &value, NULL);
+
+ return value;
+}
+
+BOOL APIENTRY binn_list_null(void *list, int pos) {
+
+ return binn_list_get(list, pos, BINN_NULL, NULL, NULL);
+
+}
+
+char * APIENTRY binn_list_str(void *list, int pos) {
+ char *value;
+
+ binn_list_get(list, pos, BINN_STRING, &value, NULL);
+
+ return value;
+}
+
+void * APIENTRY binn_list_blob(void *list, int pos, int *psize) {
+ void *value;
+
+ binn_list_get(list, pos, BINN_BLOB, &value, psize);
+
+ return value;
+}
+
+void * APIENTRY binn_list_list(void *list, int pos) {
+ void *value;
+
+ binn_list_get(list, pos, BINN_LIST, &value, NULL);
+
+ return value;
+}
+
+void * APIENTRY binn_list_map(void *list, int pos) {
+ void *value;
+
+ binn_list_get(list, pos, BINN_MAP, &value, NULL);
+
+ return value;
+}
+
+void * APIENTRY binn_list_object(void *list, int pos) {
+ void *value;
+
+ binn_list_get(list, pos, BINN_OBJECT, &value, NULL);
+
+ return value;
+}
+
+/***************************************************************************/
+
+signed char APIENTRY binn_map_int8(void *map, int id) {
+ signed char value;
+
+ binn_map_get(map, id, BINN_INT8, &value, NULL);
+
+ return value;
+}
+
+short APIENTRY binn_map_int16(void *map, int id) {
+ short value;
+
+ binn_map_get(map, id, BINN_INT16, &value, NULL);
+
+ return value;
+}
+
+int APIENTRY binn_map_int32(void *map, int id) {
+ int value;
+
+ binn_map_get(map, id, BINN_INT32, &value, NULL);
+
+ return value;
+}
+
+int64 APIENTRY binn_map_int64(void *map, int id) {
+ int64 value;
+
+ binn_map_get(map, id, BINN_INT64, &value, NULL);
+
+ return value;
+}
+
+unsigned char APIENTRY binn_map_uint8(void *map, int id) {
+ unsigned char value;
+
+ binn_map_get(map, id, BINN_UINT8, &value, NULL);
+
+ return value;
+}
+
+unsigned short APIENTRY binn_map_uint16(void *map, int id) {
+ unsigned short value;
+
+ binn_map_get(map, id, BINN_UINT16, &value, NULL);
+
+ return value;
+}
+
+unsigned int APIENTRY binn_map_uint32(void *map, int id) {
+ unsigned int value;
+
+ binn_map_get(map, id, BINN_UINT32, &value, NULL);
+
+ return value;
+}
+
+uint64 APIENTRY binn_map_uint64(void *map, int id) {
+ uint64 value;
+
+ binn_map_get(map, id, BINN_UINT64, &value, NULL);
+
+ return value;
+}
+
+float APIENTRY binn_map_float(void *map, int id) {
+ float value;
+
+ binn_map_get(map, id, BINN_FLOAT32, &value, NULL);
+
+ return value;
+}
+
+double APIENTRY binn_map_double(void *map, int id) {
+ double value;
+
+ binn_map_get(map, id, BINN_FLOAT64, &value, NULL);
+
+ return value;
+}
+
+BOOL APIENTRY binn_map_bool(void *map, int id) {
+ BOOL value;
+
+ binn_map_get(map, id, BINN_BOOL, &value, NULL);
+
+ return value;
+}
+
+BOOL APIENTRY binn_map_null(void *map, int id) {
+
+ return binn_map_get(map, id, BINN_NULL, NULL, NULL);
+
+}
+
+char * APIENTRY binn_map_str(void *map, int id) {
+ char *value;
+
+ binn_map_get(map, id, BINN_STRING, &value, NULL);
+
+ return value;
+}
+
+void * APIENTRY binn_map_blob(void *map, int id, int *psize) {
+ void *value;
+
+ binn_map_get(map, id, BINN_BLOB, &value, psize);
+
+ return value;
+}
+
+void * APIENTRY binn_map_list(void *map, int id) {
+ void *value;
+
+ binn_map_get(map, id, BINN_LIST, &value, NULL);
+
+ return value;
+}
+
+void * APIENTRY binn_map_map(void *map, int id) {
+ void *value;
+
+ binn_map_get(map, id, BINN_MAP, &value, NULL);
+
+ return value;
+}
+
+void * APIENTRY binn_map_object(void *map, int id) {
+ void *value;
+
+ binn_map_get(map, id, BINN_OBJECT, &value, NULL);
+
+ return value;
+}
+
+/***************************************************************************/
+
+signed char APIENTRY binn_object_int8(void *obj, char *key) {
+ signed char value;
+
+ binn_object_get(obj, key, BINN_INT8, &value, NULL);
+
+ return value;
+}
+
+short APIENTRY binn_object_int16(void *obj, char *key) {
+ short value;
+
+ binn_object_get(obj, key, BINN_INT16, &value, NULL);
+
+ return value;
+}
+
+int APIENTRY binn_object_int32(void *obj, char *key) {
+ int value;
+
+ binn_object_get(obj, key, BINN_INT32, &value, NULL);
+
+ return value;
+}
+
+int64 APIENTRY binn_object_int64(void *obj, char *key) {
+ int64 value;
+
+ binn_object_get(obj, key, BINN_INT64, &value, NULL);
+
+ return value;
+}
+
+unsigned char APIENTRY binn_object_uint8(void *obj, char *key) {
+ unsigned char value;
+
+ binn_object_get(obj, key, BINN_UINT8, &value, NULL);
+
+ return value;
+}
+
+unsigned short APIENTRY binn_object_uint16(void *obj, char *key) {
+ unsigned short value;
+
+ binn_object_get(obj, key, BINN_UINT16, &value, NULL);
+
+ return value;
+}
+
+unsigned int APIENTRY binn_object_uint32(void *obj, char *key) {
+ unsigned int value;
+
+ binn_object_get(obj, key, BINN_UINT32, &value, NULL);
+
+ return value;
+}
+
+uint64 APIENTRY binn_object_uint64(void *obj, char *key) {
+ uint64 value;
+
+ binn_object_get(obj, key, BINN_UINT64, &value, NULL);
+
+ return value;
+}
+
+float APIENTRY binn_object_float(void *obj, char *key) {
+ float value;
+
+ binn_object_get(obj, key, BINN_FLOAT32, &value, NULL);
+
+ return value;
+}
+
+double APIENTRY binn_object_double(void *obj, char *key) {
+ double value;
+
+ binn_object_get(obj, key, BINN_FLOAT64, &value, NULL);
+
+ return value;
+}
+
+BOOL APIENTRY binn_object_bool(void *obj, char *key) {
+ BOOL value;
+
+ binn_object_get(obj, key, BINN_BOOL, &value, NULL);
+
+ return value;
+}
+
+BOOL APIENTRY binn_object_null(void *obj, char *key) {
+
+ return binn_object_get(obj, key, BINN_NULL, NULL, NULL);
+
+}
+
+char * APIENTRY binn_object_str(void *obj, char *key) {
+ char *value;
+
+ binn_object_get(obj, key, BINN_STRING, &value, NULL);
+
+ return value;
+}
+
+void * APIENTRY binn_object_blob(void *obj, char *key, int *psize) {
+ void *value;
+
+ binn_object_get(obj, key, BINN_BLOB, &value, psize);
+
+ return value;
+}
+
+void * APIENTRY binn_object_list(void *obj, char *key) {
+ void *value;
+
+ binn_object_get(obj, key, BINN_LIST, &value, NULL);
+
+ return value;
+}
+
+void * APIENTRY binn_object_map(void *obj, char *key) {
+ void *value;
+
+ binn_object_get(obj, key, BINN_MAP, &value, NULL);
+
+ return value;
+}
+
+void * APIENTRY binn_object_object(void *obj, char *key) {
+ void *value;
+
+ binn_object_get(obj, key, BINN_OBJECT, &value, NULL);
+
+ return value;
+}
+
+/*************************************************************************************/
+/*************************************************************************************/
+
+BINN_PRIVATE binn * binn_alloc_item() {
+ binn *item;
+ item = (binn *) binn_malloc(sizeof(binn));
+ if (item) {
+ memset(item, 0, sizeof(binn));
+ item->header = BINN_MAGIC;
+ item->allocated = TRUE;
+ //item->writable = FALSE; -- already zeroed
+ }
+ return item;
+}
+
+/*************************************************************************************/
+
+binn * APIENTRY binn_value(int type, void *pvalue, int size, binn_mem_free freefn) {
+ int storage_type;
+ binn *item = binn_alloc_item();
+ if (item) {
+ item->type = type;
+ binn_get_type_info(type, &storage_type, NULL);
+ switch (storage_type) {
+ case BINN_STORAGE_NOBYTES:
+ break;
+ case BINN_STORAGE_STRING:
+ if (size == 0) size = strlen((char*)pvalue) + 1;
+ case BINN_STORAGE_BLOB:
+ case BINN_STORAGE_CONTAINER:
+ if (freefn == BINN_TRANSIENT) {
+ item->ptr = binn_memdup(pvalue, size);
+ if (item->ptr == NULL) {
+ free_fn(item);
+ return NULL;
+ }
+ item->freefn = free_fn;
+ if (storage_type == BINN_STORAGE_STRING) size--;
+ } else {
+ item->ptr = pvalue;
+ item->freefn = freefn;
+ }
+ item->size = size;
+ break;
+ default:
+ item->ptr = &item->vint32;
+ copy_raw_value(pvalue, item->ptr, storage_type);
+ }
+ }
+ return item;
+}
+
+/*************************************************************************************/
+
+BOOL APIENTRY binn_set_string(binn *item, char *str, binn_mem_free pfree) {
+
+ if (item == NULL || str == NULL) return FALSE;
+
+ if (pfree == BINN_TRANSIENT) {
+ item->ptr = binn_memdup(str, strlen(str) + 1);
+ if (item->ptr == NULL) return FALSE;
+ item->freefn = free_fn;
+ } else {
+ item->ptr = str;
+ item->freefn = pfree;
+ }
+
+ item->type = BINN_STRING;
+ return TRUE;
+
+}
+
+/*************************************************************************************/
+
+BOOL APIENTRY binn_set_blob(binn *item, void *ptr, int size, binn_mem_free pfree) {
+
+ if (item == NULL || ptr == NULL) return FALSE;
+
+ if (pfree == BINN_TRANSIENT) {
+ item->ptr = binn_memdup(ptr, size);
+ if (item->ptr == NULL) return FALSE;
+ item->freefn = free_fn;
+ } else {
+ item->ptr = ptr;
+ item->freefn = pfree;
+ }
+
+ item->type = BINN_BLOB;
+ item->size = size;
+ return TRUE;
+
+}
+
+/*************************************************************************************/
+/*** READ CONVERTED VALUE ************************************************************/
+/*************************************************************************************/
+
+#ifdef _MSC_VER
+#define atoi64 _atoi64
+#else
+int64 atoi64(char *str) {
+ int64 retval;
+ int is_negative=0;
+
+ if (*str == '-') {
+ is_negative = 1;
+ str++;
+ }
+ retval = 0;
+ for (; *str; str++) {
+ retval = 10 * retval + (*str - '0');
+ }
+ if (is_negative) retval *= -1;
+ return retval;
+}
+#endif
+
+/*****************************************************************************/
+
+BINN_PRIVATE BOOL is_integer(char *p) {
+ BOOL retval;
+
+ if (p == NULL) return FALSE;
+ if (*p == '-') p++;
+ if (*p == 0) return FALSE;
+
+ retval = TRUE;
+
+ for (; *p; p++) {
+ if ( (*p < '0') || (*p > '9') ) {
+ retval = FALSE;
+ }
+ }
+
+ return retval;
+}
+
+/*****************************************************************************/
+
+BINN_PRIVATE BOOL is_float(char *p) {
+ BOOL retval, number_found=FALSE;
+
+ if (p == NULL) return FALSE;
+ if (*p == '-') p++;
+ if (*p == 0) return FALSE;
+
+ retval = TRUE;
+
+ for (; *p; p++) {
+ if ((*p == '.') || (*p == ',')) {
+ if (!number_found) retval = FALSE;
+ } else if ( (*p >= '0') && (*p <= '9') ) {
+ number_found = TRUE;
+ } else {
+ return FALSE;
+ }
+ }
+
+ return retval;
+}
+
+/*************************************************************************************/
+
+BINN_PRIVATE BOOL is_bool_str(char *str, BOOL *pbool) {
+ int64 vint;
+ double vdouble;
+
+ if (str == NULL || pbool == NULL) return FALSE;
+
+ if (stricmp(str, "true") == 0) goto loc_true;
+ if (stricmp(str, "yes") == 0) goto loc_true;
+ if (stricmp(str, "on") == 0) goto loc_true;
+ //if (stricmp(str, "1") == 0) goto loc_true;
+
+ if (stricmp(str, "false") == 0) goto loc_false;
+ if (stricmp(str, "no") == 0) goto loc_false;
+ if (stricmp(str, "off") == 0) goto loc_false;
+ //if (stricmp(str, "0") == 0) goto loc_false;
+
+ if (is_integer(str)) {
+ vint = atoi64(str);
+ *pbool = (vint != 0) ? TRUE : FALSE;
+ return TRUE;
+ } else if (is_float(str)) {
+ vdouble = atof(str);
+ *pbool = (vdouble != 0) ? TRUE : FALSE;
+ return TRUE;
+ }
+
+ return FALSE;
+
+loc_true:
+ *pbool = TRUE;
+ return TRUE;
+
+loc_false:
+ *pbool = FALSE;
+ return TRUE;
+
+}
+
+/*************************************************************************************/
+
+BOOL APIENTRY binn_get_int32(binn *value, int *pint) {
+
+ if (value == NULL || pint == NULL) return FALSE;
+
+ if (type_family(value->type) == BINN_FAMILY_INT) {
+ return copy_int_value(value->ptr, pint, value->type, BINN_INT32);
+ }
+
+ switch (value->type) {
+ case BINN_FLOAT:
+ if ((value->vfloat < INT32_MIN) || (value->vfloat > INT32_MAX)) return FALSE;
+ *pint = round(value->vfloat);
+ break;
+ case BINN_DOUBLE:
+ if ((value->vdouble < INT32_MIN) || (value->vdouble > INT32_MAX)) return FALSE;
+ *pint = round(value->vdouble);
+ break;
+ case BINN_STRING:
+ if (is_integer((char*)value->ptr))
+ *pint = atoi((char*)value->ptr);
+ else if (is_float((char*)value->ptr))
+ *pint = round(atof((char*)value->ptr));
+ else
+ return FALSE;
+ break;
+ case BINN_BOOL:
+ *pint = value->vbool;
+ break;
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*************************************************************************************/
+
+BOOL APIENTRY binn_get_int64(binn *value, int64 *pint) {
+
+ if (value == NULL || pint == NULL) return FALSE;
+
+ if (type_family(value->type) == BINN_FAMILY_INT) {
+ return copy_int_value(value->ptr, pint, value->type, BINN_INT64);
+ }
+
+ switch (value->type) {
+ case BINN_FLOAT:
+ if ((value->vfloat < INT64_MIN) || (value->vfloat > INT64_MAX)) return FALSE;
+ *pint = round(value->vfloat);
+ break;
+ case BINN_DOUBLE:
+ if ((value->vdouble < INT64_MIN) || (value->vdouble > INT64_MAX)) return FALSE;
+ *pint = round(value->vdouble);
+ break;
+ case BINN_STRING:
+ if (is_integer((char*)value->ptr))
+ *pint = atoi64((char*)value->ptr);
+ else if (is_float((char*)value->ptr))
+ *pint = round(atof((char*)value->ptr));
+ else
+ return FALSE;
+ break;
+ case BINN_BOOL:
+ *pint = value->vbool;
+ break;
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*************************************************************************************/
+
+BOOL APIENTRY binn_get_double(binn *value, double *pfloat) {
+ int64 vint;
+
+ if (value == NULL || pfloat == NULL) return FALSE;
+
+ if (type_family(value->type) == BINN_FAMILY_INT) {
+ if (copy_int_value(value->ptr, &vint, value->type, BINN_INT64) == FALSE) return FALSE;
+ *pfloat = (double) vint;
+ return TRUE;
+ }
+
+ switch (value->type) {
+ case BINN_FLOAT:
+ *pfloat = value->vfloat;
+ break;
+ case BINN_DOUBLE:
+ *pfloat = value->vdouble;
+ break;
+ case BINN_STRING:
+ if (is_integer((char*)value->ptr))
+ *pfloat = (double) atoi64((char*)value->ptr);
+ else if (is_float((char*)value->ptr))
+ *pfloat = atof((char*)value->ptr);
+ else
+ return FALSE;
+ break;
+ case BINN_BOOL:
+ *pfloat = value->vbool;
+ break;
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*************************************************************************************/
+
+BOOL APIENTRY binn_get_bool(binn *value, BOOL *pbool) {
+ int64 vint;
+
+ if (value == NULL || pbool == NULL) return FALSE;
+
+ if (type_family(value->type) == BINN_FAMILY_INT) {
+ if (copy_int_value(value->ptr, &vint, value->type, BINN_INT64) == FALSE) return FALSE;
+ *pbool = (vint != 0) ? TRUE : FALSE;
+ return TRUE;
+ }
+
+ switch (value->type) {
+ case BINN_BOOL:
+ *pbool = value->vbool;
+ break;
+ case BINN_FLOAT:
+ *pbool = (value->vfloat != 0) ? TRUE : FALSE;
+ break;
+ case BINN_DOUBLE:
+ *pbool = (value->vdouble != 0) ? TRUE : FALSE;
+ break;
+ case BINN_STRING:
+ return is_bool_str((char*)value->ptr, pbool);
+ default:
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/*************************************************************************************/
+
+char * APIENTRY binn_get_str(binn *value) {
+ int64 vint;
+ char buf[128];
+
+ if (value == NULL) return NULL;
+
+ if (type_family(value->type) == BINN_FAMILY_INT) {
+ if (copy_int_value(value->ptr, &vint, value->type, BINN_INT64) == FALSE) return NULL;
+ sprintf(buf, "%" INT64_FORMAT, vint);
+ goto loc_convert_value;
+ }
+
+ switch (value->type) {
+ case BINN_FLOAT:
+ value->vdouble = value->vfloat;
+ case BINN_DOUBLE:
+ sprintf(buf, "%g", value->vdouble);
+ goto loc_convert_value;
+ case BINN_STRING:
+ return (char*) value->ptr;
+ case BINN_BOOL:
+ if (value->vbool)
+ strcpy(buf, "true");
+ else
+ strcpy(buf, "false");
+ goto loc_convert_value;
+ }
+
+ return NULL;
+
+loc_convert_value:
+
+ //value->vint64 = 0;
+ value->ptr = strdup(buf);
+ if (value->ptr == NULL) return NULL;
+ value->freefn = free;
+ value->type = BINN_STRING;
+ return (char*) value->ptr;
+
+}
+
+/*************************************************************************************/
+/*** GENERAL FUNCTIONS ***************************************************************/
+/*************************************************************************************/
+
+BOOL APIENTRY binn_is_container(binn *item) {
+
+ if (item == NULL) return FALSE;
+
+ switch (item->type) {
+ case BINN_LIST:
+ case BINN_MAP:
+ case BINN_OBJECT:
+ return TRUE;
+ default:
+ return FALSE;
+ }
+
+}
+
+/*************************************************************************************/
diff --git a/src/lib/capnp-c/capn-malloc.cc b/src/lib/capnp-c/capn-malloc.cc
new file mode 100644
index 0000000..79f08c8
--- /dev/null
+++ b/src/lib/capnp-c/capn-malloc.cc
@@ -0,0 +1,424 @@
+/* vim: set sw=8 ts=8 sts=8 noet: */
+/* capn-malloc.c
+ *
+ * Copyright (C) 2013 James McKaskill
+ * Copyright (C) 2014 Steve Dee
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+#ifdef __GNUC__
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#endif
+
+#include "capnp_c.h"
+#include "capnp_priv.h"
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+
+#ifdef MULTIPASS_TRACE_MALLOC
+#include "lib/mpmalloc.h"
+#else
+#define mpcalloc calloc
+#define mpfree free
+#endif
+
+/*
+ * 8 byte alignment is required for struct capn_segment.
+ * This struct check_segment_alignment verifies this at compile time.
+ *
+ * Unless capn_segment is defined with 8 byte alignment, check_segment_alignment
+ * fails to compile in x86 mode (or on another CPU with 32-bit pointers),
+ * as (sizeof(struct capn_segment)&7) -> (44 & 7) evaluates to 4.
+ * It compiles in x64 mode (or on another CPU with 64-bit pointers),
+ * as (sizeof(struct capn_segment)&7) -> (80 & 7) evaluates to 0.
+ */
+struct check_segment_alignment {
+ unsigned int foo : (sizeof(struct capn_segment)&7) ? -1 : 1;
+};
+
+static struct capn_segment *create(void *u, uint32_t id, int sz) {
+ struct capn_segment *s;
+ sz += sizeof(*s);
+#if 0
+ if (sz < 1024) {
+ sz = 1024;
+ } else {
+ sz = (sz + 1023) & ~1023;
+ }
+#else
+ if (sz < 4096) {
+ sz = 4096;
+ } else {
+ sz = (sz + 4095) & ~4095;
+ }
+#endif
+ s = (struct capn_segment*) mpcalloc(1, sz);
+ s->data = (char*) (s+1);
+ s->cap = sz - sizeof(*s);
+ s->user = s;
+ return s;
+}
+
+static struct capn_segment *create_local(void *u, int sz) {
+ return create(u, 0, sz);
+}
+
+void capn_init_malloc(struct capn *c) {
+ memset(c, 0, sizeof(*c));
+ c->create = &create;
+ c->create_local = &create_local;
+}
+
+void capn_free(struct capn *c) {
+ struct capn_segment *s = c->seglist;
+ while (s != NULL) {
+ struct capn_segment *n = s->next;
+ mpfree(s->user);
+ s = n;
+ }
+ capn_reset_copy(c);
+}
+
+void capn_reset_copy(struct capn *c) {
+ struct capn_segment *s = c->copylist;
+ while (s != NULL) {
+ struct capn_segment *n = s->next;
+ mpfree(s->user);
+ s = n;
+ }
+ c->copy = NULL;
+ c->copylist = NULL;
+}
+
+#define ZBUF_SZ 4096
+
+static int read_fp(void *p, size_t sz, FILE *f, struct capn_stream *z, uint8_t* zbuf, int packed) {
+ if (f && packed) {
+ z->next_out = (uint8_t*) p;
+ z->avail_out = sz;
+
+ while (z->avail_out && capn_inflate(z) == CAPN_NEED_MORE) {
+ int r;
+ memmove(zbuf, z->next_in, z->avail_in);
+ r = fread(zbuf+z->avail_in, 1, ZBUF_SZ - z->avail_in, f);
+ if (r <= 0)
+ return -1;
+ z->avail_in += r;
+ }
+ return 0;
+
+ } else if (f && !packed) {
+ return fread(p, sz, 1, f) != 1;
+
+ } else if (packed) {
+ z->next_out = (uint8_t*) p;
+ z->avail_out = sz;
+ return capn_inflate(z) != 0;
+
+ } else {
+ if (z->avail_in < sz)
+ return -1;
+ memcpy(p, z->next_in, sz);
+ z->next_in += sz;
+ z->avail_in -= sz;
+ return 0;
+ }
+}
+
+static int init_fp(struct capn *c, FILE *f, struct capn_stream *z, int packed) {
+ /*
+ * Initialize 'c' from the contents of 'f', assuming the message has been
+ * serialized with the standard framing format. From https://capnproto.org/encoding.html:
+ *
+ * When transmitting over a stream, the following should be sent. All integers are unsigned and little-endian.
+ * (4 bytes) The number of segments, minus one (since there is always at least one segment).
+ * (N * 4 bytes) The size of each segment, in words.
+ * (0 or 4 bytes) Padding up to the next word boundary.
+ * The content of each segment, in order.
+ */
+
+ struct capn_segment *s = NULL;
+ uint32_t i, segnum, total = 0;
+ uint32_t hdr[1024];
+ uint8_t zbuf[ZBUF_SZ];
+ char *data = NULL;
+
+ capn_init_malloc(c);
+
+ /* Read the first four bytes to know how many headers we have */
+ if (read_fp(&segnum, 4, f, z, zbuf, packed))
+ goto err;
+
+ segnum = capn_flip32(segnum);
+ if (segnum > 1023)
+ goto err;
+ segnum++; /* The wire encoding was zero-based */
+
+ /* Read the header list */
+ if (read_fp(hdr, 8 * (segnum/2) + 4, f, z, zbuf, packed))
+ goto err;
+
+ for (i = 0; i < segnum; i++) {
+ uint32_t n = capn_flip32(hdr[i]);
+ if (n > INT_MAX/8 || n > UINT32_MAX/8 || UINT32_MAX - total < n*8)
+ goto err;
+ hdr[i] = n*8;
+ total += hdr[i];
+ }
+
+ /* Allocate space for the data and the capn_segment structs */
+ s = (struct capn_segment*) mpcalloc(1, total + (sizeof(*s) * segnum));
+ if (!s)
+ goto err;
+
+ /* Now read the data and setup the capn_segment structs */
+ data = (char*) (s+segnum);
+ if (read_fp(data, total, f, z, zbuf, packed))
+ goto err;
+
+ for (i = 0; i < segnum; i++) {
+ s[i].len = s[i].cap = hdr[i];
+ s[i].data = data;
+ data += s[i].len;
+ capn_append_segment(c, &s[i]);
+ }
+
+ /* Set the entire region to be freed on the last segment */
+ s[segnum-1].user = s;
+
+ return 0;
+
+err:
+ memset(c, 0, sizeof(*c));
+ mpfree(s);
+ return -1;
+}
+
+int capn_init_fp(struct capn *c, FILE *f, int packed) {
+ struct capn_stream z;
+ memset(&z, 0, sizeof(z));
+ return init_fp(c, f, &z, packed);
+}
+
+int capn_init_mem(struct capn *c, const uint8_t *p, size_t sz, int packed) {
+ struct capn_stream z;
+ memset(&z, 0, sizeof(z));
+ z.next_in = p;
+ z.avail_in = sz;
+ return init_fp(c, NULL, &z, packed);
+}
+
+static void header_calc(struct capn *c, uint32_t *headerlen, size_t *headersz)
+{
+ /* segnum == 1:
+ * [segnum][segsiz]
+ * segnum == 2:
+ * [segnum][segsiz][segsiz][zeroes]
+ * segnum == 3:
+ * [segnum][segsiz][segsiz][segsiz]
+ * segnum == 4:
+ * [segnum][segsiz][segsiz][segsiz][segsiz][zeroes]
+ */
+ *headerlen = ((2 + c->segnum) / 2) * 2;
+ *headersz = 4 * *headerlen;
+}
+
+static int header_render(struct capn *c, struct capn_segment *seg, uint32_t *header, uint32_t headerlen, size_t *datasz)
+{
+ size_t i;
+
+ header[0] = capn_flip32(c->segnum - 1);
+ header[headerlen-1] = 0; /* Zero out the spare position in the header sizes */
+ for (i = 0; i < c->segnum; i++, seg = seg->next) {
+ if (0 == seg)
+ return -1;
+ *datasz += seg->len;
+ header[1 + i] = capn_flip32(seg->len / 8);
+ }
+ if (0 != seg)
+ return -1;
+
+ return 0;
+}
+
+static int capn_write_mem_packed(struct capn *c, uint8_t *p, size_t sz)
+{
+ struct capn_segment *seg;
+ struct capn_ptr root;
+ uint32_t headerlen;
+ size_t headersz, datasz = 0;
+ uint32_t *header;
+ struct capn_stream z;
+ int ret;
+
+ root = capn_root(c);
+ header_calc(c, &headerlen, &headersz);
+ header = (uint32_t*) (p + headersz + 2); /* must reserve two bytes for worst case expansion */
+
+ if (sz < headersz*2 + 2) /* We must have space for temporary writing of header to deflate */
+ return -1;
+
+ ret = header_render(c, root.seg, header, headerlen, &datasz);
+ if (ret != 0)
+ return -1;
+
+ memset(&z, 0, sizeof(z));
+ z.next_in = (uint8_t *)header;
+ z.avail_in = headersz;
+ z.next_out = p;
+ z.avail_out = sz;
+
+ // pack the headers
+ ret = capn_deflate(&z);
+ if (ret != 0 || z.avail_in != 0)
+ return -1;
+
+ for (seg = root.seg; seg; seg = seg->next) {
+ z.next_in = (uint8_t *)seg->data;
+ z.avail_in = seg->len;
+ ret = capn_deflate(&z);
+ if (ret != 0 || z.avail_in != 0)
+ return -1;
+ }
+
+ return sz - z.avail_out;
+}
+
+int
+capn_write_mem(struct capn *c, uint8_t *p, size_t sz, int packed)
+{
+ struct capn_segment *seg;
+ struct capn_ptr root;
+ uint32_t headerlen;
+ size_t headersz, datasz = 0;
+ uint32_t *header;
+ int ret;
+
+ if (c->segnum == 0)
+ return -1;
+
+ if (packed)
+ return capn_write_mem_packed(c, p, sz);
+
+ root = capn_root(c);
+ header_calc(c, &headerlen, &headersz);
+ header = (uint32_t*) p;
+
+ if (sz < headersz)
+ return -1;
+
+ ret = header_render(c, root.seg, header, headerlen, &datasz);
+ if (ret != 0)
+ return -1;
+
+ if (sz < headersz + datasz)
+ return -1;
+
+ p += headersz;
+
+ for (seg = root.seg; seg; seg = seg->next) {
+ memcpy(p, seg->data, seg->len);
+ p += seg->len;
+ }
+
+ return headersz+datasz;
+}
+
+static int _write_fd(ssize_t (*write_fd)(int fd, const void *p, size_t count), int fd, void *p, size_t count)
+{
+ ssize_t ret;
+ size_t sent = 0;
+
+ while (sent < count) {
+ ret = write_fd(fd, ((uint8_t*)p)+sent, count-sent);
+ if (ret < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ else
+ return -1;
+ }
+ sent += ret;
+ }
+
+ return 0;
+}
+
+int capn_write_fd(struct capn *c, ssize_t (*write_fd)(int fd, const void *p, size_t count), int fd, int packed)
+{
+ unsigned char buf[4096];
+ struct capn_segment *seg;
+ struct capn_ptr root;
+ uint32_t headerlen;
+ size_t headersz, datasz = 0;
+ int ret;
+ struct capn_stream z;
+ unsigned char *p;
+
+ if (c->segnum == 0)
+ return -1;
+
+ root = capn_root(c);
+ header_calc(c, &headerlen, &headersz);
+
+ if (sizeof(buf) < headersz)
+ return -1;
+
+ ret = header_render(c, root.seg, (uint32_t*)buf, headerlen, &datasz);
+ if (ret != 0)
+ return -1;
+
+ if (packed) {
+ const int headerrem = sizeof(buf) - headersz;
+ const int maxpack = headersz + 2;
+ if (headerrem < maxpack)
+ return -1;
+
+ memset(&z, 0, sizeof(z));
+ z.next_in = buf;
+ z.avail_in = headersz;
+ z.next_out = buf + headersz;
+ z.avail_out = headerrem;
+ ret = capn_deflate(&z);
+ if (ret != 0)
+ return -1;
+
+ p = buf + headersz;
+ headersz = headerrem - z.avail_out;
+ } else {
+ p = buf;
+ }
+
+ ret = _write_fd(write_fd, fd, p, headersz);
+ if (ret < 0)
+ return -1;
+
+ datasz = headersz;
+ for (seg = root.seg; seg; seg = seg->next) {
+ size_t bufsz;
+ if (packed) {
+ memset(&z, 0, sizeof(z));
+ z.next_in = (uint8_t*)seg->data;
+ z.avail_in = seg->len;
+ z.next_out = buf;
+ z.avail_out = sizeof(buf);
+ ret = capn_deflate(&z);
+ if (ret != 0)
+ return -1;
+ p = buf;
+ bufsz = sizeof(buf) - z.avail_out;
+ } else {
+ p = (uint8_t*)seg->data;
+ bufsz = seg->len;
+ }
+ ret = _write_fd(write_fd, fd, p, bufsz);
+ if (ret < 0)
+ return -1;
+ datasz += bufsz;
+ }
+
+ return datasz;
+}
diff --git a/src/lib/capnp-c/capn-stream.cc b/src/lib/capnp-c/capn-stream.cc
new file mode 100644
index 0000000..135d1b2
--- /dev/null
+++ b/src/lib/capnp-c/capn-stream.cc
@@ -0,0 +1,217 @@
+/* vim: set sw=8 ts=8 sts=8 noet: */
+/* capn-stream.c
+ *
+ * Copyright (C) 2013 James McKaskill
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+#include "capnp_c.h"
+#include "capnp_priv.h"
+#include <string.h>
+
+#ifndef min
+static unsigned min(unsigned a, unsigned b) { return (a < b) ? a : b; }
+#endif
+
+int capn_deflate(struct capn_stream* s) {
+ if (s->avail_in % 8) {
+ return CAPN_MISALIGNED;
+ }
+
+ while (s->avail_in) {
+ int i;
+ size_t sz;
+ uint8_t hdr = 0;
+ uint8_t *p;
+
+ if (!s->avail_out)
+ return CAPN_NEED_MORE;
+
+ if (s->raw > 0) {
+ sz = min(s->raw, min(s->avail_in, s->avail_out));
+ memcpy(s->next_out, s->next_in, sz);
+ s->next_out += sz;
+ s->next_in += sz;
+ s->avail_out -= sz;
+ s->avail_in -= sz;
+ s->raw -= sz;
+ continue;
+ }
+
+ if (s->avail_in < 8)
+ return CAPN_NEED_MORE;
+
+ sz = 0;
+ for (i = 0; i < 8; i++) {
+ if (s->next_in[i]) {
+ sz ++;
+ hdr |= 1 << i;
+ }
+ }
+
+ switch (sz) {
+ case 0:
+ if (s->avail_out < 2)
+ return CAPN_NEED_MORE;
+
+ s->next_out[0] = 0;
+ for (sz = 1; sz < min(s->avail_in/8, 256); sz++) {
+ if (((uint64_t*) s->next_in)[sz] != 0) {
+ break;
+ }
+ }
+
+ s->next_out[1] = (uint8_t) (sz-1);
+ s->next_in += sz*8;
+ s->avail_in -= sz*8;
+ s->next_out += 2;
+ s->avail_out -= 2;
+ continue;
+
+ case 8:
+ if (s->avail_out < 10)
+ return CAPN_NEED_MORE;
+
+ s->next_out[0] = 0xFF;
+ memcpy(s->next_out+1, s->next_in, 8);
+ s->next_in += 8;
+ s->avail_in -= 8;
+
+ s->raw = min(s->avail_in, 256*8);
+ if ((p = (uint8_t*) memchr(s->next_in, 0, s->raw)) != NULL) {
+ s->raw = (p - s->next_in) & ~7;
+ }
+
+ s->next_out[9] = (uint8_t) (s->raw/8);
+ s->next_out += 10;
+ s->avail_out -= 10;
+ continue;
+
+ default:
+ if (s->avail_out < 1U + sz)
+ return CAPN_NEED_MORE;
+
+ *(s->next_out++) = hdr;
+ for (i = 0; i < 8; i++) {
+ if (s->next_in[i]) {
+ *(s->next_out++) = s->next_in[i];
+ }
+ }
+ s->avail_out -= sz + 1;
+ s->next_in += 8;
+ s->avail_in -= 8;
+ continue;
+ }
+ }
+
+ return 0;
+}
+
+int capn_inflate(struct capn_stream* s) {
+ while (s->avail_out) {
+ int i;
+ size_t sz;
+ uint8_t hdr;
+ uint8_t *wr;
+
+ if (s->avail_buf && s->avail_out >= s->avail_buf) {
+ memcpy(s->next_out, s->inflate_buf, s->avail_buf);
+ s->next_out += s->avail_buf;
+ s->avail_out -= s->avail_buf;
+ s->avail_buf = 0;
+ if (!s->avail_out)
+ return 0;
+ }
+ if (s->avail_buf && s->avail_out < s->avail_buf) {
+ memcpy(s->next_out, s->inflate_buf, s->avail_out);
+ memmove(s->inflate_buf, s->inflate_buf + s->avail_out,
+ s->avail_buf - s->avail_out);
+ s->avail_buf -= s->avail_out;
+ s->avail_out = 0;
+ return 0;
+ }
+
+ if (s->zeros > 0) {
+ sz = min(s->avail_out, s->zeros);
+ memset(s->next_out, 0, sz);
+ s->next_out += sz;
+ s->avail_out -= sz;
+ s->zeros -= sz;
+ continue;
+ }
+
+ if (s->raw > 0) {
+ if (s->avail_in == 0)
+ return CAPN_NEED_MORE;
+
+ sz = min(min(s->avail_out, s->raw), s->avail_in);
+ memcpy(s->next_out, s->next_in, sz);
+ s->next_in += sz;
+ s->next_out += sz;
+ s->avail_in -= sz;
+ s->avail_out -= sz;
+ s->raw -= sz;
+ continue;
+ }
+
+ if (s->avail_in == 0)
+ return 0;
+ else if (s->avail_in < 2)
+ return CAPN_NEED_MORE;
+
+ switch (s->next_in[0]) {
+ case 0xFF:
+ /* 0xFF is followed by 8 bytes raw, followed by
+ * a byte with length in words to read raw */
+ if (s->avail_in < 10)
+ return CAPN_NEED_MORE;
+
+ memcpy(s->inflate_buf, s->next_in+1, 8);
+ s->avail_buf = 8;
+
+ s->raw = s->next_in[9] * 8;
+ s->next_in += 10;
+ s->avail_in -= 10;
+ continue;
+
+ case 0x00:
+ /* 0x00 is followed by a single byte indicating
+ * the count of consecutive zero value words
+ * minus 1 */
+ s->zeros = (s->next_in[1] + 1) * 8;
+ s->next_in += 2;
+ s->avail_in -= 2;
+ continue;
+
+ default:
+ hdr = s->next_in[0];
+ sz = 0;
+ for (i = 0; i < 8; i++) {
+ if (hdr & (1 << i))
+ sz++;
+ }
+ if (s->avail_in < 1U + sz)
+ return CAPN_NEED_MORE;
+
+ s->next_in += 1;
+
+ wr = s->inflate_buf;
+ for (i = 0; i < 8; i++) {
+ if (hdr & (1 << i)) {
+ *wr++ = *s->next_in++;
+ } else {
+ *wr++ = 0;
+ }
+ }
+
+ s->avail_buf = 8;
+ s->avail_in -= 1 + sz;
+ continue;
+ }
+ }
+
+ return 0;
+}
+
diff --git a/src/lib/capnp-c/capn.cc b/src/lib/capnp-c/capn.cc
new file mode 100644
index 0000000..bbd7be3
--- /dev/null
+++ b/src/lib/capnp-c/capn.cc
@@ -0,0 +1,1117 @@
+/* vim: set sw=8 ts=8 sts=8 noet: */
+/* capn.c
+ *
+ * Copyright (C) 2013 James McKaskill
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+#ifdef __GNUC__
+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+#endif
+
+#include "capnp_c.h"
+
+#include <stdlib.h>
+#include <string.h>
+#ifndef _MSC_VER
+#ifndef MULTIPASS_ARCH_arduino_nano
+#include <sys/param.h>
+#endif
+#endif
+
+#define STRUCT_PTR 0
+#define LIST_PTR 1
+#define FAR_PTR 2
+#define DOUBLE_PTR 6
+
+#define VOID_LIST 0
+#define BIT_1_LIST 1
+#define BYTE_1_LIST 2
+#define BYTE_2_LIST 3
+#define BYTE_4_LIST 4
+#define BYTE_8_LIST 5
+#define PTR_LIST 6
+#define COMPOSITE_LIST 7
+
+#define U64(val) ((uint64_t) (val))
+#define I64(val) ((int64_t) (val))
+#define U32(val) ((uint32_t) (val))
+#define I32(val) ((int32_t) (val))
+#define U16(val) ((uint16_t) (val))
+#define I16(val) ((int16_t) (val))
+
+#ifndef min
+static int min(int a, int b) { return (a < b) ? a : b; }
+#endif
+
+#ifdef BYTE_ORDER
+#define CAPN_LITTLE (BYTE_ORDER == LITTLE_ENDIAN)
+#elif defined(__BYTE_ORDER)
+#define CAPN_LITTLE (__BYTE_ORDER == __LITTLE_ENDIAN)
+#else
+#define CAPN_LITTLE 0
+#endif
+
+struct capn_tree *capn_tree_insert(struct capn_tree *root, struct capn_tree *n) {
+ n->red = 1;
+ n->link[0] = n->link[1] = NULL;
+
+ for (;;) {
+ /* parent, uncle, grandparent, great grandparent link */
+ struct capn_tree *p, *u, *g, **gglink;
+ int dir;
+
+ /* Case 1: N is root */
+ p = n->parent;
+ if (!p) {
+ n->red = 0;
+ root = n;
+ break;
+ }
+
+ /* Case 2: p is black */
+ if (!p->red) {
+ break;
+ }
+
+ g = p->parent;
+ dir = (p == g->link[1]);
+
+ /* Case 3: P and U are red, switch g to red, but must
+ * loop as G could be root or have a red parent
+ * g to G
+ * / \ / \
+ * P U p u
+ * / /
+ * N N
+ */
+ u = g->link[!dir];
+ if (u != NULL && u->red) {
+ p->red = 0;
+ u->red = 0;
+ g->red = 1;
+ n = g;
+ continue;
+ }
+
+ if (!g->parent) {
+ gglink = &root;
+ } else if (g->parent->link[1] == g) {
+ gglink = &g->parent->link[1];
+ } else {
+ gglink = &g->parent->link[0];
+ }
+
+ if (dir != (n == p->link[1])) {
+ /* Case 4: rotate on P, then on g
+ * here dir is /
+ * g to g to n
+ * / \ / \ / \
+ * P u N u P G
+ * / \ / \ /| / \
+ * 1 N P 3 1 2 3 u
+ * / \ / \
+ * 2 3 1 2
+ */
+ struct capn_tree *two = n->link[dir];
+ struct capn_tree *three = n->link[!dir];
+ p->link[!dir] = two;
+ g->link[dir] = three;
+ n->link[dir] = p;
+ n->link[!dir] = g;
+ *gglink = n;
+ n->parent = g->parent;
+ p->parent = n;
+ g->parent = n;
+ if (two)
+ two->parent = p;
+ if (three)
+ three->parent = g;
+ n->red = 0;
+ g->red = 1;
+ } else {
+ /* Case 5: rotate on g
+ * here dir is /
+ * g to p
+ * / \ / \
+ * P u N G
+ * / \ /| / \
+ * N 3 1 2 3 u
+ * / \
+ * 1 2
+ */
+ struct capn_tree *three = p->link[!dir];
+ g->link[dir] = three;
+ p->link[!dir] = g;
+ *gglink = p;
+ p->parent = g->parent;
+ g->parent = p;
+ if (three)
+ three->parent = g;
+ g->red = 1;
+ p->red = 0;
+ }
+
+ break;
+ }
+
+ return root;
+}
+
+void capn_append_segment(struct capn *c, struct capn_segment *s) {
+ s->id = c->segnum++;
+ s->capn = c;
+ s->next = NULL;
+
+ if (c->lastseg) {
+ c->lastseg->next = s;
+ c->lastseg->hdr.link[1] = &s->hdr;
+ s->hdr.parent = &c->lastseg->hdr;
+ } else {
+ c->seglist = s;
+ s->hdr.parent = NULL;
+ }
+
+ c->lastseg = s;
+ c->segtree = capn_tree_insert(c->segtree, &s->hdr);
+}
+
+static char *new_data(struct capn *c, int sz, struct capn_segment **ps) {
+ struct capn_segment *s;
+
+ /* find a segment with sufficient data */
+ for (s = c->seglist; s != NULL; s = s->next) {
+ if (s->len + sz <= s->cap) {
+ goto end;
+ }
+ }
+
+ s = c->create ? c->create(c->user, c->segnum, sz) : NULL;
+ if (!s) {
+ *ps = NULL;
+ return NULL;
+ }
+
+ capn_append_segment(c, s);
+end:
+ *ps = s;
+ s->len += sz;
+ return s->data + s->len - sz;
+}
+
+static struct capn_segment *lookup_segment(struct capn* c, struct capn_segment *s, uint32_t id) {
+ struct capn_tree **x;
+ struct capn_segment *y = NULL;
+
+ if (s && s->id == id)
+ return s;
+ if (!c)
+ return NULL;
+
+ if (id < c->segnum) {
+ x = &c->segtree;
+ while (*x) {
+ y = (struct capn_segment*) *x;
+ if (id == y->id) {
+ return y;
+ } else if (id < y->id) {
+ x = &y->hdr.link[0];
+ } else {
+ x = &y->hdr.link[1];
+ }
+ }
+ } else {
+ /* Otherwise `x` may be uninitialized */
+ return NULL;
+ }
+
+ s = c->lookup ? c->lookup(c->user, id) : NULL;
+ if (!s)
+ return NULL;
+
+ if (id < c->segnum) {
+ s->id = id;
+ s->capn = c;
+ s->next = c->seglist;
+ c->seglist = s;
+ s->hdr.parent = &y->hdr;
+ *x = &s->hdr;
+ c->segtree = capn_tree_insert(c->segtree, &s->hdr);
+ } else {
+ c->segnum = id;
+ capn_append_segment(c, s);
+ }
+
+ return s;
+}
+
+static uint64_t lookup_double(struct capn_segment **s, char **d, uint64_t val) {
+ uint64_t far, tag;
+ size_t off = (U32(val) >> 3) * 8;
+ char *p;
+
+ if ((*s = lookup_segment((*s)->capn, *s, U32(val >> 32))) == NULL) {
+ return 0;
+ }
+
+ p = (*s)->data + off;
+ if (off + 16 > (*s)->len) {
+ return 0;
+ }
+
+ far = capn_flip64(*(uint64_t*) p);
+ tag = capn_flip64(*(uint64_t*) (p+8));
+
+ /* the far tag should not be another double, and the tag
+ * should be struct/list and have no offset */
+ if ((far&7) != FAR_PTR || U32(tag) > LIST_PTR) {
+ return 0;
+ }
+
+ if ((*s = lookup_segment((*s)->capn, *s, U32(far >> 32))) == NULL) {
+ return 0;
+ }
+
+ /* -8 because far pointers reference from the start of
+ * the segment, but offsets reference the end of the
+ * pointer data. Here *d points to where an equivalent
+ * ptr would be.
+ */
+ *d = (*s)->data - 8;
+ return U64(U32(far) >> 3 << 2) | tag;
+}
+
+static uint64_t lookup_far(struct capn_segment **s, char **d, uint64_t val) {
+ size_t off = (U32(val) >> 3) * 8;
+
+ if ((*s = lookup_segment((*s)->capn, *s, U32(val >> 32))) == NULL) {
+ return 0;
+ }
+
+ if (off + 8 > (*s)->len) {
+ return 0;
+ }
+
+ *d = (*s)->data + off;
+ return capn_flip64(*(uint64_t*)*d);
+}
+
+static char *struct_ptr(struct capn_segment *s, char *d, int minsz) {
+ uint64_t val = capn_flip64(*(uint64_t*)d);
+ uint16_t datasz;
+
+ switch (val&7) {
+ case FAR_PTR:
+ val = lookup_far(&s, &d, val);
+ break;
+ case DOUBLE_PTR:
+ val = lookup_double(&s, &d, val);
+ break;
+ }
+
+ datasz = U16(val >> 32);
+ d += (I32(U32(val)) << 1) + 8;
+
+ if (val != 0 && (val&3) != STRUCT_PTR && datasz >= minsz && s->data <= d && d < s->data + s->len) {
+ return d;
+ }
+
+ return NULL;
+}
+
+static capn_ptr read_ptr(struct capn_segment *s, char *d) {
+ capn_ptr ret = {CAPN_NULL};
+ uint64_t val;
+ char *e = 0;
+
+ val = capn_flip64(*(uint64_t*) d);
+
+ switch (val&7) {
+ case FAR_PTR:
+ val = lookup_far(&s, &d, val);
+ ret.has_ptr_tag = (U32(val) >> 2) == 0;
+ break;
+ case DOUBLE_PTR:
+ val = lookup_double(&s, &d, val);
+ break;
+ }
+
+ d += (I32(U32(val)) >> 2) * 8 + 8;
+
+ if (d < s->data) {
+ goto err;
+ }
+
+ switch (val & 3) {
+ case STRUCT_PTR:
+ ret.type = val ? CAPN_STRUCT : CAPN_NULL;
+ goto struct_common;
+
+ struct_common:
+ ret.datasz = U32(U16(val >> 32)) * 8;
+ ret.ptrs = U32(U16(val >> 48));
+ e = d + ret.datasz + 8 * ret.ptrs;
+ break;
+
+ case LIST_PTR:
+ ret.type = CAPN_LIST;
+ ret.len = val >> 35;
+
+ switch ((val >> 32) & 7) {
+ case VOID_LIST:
+ e = d;
+ break;
+ case BIT_1_LIST:
+ ret.type = CAPN_BIT_LIST;
+ ret.datasz = (ret.len+7)/8;
+ e = d + ret.datasz;
+ break;
+ case BYTE_1_LIST:
+ ret.datasz = 1;
+ e = d + ret.len;
+ break;
+ case BYTE_2_LIST:
+ ret.datasz = 2;
+ e = d + ret.len * 2;
+ break;
+ case BYTE_4_LIST:
+ ret.datasz = 4;
+ e = d + ret.len * 4;
+ break;
+ case BYTE_8_LIST:
+ ret.datasz = 8;
+ e = d + ret.len * 8;
+ break;
+ case PTR_LIST:
+ ret.type = CAPN_PTR_LIST;
+ e = d + ret.len * 8;
+ break;
+ case COMPOSITE_LIST:
+ if ((size_t)((d+8) - s->data) > s->len) {
+ goto err;
+ }
+
+ val = capn_flip64(*(uint64_t*) d);
+
+ d += 8;
+ e = d + ret.len * 8;
+
+ ret.datasz = U32(U16(val >> 32)) * 8;
+ ret.ptrs = U32(U16(val >> 48));
+ ret.len = U32(val) >> 2;
+ ret.is_composite_list = 1;
+
+ if ((ret.datasz + 8*ret.ptrs) * ret.len != e - d) {
+ goto err;
+ }
+ break;
+ }
+ break;
+
+ default:
+ goto err;
+ }
+
+ if ((size_t)(e - s->data) > s->len)
+ goto err;
+
+ ret.data = d;
+ ret.seg = s;
+ return ret;
+err:
+ memset(&ret, 0, sizeof(ret));
+ return ret;
+}
+
+void capn_resolve(capn_ptr *p) {
+ if (p->type == CAPN_FAR_POINTER) {
+ *p = read_ptr(p->seg, p->data);
+ }
+}
+
+/* TODO: should this handle CAPN_BIT_LIST? */
+capn_ptr capn_getp(capn_ptr p, int off, int resolve) {
+ capn_ptr ret = {CAPN_FAR_POINTER};
+ ret.seg = p.seg;
+
+ capn_resolve(&p);
+
+ switch (p.type) {
+ case CAPN_LIST:
+ /* Return an inner pointer */
+ if (off < p.len) {
+ capn_ptr ret = {CAPN_STRUCT};
+ ret.is_list_member = 1;
+ ret.data = p.data + off * (p.datasz + 8*p.ptrs);
+ ret.seg = p.seg;
+ ret.datasz = p.datasz;
+ ret.ptrs = p.ptrs;
+ return ret;
+ } else {
+ goto err;
+ }
+
+ case CAPN_STRUCT:
+ if (off >= p.ptrs) {
+ goto err;
+ }
+ ret.data = p.data + p.datasz + 8*off;
+ break;
+
+ case CAPN_PTR_LIST:
+ if (off >= p.len) {
+ goto err;
+ }
+ ret.data = p.data + 8*off;
+ break;
+
+ default:
+ goto err;
+ }
+
+ if (resolve) {
+ ret = read_ptr(ret.seg, ret.data);
+ }
+
+ return ret;
+
+err:
+ memset(&p, 0, sizeof(p));
+ return p;
+}
+
+static void write_ptr_tag(char *d, capn_ptr p, int off) {
+ uint64_t val = U64(U32(I32(off/8) << 2));
+
+ switch (p.type) {
+ case CAPN_STRUCT:
+ val |= STRUCT_PTR | (U64(p.datasz/8) << 32) | (U64(p.ptrs) << 48);
+ break;
+
+ case CAPN_LIST:
+ if (p.is_composite_list) {
+ val |= LIST_PTR | (U64(COMPOSITE_LIST) << 32) | (U64(p.len * (p.datasz/8 + p.ptrs)) << 35);
+ } else {
+ val |= LIST_PTR | (U64(p.len) << 35);
+
+ switch (p.datasz) {
+ case 8:
+ val |= (U64(BYTE_8_LIST) << 32);
+ break;
+ case 4:
+ val |= (U64(BYTE_4_LIST) << 32);
+ break;
+ case 2:
+ val |= (U64(BYTE_2_LIST) << 32);
+ break;
+ case 1:
+ val |= (U64(BYTE_1_LIST) << 32);
+ break;
+ case 0:
+ val |= (U64(VOID_LIST) << 32);
+ break;
+ }
+ }
+ break;
+
+ case CAPN_BIT_LIST:
+ val |= LIST_PTR | (U64(BIT_1_LIST) << 32) | (U64(p.len) << 35);
+ break;
+
+ case CAPN_PTR_LIST:
+ val |= LIST_PTR | (U64(PTR_LIST) << 32) | (U64(p.len) << 35);
+ break;
+
+ default:
+ val = 0;
+ break;
+ }
+
+ *(uint64_t*) d = capn_flip64(val);
+}
+
+static void write_far_ptr(char *d, struct capn_segment *s, char *tgt) {
+ *(uint64_t*) d = capn_flip64(FAR_PTR | U64(tgt - s->data) | (U64(s->id) << 32));
+}
+
+static void write_double_far(char *d, struct capn_segment *s, char *tgt) {
+ *(uint64_t*) d = capn_flip64(DOUBLE_PTR | U64(tgt - s->data) | (U64(s->id) << 32));
+}
+
+#define NEED_TO_COPY 1
+
+static int write_ptr(struct capn_segment *s, char *d, capn_ptr p) {
+ /* note p.seg can be NULL if its a ptr to static data */
+ char *pdata = p.data - 8*p.is_composite_list;
+
+ if (p.type == CAPN_NULL || (p.type == CAPN_STRUCT && p.datasz == 0 && p.ptrs == 0)) {
+ write_ptr_tag(d, p, 0);
+ return 0;
+
+ } else if (!p.seg || p.seg->capn != s->capn || p.is_list_member) {
+ return NEED_TO_COPY;
+
+ } else if (p.seg == s) {
+ write_ptr_tag(d, p, pdata - d - 8);
+ return 0;
+
+ } else if (p.has_ptr_tag) {
+ /* By lucky chance, the data has a tag in front
+ * of it. This happens when new_object had to move
+ * the data to a new segment. */
+ write_far_ptr(d, p.seg, pdata-8);
+ return 0;
+
+ } else if (p.seg->len + 8 <= p.seg->cap) {
+ /* The target segment has enough room for tag */
+ char *t = p.seg->data + p.seg->len;
+ write_ptr_tag(t, p, pdata - t - 8);
+ write_far_ptr(d, p.seg, t);
+ p.seg->len += 8;
+ return 0;
+
+ } else {
+ /* have to allocate room for a double far
+ * pointer */
+ char *t;
+
+ if (s->len + 16 <= s->cap) {
+ /* Try and allocate in the src segment
+ * first. This should improve lookup on
+ * read. */
+ t = s->data + s->len;
+ s->len += 16;
+ } else {
+ t = new_data(s->capn, 16, &s);
+ if (!t) return -1;
+ }
+
+ write_far_ptr(t, p.seg, pdata);
+ write_ptr_tag(t+8, p, 0);
+ write_double_far(d, s, t);
+ return 0;
+ }
+}
+
+struct copy {
+ struct capn_tree hdr;
+ struct capn_ptr to, from;
+ char *fbegin, *fend;
+};
+
+static capn_ptr new_clone(struct capn_segment *s, capn_ptr p) {
+ switch (p.type) {
+ case CAPN_STRUCT:
+ return capn_new_struct(s, p.datasz, p.ptrs);
+ case CAPN_PTR_LIST:
+ return capn_new_ptr_list(s, p.len);
+ case CAPN_BIT_LIST:
+ return capn_new_list1(s, p.len).p;
+ case CAPN_LIST:
+ return capn_new_list(s, p.len, p.datasz, p.ptrs);
+ default:
+ return p;
+ }
+}
+
+static int is_ptr_equal(const struct capn_ptr *a, const struct capn_ptr *b) {
+ return a->data == b->data
+ && a->type == b->type
+ && a->len == b->len
+ && a->datasz == b->datasz
+ && a->ptrs == b->ptrs;
+}
+
+static int data_size(struct capn_ptr p) {
+ switch (p.type) {
+ case CAPN_BIT_LIST:
+ return p.datasz;
+ case CAPN_PTR_LIST:
+ return p.len*8;
+ case CAPN_STRUCT:
+ return p.datasz + 8*p.ptrs;
+ case CAPN_LIST:
+ return p.len * (p.datasz + 8*p.ptrs) + 8*p.is_composite_list;
+ default:
+ return 0;
+ }
+}
+
+static int copy_ptr(struct capn_segment *seg, char *data, struct capn_ptr *t, struct capn_ptr *f, int *dep) {
+ struct capn *c = seg->capn;
+ struct copy *cp = NULL;
+ struct capn_tree **xcp;
+ char *fbegin = f->data - 8*f->is_composite_list;
+ char *fend = fbegin + data_size(*f);
+ int zero_sized = (fend == fbegin);
+
+ /* We always copy list members as it would otherwise be an
+ * overlapped pointer (the data is owned by the enclosing list).
+ * We do not bother with the overlapped lookup for zero sized
+ * structures/lists as they never overlap. Nor do we add them to
+ * the copy list as there is no data to be shared by multiple
+ * pointers.
+ */
+
+ xcp = &c->copy;
+ while (*xcp && !zero_sized) {
+ cp = (struct copy*) *xcp;
+ if (fend <= cp->fbegin) {
+ xcp = &cp->hdr.link[0];
+ } else if (cp->fend <= fbegin) {
+ xcp = &cp->hdr.link[1];
+ } else if (is_ptr_equal(f, &cp->from)) {
+ /* we already have a copy so just point to that */
+ return write_ptr(seg, data, cp->to);
+ } else {
+ /* pointer to overlapped data */
+ return -1;
+ }
+ }
+
+ /* no copy found - have to create a new copy */
+ *t = new_clone(seg, *f);
+
+ if (write_ptr(seg, data, *t))
+ return -1;
+
+ /* add the copy to the copy tree so we can look for overlapping
+ * source pointers and handle recursive structures */
+ if (!zero_sized) {
+ struct copy *n;
+ struct capn_segment *cs = c->copylist;
+
+ /* need to allocate a struct copy */
+ if (!cs || cs->len + (int)sizeof(*n) > cs->cap) {
+ cs = c->create_local ? c->create_local(c->user, sizeof(*n)) : NULL;
+ if (!cs) {
+ /* can't allocate a copy structure */
+ return -1;
+ }
+ cs->next = c->copylist;
+ c->copylist = cs;
+ }
+
+ n = (struct copy*) (cs->data + cs->len);
+ cs->len += sizeof(*n);
+
+ n->from = *f;
+ n->to = *t;
+ n->fbegin = fbegin;
+ n->fend = fend;
+
+ *xcp = &n->hdr;
+ n->hdr.parent = &cp->hdr;
+
+ c->copy = capn_tree_insert(c->copy, &n->hdr);
+ }
+
+ /* minimize the number of types the main copy routine has to
+ * deal with to just CAPN_LIST and CAPN_PTR_LIST. ptr list only
+ * needs t->type, t->len, t->data, t->seg, f->data, f->seg to
+ * be valid */
+ switch (t->type) {
+ case CAPN_STRUCT:
+ if (t->datasz) {
+ memcpy(t->data, f->data, t->datasz);
+ t->data += t->datasz;
+ f->data += t->datasz;
+ }
+ if (t->ptrs) {
+ t->type = CAPN_PTR_LIST;
+ t->len = t->ptrs;
+ (*dep)++;
+ }
+ return 0;
+
+ case CAPN_BIT_LIST:
+ memcpy(t->data, f->data, t->datasz);
+ return 0;
+
+ case CAPN_LIST:
+ if (!t->len) {
+ /* empty list - nothing to copy */
+ } else if (t->ptrs && t->datasz) {
+ (*dep)++;
+ } else if (t->datasz) {
+ memcpy(t->data, f->data, t->len * t->datasz);
+ } else if (t->ptrs) {
+ t->type = CAPN_PTR_LIST;
+ t->len *= t->ptrs;
+ (*dep)++;
+ }
+ return 0;
+
+ case CAPN_PTR_LIST:
+ if (t->len) {
+ (*dep)++;
+ }
+ return 0;
+
+ default:
+ return -1;
+ }
+}
+
+static void copy_list_member(capn_ptr* t, capn_ptr *f, int *dep) {
+ /* copy struct data */
+ int sz = min(t->datasz, f->datasz);
+ memcpy(t->data, f->data, sz);
+ memset(t->data + sz, 0, t->datasz - sz);
+ t->data += t->datasz;
+ f->data += f->datasz;
+
+ /* reset excess pointers */
+ sz = min(t->ptrs, f->ptrs);
+ memset(t->data + sz, 0, 8*(t->ptrs - sz));
+
+ /* create a pointer list for the main loop to copy */
+ if (t->ptrs) {
+ t->type = CAPN_PTR_LIST;
+ t->len = t->ptrs;
+ (*dep)++;
+ }
+}
+
+#define MAX_COPY_DEPTH 32
+
+/* TODO: handle CAPN_BIT_LIST and setting from an inner bit list member */
+int capn_setp(capn_ptr p, int off, capn_ptr tgt) {
+ struct capn_ptr to[MAX_COPY_DEPTH], from[MAX_COPY_DEPTH];
+ char *data;
+ int err, dep = 0;
+
+ capn_resolve(&p);
+
+ if (tgt.type == CAPN_FAR_POINTER && tgt.seg->capn == p.seg->capn) {
+ uint64_t val = capn_flip64(*(uint64_t*) tgt.data);
+ if ((val & 3) == FAR_PTR) {
+ *(uint64_t*) p.data = *(uint64_t*) tgt.data;
+ return 0;
+ }
+ }
+
+ capn_resolve(&tgt);
+
+ switch (p.type) {
+ case CAPN_LIST:
+ if (off >= p.len || tgt.type != CAPN_STRUCT)
+ return -1;
+
+ to[0] = p;
+ to[0].data += off * (p.datasz + 8*p.ptrs);
+ from[0] = tgt;
+ copy_list_member(to, from, &dep);
+ break;
+
+ case CAPN_PTR_LIST:
+ if (off >= p.len)
+ return -1;
+ data = p.data + 8*off;
+ goto copy_ptr;
+
+ case CAPN_STRUCT:
+ if (off >= p.ptrs)
+ return -1;
+ data = p.data + p.datasz + 8*off;
+ goto copy_ptr;
+
+ copy_ptr:
+ err = write_ptr(p.seg, data, tgt);
+ if (err != NEED_TO_COPY)
+ return err;
+
+ /* Depth first copy the source whilst using a pointer stack to
+ * maintain the ptr to set and size left to copy at each level.
+ * We also maintain a rbtree (capn->copy) of the copies indexed
+ * by the source data. This way we can detect overlapped
+ * pointers in the source (and bail) and recursive structures
+ * (and point to the previous copy).
+ */
+
+ from[0] = tgt;
+ if (copy_ptr(p.seg, data, to, from, &dep))
+ return -1;
+ break;
+
+ default:
+ return -1;
+ }
+
+ while (dep) {
+ struct capn_ptr *tc = &to[dep-1], *tn = &to[dep];
+ struct capn_ptr *fc = &from[dep-1], *fn = &from[dep];
+
+ if (dep+1 == MAX_COPY_DEPTH) {
+ return -1;
+ }
+
+ if (!tc->len) {
+ dep--;
+ continue;
+ }
+
+ if (tc->type == CAPN_LIST) {
+ *fn = capn_getp(*fc, 0, 1);
+ *tn = capn_getp(*tc, 0, 1);
+
+ copy_list_member(tn, fn, &dep);
+
+ fc->data += fc->datasz + 8*fc->ptrs;
+ tc->data += tc->datasz + 8*tc->ptrs;
+ tc->len--;
+
+ } else { /* CAPN_PTR_LIST */
+ *fn = read_ptr(fc->seg, fc->data);
+
+ if (fn->type && copy_ptr(tc->seg, tc->data, tn, fn, &dep))
+ return -1;
+
+ fc->data += 8;
+ tc->data += 8;
+ tc->len--;
+ }
+ }
+
+ return 0;
+}
+
+/* TODO: handle CAPN_LIST, CAPN_PTR_LIST for bit lists */
+
+int capn_get1(capn_list1 l, int off) {
+ return l.p.type == CAPN_BIT_LIST
+ && off < l.p.len
+ && (l.p.data[off/8] & (1 << (off%8))) != 0;
+}
+
+int capn_set1(capn_list1 l, int off, int val) {
+ if (l.p.type != CAPN_BIT_LIST || off >= l.p.len)
+ return -1;
+ if (val) {
+ l.p.data[off/8] |= 1 << (off%8);
+ } else {
+ l.p.data[off/8] &= ~(1 << (off%8));
+ }
+ return 0;
+}
+
+int capn_getv1(capn_list1 l, int off, uint8_t *data, int sz) {
+ /* Note we only support aligned reads */
+ int bsz;
+ capn_ptr p;
+ capn_resolve(&l.p);
+ p = l.p;
+ if (p.type != CAPN_BIT_LIST || (off & 7) != 0)
+ return -1;
+
+ bsz = (sz + 7) / 8;
+ off /= 8;
+
+ if (off + sz > p.datasz) {
+ memcpy(data, p.data + off, p.datasz - off);
+ return p.len - off*8;
+ } else {
+ memcpy(data, p.data + off, bsz);
+ return sz;
+ }
+}
+
+int capn_setv1(capn_list1 l, int off, const uint8_t *data, int sz) {
+ /* Note we only support aligned writes */
+ int bsz;
+ capn_ptr p = l.p;
+ if (p.type != CAPN_BIT_LIST || (off & 7) != 0)
+ return -1;
+
+ bsz = (sz + 7) / 8;
+ off /= 8;
+
+ if (off + sz > p.datasz) {
+ memcpy(p.data + off, data, p.datasz - off);
+ return p.len - off*8;
+ } else {
+ memcpy(p.data + off, data, bsz);
+ return sz;
+ }
+}
+
+/* pull out whether we add a tag or not as a define so the unit test can
+ * test double far pointers by not creating tags */
+#ifndef ADD_TAG
+#define ADD_TAG 1
+#endif
+
+static void new_object(capn_ptr *p, int bytes) {
+ struct capn_segment *s = p->seg;
+
+ if (!s) {
+ memset(p, 0, sizeof(*p));
+ return;
+ }
+
+ /* pointer needs to be initialised to get a valid offset on write */
+ if (!bytes) {
+ p->data = s->data + s->len;
+ return;
+ }
+
+ /* all allocations are 8 byte aligned */
+ bytes = (bytes + 7) & ~7;
+
+ if (s->len + bytes <= s->cap) {
+ p->data = s->data + s->len;
+ s->len += bytes;
+ return;
+ }
+
+ /* add a tag whenever we switch segments so that write_ptr can
+ * use it */
+ p->data = new_data(s->capn, bytes + ADD_TAG*8, &p->seg);
+ if (!p->data) {
+ memset(p, 0, sizeof(*p));
+ return;
+ }
+
+ if (ADD_TAG) {
+ write_ptr_tag(p->data, *p, 0);
+ p->data += 8;
+ p->has_ptr_tag = 1;
+ }
+}
+
+capn_ptr capn_root(struct capn *c) {
+ capn_ptr r = {CAPN_PTR_LIST};
+ r.seg = lookup_segment(c, NULL, 0);
+ r.data = r.seg ? r.seg->data : new_data(c, 8, &r.seg);
+ r.len = 1;
+
+ if (!r.seg || r.seg->cap < 8) {
+ memset(&r, 0, sizeof(r));
+ } else if (r.seg->len < 8) {
+ r.seg->len = 8;
+ }
+
+ return r;
+}
+
+capn_ptr capn_new_struct(struct capn_segment *seg, int datasz, int ptrs) {
+ capn_ptr p = {CAPN_STRUCT};
+ p.seg = seg;
+ p.datasz = (datasz + 7) & ~7;
+ p.ptrs = ptrs;
+ new_object(&p, p.datasz + 8*p.ptrs);
+ return p;
+}
+
+capn_ptr capn_new_list(struct capn_segment *seg, int sz, int datasz, int ptrs) {
+ capn_ptr p = {CAPN_LIST};
+ p.seg = seg;
+ p.len = sz;
+
+ if (ptrs || datasz > 8) {
+ p.is_composite_list = 1;
+ p.datasz = (datasz + 7) & ~7;
+ p.ptrs = ptrs;
+ new_object(&p, p.len * (p.datasz + 8*p.ptrs) + 8);
+ if (p.data) {
+ uint64_t hdr = STRUCT_PTR | (U64(p.len) << 2) | (U64(p.datasz/8) << 32) | (U64(ptrs) << 48);
+ *(uint64_t*) p.data = capn_flip64(hdr);
+ p.data += 8;
+ }
+ } else if (datasz > 4) {
+ p.datasz = 8;
+ new_object(&p, p.len * 8);
+ } else if (datasz > 2) {
+ p.datasz = 4;
+ new_object(&p, p.len * 4);
+ } else {
+ p.datasz = datasz;
+ new_object(&p, p.len * datasz);
+ }
+
+ return p;
+}
+
+capn_list1 capn_new_list1(struct capn_segment *seg, int sz) {
+ capn_list1 l = {{CAPN_BIT_LIST}};
+ l.p.seg = seg;
+ l.p.datasz = (sz+7)/8;
+ l.p.len = sz;
+ new_object(&l.p, l.p.datasz);
+ return l;
+}
+
+capn_ptr capn_new_ptr_list(struct capn_segment *seg, int sz) {
+ capn_ptr p = {CAPN_PTR_LIST};
+ p.seg = seg;
+ p.len = sz;
+ p.ptrs = 0;
+ p.datasz = 0;
+ new_object(&p, sz*8);
+ return p;
+}
+
+capn_ptr capn_new_string(struct capn_segment *seg, const char *str, ssize_t sz) {
+ capn_ptr p = {CAPN_LIST};
+ p.seg = seg;
+ p.len = ((sz >= 0) ? (size_t)sz : strlen(str)) + 1;
+ p.datasz = 1;
+ new_object(&p, p.len);
+ if (p.data) {
+ memcpy(p.data, str, p.len - 1);
+ p.data[p.len - 1] = '\0';
+ }
+ return p;
+}
+
+capn_text capn_get_text(capn_ptr p, int off, capn_text def) {
+ capn_ptr m = capn_getp(p, off, 1);
+ capn_text ret = def;
+ if (m.type == CAPN_LIST && m.datasz == 1 && m.len && m.data[m.len - 1] == 0) {
+ ret.seg = m.seg;
+ ret.str = m.data;
+ ret.len = m.len - 1;
+ }
+ return ret;
+}
+
+int capn_set_text(capn_ptr p, int off, capn_text tgt) {
+ capn_ptr m = {CAPN_NULL};
+ if (tgt.seg) {
+ m.type = CAPN_LIST;
+ m.seg = tgt.seg;
+ m.data = (char*)tgt.str;
+ m.len = tgt.len + 1;
+ m.datasz = 1;
+ } else if (tgt.str) {
+ m = capn_new_string(p.seg, tgt.str, tgt.len);
+ }
+ return capn_setp(p, off, m);
+}
+
+capn_data capn_get_data(capn_ptr p, int off) {
+ capn_data ret;
+ ret.p = capn_getp(p, off, 1);
+ if (ret.p.type != CAPN_LIST || ret.p.datasz != 1) {
+ memset(&ret, 0, sizeof(ret));
+ }
+ return ret;
+}
+
+#define SZ 8
+#include "capn-list.inc"
+#undef SZ
+
+#define SZ 16
+#include "capn-list.inc"
+#undef SZ
+
+#define SZ 32
+#include "capn-list.inc"
+#undef SZ
+
+#define SZ 64
+#include "capn-list.inc"
+#undef SZ
diff --git a/src/lib/mpack/mpack.cc b/src/lib/mpack/mpack.cc
new file mode 100644
index 0000000..67e54e8
--- /dev/null
+++ b/src/lib/mpack/mpack.cc
@@ -0,0 +1,6440 @@
+/**
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015-2018 Nicholas Fraser
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+/*
+ * This is the MPack 1.0 amalgamation package.
+ *
+ * http://github.com/ludocode/mpack
+ */
+
+#define MPACK_INTERNAL 1
+#define MPACK_EMIT_INLINE_DEFS 1
+
+#include "mpack.h"
+
+
+/* mpack/mpack-platform.c.c */
+
+
+// We define MPACK_EMIT_INLINE_DEFS and include mpack.h to emit
+// standalone definitions of all (non-static) inline functions in MPack.
+
+#define MPACK_INTERNAL 1
+#define MPACK_EMIT_INLINE_DEFS 1
+
+/* #include "mpack-platform.h" */
+/* #include "mpack.h" */
+
+
+#if MPACK_DEBUG && MPACK_STDIO
+#include <stdarg.h>
+#endif
+
+
+
+#if MPACK_DEBUG
+
+#if MPACK_STDIO
+void mpack_assert_fail_format(const char* format, ...) {
+ char buffer[512];
+ va_list args;
+ va_start(args, format);
+ vsnprintf(buffer, sizeof(buffer), format, args);
+ va_end(args);
+ buffer[sizeof(buffer) - 1] = 0;
+ mpack_assert_fail_wrapper(buffer);
+}
+
+void mpack_break_hit_format(const char* format, ...) {
+ char buffer[512];
+ va_list args;
+ va_start(args, format);
+ vsnprintf(buffer, sizeof(buffer), format, args);
+ va_end(args);
+ buffer[sizeof(buffer) - 1] = 0;
+ mpack_break_hit(buffer);
+}
+#endif
+
+#if !MPACK_CUSTOM_ASSERT
+void mpack_assert_fail(const char* message) {
+ MPACK_UNUSED(message);
+
+ #if MPACK_STDIO
+ fprintf(stderr, "%s\n", message);
+ #endif
+}
+#endif
+
+// We split the assert failure from the wrapper so that a
+// custom assert function can return.
+void mpack_assert_fail_wrapper(const char* message) {
+
+ #ifdef MPACK_GCOV
+ // gcov marks even __builtin_unreachable() as an uncovered line. this
+ // silences it.
+ (mpack_assert_fail(message), __builtin_unreachable());
+
+ #else
+ mpack_assert_fail(message);
+
+ // mpack_assert_fail() is not supposed to return. in case it does, we
+ // abort.
+
+ #if !MPACK_NO_BUILTINS
+ #if defined(__GNUC__) || defined(__clang__)
+ __builtin_trap();
+ #elif defined(WIN32)
+ __debugbreak();
+ #endif
+ #endif
+
+ #if (defined(__GNUC__) || defined(__clang__)) && !MPACK_NO_BUILTINS
+ __builtin_abort();
+ #elif MPACK_STDLIB
+ abort();
+ #endif
+
+ MPACK_UNREACHABLE;
+ #endif
+}
+
+#if !MPACK_CUSTOM_BREAK
+
+// If we have a custom assert handler, break wraps it by default.
+// This allows users of MPack to only implement mpack_assert_fail() without
+// having to worry about the difference between assert and break.
+//
+// MPACK_CUSTOM_BREAK is available to define a separate break handler
+// (which is needed by the unit test suite), but this is not offered in
+// mpack-config.h for simplicity.
+
+#if MPACK_CUSTOM_ASSERT
+void mpack_break_hit(const char* message) {
+ mpack_assert_fail_wrapper(message);
+}
+#else
+void mpack_break_hit(const char* message) {
+ MPACK_UNUSED(message);
+
+ #if MPACK_STDIO
+ fprintf(stderr, "%s\n", message);
+ #endif
+
+ #if defined(__GNUC__) || defined(__clang__) && !MPACK_NO_BUILTINS
+ __builtin_trap();
+ #elif defined(WIN32) && !MPACK_NO_BUILTINS
+ __debugbreak();
+ #elif MPACK_STDLIB
+ abort();
+ #endif
+}
+#endif
+
+#endif
+
+#endif
+
+
+
+// The below are adapted from the C wikibook:
+// https://en.wikibooks.org/wiki/C_Programming/Strings
+
+#ifndef mpack_memcmp
+int mpack_memcmp(const void* s1, const void* s2, size_t n) {
+ const unsigned char *us1 = (const unsigned char *) s1;
+ const unsigned char *us2 = (const unsigned char *) s2;
+ while (n-- != 0) {
+ if (*us1 != *us2)
+ return (*us1 < *us2) ? -1 : +1;
+ us1++;
+ us2++;
+ }
+ return 0;
+}
+#endif
+
+#ifndef mpack_memcpy
+void* mpack_memcpy(void* MPACK_RESTRICT s1, const void* MPACK_RESTRICT s2, size_t n) {
+ char* MPACK_RESTRICT dst = (char *)s1;
+ const char* MPACK_RESTRICT src = (const char *)s2;
+ while (n-- != 0)
+ *dst++ = *src++;
+ return s1;
+}
+#endif
+
+#ifndef mpack_memmove
+void* mpack_memmove(void* s1, const void* s2, size_t n) {
+ char *p1 = (char *)s1;
+ const char *p2 = (const char *)s2;
+ if (p2 < p1 && p1 < p2 + n) {
+ p2 += n;
+ p1 += n;
+ while (n-- != 0)
+ *--p1 = *--p2;
+ } else
+ while (n-- != 0)
+ *p1++ = *p2++;
+ return s1;
+}
+#endif
+
+#ifndef mpack_memset
+void* mpack_memset(void* s, int c, size_t n) {
+ unsigned char *us = (unsigned char *)s;
+ unsigned char uc = (unsigned char)c;
+ while (n-- != 0)
+ *us++ = uc;
+ return s;
+}
+#endif
+
+#ifndef mpack_strlen
+size_t mpack_strlen(const char* s) {
+ const char* p = s;
+ while (*p != '\0')
+ p++;
+ return (size_t)(p - s);
+}
+#endif
+
+
+
+#if defined(MPACK_MALLOC) && !defined(MPACK_REALLOC)
+void* mpack_realloc(void* old_ptr, size_t used_size, size_t new_size) {
+ if (new_size == 0) {
+ if (old_ptr)
+ MPACK_FREE(old_ptr);
+ return NULL;
+ }
+
+ void* new_ptr = MPACK_MALLOC(new_size);
+ if (new_ptr == NULL)
+ return NULL;
+
+ mpack_memcpy(new_ptr, old_ptr, used_size);
+ MPACK_FREE(old_ptr);
+ return new_ptr;
+}
+#endif
+
+/* mpack/mpack-common.c.c */
+
+#define MPACK_INTERNAL 1
+
+/* #include "mpack-common.h" */
+
+#if MPACK_DEBUG && MPACK_STDIO
+#include <stdarg.h>
+#endif
+
+const char* mpack_error_to_string(mpack_error_t error) {
+ #if MPACK_STRINGS
+ switch (error) {
+ #define MPACK_ERROR_STRING_CASE(e) case e: return #e
+ MPACK_ERROR_STRING_CASE(mpack_ok);
+ MPACK_ERROR_STRING_CASE(mpack_error_io);
+ MPACK_ERROR_STRING_CASE(mpack_error_invalid);
+ MPACK_ERROR_STRING_CASE(mpack_error_unsupported);
+ MPACK_ERROR_STRING_CASE(mpack_error_type);
+ MPACK_ERROR_STRING_CASE(mpack_error_too_big);
+ MPACK_ERROR_STRING_CASE(mpack_error_memory);
+ MPACK_ERROR_STRING_CASE(mpack_error_bug);
+ MPACK_ERROR_STRING_CASE(mpack_error_data);
+ MPACK_ERROR_STRING_CASE(mpack_error_eof);
+ #undef MPACK_ERROR_STRING_CASE
+ }
+ mpack_assert(0, "unrecognized error %i", (int)error);
+ return "(unknown mpack_error_t)";
+ #else
+ MPACK_UNUSED(error);
+ return "";
+ #endif
+}
+
+const char* mpack_type_to_string(mpack_type_t type) {
+ #if MPACK_STRINGS
+ switch (type) {
+ #define MPACK_TYPE_STRING_CASE(e) case e: return #e
+ MPACK_TYPE_STRING_CASE(mpack_type_missing);
+ MPACK_TYPE_STRING_CASE(mpack_type_nil);
+ MPACK_TYPE_STRING_CASE(mpack_type_bool);
+ MPACK_TYPE_STRING_CASE(mpack_type_float);
+ MPACK_TYPE_STRING_CASE(mpack_type_double);
+ MPACK_TYPE_STRING_CASE(mpack_type_int);
+ MPACK_TYPE_STRING_CASE(mpack_type_uint);
+ MPACK_TYPE_STRING_CASE(mpack_type_str);
+ MPACK_TYPE_STRING_CASE(mpack_type_bin);
+ MPACK_TYPE_STRING_CASE(mpack_type_array);
+ MPACK_TYPE_STRING_CASE(mpack_type_map);
+ #if MPACK_EXTENSIONS
+ MPACK_TYPE_STRING_CASE(mpack_type_ext);
+ #endif
+ #undef MPACK_TYPE_STRING_CASE
+ }
+ mpack_assert(0, "unrecognized type %i", (int)type);
+ return "(unknown mpack_type_t)";
+ #else
+ MPACK_UNUSED(type);
+ return "";
+ #endif
+}
+
+int mpack_tag_cmp(mpack_tag_t left, mpack_tag_t right) {
+
+ // positive numbers may be stored as int; convert to uint
+ if (left.type == mpack_type_int && left.v.i >= 0) {
+ left.type = mpack_type_uint;
+ left.v.u = (uint64_t)left.v.i;
+ }
+ if (right.type == mpack_type_int && right.v.i >= 0) {
+ right.type = mpack_type_uint;
+ right.v.u = (uint64_t)right.v.i;
+ }
+
+ if (left.type != right.type)
+ return ((int)left.type < (int)right.type) ? -1 : 1;
+
+ switch (left.type) {
+ case mpack_type_missing: // fallthrough
+ case mpack_type_nil:
+ return 0;
+
+ case mpack_type_bool:
+ return (int)left.v.b - (int)right.v.b;
+
+ case mpack_type_int:
+ if (left.v.i == right.v.i)
+ return 0;
+ return (left.v.i < right.v.i) ? -1 : 1;
+
+ case mpack_type_uint:
+ if (left.v.u == right.v.u)
+ return 0;
+ return (left.v.u < right.v.u) ? -1 : 1;
+
+ case mpack_type_array:
+ case mpack_type_map:
+ if (left.v.n == right.v.n)
+ return 0;
+ return (left.v.n < right.v.n) ? -1 : 1;
+
+ case mpack_type_str:
+ case mpack_type_bin:
+ if (left.v.l == right.v.l)
+ return 0;
+ return (left.v.l < right.v.l) ? -1 : 1;
+
+ #if MPACK_EXTENSIONS
+ case mpack_type_ext:
+ if (left.exttype == right.exttype) {
+ if (left.v.l == right.v.l)
+ return 0;
+ return (left.v.l < right.v.l) ? -1 : 1;
+ }
+ return (int)left.exttype - (int)right.exttype;
+ #endif
+
+ // floats should not normally be compared for equality. we compare
+ // with memcmp() to silence compiler warnings, but this will return
+ // equal if both are NaNs with the same representation (though we may
+ // want this, for instance if you are for some bizarre reason using
+ // floats as map keys.) i'm not sure what the right thing to
+ // do is here. check for NaN first? always return false if the type
+ // is float? use operator== and pragmas to silence compiler warning?
+ // please send me your suggestions.
+ // note also that we don't convert floats to doubles, so when this is
+ // used for ordering purposes, all floats are ordered before all
+ // doubles.
+ case mpack_type_float:
+ return mpack_memcmp(&left.v.f, &right.v.f, sizeof(left.v.f));
+ case mpack_type_double:
+ return mpack_memcmp(&left.v.d, &right.v.d, sizeof(left.v.d));
+ }
+
+ mpack_assert(0, "unrecognized type %i", (int)left.type);
+ return false;
+}
+
+#if MPACK_DEBUG && MPACK_STDIO
+static char mpack_hex_char(uint8_t hex_value) {
+ return (hex_value < 10) ? (char)('0' + hex_value) : (char)('a' + (hex_value - 10));
+}
+
+static void mpack_tag_debug_complete_bin_ext(mpack_tag_t tag, size_t string_length, char* buffer, size_t buffer_size,
+ const char* prefix, size_t prefix_size)
+{
+ // If at any point in this function we run out of space in the buffer, we
+ // bail out. The outer tag print wrapper will make sure we have a
+ // null-terminator.
+
+ if (string_length == 0 || string_length >= buffer_size)
+ return;
+ buffer += string_length;
+ buffer_size -= string_length;
+
+ size_t total = mpack_tag_bytes(&tag);
+ if (total == 0) {
+ strncpy(buffer, ">", buffer_size);
+ return;
+ }
+
+ strncpy(buffer, ": ", buffer_size);
+ if (buffer_size < 2)
+ return;
+ buffer += 2;
+ buffer_size -= 2;
+
+ size_t hex_bytes = 0;
+ for (size_t i = 0; i < MPACK_PRINT_BYTE_COUNT && i < prefix_size && buffer_size > 2; ++i) {
+ uint8_t byte = (uint8_t)prefix[i];
+ buffer[0] = mpack_hex_char((uint8_t)(byte >> 4));
+ buffer[1] = mpack_hex_char((uint8_t)(byte & 0xfu));
+ buffer += 2;
+ buffer_size -= 2;
+ ++hex_bytes;
+ }
+
+ if (buffer_size != 0)
+ mpack_snprintf(buffer, buffer_size, "%s>", (total > hex_bytes) ? "..." : "");
+}
+
+static void mpack_tag_debug_pseudo_json_bin(mpack_tag_t tag, char* buffer, size_t buffer_size,
+ const char* prefix, size_t prefix_size)
+{
+ mpack_assert(mpack_tag_type(&tag) == mpack_type_bin);
+ size_t length = (size_t)mpack_snprintf(buffer, buffer_size, "<binary data of length %u", tag.v.l);
+ mpack_tag_debug_complete_bin_ext(tag, length, buffer, buffer_size, prefix, prefix_size);
+}
+
+#if MPACK_EXTENSIONS
+static void mpack_tag_debug_pseudo_json_ext(mpack_tag_t tag, char* buffer, size_t buffer_size,
+ const char* prefix, size_t prefix_size)
+{
+ mpack_assert(mpack_tag_type(&tag) == mpack_type_ext);
+ size_t length = (size_t)mpack_snprintf(buffer, buffer_size, "<ext data of type %i and length %u",
+ mpack_tag_ext_exttype(&tag), mpack_tag_ext_length(&tag));
+ mpack_tag_debug_complete_bin_ext(tag, length, buffer, buffer_size, prefix, prefix_size);
+}
+#endif
+
+static void mpack_tag_debug_pseudo_json_impl(mpack_tag_t tag, char* buffer, size_t buffer_size,
+ const char* prefix, size_t prefix_size)
+{
+ switch (tag.type) {
+ case mpack_type_missing:
+ mpack_snprintf(buffer, buffer_size, "<missing!>");
+ return;
+ case mpack_type_nil:
+ mpack_snprintf(buffer, buffer_size, "null");
+ return;
+ case mpack_type_bool:
+ mpack_snprintf(buffer, buffer_size, tag.v.b ? "true" : "false");
+ return;
+ case mpack_type_int:
+ mpack_snprintf(buffer, buffer_size, "%" PRIi64, tag.v.i);
+ return;
+ case mpack_type_uint:
+ mpack_snprintf(buffer, buffer_size, "%" PRIu64, tag.v.u);
+ return;
+ case mpack_type_float:
+ mpack_snprintf(buffer, buffer_size, "%f", tag.v.f);
+ return;
+ case mpack_type_double:
+ mpack_snprintf(buffer, buffer_size, "%f", tag.v.d);
+ return;
+
+ case mpack_type_str:
+ mpack_snprintf(buffer, buffer_size, "<string of %u bytes>", tag.v.l);
+ return;
+ case mpack_type_bin:
+ mpack_tag_debug_pseudo_json_bin(tag, buffer, buffer_size, prefix, prefix_size);
+ return;
+ #if MPACK_EXTENSIONS
+ case mpack_type_ext:
+ mpack_tag_debug_pseudo_json_ext(tag, buffer, buffer_size, prefix, prefix_size);
+ return;
+ #endif
+
+ case mpack_type_array:
+ mpack_snprintf(buffer, buffer_size, "<array of %u elements>", tag.v.n);
+ return;
+ case mpack_type_map:
+ mpack_snprintf(buffer, buffer_size, "<map of %u key-value pairs>", tag.v.n);
+ return;
+ }
+
+ mpack_snprintf(buffer, buffer_size, "<unknown!>");
+}
+
+void mpack_tag_debug_pseudo_json(mpack_tag_t tag, char* buffer, size_t buffer_size,
+ const char* prefix, size_t prefix_size)
+{
+ mpack_assert(buffer_size > 0, "buffer size cannot be zero!");
+ buffer[0] = 0;
+
+ mpack_tag_debug_pseudo_json_impl(tag, buffer, buffer_size, prefix, prefix_size);
+
+ // We always null-terminate the buffer manually just in case the snprintf()
+ // function doesn't null-terminate when the string doesn't fit.
+ buffer[buffer_size - 1] = 0;
+}
+
+static void mpack_tag_debug_describe_impl(mpack_tag_t tag, char* buffer, size_t buffer_size) {
+ switch (tag.type) {
+ case mpack_type_missing:
+ mpack_snprintf(buffer, buffer_size, "missing");
+ return;
+ case mpack_type_nil:
+ mpack_snprintf(buffer, buffer_size, "nil");
+ return;
+ case mpack_type_bool:
+ mpack_snprintf(buffer, buffer_size, tag.v.b ? "true" : "false");
+ return;
+ case mpack_type_int:
+ mpack_snprintf(buffer, buffer_size, "int %" PRIi64, tag.v.i);
+ return;
+ case mpack_type_uint:
+ mpack_snprintf(buffer, buffer_size, "uint %" PRIu64, tag.v.u);
+ return;
+ case mpack_type_float:
+ mpack_snprintf(buffer, buffer_size, "float %f", tag.v.f);
+ return;
+ case mpack_type_double:
+ mpack_snprintf(buffer, buffer_size, "double %f", tag.v.d);
+ return;
+ case mpack_type_str:
+ mpack_snprintf(buffer, buffer_size, "str of %u bytes", tag.v.l);
+ return;
+ case mpack_type_bin:
+ mpack_snprintf(buffer, buffer_size, "bin of %u bytes", tag.v.l);
+ return;
+ #if MPACK_EXTENSIONS
+ case mpack_type_ext:
+ mpack_snprintf(buffer, buffer_size, "ext of type %i, %u bytes",
+ mpack_tag_ext_exttype(&tag), mpack_tag_ext_length(&tag));
+ return;
+ #endif
+ case mpack_type_array:
+ mpack_snprintf(buffer, buffer_size, "array of %u elements", tag.v.n);
+ return;
+ case mpack_type_map:
+ mpack_snprintf(buffer, buffer_size, "map of %u key-value pairs", tag.v.n);
+ return;
+ }
+
+ mpack_snprintf(buffer, buffer_size, "unknown!");
+}
+
+void mpack_tag_debug_describe(mpack_tag_t tag, char* buffer, size_t buffer_size) {
+ mpack_assert(buffer_size > 0, "buffer size cannot be zero!");
+ buffer[0] = 0;
+
+ mpack_tag_debug_describe_impl(tag, buffer, buffer_size);
+
+ // We always null-terminate the buffer manually just in case the snprintf()
+ // function doesn't null-terminate when the string doesn't fit.
+ buffer[buffer_size - 1] = 0;
+}
+#endif
+
+
+
+#if MPACK_READ_TRACKING || MPACK_WRITE_TRACKING
+
+#ifndef MPACK_TRACKING_INITIAL_CAPACITY
+// seems like a reasonable number. we grow by doubling, and it only
+// needs to be as long as the maximum depth of the message.
+#define MPACK_TRACKING_INITIAL_CAPACITY 8
+#endif
+
+mpack_error_t mpack_track_init(mpack_track_t* track) {
+ track->count = 0;
+ track->capacity = MPACK_TRACKING_INITIAL_CAPACITY;
+ track->elements = (mpack_track_element_t*)MPACK_MALLOC(sizeof(mpack_track_element_t) * track->capacity);
+ if (track->elements == NULL)
+ return mpack_error_memory;
+ return mpack_ok;
+}
+
+mpack_error_t mpack_track_grow(mpack_track_t* track) {
+ mpack_assert(track->elements, "null track elements!");
+ mpack_assert(track->count == track->capacity, "incorrect growing?");
+
+ size_t new_capacity = track->capacity * 2;
+
+ mpack_track_element_t* new_elements = (mpack_track_element_t*)mpack_realloc(track->elements,
+ sizeof(mpack_track_element_t) * track->count, sizeof(mpack_track_element_t) * new_capacity);
+ if (new_elements == NULL)
+ return mpack_error_memory;
+
+ track->elements = new_elements;
+ track->capacity = new_capacity;
+ return mpack_ok;
+}
+
+mpack_error_t mpack_track_push(mpack_track_t* track, mpack_type_t type, uint64_t count) {
+ mpack_assert(track->elements, "null track elements!");
+ mpack_log("track pushing %s count %i\n", mpack_type_to_string(type), (int)count);
+
+ // maps have twice the number of elements (key/value pairs)
+ if (type == mpack_type_map)
+ count *= 2;
+
+ // grow if needed
+ if (track->count == track->capacity) {
+ mpack_error_t error = mpack_track_grow(track);
+ if (error != mpack_ok)
+ return error;
+ }
+
+ // insert new track
+ track->elements[track->count].type = type;
+ track->elements[track->count].left = count;
+ ++track->count;
+ return mpack_ok;
+}
+
+mpack_error_t mpack_track_pop(mpack_track_t* track, mpack_type_t type) {
+ mpack_assert(track->elements, "null track elements!");
+ mpack_log("track popping %s\n", mpack_type_to_string(type));
+
+ if (track->count == 0) {
+ mpack_break("attempting to close a %s but nothing was opened!", mpack_type_to_string(type));
+ return mpack_error_bug;
+ }
+
+ mpack_track_element_t* element = &track->elements[track->count - 1];
+
+ if (element->type != type) {
+ mpack_break("attempting to close a %s but the open element is a %s!",
+ mpack_type_to_string(type), mpack_type_to_string(element->type));
+ return mpack_error_bug;
+ }
+
+ if (element->left != 0) {
+ mpack_break("attempting to close a %s but there are %" PRIu64 " %s left",
+ mpack_type_to_string(type), element->left,
+ (type == mpack_type_map || type == mpack_type_array) ? "elements" : "bytes");
+ return mpack_error_bug;
+ }
+
+ --track->count;
+ return mpack_ok;
+}
+
+mpack_error_t mpack_track_peek_element(mpack_track_t* track, bool read) {
+ MPACK_UNUSED(read);
+ mpack_assert(track->elements, "null track elements!");
+
+ // if there are no open elements, that's fine, we can read/write elements at will
+ if (track->count == 0)
+ return mpack_ok;
+
+ mpack_track_element_t* element = &track->elements[track->count - 1];
+
+ if (element->type != mpack_type_map && element->type != mpack_type_array) {
+ mpack_break("elements cannot be %s within an %s", read ? "read" : "written",
+ mpack_type_to_string(element->type));
+ return mpack_error_bug;
+ }
+
+ if (element->left == 0) {
+ mpack_break("too many elements %s for %s", read ? "read" : "written",
+ mpack_type_to_string(element->type));
+ return mpack_error_bug;
+ }
+
+ return mpack_ok;
+}
+
+mpack_error_t mpack_track_element(mpack_track_t* track, bool read) {
+ mpack_error_t error = mpack_track_peek_element(track, read);
+ if (track->count > 0 && error == mpack_ok)
+ --track->elements[track->count - 1].left;
+ return error;
+}
+
+mpack_error_t mpack_track_bytes(mpack_track_t* track, bool read, uint64_t count) {
+ MPACK_UNUSED(read);
+ mpack_assert(track->elements, "null track elements!");
+
+ if (track->count == 0) {
+ mpack_break("bytes cannot be %s with no open bin, str or ext", read ? "read" : "written");
+ return mpack_error_bug;
+ }
+
+ mpack_track_element_t* element = &track->elements[track->count - 1];
+
+ if (element->type == mpack_type_map || element->type == mpack_type_array) {
+ mpack_break("bytes cannot be %s within an %s", read ? "read" : "written",
+ mpack_type_to_string(element->type));
+ return mpack_error_bug;
+ }
+
+ if (element->left < count) {
+ mpack_break("too many bytes %s for %s", read ? "read" : "written",
+ mpack_type_to_string(element->type));
+ return mpack_error_bug;
+ }
+
+ element->left -= count;
+ return mpack_ok;
+}
+
+mpack_error_t mpack_track_str_bytes_all(mpack_track_t* track, bool read, uint64_t count) {
+ mpack_error_t error = mpack_track_bytes(track, read, count);
+ if (error != mpack_ok)
+ return error;
+
+ mpack_track_element_t* element = &track->elements[track->count - 1];
+
+ if (element->type != mpack_type_str) {
+ mpack_break("the open type must be a string, not a %s", mpack_type_to_string(element->type));
+ return mpack_error_bug;
+ }
+
+ if (element->left != 0) {
+ mpack_break("not all bytes were read; the wrong byte count was requested for a string read.");
+ return mpack_error_bug;
+ }
+
+ return mpack_ok;
+}
+
+mpack_error_t mpack_track_check_empty(mpack_track_t* track) {
+ if (track->count != 0) {
+ mpack_break("unclosed %s", mpack_type_to_string(track->elements[0].type));
+ return mpack_error_bug;
+ }
+ return mpack_ok;
+}
+
+mpack_error_t mpack_track_destroy(mpack_track_t* track, bool cancel) {
+ mpack_error_t error = cancel ? mpack_ok : mpack_track_check_empty(track);
+ if (track->elements) {
+ MPACK_FREE(track->elements);
+ track->elements = NULL;
+ }
+ return error;
+}
+#endif
+
+
+
+static bool mpack_utf8_check_impl(const uint8_t* str, size_t count, bool allow_null) {
+ while (count > 0) {
+ uint8_t lead = str[0];
+
+ // NUL
+ if (!allow_null && lead == '\0') // we don't allow NUL bytes in MPack C-strings
+ return false;
+
+ // ASCII
+ if (lead <= 0x7F) {
+ ++str;
+ --count;
+
+ // 2-byte sequence
+ } else if ((lead & 0xE0) == 0xC0) {
+ if (count < 2) // truncated sequence
+ return false;
+
+ uint8_t cont = str[1];
+ if ((cont & 0xC0) != 0x80) // not a continuation byte
+ return false;
+
+ str += 2;
+ count -= 2;
+
+ uint32_t z = ((uint32_t)(lead & ~0xE0) << 6) |
+ (uint32_t)(cont & ~0xC0);
+
+ if (z < 0x80) // overlong sequence
+ return false;
+
+ // 3-byte sequence
+ } else if ((lead & 0xF0) == 0xE0) {
+ if (count < 3) // truncated sequence
+ return false;
+
+ uint8_t cont1 = str[1];
+ if ((cont1 & 0xC0) != 0x80) // not a continuation byte
+ return false;
+ uint8_t cont2 = str[2];
+ if ((cont2 & 0xC0) != 0x80) // not a continuation byte
+ return false;
+
+ str += 3;
+ count -= 3;
+
+ uint32_t z = ((uint32_t)(lead & ~0xF0) << 12) |
+ ((uint32_t)(cont1 & ~0xC0) << 6) |
+ (uint32_t)(cont2 & ~0xC0);
+
+ if (z < 0x800) // overlong sequence
+ return false;
+ if (z >= 0xD800 && z <= 0xDFFF) // surrogate
+ return false;
+
+ // 4-byte sequence
+ } else if ((lead & 0xF8) == 0xF0) {
+ if (count < 4) // truncated sequence
+ return false;
+
+ uint8_t cont1 = str[1];
+ if ((cont1 & 0xC0) != 0x80) // not a continuation byte
+ return false;
+ uint8_t cont2 = str[2];
+ if ((cont2 & 0xC0) != 0x80) // not a continuation byte
+ return false;
+ uint8_t cont3 = str[3];
+ if ((cont3 & 0xC0) != 0x80) // not a continuation byte
+ return false;
+
+ str += 4;
+ count -= 4;
+
+ uint32_t z = ((uint32_t)(lead & ~0xF8) << 18) |
+ ((uint32_t)(cont1 & ~0xC0) << 12) |
+ ((uint32_t)(cont2 & ~0xC0) << 6) |
+ (uint32_t)(cont3 & ~0xC0);
+
+ if (z < 0x10000) // overlong sequence
+ return false;
+ if (z > 0x10FFFF) // codepoint limit
+ return false;
+
+ } else {
+ return false; // continuation byte without a lead, or lead for a 5-byte sequence or longer
+ }
+ }
+ return true;
+}
+
+bool mpack_utf8_check(const char* str, size_t bytes) {
+ return mpack_utf8_check_impl((const uint8_t*)str, bytes, true);
+}
+
+bool mpack_utf8_check_no_null(const char* str, size_t bytes) {
+ return mpack_utf8_check_impl((const uint8_t*)str, bytes, false);
+}
+
+bool mpack_str_check_no_null(const char* str, size_t bytes) {
+ for (size_t i = 0; i < bytes; ++i)
+ if (str[i] == '\0')
+ return false;
+ return true;
+}
+
+#if MPACK_DEBUG && MPACK_STDIO
+void mpack_print_append(mpack_print_t* print, const char* data, size_t count) {
+
+ // copy whatever fits into the buffer
+ size_t copy = print->size - print->count;
+ if (copy > count)
+ copy = count;
+ mpack_memcpy(print->buffer + print->count, data, copy);
+ print->count += copy;
+ data += copy;
+ count -= copy;
+
+ // if we don't need to flush or can't flush there's nothing else to do
+ if (count == 0 || print->callback == NULL)
+ return;
+
+ // flush the buffer
+ print->callback(print->context, print->buffer, print->count);
+
+ if (count > print->size / 2) {
+ // flush the rest of the data
+ print->count = 0;
+ print->callback(print->context, data, count);
+ } else {
+ // copy the rest of the data into the buffer
+ mpack_memcpy(print->buffer, data, count);
+ print->count = count;
+ }
+
+}
+
+void mpack_print_flush(mpack_print_t* print) {
+ if (print->count > 0 && print->callback != NULL) {
+ print->callback(print->context, print->buffer, print->count);
+ print->count = 0;
+ }
+}
+
+void mpack_print_file_callback(void* context, const char* data, size_t count) {
+ FILE* file = (FILE*)context;
+ fwrite(data, 1, count, file);
+}
+#endif
+
+/* mpack/mpack-writer.c.c */
+
+#define MPACK_INTERNAL 1
+
+/* #include "mpack-writer.h" */
+
+#if MPACK_WRITER
+
+#if MPACK_WRITE_TRACKING
+static void mpack_writer_flag_if_error(mpack_writer_t* writer, mpack_error_t error) {
+ if (error != mpack_ok)
+ mpack_writer_flag_error(writer, error);
+}
+
+void mpack_writer_track_push(mpack_writer_t* writer, mpack_type_t type, uint64_t count) {
+ if (writer->error == mpack_ok)
+ mpack_writer_flag_if_error(writer, mpack_track_push(&writer->track, type, count));
+}
+
+void mpack_writer_track_pop(mpack_writer_t* writer, mpack_type_t type) {
+ if (writer->error == mpack_ok)
+ mpack_writer_flag_if_error(writer, mpack_track_pop(&writer->track, type));
+}
+
+void mpack_writer_track_element(mpack_writer_t* writer) {
+ if (writer->error == mpack_ok)
+ mpack_writer_flag_if_error(writer, mpack_track_element(&writer->track, false));
+}
+
+void mpack_writer_track_bytes(mpack_writer_t* writer, size_t count) {
+ if (writer->error == mpack_ok)
+ mpack_writer_flag_if_error(writer, mpack_track_bytes(&writer->track, false, count));
+}
+#endif
+
+static void mpack_writer_clear(mpack_writer_t* writer) {
+ #if MPACK_COMPATIBILITY
+ writer->version = mpack_version_current;
+ #endif
+ writer->flush = NULL;
+ writer->error_fn = NULL;
+ writer->teardown = NULL;
+ writer->context = NULL;
+
+ writer->buffer = NULL;
+ writer->current = NULL;
+ writer->end = NULL;
+ writer->error = mpack_ok;
+
+ #if MPACK_WRITE_TRACKING
+ mpack_memset(&writer->track, 0, sizeof(writer->track));
+ #endif
+}
+
+void mpack_writer_init(mpack_writer_t* writer, char* buffer, size_t size) {
+ mpack_assert(buffer != NULL, "cannot initialize writer with empty buffer");
+ mpack_writer_clear(writer);
+ writer->buffer = buffer;
+ writer->current = buffer;
+ writer->end = writer->buffer + size;
+
+ #if MPACK_WRITE_TRACKING
+ mpack_writer_flag_if_error(writer, mpack_track_init(&writer->track));
+ #endif
+
+ mpack_log("===========================\n");
+ mpack_log("initializing writer with buffer size %i\n", (int)size);
+}
+
+void mpack_writer_init_error(mpack_writer_t* writer, mpack_error_t error) {
+ mpack_writer_clear(writer);
+ writer->error = error;
+
+ mpack_log("===========================\n");
+ mpack_log("initializing writer in error state %i\n", (int)error);
+}
+
+void mpack_writer_set_flush(mpack_writer_t* writer, mpack_writer_flush_t flush) {
+ MPACK_STATIC_ASSERT(MPACK_WRITER_MINIMUM_BUFFER_SIZE >= MPACK_MAXIMUM_TAG_SIZE,
+ "minimum buffer size must fit any tag!");
+ MPACK_STATIC_ASSERT(31 + MPACK_TAG_SIZE_FIXSTR >= MPACK_WRITER_MINIMUM_BUFFER_SIZE,
+ "minimum buffer size must fit the largest possible fixstr!");
+
+ if (mpack_writer_buffer_size(writer) < MPACK_WRITER_MINIMUM_BUFFER_SIZE) {
+ mpack_break("buffer size is %i, but minimum buffer size for flush is %i",
+ (int)mpack_writer_buffer_size(writer), MPACK_WRITER_MINIMUM_BUFFER_SIZE);
+ mpack_writer_flag_error(writer, mpack_error_bug);
+ return;
+ }
+
+ writer->flush = flush;
+}
+
+#ifdef MPACK_MALLOC
+typedef struct mpack_growable_writer_t {
+ char** target_data;
+ size_t* target_size;
+} mpack_growable_writer_t;
+
+static char* mpack_writer_get_reserved(mpack_writer_t* writer) {
+ // This is in a separate function in order to avoid false strict aliasing
+ // warnings. We aren't actually violating strict aliasing (the reserved
+ // space is only ever dereferenced as an mpack_growable_writer_t.)
+ return (char*)writer->reserved;
+}
+
+static void mpack_growable_writer_flush(mpack_writer_t* writer, const char* data, size_t count) {
+
+ // This is an intrusive flush function which modifies the writer's buffer
+ // in response to a flush instead of emptying it in order to add more
+ // capacity for data. This removes the need to copy data from a fixed buffer
+ // into a growable one, improving performance.
+ //
+ // There are three ways flush can be called:
+ // - flushing the buffer during writing (used is zero, count is all data, data is buffer)
+ // - flushing extra data during writing (used is all flushed data, count is extra data, data is not buffer)
+ // - flushing during teardown (used and count are both all flushed data, data is buffer)
+ //
+ // In the first two cases, we grow the buffer by at least double, enough
+ // to ensure that new data will fit. We ignore the teardown flush.
+
+ if (data == writer->buffer) {
+
+ // teardown, do nothing
+ if (mpack_writer_buffer_used(writer) == count)
+ return;
+
+ // otherwise leave the data in the buffer and just grow
+ writer->current = writer->buffer + count;
+ count = 0;
+ }
+
+ size_t used = mpack_writer_buffer_used(writer);
+ size_t size = mpack_writer_buffer_size(writer);
+
+ mpack_log("flush size %i used %i data %p buffer %p\n",
+ (int)count, (int)used, data, writer->buffer);
+
+ mpack_assert(data == writer->buffer || used + count > size,
+ "extra flush for %i but there is %i space left in the buffer! (%i/%i)",
+ (int)count, (int)mpack_writer_buffer_left(writer), (int)used, (int)size);
+
+ // grow to fit the data
+ // TODO: this really needs to correctly test for overflow
+ size_t new_size = size * 2;
+ while (new_size < used + count)
+ new_size *= 2;
+
+ mpack_log("flush growing buffer size from %i to %i\n", (int)size, (int)new_size);
+
+ // grow the buffer
+ char* new_buffer = (char*)mpack_realloc(writer->buffer, used, new_size);
+ if (new_buffer == NULL) {
+ mpack_writer_flag_error(writer, mpack_error_memory);
+ return;
+ }
+ writer->current = new_buffer + used;
+ writer->buffer = new_buffer;
+ writer->end = writer->buffer + new_size;
+
+ // append the extra data
+ if (count > 0) {
+ mpack_memcpy(writer->current, data, count);
+ writer->current += count;
+ }
+
+ mpack_log("new buffer %p, used %i\n", new_buffer, (int)mpack_writer_buffer_used(writer));
+}
+
+static void mpack_growable_writer_teardown(mpack_writer_t* writer) {
+ mpack_growable_writer_t* growable_writer = (mpack_growable_writer_t*)mpack_writer_get_reserved(writer);
+
+ if (mpack_writer_error(writer) == mpack_ok) {
+
+ // shrink the buffer to an appropriate size if the data is
+ // much smaller than the buffer
+ if (mpack_writer_buffer_used(writer) < mpack_writer_buffer_size(writer) / 2) {
+ size_t used = mpack_writer_buffer_used(writer);
+
+ // We always return a non-null pointer that must be freed, even if
+ // nothing was written. malloc() and realloc() do not necessarily
+ // do this so we enforce it ourselves.
+ size_t size = (used != 0) ? used : 1;
+
+ char* buffer = (char*)mpack_realloc(writer->buffer, used, size);
+ if (!buffer) {
+ MPACK_FREE(writer->buffer);
+ mpack_writer_flag_error(writer, mpack_error_memory);
+ return;
+ }
+ writer->buffer = buffer;
+ writer->end = (writer->current = writer->buffer + used);
+ }
+
+ *growable_writer->target_data = writer->buffer;
+ *growable_writer->target_size = mpack_writer_buffer_used(writer);
+ writer->buffer = NULL;
+
+ } else if (writer->buffer) {
+ MPACK_FREE(writer->buffer);
+ writer->buffer = NULL;
+ }
+
+ writer->context = NULL;
+}
+
+void mpack_writer_init_growable(mpack_writer_t* writer, char** target_data, size_t* target_size) {
+ mpack_assert(target_data != NULL, "cannot initialize writer without a destination for the data");
+ mpack_assert(target_size != NULL, "cannot initialize writer without a destination for the size");
+
+ *target_data = NULL;
+ *target_size = 0;
+
+ MPACK_STATIC_ASSERT(sizeof(mpack_growable_writer_t) <= sizeof(writer->reserved),
+ "not enough reserved space for growable writer!");
+ mpack_growable_writer_t* growable_writer = (mpack_growable_writer_t*)mpack_writer_get_reserved(writer);
+
+ growable_writer->target_data = target_data;
+ growable_writer->target_size = target_size;
+
+ size_t capacity = MPACK_BUFFER_SIZE;
+ char* buffer = (char*)MPACK_MALLOC(capacity);
+ if (buffer == NULL) {
+ mpack_writer_init_error(writer, mpack_error_memory);
+ return;
+ }
+
+ mpack_writer_init(writer, buffer, capacity);
+ mpack_writer_set_flush(writer, mpack_growable_writer_flush);
+ mpack_writer_set_teardown(writer, mpack_growable_writer_teardown);
+}
+#endif
+
+#if MPACK_STDIO
+static void mpack_file_writer_flush(mpack_writer_t* writer, const char* buffer, size_t count) {
+ FILE* file = (FILE*)writer->context;
+ size_t written = fwrite((const void*)buffer, 1, count, file);
+ if (written != count)
+ mpack_writer_flag_error(writer, mpack_error_io);
+}
+
+static void mpack_file_writer_teardown(mpack_writer_t* writer) {
+ MPACK_FREE(writer->buffer);
+ writer->buffer = NULL;
+ writer->context = NULL;
+}
+
+static void mpack_file_writer_teardown_close(mpack_writer_t* writer) {
+ FILE* file = (FILE*)writer->context;
+
+ if (file) {
+ int ret = fclose(file);
+ if (ret != 0)
+ mpack_writer_flag_error(writer, mpack_error_io);
+ }
+
+ mpack_file_writer_teardown(writer);
+}
+
+void mpack_writer_init_stdfile(mpack_writer_t* writer, FILE* file, bool close_when_done) {
+ mpack_assert(file != NULL, "file is NULL");
+
+ size_t capacity = MPACK_BUFFER_SIZE;
+ char* buffer = (char*)MPACK_MALLOC(capacity);
+ if (buffer == NULL) {
+ mpack_writer_init_error(writer, mpack_error_memory);
+ if (close_when_done) {
+ fclose(file);
+ }
+ return;
+ }
+
+ mpack_writer_init(writer, buffer, capacity);
+ mpack_writer_set_context(writer, file);
+ mpack_writer_set_flush(writer, mpack_file_writer_flush);
+ mpack_writer_set_teardown(writer, close_when_done ?
+ mpack_file_writer_teardown_close :
+ mpack_file_writer_teardown);
+}
+
+void mpack_writer_init_filename(mpack_writer_t* writer, const char* filename) {
+ mpack_assert(filename != NULL, "filename is NULL");
+
+ FILE* file = fopen(filename, "wb");
+ if (file == NULL) {
+ mpack_writer_init_error(writer, mpack_error_io);
+ return;
+ }
+
+ mpack_writer_init_stdfile(writer, file, true);
+}
+#endif
+
+void mpack_writer_flag_error(mpack_writer_t* writer, mpack_error_t error) {
+ mpack_log("writer %p setting error %i: %s\n", writer, (int)error, mpack_error_to_string(error));
+
+ if (writer->error == mpack_ok) {
+ writer->error = error;
+ if (writer->error_fn)
+ writer->error_fn(writer, writer->error);
+ }
+}
+
+MPACK_STATIC_INLINE void mpack_writer_flush_unchecked(mpack_writer_t* writer) {
+ // This is a bit ugly; we reset used before calling flush so that
+ // a flush function can distinguish between flushing the buffer
+ // versus flushing external data. see mpack_growable_writer_flush()
+ size_t used = mpack_writer_buffer_used(writer);
+ writer->current = writer->buffer;
+ writer->flush(writer, writer->buffer, used);
+}
+
+void mpack_writer_flush_message(mpack_writer_t* writer) {
+ if (writer->error != mpack_ok)
+ return;
+
+ #if MPACK_WRITE_TRACKING
+ mpack_writer_flag_if_error(writer, mpack_track_check_empty(&writer->track));
+ if (writer->error != mpack_ok)
+ return;
+ #endif
+
+ if (writer->flush == NULL) {
+ mpack_break("cannot call mpack_writer_flush_message() without a flush function!");
+ mpack_writer_flag_error(writer, mpack_error_bug);
+ return;
+ }
+
+ if (mpack_writer_buffer_used(writer) > 0)
+ mpack_writer_flush_unchecked(writer);
+}
+
+// Ensures there are at least count bytes free in the buffer. This
+// will flag an error if the flush function fails to make enough
+// room in the buffer.
+MPACK_NOINLINE static bool mpack_writer_ensure(mpack_writer_t* writer, size_t count) {
+ mpack_assert(count != 0, "cannot ensure zero bytes!");
+ mpack_assert(count <= MPACK_WRITER_MINIMUM_BUFFER_SIZE,
+ "cannot ensure %i bytes, this is more than the minimum buffer size %i!",
+ (int)count, (int)MPACK_WRITER_MINIMUM_BUFFER_SIZE);
+ mpack_assert(count > mpack_writer_buffer_left(writer),
+ "request to ensure %i bytes but there are already %i left in the buffer!",
+ (int)count, (int)mpack_writer_buffer_left(writer));
+
+ mpack_log("ensuring %i bytes, %i left\n", (int)count, (int)mpack_writer_buffer_left(writer));
+
+ if (mpack_writer_error(writer) != mpack_ok)
+ return false;
+
+ if (writer->flush == NULL) {
+ mpack_writer_flag_error(writer, mpack_error_too_big);
+ return false;
+ }
+
+ mpack_writer_flush_unchecked(writer);
+ if (mpack_writer_error(writer) != mpack_ok)
+ return false;
+
+ if (mpack_writer_buffer_left(writer) >= count)
+ return true;
+
+ mpack_writer_flag_error(writer, mpack_error_io);
+ return false;
+}
+
+// Writes encoded bytes to the buffer when we already know the data
+// does not fit in the buffer (i.e. it straddles the edge of the
+// buffer.) If there is a flush function, it is guaranteed to be
+// called; otherwise mpack_error_too_big is raised.
+MPACK_NOINLINE static void mpack_write_native_straddle(mpack_writer_t* writer, const char* p, size_t count) {
+ mpack_assert(count == 0 || p != NULL, "data pointer for %i bytes is NULL", (int)count);
+
+ if (mpack_writer_error(writer) != mpack_ok)
+ return;
+ mpack_log("big write for %i bytes from %p, %i space left in buffer\n",
+ (int)count, p, (int)mpack_writer_buffer_left(writer));
+ mpack_assert(count > mpack_writer_buffer_left(writer),
+ "big write requested for %i bytes, but there is %i available "
+ "space in buffer. should have called mpack_write_native() instead",
+ (int)count, (int)(mpack_writer_buffer_left(writer)));
+
+ // we'll need a flush function
+ if (!writer->flush) {
+ mpack_writer_flag_error(writer, mpack_error_too_big);
+ return;
+ }
+
+ // flush the buffer
+ mpack_writer_flush_unchecked(writer);
+ if (mpack_writer_error(writer) != mpack_ok)
+ return;
+
+ // note that an intrusive flush function (such as mpack_growable_writer_flush())
+ // may have changed size and/or reset used to a non-zero value. we treat both as
+ // though they may have changed, and there may still be data in the buffer.
+
+ // flush the extra data directly if it doesn't fit in the buffer
+ if (count > mpack_writer_buffer_left(writer)) {
+ writer->flush(writer, p, count);
+ if (mpack_writer_error(writer) != mpack_ok)
+ return;
+ } else {
+ mpack_memcpy(writer->current, p, count);
+ writer->current += count;
+ }
+}
+
+// Writes encoded bytes to the buffer, flushing if necessary.
+MPACK_STATIC_INLINE void mpack_write_native(mpack_writer_t* writer, const char* p, size_t count) {
+ mpack_assert(count == 0 || p != NULL, "data pointer for %i bytes is NULL", (int)count);
+
+ if (mpack_writer_buffer_left(writer) < count) {
+ mpack_write_native_straddle(writer, p, count);
+ } else {
+ mpack_memcpy(writer->current, p, count);
+ writer->current += count;
+ }
+}
+
+mpack_error_t mpack_writer_destroy(mpack_writer_t* writer) {
+
+ // clean up tracking, asserting if we're not already in an error state
+ #if MPACK_WRITE_TRACKING
+ mpack_track_destroy(&writer->track, writer->error != mpack_ok);
+ #endif
+
+ // flush any outstanding data
+ if (mpack_writer_error(writer) == mpack_ok && mpack_writer_buffer_used(writer) != 0 && writer->flush != NULL) {
+ writer->flush(writer, writer->buffer, mpack_writer_buffer_used(writer));
+ writer->flush = NULL;
+ }
+
+ if (writer->teardown) {
+ writer->teardown(writer);
+ writer->teardown = NULL;
+ }
+
+ return writer->error;
+}
+
+void mpack_write_tag(mpack_writer_t* writer, mpack_tag_t value) {
+ switch (value.type) {
+ case mpack_type_missing:
+ mpack_break("cannot write a missing value!");
+ mpack_writer_flag_error(writer, mpack_error_bug);
+ return;
+
+ case mpack_type_nil: mpack_write_nil (writer); return;
+ case mpack_type_bool: mpack_write_bool (writer, value.v.b); return;
+ case mpack_type_float: mpack_write_float (writer, value.v.f); return;
+ case mpack_type_double: mpack_write_double(writer, value.v.d); return;
+ case mpack_type_int: mpack_write_int (writer, value.v.i); return;
+ case mpack_type_uint: mpack_write_uint (writer, value.v.u); return;
+
+ case mpack_type_str: mpack_start_str(writer, value.v.l); return;
+ case mpack_type_bin: mpack_start_bin(writer, value.v.l); return;
+
+ #if MPACK_EXTENSIONS
+ case mpack_type_ext:
+ mpack_start_ext(writer, mpack_tag_ext_exttype(&value), mpack_tag_ext_length(&value));
+ return;
+ #endif
+
+ case mpack_type_array: mpack_start_array(writer, value.v.n); return;
+ case mpack_type_map: mpack_start_map(writer, value.v.n); return;
+ }
+
+ mpack_break("unrecognized type %i", (int)value.type);
+ mpack_writer_flag_error(writer, mpack_error_bug);
+}
+
+MPACK_STATIC_INLINE void mpack_write_byte_element(mpack_writer_t* writer, char value) {
+ mpack_writer_track_element(writer);
+ if (MPACK_LIKELY(mpack_writer_buffer_left(writer) >= 1) || mpack_writer_ensure(writer, 1))
+ *(writer->current++) = value;
+}
+
+void mpack_write_nil(mpack_writer_t* writer) {
+ mpack_write_byte_element(writer, (char)0xc0);
+}
+
+void mpack_write_bool(mpack_writer_t* writer, bool value) {
+ mpack_write_byte_element(writer, (char)(0xc2 | (value ? 1 : 0)));
+}
+
+void mpack_write_true(mpack_writer_t* writer) {
+ mpack_write_byte_element(writer, (char)0xc3);
+}
+
+void mpack_write_false(mpack_writer_t* writer) {
+ mpack_write_byte_element(writer, (char)0xc2);
+}
+
+void mpack_write_object_bytes(mpack_writer_t* writer, const char* data, size_t bytes) {
+ mpack_writer_track_element(writer);
+ mpack_write_native(writer, data, bytes);
+}
+
+/*
+ * Encode functions
+ */
+
+MPACK_STATIC_INLINE void mpack_encode_fixuint(char* p, uint8_t value) {
+ mpack_assert(value <= 127);
+ mpack_store_u8(p, value);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_u8(char* p, uint8_t value) {
+ mpack_assert(value > 127);
+ mpack_store_u8(p, 0xcc);
+ mpack_store_u8(p + 1, value);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_u16(char* p, uint16_t value) {
+ mpack_assert(value > UINT8_MAX);
+ mpack_store_u8(p, 0xcd);
+ mpack_store_u16(p + 1, value);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_u32(char* p, uint32_t value) {
+ mpack_assert(value > UINT16_MAX);
+ mpack_store_u8(p, 0xce);
+ mpack_store_u32(p + 1, value);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_u64(char* p, uint64_t value) {
+ mpack_assert(value > UINT32_MAX);
+ mpack_store_u8(p, 0xcf);
+ mpack_store_u64(p + 1, value);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_fixint(char* p, int8_t value) {
+ // this can encode positive or negative fixints
+ mpack_assert(value >= -32);
+ mpack_store_i8(p, value);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_i8(char* p, int8_t value) {
+ mpack_assert(value < -32);
+ mpack_store_u8(p, 0xd0);
+ mpack_store_i8(p + 1, value);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_i16(char* p, int16_t value) {
+ mpack_assert(value < INT8_MIN);
+ mpack_store_u8(p, 0xd1);
+ mpack_store_i16(p + 1, value);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_i32(char* p, int32_t value) {
+ mpack_assert(value < INT16_MIN);
+ mpack_store_u8(p, 0xd2);
+ mpack_store_i32(p + 1, value);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_i64(char* p, int64_t value) {
+ mpack_assert(value < INT32_MIN);
+ mpack_store_u8(p, 0xd3);
+ mpack_store_i64(p + 1, value);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_float(char* p, float value) {
+ mpack_store_u8(p, 0xca);
+ mpack_store_float(p + 1, value);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_double(char* p, double value) {
+ mpack_store_u8(p, 0xcb);
+ mpack_store_double(p + 1, value);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_fixarray(char* p, uint8_t count) {
+ mpack_assert(count <= 15);
+ mpack_store_u8(p, (uint8_t)(0x90 | count));
+}
+
+MPACK_STATIC_INLINE void mpack_encode_array16(char* p, uint16_t count) {
+ mpack_assert(count > 15);
+ mpack_store_u8(p, 0xdc);
+ mpack_store_u16(p + 1, count);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_array32(char* p, uint32_t count) {
+ mpack_assert(count > UINT16_MAX);
+ mpack_store_u8(p, 0xdd);
+ mpack_store_u32(p + 1, count);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_fixmap(char* p, uint8_t count) {
+ mpack_assert(count <= 15);
+ mpack_store_u8(p, (uint8_t)(0x80 | count));
+}
+
+MPACK_STATIC_INLINE void mpack_encode_map16(char* p, uint16_t count) {
+ mpack_assert(count > 15);
+ mpack_store_u8(p, 0xde);
+ mpack_store_u16(p + 1, count);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_map32(char* p, uint32_t count) {
+ mpack_assert(count > UINT16_MAX);
+ mpack_store_u8(p, 0xdf);
+ mpack_store_u32(p + 1, count);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_fixstr(char* p, uint8_t count) {
+ mpack_assert(count <= 31);
+ mpack_store_u8(p, (uint8_t)(0xa0 | count));
+}
+
+MPACK_STATIC_INLINE void mpack_encode_str8(char* p, uint8_t count) {
+ mpack_assert(count > 31);
+ mpack_store_u8(p, 0xd9);
+ mpack_store_u8(p + 1, count);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_str16(char* p, uint16_t count) {
+ // we might be encoding a raw in compatibility mode, so we
+ // allow count to be in the range [32, UINT8_MAX].
+ mpack_assert(count > 31);
+ mpack_store_u8(p, 0xda);
+ mpack_store_u16(p + 1, count);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_str32(char* p, uint32_t count) {
+ mpack_assert(count > UINT16_MAX);
+ mpack_store_u8(p, 0xdb);
+ mpack_store_u32(p + 1, count);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_bin8(char* p, uint8_t count) {
+ mpack_store_u8(p, 0xc4);
+ mpack_store_u8(p + 1, count);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_bin16(char* p, uint16_t count) {
+ mpack_assert(count > UINT8_MAX);
+ mpack_store_u8(p, 0xc5);
+ mpack_store_u16(p + 1, count);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_bin32(char* p, uint32_t count) {
+ mpack_assert(count > UINT16_MAX);
+ mpack_store_u8(p, 0xc6);
+ mpack_store_u32(p + 1, count);
+}
+
+#if MPACK_EXTENSIONS
+MPACK_STATIC_INLINE void mpack_encode_fixext1(char* p, int8_t exttype) {
+ mpack_store_u8(p, 0xd4);
+ mpack_store_i8(p + 1, exttype);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_fixext2(char* p, int8_t exttype) {
+ mpack_store_u8(p, 0xd5);
+ mpack_store_i8(p + 1, exttype);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_fixext4(char* p, int8_t exttype) {
+ mpack_store_u8(p, 0xd6);
+ mpack_store_i8(p + 1, exttype);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_fixext8(char* p, int8_t exttype) {
+ mpack_store_u8(p, 0xd7);
+ mpack_store_i8(p + 1, exttype);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_fixext16(char* p, int8_t exttype) {
+ mpack_store_u8(p, 0xd8);
+ mpack_store_i8(p + 1, exttype);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_ext8(char* p, int8_t exttype, uint8_t count) {
+ mpack_assert(count != 1 && count != 2 && count != 4 && count != 8 && count != 16);
+ mpack_store_u8(p, 0xc7);
+ mpack_store_u8(p + 1, count);
+ mpack_store_i8(p + 2, exttype);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_ext16(char* p, int8_t exttype, uint16_t count) {
+ mpack_assert(count > UINT8_MAX);
+ mpack_store_u8(p, 0xc8);
+ mpack_store_u16(p + 1, count);
+ mpack_store_i8(p + 3, exttype);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_ext32(char* p, int8_t exttype, uint32_t count) {
+ mpack_assert(count > UINT16_MAX);
+ mpack_store_u8(p, 0xc9);
+ mpack_store_u32(p + 1, count);
+ mpack_store_i8(p + 5, exttype);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_timestamp_4(char* p, uint32_t seconds) {
+ mpack_encode_fixext4(p, MPACK_EXTTYPE_TIMESTAMP);
+ mpack_store_u32(p + MPACK_TAG_SIZE_FIXEXT4, seconds);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_timestamp_8(char* p, int64_t seconds, uint32_t nanoseconds) {
+ mpack_assert(nanoseconds <= MPACK_TIMESTAMP_NANOSECONDS_MAX);
+ mpack_encode_fixext8(p, MPACK_EXTTYPE_TIMESTAMP);
+ uint64_t encoded = ((uint64_t)nanoseconds << 34) | (uint64_t)seconds;
+ mpack_store_u64(p + MPACK_TAG_SIZE_FIXEXT8, encoded);
+}
+
+MPACK_STATIC_INLINE void mpack_encode_timestamp_12(char* p, int64_t seconds, uint32_t nanoseconds) {
+ mpack_assert(nanoseconds <= MPACK_TIMESTAMP_NANOSECONDS_MAX);
+ mpack_encode_ext8(p, MPACK_EXTTYPE_TIMESTAMP, 12);
+ mpack_store_u32(p + MPACK_TAG_SIZE_EXT8, nanoseconds);
+ mpack_store_i64(p + MPACK_TAG_SIZE_EXT8 + 4, seconds);
+}
+#endif
+
+
+
+/*
+ * Write functions
+ */
+
+// This is a macro wrapper to the encode functions to encode
+// directly into the buffer. If mpack_writer_ensure() fails
+// it will flag an error so we don't have to do anything.
+#define MPACK_WRITE_ENCODED(encode_fn, size, ...) do { \
+ if (MPACK_LIKELY(mpack_writer_buffer_left(writer) >= size) || mpack_writer_ensure(writer, size)) { \
+ MPACK_EXPAND(encode_fn(writer->current, __VA_ARGS__)); \
+ writer->current += size; \
+ } \
+} while (0)
+
+void mpack_write_u8(mpack_writer_t* writer, uint8_t value) {
+ #if MPACK_OPTIMIZE_FOR_SIZE
+ mpack_write_u64(writer, value);
+ #else
+ mpack_writer_track_element(writer);
+ if (value <= 127) {
+ MPACK_WRITE_ENCODED(mpack_encode_fixuint, MPACK_TAG_SIZE_FIXUINT, value);
+ } else {
+ MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, value);
+ }
+ #endif
+}
+
+void mpack_write_u16(mpack_writer_t* writer, uint16_t value) {
+ #if MPACK_OPTIMIZE_FOR_SIZE
+ mpack_write_u64(writer, value);
+ #else
+ mpack_writer_track_element(writer);
+ if (value <= 127) {
+ MPACK_WRITE_ENCODED(mpack_encode_fixuint, MPACK_TAG_SIZE_FIXUINT, (uint8_t)value);
+ } else if (value <= UINT8_MAX) {
+ MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value);
+ } else {
+ MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, value);
+ }
+ #endif
+}
+
+void mpack_write_u32(mpack_writer_t* writer, uint32_t value) {
+ #if MPACK_OPTIMIZE_FOR_SIZE
+ mpack_write_u64(writer, value);
+ #else
+ mpack_writer_track_element(writer);
+ if (value <= 127) {
+ MPACK_WRITE_ENCODED(mpack_encode_fixuint, MPACK_TAG_SIZE_FIXUINT, (uint8_t)value);
+ } else if (value <= UINT8_MAX) {
+ MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value);
+ } else if (value <= UINT16_MAX) {
+ MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value);
+ } else {
+ MPACK_WRITE_ENCODED(mpack_encode_u32, MPACK_TAG_SIZE_U32, value);
+ }
+ #endif
+}
+
+void mpack_write_u64(mpack_writer_t* writer, uint64_t value) {
+ mpack_writer_track_element(writer);
+
+ if (value <= 127) {
+ MPACK_WRITE_ENCODED(mpack_encode_fixuint, MPACK_TAG_SIZE_FIXUINT, (uint8_t)value);
+ } else if (value <= UINT8_MAX) {
+ MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value);
+ } else if (value <= UINT16_MAX) {
+ MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value);
+ } else if (value <= UINT32_MAX) {
+ MPACK_WRITE_ENCODED(mpack_encode_u32, MPACK_TAG_SIZE_U32, (uint32_t)value);
+ } else {
+ MPACK_WRITE_ENCODED(mpack_encode_u64, MPACK_TAG_SIZE_U64, value);
+ }
+}
+
+void mpack_write_i8(mpack_writer_t* writer, int8_t value) {
+ #if MPACK_OPTIMIZE_FOR_SIZE
+ mpack_write_i64(writer, value);
+ #else
+ mpack_writer_track_element(writer);
+ if (value >= -32) {
+ // we encode positive and negative fixints together
+ MPACK_WRITE_ENCODED(mpack_encode_fixint, MPACK_TAG_SIZE_FIXINT, (int8_t)value);
+ } else {
+ MPACK_WRITE_ENCODED(mpack_encode_i8, MPACK_TAG_SIZE_I8, (int8_t)value);
+ }
+ #endif
+}
+
+void mpack_write_i16(mpack_writer_t* writer, int16_t value) {
+ #if MPACK_OPTIMIZE_FOR_SIZE
+ mpack_write_i64(writer, value);
+ #else
+ mpack_writer_track_element(writer);
+ if (value >= -32) {
+ if (value <= 127) {
+ // we encode positive and negative fixints together
+ MPACK_WRITE_ENCODED(mpack_encode_fixint, MPACK_TAG_SIZE_FIXINT, (int8_t)value);
+ } else if (value <= UINT8_MAX) {
+ MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value);
+ } else {
+ MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value);
+ }
+ } else if (value >= INT8_MIN) {
+ MPACK_WRITE_ENCODED(mpack_encode_i8, MPACK_TAG_SIZE_I8, (int8_t)value);
+ } else {
+ MPACK_WRITE_ENCODED(mpack_encode_i16, MPACK_TAG_SIZE_I16, (int16_t)value);
+ }
+ #endif
+}
+
+void mpack_write_i32(mpack_writer_t* writer, int32_t value) {
+ #if MPACK_OPTIMIZE_FOR_SIZE
+ mpack_write_i64(writer, value);
+ #else
+ mpack_writer_track_element(writer);
+ if (value >= -32) {
+ if (value <= 127) {
+ // we encode positive and negative fixints together
+ MPACK_WRITE_ENCODED(mpack_encode_fixint, MPACK_TAG_SIZE_FIXINT, (int8_t)value);
+ } else if (value <= UINT8_MAX) {
+ MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value);
+ } else if (value <= UINT16_MAX) {
+ MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value);
+ } else {
+ MPACK_WRITE_ENCODED(mpack_encode_u32, MPACK_TAG_SIZE_U32, (uint32_t)value);
+ }
+ } else if (value >= INT8_MIN) {
+ MPACK_WRITE_ENCODED(mpack_encode_i8, MPACK_TAG_SIZE_I8, (int8_t)value);
+ } else if (value >= INT16_MIN) {
+ MPACK_WRITE_ENCODED(mpack_encode_i16, MPACK_TAG_SIZE_I16, (int16_t)value);
+ } else {
+ MPACK_WRITE_ENCODED(mpack_encode_i32, MPACK_TAG_SIZE_I32, value);
+ }
+ #endif
+}
+
+void mpack_write_i64(mpack_writer_t* writer, int64_t value) {
+ #if MPACK_OPTIMIZE_FOR_SIZE
+ if (value > 127) {
+ // for non-fix positive ints we call the u64 writer to save space
+ mpack_write_u64(writer, (uint64_t)value);
+ return;
+ }
+ #endif
+
+ mpack_writer_track_element(writer);
+ if (value >= -32) {
+ #if MPACK_OPTIMIZE_FOR_SIZE
+ MPACK_WRITE_ENCODED(mpack_encode_fixint, MPACK_TAG_SIZE_FIXINT, (int8_t)value);
+ #else
+ if (value <= 127) {
+ MPACK_WRITE_ENCODED(mpack_encode_fixint, MPACK_TAG_SIZE_FIXINT, (int8_t)value);
+ } else if (value <= UINT8_MAX) {
+ MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value);
+ } else if (value <= UINT16_MAX) {
+ MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value);
+ } else if (value <= UINT32_MAX) {
+ MPACK_WRITE_ENCODED(mpack_encode_u32, MPACK_TAG_SIZE_U32, (uint32_t)value);
+ } else {
+ MPACK_WRITE_ENCODED(mpack_encode_u64, MPACK_TAG_SIZE_U64, (uint64_t)value);
+ }
+ #endif
+ } else if (value >= INT8_MIN) {
+ MPACK_WRITE_ENCODED(mpack_encode_i8, MPACK_TAG_SIZE_I8, (int8_t)value);
+ } else if (value >= INT16_MIN) {
+ MPACK_WRITE_ENCODED(mpack_encode_i16, MPACK_TAG_SIZE_I16, (int16_t)value);
+ } else if (value >= INT32_MIN) {
+ MPACK_WRITE_ENCODED(mpack_encode_i32, MPACK_TAG_SIZE_I32, (int32_t)value);
+ } else {
+ MPACK_WRITE_ENCODED(mpack_encode_i64, MPACK_TAG_SIZE_I64, value);
+ }
+}
+
+void mpack_write_float(mpack_writer_t* writer, float value) {
+ mpack_writer_track_element(writer);
+ MPACK_WRITE_ENCODED(mpack_encode_float, MPACK_TAG_SIZE_FLOAT, value);
+}
+
+void mpack_write_double(mpack_writer_t* writer, double value) {
+ mpack_writer_track_element(writer);
+ MPACK_WRITE_ENCODED(mpack_encode_double, MPACK_TAG_SIZE_DOUBLE, value);
+}
+
+#if MPACK_EXTENSIONS
+void mpack_write_timestamp(mpack_writer_t* writer, int64_t seconds, uint32_t nanoseconds) {
+ #if MPACK_COMPATIBILITY
+ if (writer->version <= mpack_version_v4) {
+ mpack_break("Timestamps require spec version v5 or later. This writer is in v%i mode.", (int)writer->version);
+ mpack_writer_flag_error(writer, mpack_error_bug);
+ return;
+ }
+ #endif
+
+ if (nanoseconds > MPACK_TIMESTAMP_NANOSECONDS_MAX) {
+ mpack_break("timestamp nanoseconds out of bounds: %u", nanoseconds);
+ mpack_writer_flag_error(writer, mpack_error_bug);
+ return;
+ }
+
+ mpack_writer_track_element(writer);
+
+ if (seconds < 0 || seconds >= (INT64_C(1) << 34)) {
+ MPACK_WRITE_ENCODED(mpack_encode_timestamp_12, MPACK_EXT_SIZE_TIMESTAMP12, seconds, nanoseconds);
+ } else if (seconds > UINT32_MAX || nanoseconds > 0) {
+ MPACK_WRITE_ENCODED(mpack_encode_timestamp_8, MPACK_EXT_SIZE_TIMESTAMP8, seconds, nanoseconds);
+ } else {
+ MPACK_WRITE_ENCODED(mpack_encode_timestamp_4, MPACK_EXT_SIZE_TIMESTAMP4, (uint32_t)seconds);
+ }
+}
+#endif
+
+void mpack_start_array(mpack_writer_t* writer, uint32_t count) {
+ mpack_writer_track_element(writer);
+
+ if (count <= 15) {
+ MPACK_WRITE_ENCODED(mpack_encode_fixarray, MPACK_TAG_SIZE_FIXARRAY, (uint8_t)count);
+ } else if (count <= UINT16_MAX) {
+ MPACK_WRITE_ENCODED(mpack_encode_array16, MPACK_TAG_SIZE_ARRAY16, (uint16_t)count);
+ } else {
+ MPACK_WRITE_ENCODED(mpack_encode_array32, MPACK_TAG_SIZE_ARRAY32, (uint32_t)count);
+ }
+
+ mpack_writer_track_push(writer, mpack_type_array, count);
+}
+
+void mpack_start_map(mpack_writer_t* writer, uint32_t count) {
+ mpack_writer_track_element(writer);
+
+ if (count <= 15) {
+ MPACK_WRITE_ENCODED(mpack_encode_fixmap, MPACK_TAG_SIZE_FIXMAP, (uint8_t)count);
+ } else if (count <= UINT16_MAX) {
+ MPACK_WRITE_ENCODED(mpack_encode_map16, MPACK_TAG_SIZE_MAP16, (uint16_t)count);
+ } else {
+ MPACK_WRITE_ENCODED(mpack_encode_map32, MPACK_TAG_SIZE_MAP32, (uint32_t)count);
+ }
+
+ mpack_writer_track_push(writer, mpack_type_map, count);
+}
+
+static void mpack_start_str_notrack(mpack_writer_t* writer, uint32_t count) {
+ if (count <= 31) {
+ MPACK_WRITE_ENCODED(mpack_encode_fixstr, MPACK_TAG_SIZE_FIXSTR, (uint8_t)count);
+
+ // str8 is only supported in v5 or later.
+ } else if (count <= UINT8_MAX
+ #if MPACK_COMPATIBILITY
+ && writer->version >= mpack_version_v5
+ #endif
+ ) {
+ MPACK_WRITE_ENCODED(mpack_encode_str8, MPACK_TAG_SIZE_STR8, (uint8_t)count);
+
+ } else if (count <= UINT16_MAX) {
+ MPACK_WRITE_ENCODED(mpack_encode_str16, MPACK_TAG_SIZE_STR16, (uint16_t)count);
+ } else {
+ MPACK_WRITE_ENCODED(mpack_encode_str32, MPACK_TAG_SIZE_STR32, (uint32_t)count);
+ }
+}
+
+static void mpack_start_bin_notrack(mpack_writer_t* writer, uint32_t count) {
+ #if MPACK_COMPATIBILITY
+ // In the v4 spec, there was only the raw type for any kind of
+ // variable-length data. In v4 mode, we support the bin functions,
+ // but we produce an old-style raw.
+ if (writer->version <= mpack_version_v4) {
+ mpack_start_str_notrack(writer, count);
+ return;
+ }
+ #endif
+
+ if (count <= UINT8_MAX) {
+ MPACK_WRITE_ENCODED(mpack_encode_bin8, MPACK_TAG_SIZE_BIN8, (uint8_t)count);
+ } else if (count <= UINT16_MAX) {
+ MPACK_WRITE_ENCODED(mpack_encode_bin16, MPACK_TAG_SIZE_BIN16, (uint16_t)count);
+ } else {
+ MPACK_WRITE_ENCODED(mpack_encode_bin32, MPACK_TAG_SIZE_BIN32, (uint32_t)count);
+ }
+}
+
+void mpack_start_str(mpack_writer_t* writer, uint32_t count) {
+ mpack_writer_track_element(writer);
+ mpack_start_str_notrack(writer, count);
+ mpack_writer_track_push(writer, mpack_type_str, count);
+}
+
+void mpack_start_bin(mpack_writer_t* writer, uint32_t count) {
+ mpack_writer_track_element(writer);
+ mpack_start_bin_notrack(writer, count);
+ mpack_writer_track_push(writer, mpack_type_bin, count);
+}
+
+#if MPACK_EXTENSIONS
+void mpack_start_ext(mpack_writer_t* writer, int8_t exttype, uint32_t count) {
+ #if MPACK_COMPATIBILITY
+ if (writer->version <= mpack_version_v4) {
+ mpack_break("Ext types require spec version v5 or later. This writer is in v%i mode.", (int)writer->version);
+ mpack_writer_flag_error(writer, mpack_error_bug);
+ return;
+ }
+ #endif
+
+ mpack_writer_track_element(writer);
+
+ if (count == 1) {
+ MPACK_WRITE_ENCODED(mpack_encode_fixext1, MPACK_TAG_SIZE_FIXEXT1, exttype);
+ } else if (count == 2) {
+ MPACK_WRITE_ENCODED(mpack_encode_fixext2, MPACK_TAG_SIZE_FIXEXT2, exttype);
+ } else if (count == 4) {
+ MPACK_WRITE_ENCODED(mpack_encode_fixext4, MPACK_TAG_SIZE_FIXEXT4, exttype);
+ } else if (count == 8) {
+ MPACK_WRITE_ENCODED(mpack_encode_fixext8, MPACK_TAG_SIZE_FIXEXT8, exttype);
+ } else if (count == 16) {
+ MPACK_WRITE_ENCODED(mpack_encode_fixext16, MPACK_TAG_SIZE_FIXEXT16, exttype);
+ } else if (count <= UINT8_MAX) {
+ MPACK_WRITE_ENCODED(mpack_encode_ext8, MPACK_TAG_SIZE_EXT8, exttype, (uint8_t)count);
+ } else if (count <= UINT16_MAX) {
+ MPACK_WRITE_ENCODED(mpack_encode_ext16, MPACK_TAG_SIZE_EXT16, exttype, (uint16_t)count);
+ } else {
+ MPACK_WRITE_ENCODED(mpack_encode_ext32, MPACK_TAG_SIZE_EXT32, exttype, (uint32_t)count);
+ }
+
+ mpack_writer_track_push(writer, mpack_type_ext, count);
+}
+#endif
+
+
+
+/*
+ * Compound helpers and other functions
+ */
+
+void mpack_write_str(mpack_writer_t* writer, const char* data, uint32_t count) {
+ mpack_assert(data != NULL, "data for string of length %i is NULL", (int)count);
+
+ #if MPACK_OPTIMIZE_FOR_SIZE
+ mpack_writer_track_element(writer);
+ mpack_start_str_notrack(writer, count);
+ mpack_write_native(writer, data, count);
+ #else
+
+ mpack_writer_track_element(writer);
+
+ if (count <= 31) {
+ // The minimum buffer size when using a flush function is guaranteed to
+ // fit the largest possible fixstr.
+ size_t size = count + MPACK_TAG_SIZE_FIXSTR;
+ if (MPACK_LIKELY(mpack_writer_buffer_left(writer) >= size) || mpack_writer_ensure(writer, size)) {
+ char* MPACK_RESTRICT p = writer->current;
+ mpack_encode_fixstr(p, (uint8_t)count);
+ mpack_memcpy(p + MPACK_TAG_SIZE_FIXSTR, data, count);
+ writer->current += count + MPACK_TAG_SIZE_FIXSTR;
+ }
+ return;
+ }
+
+ if (count <= UINT8_MAX
+ #if MPACK_COMPATIBILITY
+ && writer->version >= mpack_version_v5
+ #endif
+ ) {
+ if (count + MPACK_TAG_SIZE_STR8 <= mpack_writer_buffer_left(writer)) {
+ char* MPACK_RESTRICT p = writer->current;
+ mpack_encode_str8(p, (uint8_t)count);
+ mpack_memcpy(p + MPACK_TAG_SIZE_STR8, data, count);
+ writer->current += count + MPACK_TAG_SIZE_STR8;
+ } else {
+ MPACK_WRITE_ENCODED(mpack_encode_str8, MPACK_TAG_SIZE_STR8, (uint8_t)count);
+ mpack_write_native(writer, data, count);
+ }
+ return;
+ }
+
+ // str16 and str32 are likely to be a significant fraction of the buffer
+ // size, so we don't bother with a combined space check in order to
+ // minimize code size.
+ if (count <= UINT16_MAX) {
+ MPACK_WRITE_ENCODED(mpack_encode_str16, MPACK_TAG_SIZE_STR16, (uint16_t)count);
+ mpack_write_native(writer, data, count);
+ } else {
+ MPACK_WRITE_ENCODED(mpack_encode_str32, MPACK_TAG_SIZE_STR32, (uint32_t)count);
+ mpack_write_native(writer, data, count);
+ }
+
+ #endif
+}
+
+void mpack_write_bin(mpack_writer_t* writer, const char* data, uint32_t count) {
+ mpack_assert(data != NULL, "data pointer for bin of %i bytes is NULL", (int)count);
+ mpack_start_bin(writer, count);
+ mpack_write_bytes(writer, data, count);
+ mpack_finish_bin(writer);
+}
+
+#if MPACK_EXTENSIONS
+void mpack_write_ext(mpack_writer_t* writer, int8_t exttype, const char* data, uint32_t count) {
+ mpack_assert(data != NULL, "data pointer for ext of type %i and %i bytes is NULL", exttype, (int)count);
+ mpack_start_ext(writer, exttype, count);
+ mpack_write_bytes(writer, data, count);
+ mpack_finish_ext(writer);
+}
+#endif
+
+void mpack_write_bytes(mpack_writer_t* writer, const char* data, size_t count) {
+ mpack_assert(data != NULL, "data pointer for %i bytes is NULL", (int)count);
+ mpack_writer_track_bytes(writer, count);
+ mpack_write_native(writer, data, count);
+}
+
+void mpack_write_cstr(mpack_writer_t* writer, const char* cstr) {
+ mpack_assert(cstr != NULL, "cstr pointer is NULL");
+ size_t length = mpack_strlen(cstr);
+ if (length > UINT32_MAX)
+ mpack_writer_flag_error(writer, mpack_error_invalid);
+ mpack_write_str(writer, cstr, (uint32_t)length);
+}
+
+void mpack_write_cstr_or_nil(mpack_writer_t* writer, const char* cstr) {
+ if (cstr)
+ mpack_write_cstr(writer, cstr);
+ else
+ mpack_write_nil(writer);
+}
+
+void mpack_write_utf8(mpack_writer_t* writer, const char* str, uint32_t length) {
+ mpack_assert(str != NULL, "data for string of length %i is NULL", (int)length);
+ if (!mpack_utf8_check(str, length)) {
+ mpack_writer_flag_error(writer, mpack_error_invalid);
+ return;
+ }
+ mpack_write_str(writer, str, length);
+}
+
+void mpack_write_utf8_cstr(mpack_writer_t* writer, const char* cstr) {
+ mpack_assert(cstr != NULL, "cstr pointer is NULL");
+ size_t length = mpack_strlen(cstr);
+ if (length > UINT32_MAX) {
+ mpack_writer_flag_error(writer, mpack_error_invalid);
+ return;
+ }
+ mpack_write_utf8(writer, cstr, (uint32_t)length);
+}
+
+void mpack_write_utf8_cstr_or_nil(mpack_writer_t* writer, const char* cstr) {
+ if (cstr)
+ mpack_write_utf8_cstr(writer, cstr);
+ else
+ mpack_write_nil(writer);
+}
+
+#endif
+
+
+/* mpack/mpack-reader.c.c */
+
+#define MPACK_INTERNAL 1
+
+/* #include "mpack-reader.h" */
+
+#if MPACK_READER
+
+static void mpack_reader_skip_using_fill(mpack_reader_t* reader, size_t count);
+
+void mpack_reader_init(mpack_reader_t* reader, char* buffer, size_t size, size_t count) {
+ mpack_assert(buffer != NULL, "buffer is NULL");
+
+ mpack_memset(reader, 0, sizeof(*reader));
+ reader->buffer = buffer;
+ reader->size = size;
+ reader->data = buffer;
+ reader->end = buffer + count;
+
+ #if MPACK_READ_TRACKING
+ mpack_reader_flag_if_error(reader, mpack_track_init(&reader->track));
+ #endif
+
+ mpack_log("===========================\n");
+ mpack_log("initializing reader with buffer size %i\n", (int)size);
+}
+
+void mpack_reader_init_error(mpack_reader_t* reader, mpack_error_t error) {
+ mpack_memset(reader, 0, sizeof(*reader));
+ reader->error = error;
+
+ mpack_log("===========================\n");
+ mpack_log("initializing reader error state %i\n", (int)error);
+}
+
+void mpack_reader_init_data(mpack_reader_t* reader, const char* data, size_t count) {
+ mpack_assert(data != NULL, "data is NULL");
+
+ mpack_memset(reader, 0, sizeof(*reader));
+ reader->data = data;
+ reader->end = data + count;
+
+ #if MPACK_READ_TRACKING
+ mpack_reader_flag_if_error(reader, mpack_track_init(&reader->track));
+ #endif
+
+ mpack_log("===========================\n");
+ mpack_log("initializing reader with data size %i\n", (int)count);
+}
+
+void mpack_reader_set_fill(mpack_reader_t* reader, mpack_reader_fill_t fill) {
+ MPACK_STATIC_ASSERT(MPACK_READER_MINIMUM_BUFFER_SIZE >= MPACK_MAXIMUM_TAG_SIZE,
+ "minimum buffer size must fit any tag!");
+
+ if (reader->size == 0) {
+ mpack_break("cannot use fill function without a writeable buffer!");
+ mpack_reader_flag_error(reader, mpack_error_bug);
+ return;
+ }
+
+ if (reader->size < MPACK_READER_MINIMUM_BUFFER_SIZE) {
+ mpack_break("buffer size is %i, but minimum buffer size for fill is %i",
+ (int)reader->size, MPACK_READER_MINIMUM_BUFFER_SIZE);
+ mpack_reader_flag_error(reader, mpack_error_bug);
+ return;
+ }
+
+ reader->fill = fill;
+}
+
+void mpack_reader_set_skip(mpack_reader_t* reader, mpack_reader_skip_t skip) {
+ mpack_assert(reader->size != 0, "cannot use skip function without a writeable buffer!");
+ reader->skip = skip;
+}
+
+#if MPACK_STDIO
+static size_t mpack_file_reader_fill(mpack_reader_t* reader, char* buffer, size_t count) {
+ if (feof((FILE *)reader->context)) {
+ mpack_reader_flag_error(reader, mpack_error_eof);
+ return 0;
+ }
+ return fread((void*)buffer, 1, count, (FILE*)reader->context);
+}
+
+static void mpack_file_reader_skip(mpack_reader_t* reader, size_t count) {
+ if (mpack_reader_error(reader) != mpack_ok)
+ return;
+ FILE* file = (FILE*)reader->context;
+
+ // We call ftell() to test whether the stream is seekable
+ // without causing a file error.
+ if (ftell(file) >= 0) {
+ mpack_log("seeking forward %i bytes\n", (int)count);
+ if (fseek(file, (long int)count, SEEK_CUR) == 0)
+ return;
+ mpack_log("fseek() didn't return zero!\n");
+ if (ferror(file)) {
+ mpack_reader_flag_error(reader, mpack_error_io);
+ return;
+ }
+ }
+
+ // If the stream is not seekable, fall back to the fill function.
+ mpack_reader_skip_using_fill(reader, count);
+}
+
+static void mpack_file_reader_teardown(mpack_reader_t* reader) {
+ MPACK_FREE(reader->buffer);
+ reader->buffer = NULL;
+ reader->context = NULL;
+ reader->size = 0;
+ reader->fill = NULL;
+ reader->skip = NULL;
+ reader->teardown = NULL;
+}
+
+static void mpack_file_reader_teardown_close(mpack_reader_t* reader) {
+ FILE* file = (FILE*)reader->context;
+
+ if (file) {
+ int ret = fclose(file);
+ if (ret != 0)
+ mpack_reader_flag_error(reader, mpack_error_io);
+ }
+
+ mpack_file_reader_teardown(reader);
+}
+
+void mpack_reader_init_stdfile(mpack_reader_t* reader, FILE* file, bool close_when_done) {
+ mpack_assert(file != NULL, "file is NULL");
+
+ size_t capacity = MPACK_BUFFER_SIZE;
+ char* buffer = (char*)MPACK_MALLOC(capacity);
+ if (buffer == NULL) {
+ mpack_reader_init_error(reader, mpack_error_memory);
+ if (close_when_done) {
+ fclose(file);
+ }
+ return;
+ }
+
+ mpack_reader_init(reader, buffer, capacity, 0);
+ mpack_reader_set_context(reader, file);
+ mpack_reader_set_fill(reader, mpack_file_reader_fill);
+ mpack_reader_set_skip(reader, mpack_file_reader_skip);
+ mpack_reader_set_teardown(reader, close_when_done ?
+ mpack_file_reader_teardown_close :
+ mpack_file_reader_teardown);
+}
+
+void mpack_reader_init_filename(mpack_reader_t* reader, const char* filename) {
+ mpack_assert(filename != NULL, "filename is NULL");
+
+ FILE* file = fopen(filename, "rb");
+ if (file == NULL) {
+ mpack_reader_init_error(reader, mpack_error_io);
+ return;
+ }
+
+ mpack_reader_init_stdfile(reader, file, true);
+}
+#endif
+
+mpack_error_t mpack_reader_destroy(mpack_reader_t* reader) {
+
+ // clean up tracking, asserting if we're not already in an error state
+ #if MPACK_READ_TRACKING
+ mpack_reader_flag_if_error(reader, mpack_track_destroy(&reader->track, mpack_reader_error(reader) != mpack_ok));
+ #endif
+
+ if (reader->teardown)
+ reader->teardown(reader);
+ reader->teardown = NULL;
+
+ return reader->error;
+}
+
+size_t mpack_reader_remaining(mpack_reader_t* reader, const char** data) {
+ if (mpack_reader_error(reader) != mpack_ok)
+ return 0;
+
+ #if MPACK_READ_TRACKING
+ if (mpack_reader_flag_if_error(reader, mpack_track_check_empty(&reader->track)) != mpack_ok)
+ return 0;
+ #endif
+
+ if (data)
+ *data = reader->data;
+ return (size_t)(reader->end - reader->data);
+}
+
+void mpack_reader_flag_error(mpack_reader_t* reader, mpack_error_t error) {
+ mpack_log("reader %p setting error %i: %s\n", reader, (int)error, mpack_error_to_string(error));
+
+ if (reader->error == mpack_ok) {
+ reader->error = error;
+ reader->end = reader->data;
+ if (reader->error_fn)
+ reader->error_fn(reader, error);
+ }
+}
+
+// Loops on the fill function, reading between the minimum and
+// maximum number of bytes and flagging an error if it fails.
+MPACK_NOINLINE static size_t mpack_fill_range(mpack_reader_t* reader, char* p, size_t min_bytes, size_t max_bytes) {
+ mpack_assert(reader->fill != NULL, "mpack_fill_range() called with no fill function?");
+ mpack_assert(min_bytes > 0, "cannot fill zero bytes!");
+ mpack_assert(max_bytes >= min_bytes, "min_bytes %i cannot be larger than max_bytes %i!",
+ (int)min_bytes, (int)max_bytes);
+
+ size_t count = 0;
+ while (count < min_bytes) {
+ size_t read = reader->fill(reader, p + count, max_bytes - count);
+
+ // Reader fill functions can flag an error or return 0 on failure. We
+ // also guard against functions that -1 just in case.
+ if (mpack_reader_error(reader) != mpack_ok)
+ return 0;
+ if (read == 0 || read == ((size_t)(-1))) {
+ mpack_reader_flag_error(reader, mpack_error_io);
+ return 0;
+ }
+
+ count += read;
+ }
+ return count;
+}
+
+MPACK_NOINLINE bool mpack_reader_ensure_straddle(mpack_reader_t* reader, size_t count) {
+ mpack_assert(count != 0, "cannot ensure zero bytes!");
+ mpack_assert(reader->error == mpack_ok, "reader cannot be in an error state!");
+
+ mpack_assert(count > (size_t)(reader->end - reader->data),
+ "straddling ensure requested for %i bytes, but there are %i bytes "
+ "left in buffer. call mpack_reader_ensure() instead",
+ (int)count, (int)(reader->end - reader->data));
+
+ // we'll need a fill function to get more data. if there's no
+ // fill function, the buffer should contain an entire MessagePack
+ // object, so we raise mpack_error_invalid instead of mpack_error_io
+ // on truncated data.
+ if (reader->fill == NULL) {
+ mpack_reader_flag_error(reader, mpack_error_invalid);
+ return false;
+ }
+
+ // we need enough space in the buffer. if the buffer is not
+ // big enough, we return mpack_error_too_big (since this is
+ // for an in-place read larger than the buffer size.)
+ if (count > reader->size) {
+ mpack_reader_flag_error(reader, mpack_error_too_big);
+ return false;
+ }
+
+ // move the existing data to the start of the buffer
+ size_t left = (size_t)(reader->end - reader->data);
+ mpack_memmove(reader->buffer, reader->data, left);
+ reader->end -= reader->data - reader->buffer;
+ reader->data = reader->buffer;
+
+ // read at least the necessary number of bytes, accepting up to the
+ // buffer size
+ size_t read = mpack_fill_range(reader, reader->buffer + left,
+ count - left, reader->size - left);
+ if (mpack_reader_error(reader) != mpack_ok)
+ return false;
+ reader->end += read;
+ return true;
+}
+
+// Reads count bytes into p. Used when there are not enough bytes
+// left in the buffer to satisfy a read.
+MPACK_NOINLINE void mpack_read_native_straddle(mpack_reader_t* reader, char* p, size_t count) {
+ mpack_assert(count == 0 || p != NULL, "data pointer for %i bytes is NULL", (int)count);
+
+ if (mpack_reader_error(reader) != mpack_ok) {
+ mpack_memset(p, 0, count);
+ return;
+ }
+
+ size_t left = (size_t)(reader->end - reader->data);
+ mpack_log("big read for %i bytes into %p, %i left in buffer, buffer size %i\n",
+ (int)count, p, (int)left, (int)reader->size);
+
+ if (count <= left) {
+ mpack_assert(0,
+ "big read requested for %i bytes, but there are %i bytes "
+ "left in buffer. call mpack_read_native() instead",
+ (int)count, (int)left);
+ mpack_reader_flag_error(reader, mpack_error_bug);
+ mpack_memset(p, 0, count);
+ return;
+ }
+
+ // we'll need a fill function to get more data. if there's no
+ // fill function, the buffer should contain an entire MessagePack
+ // object, so we raise mpack_error_invalid instead of mpack_error_io
+ // on truncated data.
+ if (reader->fill == NULL) {
+ mpack_reader_flag_error(reader, mpack_error_invalid);
+ mpack_memset(p, 0, count);
+ return;
+ }
+
+ if (reader->size == 0) {
+ // somewhat debatable what error should be returned here. when
+ // initializing a reader with an in-memory buffer it's not
+ // necessarily a bug if the data is blank; it might just have
+ // been truncated to zero. for this reason we return the same
+ // error as if the data was truncated.
+ mpack_reader_flag_error(reader, mpack_error_io);
+ mpack_memset(p, 0, count);
+ return;
+ }
+
+ // flush what's left of the buffer
+ if (left > 0) {
+ mpack_log("flushing %i bytes remaining in buffer\n", (int)left);
+ mpack_memcpy(p, reader->data, left);
+ count -= left;
+ p += left;
+ reader->data += left;
+ }
+
+ // if the remaining data needed is some small fraction of the
+ // buffer size, we'll try to fill the buffer as much as possible
+ // and copy the needed data out.
+ if (count <= reader->size / MPACK_READER_SMALL_FRACTION_DENOMINATOR) {
+ size_t read = mpack_fill_range(reader, reader->buffer, count, reader->size);
+ if (mpack_reader_error(reader) != mpack_ok)
+ return;
+ mpack_memcpy(p, reader->buffer, count);
+ reader->data = reader->buffer + count;
+ reader->end = reader->buffer + read;
+
+ // otherwise we read the remaining data directly into the target.
+ } else {
+ mpack_log("reading %i additional bytes\n", (int)count);
+ mpack_fill_range(reader, p, count, count);
+ }
+}
+
+MPACK_NOINLINE static void mpack_skip_bytes_straddle(mpack_reader_t* reader, size_t count) {
+
+ // we'll need at least a fill function to skip more data. if there's
+ // no fill function, the buffer should contain an entire MessagePack
+ // object, so we raise mpack_error_invalid instead of mpack_error_io
+ // on truncated data. (see mpack_read_native_straddle())
+ if (reader->fill == NULL) {
+ mpack_log("reader has no fill function!\n");
+ mpack_reader_flag_error(reader, mpack_error_invalid);
+ return;
+ }
+
+ // discard whatever's left in the buffer
+ size_t left = (size_t)(reader->end - reader->data);
+ mpack_log("discarding %i bytes still in buffer\n", (int)left);
+ count -= left;
+ reader->data = reader->end;
+
+ // use the skip function if we've got one, and if we're trying
+ // to skip a lot of data. if we only need to skip some tiny
+ // fraction of the buffer size, it's probably better to just
+ // fill the buffer and skip from it instead of trying to seek.
+ if (reader->skip && count > reader->size / 16) {
+ mpack_log("calling skip function for %i bytes\n", (int)count);
+ reader->skip(reader, count);
+ return;
+ }
+
+ mpack_reader_skip_using_fill(reader, count);
+}
+
+void mpack_skip_bytes(mpack_reader_t* reader, size_t count) {
+ if (mpack_reader_error(reader) != mpack_ok)
+ return;
+ mpack_log("skip requested for %i bytes\n", (int)count);
+ mpack_reader_track_bytes(reader, count);
+
+ // check if we have enough in the buffer already
+ size_t left = (size_t)(reader->end - reader->data);
+ if (left >= count) {
+ mpack_log("skipping %i bytes still in buffer\n", (int)count);
+ reader->data += count;
+ return;
+ }
+
+ mpack_skip_bytes_straddle(reader, count);
+}
+
+MPACK_NOINLINE static void mpack_reader_skip_using_fill(mpack_reader_t* reader, size_t count) {
+ mpack_assert(reader->fill != NULL, "missing fill function!");
+ mpack_assert(reader->data == reader->end, "there are bytes left in the buffer!");
+ mpack_assert(reader->error == mpack_ok, "should not have called this in an error state (%i)", reader->error);
+ mpack_log("skip using fill for %i bytes\n", (int)count);
+
+ // fill and discard multiples of the buffer size
+ while (count > reader->size) {
+ mpack_log("filling and discarding buffer of %i bytes\n", (int)reader->size);
+ if (mpack_fill_range(reader, reader->buffer, reader->size, reader->size) < reader->size) {
+ mpack_reader_flag_error(reader, mpack_error_io);
+ return;
+ }
+ count -= reader->size;
+ }
+
+ // fill the buffer as much as possible
+ reader->data = reader->buffer;
+ size_t read = mpack_fill_range(reader, reader->buffer, count, reader->size);
+ if (read < count) {
+ mpack_reader_flag_error(reader, mpack_error_io);
+ return;
+ }
+ reader->end = reader->data + read;
+ mpack_log("filled %i bytes into buffer; discarding %i bytes\n", (int)read, (int)count);
+ reader->data += count;
+}
+
+void mpack_read_bytes(mpack_reader_t* reader, char* p, size_t count) {
+ mpack_assert(p != NULL, "destination for read of %i bytes is NULL", (int)count);
+ mpack_reader_track_bytes(reader, count);
+ mpack_read_native(reader, p, count);
+}
+
+void mpack_read_utf8(mpack_reader_t* reader, char* p, size_t byte_count) {
+ mpack_assert(p != NULL, "destination for read of %i bytes is NULL", (int)byte_count);
+ mpack_reader_track_str_bytes_all(reader, byte_count);
+ mpack_read_native(reader, p, byte_count);
+
+ if (mpack_reader_error(reader) == mpack_ok && !mpack_utf8_check(p, byte_count))
+ mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+static void mpack_read_cstr_unchecked(mpack_reader_t* reader, char* buf, size_t buffer_size, size_t byte_count) {
+ mpack_assert(buf != NULL, "destination for read of %i bytes is NULL", (int)byte_count);
+ mpack_assert(buffer_size >= 1, "buffer size is zero; you must have room for at least a null-terminator");
+
+ if (mpack_reader_error(reader)) {
+ buf[0] = 0;
+ return;
+ }
+
+ if (byte_count > buffer_size - 1) {
+ mpack_reader_flag_error(reader, mpack_error_too_big);
+ buf[0] = 0;
+ return;
+ }
+
+ mpack_reader_track_str_bytes_all(reader, byte_count);
+ mpack_read_native(reader, buf, byte_count);
+ buf[byte_count] = 0;
+}
+
+void mpack_read_cstr(mpack_reader_t* reader, char* buf, size_t buffer_size, size_t byte_count) {
+ mpack_read_cstr_unchecked(reader, buf, buffer_size, byte_count);
+
+ // check for null bytes
+ if (mpack_reader_error(reader) == mpack_ok && !mpack_str_check_no_null(buf, byte_count)) {
+ buf[0] = 0;
+ mpack_reader_flag_error(reader, mpack_error_type);
+ }
+}
+
+void mpack_read_utf8_cstr(mpack_reader_t* reader, char* buf, size_t buffer_size, size_t byte_count) {
+ mpack_read_cstr_unchecked(reader, buf, buffer_size, byte_count);
+
+ // check encoding
+ if (mpack_reader_error(reader) == mpack_ok && !mpack_utf8_check_no_null(buf, byte_count)) {
+ buf[0] = 0;
+ mpack_reader_flag_error(reader, mpack_error_type);
+ }
+}
+
+#ifdef MPACK_MALLOC
+// Reads native bytes with error callback disabled. This allows MPack reader functions
+// to hold an allocated buffer and read native data into it without leaking it in
+// case of a non-local jump (longjmp, throw) out of an error handler.
+static void mpack_read_native_noerrorfn(mpack_reader_t* reader, char* p, size_t count) {
+ mpack_assert(reader->error == mpack_ok, "cannot call if an error is already flagged!");
+ mpack_reader_error_t error_fn = reader->error_fn;
+ reader->error_fn = NULL;
+ mpack_read_native(reader, p, count);
+ reader->error_fn = error_fn;
+}
+
+char* mpack_read_bytes_alloc_impl(mpack_reader_t* reader, size_t count, bool null_terminated) {
+
+ // track the bytes first in case it jumps
+ mpack_reader_track_bytes(reader, count);
+ if (mpack_reader_error(reader) != mpack_ok)
+ return NULL;
+
+ // cannot allocate zero bytes. this is not an error.
+ if (count == 0 && null_terminated == false)
+ return NULL;
+
+ // allocate data
+ char* data = (char*)MPACK_MALLOC(count + (null_terminated ? 1 : 0)); // TODO: can this overflow?
+ if (data == NULL) {
+ mpack_reader_flag_error(reader, mpack_error_memory);
+ return NULL;
+ }
+
+ // read with error callback disabled so we don't leak our buffer
+ mpack_read_native_noerrorfn(reader, data, count);
+
+ // report flagged errors
+ if (mpack_reader_error(reader) != mpack_ok) {
+ MPACK_FREE(data);
+ if (reader->error_fn)
+ reader->error_fn(reader, mpack_reader_error(reader));
+ return NULL;
+ }
+
+ if (null_terminated)
+ data[count] = '\0';
+ return data;
+}
+#endif
+
+// read inplace without tracking (since there are different
+// tracking modes for different inplace readers)
+static const char* mpack_read_bytes_inplace_notrack(mpack_reader_t* reader, size_t count) {
+ if (mpack_reader_error(reader) != mpack_ok)
+ return NULL;
+
+ // if we have enough bytes already in the buffer, we can return it directly.
+ if ((size_t)(reader->end - reader->data) >= count) {
+ const char* bytes = reader->data;
+ reader->data += count;
+ return bytes;
+ }
+
+ if (!mpack_reader_ensure(reader, count))
+ return NULL;
+
+ const char* bytes = reader->data;
+ reader->data += count;
+ return bytes;
+}
+
+const char* mpack_read_bytes_inplace(mpack_reader_t* reader, size_t count) {
+ mpack_reader_track_bytes(reader, count);
+ return mpack_read_bytes_inplace_notrack(reader, count);
+}
+
+const char* mpack_read_utf8_inplace(mpack_reader_t* reader, size_t count) {
+ mpack_reader_track_str_bytes_all(reader, count);
+ const char* str = mpack_read_bytes_inplace_notrack(reader, count);
+
+ if (mpack_reader_error(reader) == mpack_ok && !mpack_utf8_check(str, count)) {
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return NULL;
+ }
+
+ return str;
+}
+
+static size_t mpack_parse_tag(mpack_reader_t* reader, mpack_tag_t* tag) {
+ mpack_assert(reader->error == mpack_ok, "reader cannot be in an error state!");
+
+ if (!mpack_reader_ensure(reader, 1))
+ return 0;
+ uint8_t type = mpack_load_u8(reader->data);
+
+ // unfortunately, by far the fastest way to parse a tag is to switch
+ // on the first byte, and to explicitly list every possible byte. so for
+ // infix types, the list of cases is quite large.
+ //
+ // in size-optimized builds, we switch on the top four bits first to
+ // handle most infix types with a smaller jump table to save space.
+
+ #if MPACK_OPTIMIZE_FOR_SIZE
+ switch (type >> 4) {
+
+ // positive fixnum
+ case 0x0: case 0x1: case 0x2: case 0x3:
+ case 0x4: case 0x5: case 0x6: case 0x7:
+ *tag = mpack_tag_make_uint(type);
+ return 1;
+
+ // negative fixnum
+ case 0xe: case 0xf:
+ *tag = mpack_tag_make_int((int8_t)type);
+ return 1;
+
+ // fixmap
+ case 0x8:
+ *tag = mpack_tag_make_map(type & ~0xf0u);
+ return 1;
+
+ // fixarray
+ case 0x9:
+ *tag = mpack_tag_make_array(type & ~0xf0u);
+ return 1;
+
+ // fixstr
+ case 0xa: case 0xb:
+ *tag = mpack_tag_make_str(type & ~0xe0u);
+ return 1;
+
+ // not one of the common infix types
+ default:
+ break;
+
+ }
+ #endif
+
+ // handle individual type tags
+ switch (type) {
+
+ #if !MPACK_OPTIMIZE_FOR_SIZE
+ // positive fixnum
+ case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07:
+ case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f:
+ case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
+ case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f:
+ case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27:
+ case 0x28: case 0x29: case 0x2a: case 0x2b: case 0x2c: case 0x2d: case 0x2e: case 0x2f:
+ case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37:
+ case 0x38: case 0x39: case 0x3a: case 0x3b: case 0x3c: case 0x3d: case 0x3e: case 0x3f:
+ case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47:
+ case 0x48: case 0x49: case 0x4a: case 0x4b: case 0x4c: case 0x4d: case 0x4e: case 0x4f:
+ case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57:
+ case 0x58: case 0x59: case 0x5a: case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f:
+ case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67:
+ case 0x68: case 0x69: case 0x6a: case 0x6b: case 0x6c: case 0x6d: case 0x6e: case 0x6f:
+ case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77:
+ case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f:
+ *tag = mpack_tag_make_uint(type);
+ return 1;
+
+ // negative fixnum
+ case 0xe0: case 0xe1: case 0xe2: case 0xe3: case 0xe4: case 0xe5: case 0xe6: case 0xe7:
+ case 0xe8: case 0xe9: case 0xea: case 0xeb: case 0xec: case 0xed: case 0xee: case 0xef:
+ case 0xf0: case 0xf1: case 0xf2: case 0xf3: case 0xf4: case 0xf5: case 0xf6: case 0xf7:
+ case 0xf8: case 0xf9: case 0xfa: case 0xfb: case 0xfc: case 0xfd: case 0xfe: case 0xff:
+ *tag = mpack_tag_make_int((int8_t)type);
+ return 1;
+
+ // fixmap
+ case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87:
+ case 0x88: case 0x89: case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f:
+ *tag = mpack_tag_make_map(type & ~0xf0u);
+ return 1;
+
+ // fixarray
+ case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97:
+ case 0x98: case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d: case 0x9e: case 0x9f:
+ *tag = mpack_tag_make_array(type & ~0xf0u);
+ return 1;
+
+ // fixstr
+ case 0xa0: case 0xa1: case 0xa2: case 0xa3: case 0xa4: case 0xa5: case 0xa6: case 0xa7:
+ case 0xa8: case 0xa9: case 0xaa: case 0xab: case 0xac: case 0xad: case 0xae: case 0xaf:
+ case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7:
+ case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf:
+ *tag = mpack_tag_make_str(type & ~0xe0u);
+ return 1;
+ #endif
+
+ // nil
+ case 0xc0:
+ *tag = mpack_tag_make_nil();
+ return 1;
+
+ // bool
+ case 0xc2: case 0xc3:
+ *tag = mpack_tag_make_bool((bool)(type & 1));
+ return 1;
+
+ // bin8
+ case 0xc4:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_BIN8))
+ return 0;
+ *tag = mpack_tag_make_bin(mpack_load_u8(reader->data + 1));
+ return MPACK_TAG_SIZE_BIN8;
+
+ // bin16
+ case 0xc5:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_BIN16))
+ return 0;
+ *tag = mpack_tag_make_bin(mpack_load_u16(reader->data + 1));
+ return MPACK_TAG_SIZE_BIN16;
+
+ // bin32
+ case 0xc6:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_BIN32))
+ return 0;
+ *tag = mpack_tag_make_bin(mpack_load_u32(reader->data + 1));
+ return MPACK_TAG_SIZE_BIN32;
+
+ #if MPACK_EXTENSIONS
+ // ext8
+ case 0xc7:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_EXT8))
+ return 0;
+ *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 2), mpack_load_u8(reader->data + 1));
+ return MPACK_TAG_SIZE_EXT8;
+
+ // ext16
+ case 0xc8:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_EXT16))
+ return 0;
+ *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 3), mpack_load_u16(reader->data + 1));
+ return MPACK_TAG_SIZE_EXT16;
+
+ // ext32
+ case 0xc9:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_EXT32))
+ return 0;
+ *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 5), mpack_load_u32(reader->data + 1));
+ return MPACK_TAG_SIZE_EXT32;
+ #endif
+
+ // float
+ case 0xca:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FLOAT))
+ return 0;
+ *tag = mpack_tag_make_float(mpack_load_float(reader->data + 1));
+ return MPACK_TAG_SIZE_FLOAT;
+
+ // double
+ case 0xcb:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_DOUBLE))
+ return 0;
+ *tag = mpack_tag_make_double(mpack_load_double(reader->data + 1));
+ return MPACK_TAG_SIZE_DOUBLE;
+
+ // uint8
+ case 0xcc:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_U8))
+ return 0;
+ *tag = mpack_tag_make_uint(mpack_load_u8(reader->data + 1));
+ return MPACK_TAG_SIZE_U8;
+
+ // uint16
+ case 0xcd:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_U16))
+ return 0;
+ *tag = mpack_tag_make_uint(mpack_load_u16(reader->data + 1));
+ return MPACK_TAG_SIZE_U16;
+
+ // uint32
+ case 0xce:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_U32))
+ return 0;
+ *tag = mpack_tag_make_uint(mpack_load_u32(reader->data + 1));
+ return MPACK_TAG_SIZE_U32;
+
+ // uint64
+ case 0xcf:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_U64))
+ return 0;
+ *tag = mpack_tag_make_uint(mpack_load_u64(reader->data + 1));
+ return MPACK_TAG_SIZE_U64;
+
+ // int8
+ case 0xd0:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_I8))
+ return 0;
+ *tag = mpack_tag_make_int(mpack_load_i8(reader->data + 1));
+ return MPACK_TAG_SIZE_I8;
+
+ // int16
+ case 0xd1:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_I16))
+ return 0;
+ *tag = mpack_tag_make_int(mpack_load_i16(reader->data + 1));
+ return MPACK_TAG_SIZE_I16;
+
+ // int32
+ case 0xd2:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_I32))
+ return 0;
+ *tag = mpack_tag_make_int(mpack_load_i32(reader->data + 1));
+ return MPACK_TAG_SIZE_I32;
+
+ // int64
+ case 0xd3:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_I64))
+ return 0;
+ *tag = mpack_tag_make_int(mpack_load_i64(reader->data + 1));
+ return MPACK_TAG_SIZE_I64;
+
+ #if MPACK_EXTENSIONS
+ // fixext1
+ case 0xd4:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT1))
+ return 0;
+ *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 1);
+ return MPACK_TAG_SIZE_FIXEXT1;
+
+ // fixext2
+ case 0xd5:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT2))
+ return 0;
+ *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 2);
+ return MPACK_TAG_SIZE_FIXEXT2;
+
+ // fixext4
+ case 0xd6:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT4))
+ return 0;
+ *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 4);
+ return 2;
+
+ // fixext8
+ case 0xd7:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT8))
+ return 0;
+ *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 8);
+ return MPACK_TAG_SIZE_FIXEXT8;
+
+ // fixext16
+ case 0xd8:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT16))
+ return 0;
+ *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 16);
+ return MPACK_TAG_SIZE_FIXEXT16;
+ #endif
+
+ // str8
+ case 0xd9:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_STR8))
+ return 0;
+ *tag = mpack_tag_make_str(mpack_load_u8(reader->data + 1));
+ return MPACK_TAG_SIZE_STR8;
+
+ // str16
+ case 0xda:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_STR16))
+ return 0;
+ *tag = mpack_tag_make_str(mpack_load_u16(reader->data + 1));
+ return MPACK_TAG_SIZE_STR16;
+
+ // str32
+ case 0xdb:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_STR32))
+ return 0;
+ *tag = mpack_tag_make_str(mpack_load_u32(reader->data + 1));
+ return MPACK_TAG_SIZE_STR32;
+
+ // array16
+ case 0xdc:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_ARRAY16))
+ return 0;
+ *tag = mpack_tag_make_array(mpack_load_u16(reader->data + 1));
+ return MPACK_TAG_SIZE_ARRAY16;
+
+ // array32
+ case 0xdd:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_ARRAY32))
+ return 0;
+ *tag = mpack_tag_make_array(mpack_load_u32(reader->data + 1));
+ return MPACK_TAG_SIZE_ARRAY32;
+
+ // map16
+ case 0xde:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_MAP16))
+ return 0;
+ *tag = mpack_tag_make_map(mpack_load_u16(reader->data + 1));
+ return MPACK_TAG_SIZE_MAP16;
+
+ // map32
+ case 0xdf:
+ if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_MAP32))
+ return 0;
+ *tag = mpack_tag_make_map(mpack_load_u32(reader->data + 1));
+ return MPACK_TAG_SIZE_MAP32;
+
+ // reserved
+ case 0xc1:
+ mpack_reader_flag_error(reader, mpack_error_invalid);
+ return 0;
+
+ #if !MPACK_EXTENSIONS
+ // ext
+ case 0xc7: // fallthrough
+ case 0xc8: // fallthrough
+ case 0xc9: // fallthrough
+ // fixext
+ case 0xd4: // fallthrough
+ case 0xd5: // fallthrough
+ case 0xd6: // fallthrough
+ case 0xd7: // fallthrough
+ case 0xd8:
+ mpack_reader_flag_error(reader, mpack_error_unsupported);
+ return 0;
+ #endif
+
+ #if MPACK_OPTIMIZE_FOR_SIZE
+ // any other bytes should have been handled by the infix switch
+ default:
+ break;
+ #endif
+ }
+
+ mpack_assert(0, "unreachable");
+ return 0;
+}
+
+mpack_tag_t mpack_read_tag(mpack_reader_t* reader) {
+ mpack_log("reading tag\n");
+
+ // make sure we can read a tag
+ if (mpack_reader_error(reader) != mpack_ok)
+ return mpack_tag_nil();
+ if (mpack_reader_track_element(reader) != mpack_ok)
+ return mpack_tag_nil();
+
+ mpack_tag_t tag = MPACK_TAG_ZERO;
+ size_t count = mpack_parse_tag(reader, &tag);
+ if (count == 0)
+ return mpack_tag_nil();
+
+ #if MPACK_READ_TRACKING
+ mpack_error_t track_error = mpack_ok;
+
+ switch (tag.type) {
+ case mpack_type_map:
+ case mpack_type_array:
+ track_error = mpack_track_push(&reader->track, tag.type, tag.v.n);
+ break;
+ #if MPACK_EXTENSIONS
+ case mpack_type_ext:
+ #endif
+ case mpack_type_str:
+ case mpack_type_bin:
+ track_error = mpack_track_push(&reader->track, tag.type, tag.v.l);
+ break;
+ default:
+ break;
+ }
+
+ if (track_error != mpack_ok) {
+ mpack_reader_flag_error(reader, track_error);
+ return mpack_tag_nil();
+ }
+ #endif
+
+ reader->data += count;
+ return tag;
+}
+
+mpack_tag_t mpack_peek_tag(mpack_reader_t* reader) {
+ mpack_log("peeking tag\n");
+
+ // make sure we can peek a tag
+ if (mpack_reader_error(reader) != mpack_ok)
+ return mpack_tag_nil();
+ if (mpack_reader_track_peek_element(reader) != mpack_ok)
+ return mpack_tag_nil();
+
+ mpack_tag_t tag = MPACK_TAG_ZERO;
+ if (mpack_parse_tag(reader, &tag) == 0)
+ return mpack_tag_nil();
+ return tag;
+}
+
+void mpack_discard(mpack_reader_t* reader) {
+ mpack_tag_t var = mpack_read_tag(reader);
+ if (mpack_reader_error(reader))
+ return;
+ switch (var.type) {
+ case mpack_type_str:
+ mpack_skip_bytes(reader, var.v.l);
+ mpack_done_str(reader);
+ break;
+ case mpack_type_bin:
+ mpack_skip_bytes(reader, var.v.l);
+ mpack_done_bin(reader);
+ break;
+ #if MPACK_EXTENSIONS
+ case mpack_type_ext:
+ mpack_skip_bytes(reader, var.v.l);
+ mpack_done_ext(reader);
+ break;
+ #endif
+ case mpack_type_array: {
+ for (; var.v.n > 0; --var.v.n) {
+ mpack_discard(reader);
+ if (mpack_reader_error(reader))
+ break;
+ }
+ mpack_done_array(reader);
+ break;
+ }
+ case mpack_type_map: {
+ for (; var.v.n > 0; --var.v.n) {
+ mpack_discard(reader);
+ mpack_discard(reader);
+ if (mpack_reader_error(reader))
+ break;
+ }
+ mpack_done_map(reader);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+#if MPACK_EXTENSIONS
+mpack_timestamp_t mpack_read_timestamp(mpack_reader_t* reader, size_t size) {
+ mpack_timestamp_t timestamp = {0, 0};
+
+ if (size != 4 && size != 8 && size != 12) {
+ mpack_reader_flag_error(reader, mpack_error_invalid);
+ return timestamp;
+ }
+
+ char buf[12];
+ mpack_read_bytes(reader, buf, size);
+ mpack_done_ext(reader);
+ if (mpack_reader_error(reader) != mpack_ok)
+ return timestamp;
+
+ switch (size) {
+ case 4:
+ timestamp.seconds = (int64_t)(uint64_t)mpack_load_u32(buf);
+ break;
+
+ case 8: {
+ uint64_t packed = mpack_load_u64(buf);
+ timestamp.seconds = (int64_t)(packed & ((UINT64_C(1) << 34) - 1));
+ timestamp.nanoseconds = (uint32_t)(packed >> 34);
+ break;
+ }
+
+ case 12:
+ timestamp.nanoseconds = mpack_load_u32(buf);
+ timestamp.seconds = mpack_load_i64(buf + 4);
+ break;
+
+ default:
+ mpack_assert(false, "unreachable");
+ break;
+ }
+
+ if (timestamp.nanoseconds > MPACK_TIMESTAMP_NANOSECONDS_MAX) {
+ mpack_reader_flag_error(reader, mpack_error_invalid);
+ mpack_timestamp_t zero = {0, 0};
+ return zero;
+ }
+
+ return timestamp;
+}
+#endif
+
+#if MPACK_READ_TRACKING
+void mpack_done_type(mpack_reader_t* reader, mpack_type_t type) {
+ if (mpack_reader_error(reader) == mpack_ok)
+ mpack_reader_flag_if_error(reader, mpack_track_pop(&reader->track, type));
+}
+#endif
+
+#if MPACK_DEBUG && MPACK_STDIO
+static size_t mpack_print_read_prefix(mpack_reader_t* reader, size_t length, char* buffer, size_t buffer_size) {
+ if (length == 0)
+ return 0;
+
+ size_t read = (length < buffer_size) ? length : buffer_size;
+ mpack_read_bytes(reader, buffer, read);
+ if (mpack_reader_error(reader) != mpack_ok)
+ return 0;
+
+ mpack_skip_bytes(reader, length - read);
+ return read;
+}
+
+static void mpack_print_element(mpack_reader_t* reader, mpack_print_t* print, size_t depth) {
+ mpack_tag_t val = mpack_read_tag(reader);
+ if (mpack_reader_error(reader) != mpack_ok)
+ return;
+
+ // We read some bytes from bin and ext so we can print its prefix in hex.
+ char buffer[MPACK_PRINT_BYTE_COUNT];
+ size_t count = 0;
+
+ switch (val.type) {
+ case mpack_type_str:
+ mpack_print_append_cstr(print, "\"");
+ for (size_t i = 0; i < val.v.l; ++i) {
+ char c;
+ mpack_read_bytes(reader, &c, 1);
+ if (mpack_reader_error(reader) != mpack_ok)
+ return;
+ switch (c) {
+ case '\n': mpack_print_append_cstr(print, "\\n"); break;
+ case '\\': mpack_print_append_cstr(print, "\\\\"); break;
+ case '"': mpack_print_append_cstr(print, "\\\""); break;
+ default: mpack_print_append(print, &c, 1); break;
+ }
+ }
+ mpack_print_append_cstr(print, "\"");
+ mpack_done_str(reader);
+ return;
+
+ case mpack_type_array:
+ mpack_print_append_cstr(print, "[\n");
+ for (size_t i = 0; i < val.v.n; ++i) {
+ for (size_t j = 0; j < depth + 1; ++j)
+ mpack_print_append_cstr(print, " ");
+ mpack_print_element(reader, print, depth + 1);
+ if (mpack_reader_error(reader) != mpack_ok)
+ return;
+ if (i != val.v.n - 1)
+ mpack_print_append_cstr(print, ",");
+ mpack_print_append_cstr(print, "\n");
+ }
+ for (size_t i = 0; i < depth; ++i)
+ mpack_print_append_cstr(print, " ");
+ mpack_print_append_cstr(print, "]");
+ mpack_done_array(reader);
+ return;
+
+ case mpack_type_map:
+ mpack_print_append_cstr(print, "{\n");
+ for (size_t i = 0; i < val.v.n; ++i) {
+ for (size_t j = 0; j < depth + 1; ++j)
+ mpack_print_append_cstr(print, " ");
+ mpack_print_element(reader, print, depth + 1);
+ if (mpack_reader_error(reader) != mpack_ok)
+ return;
+ mpack_print_append_cstr(print, ": ");
+ mpack_print_element(reader, print, depth + 1);
+ if (mpack_reader_error(reader) != mpack_ok)
+ return;
+ if (i != val.v.n - 1)
+ mpack_print_append_cstr(print, ",");
+ mpack_print_append_cstr(print, "\n");
+ }
+ for (size_t i = 0; i < depth; ++i)
+ mpack_print_append_cstr(print, " ");
+ mpack_print_append_cstr(print, "}");
+ mpack_done_map(reader);
+ return;
+
+ // The above cases return so as not to print a pseudo-json value. The
+ // below cases break and print pseudo-json.
+
+ case mpack_type_bin:
+ count = mpack_print_read_prefix(reader, mpack_tag_bin_length(&val), buffer, sizeof(buffer));
+ mpack_done_bin(reader);
+ break;
+
+ #if MPACK_EXTENSIONS
+ case mpack_type_ext:
+ count = mpack_print_read_prefix(reader, mpack_tag_ext_length(&val), buffer, sizeof(buffer));
+ mpack_done_ext(reader);
+ break;
+ #endif
+
+ default:
+ break;
+ }
+
+ char buf[256];
+ mpack_tag_debug_pseudo_json(val, buf, sizeof(buf), buffer, count);
+ mpack_print_append_cstr(print, buf);
+}
+
+static void mpack_print_and_destroy(mpack_reader_t* reader, mpack_print_t* print, size_t depth) {
+ for (size_t i = 0; i < depth; ++i)
+ mpack_print_append_cstr(print, " ");
+ mpack_print_element(reader, print, depth);
+
+ size_t remaining = mpack_reader_remaining(reader, NULL);
+
+ char buf[256];
+ if (mpack_reader_destroy(reader) != mpack_ok) {
+ mpack_snprintf(buf, sizeof(buf), "\n<mpack parsing error %s>", mpack_error_to_string(mpack_reader_error(reader)));
+ buf[sizeof(buf) - 1] = '\0';
+ mpack_print_append_cstr(print, buf);
+ } else if (remaining > 0) {
+ mpack_snprintf(buf, sizeof(buf), "\n<%i extra bytes at end of message>", (int)remaining);
+ buf[sizeof(buf) - 1] = '\0';
+ mpack_print_append_cstr(print, buf);
+ }
+}
+
+static void mpack_print_data(const char* data, size_t len, mpack_print_t* print, size_t depth) {
+ mpack_reader_t reader;
+ mpack_reader_init_data(&reader, data, len);
+ mpack_print_and_destroy(&reader, print, depth);
+}
+
+void mpack_print_data_to_buffer(const char* data, size_t data_size, char* buffer, size_t buffer_size) {
+ if (buffer_size == 0) {
+ mpack_assert(false, "buffer size is zero!");
+ return;
+ }
+
+ mpack_print_t print;
+ mpack_memset(&print, 0, sizeof(print));
+ print.buffer = buffer;
+ print.size = buffer_size;
+ mpack_print_data(data, data_size, &print, 0);
+ mpack_print_append(&print, "", 1); // null-terminator
+ mpack_print_flush(&print);
+
+ // we always make sure there's a null-terminator at the end of the buffer
+ // in case we ran out of space.
+ print.buffer[print.size - 1] = '\0';
+}
+
+void mpack_print_data_to_callback(const char* data, size_t size, mpack_print_callback_t callback, void* context) {
+ char buffer[1024];
+ mpack_print_t print;
+ mpack_memset(&print, 0, sizeof(print));
+ print.buffer = buffer;
+ print.size = sizeof(buffer);
+ print.callback = callback;
+ print.context = context;
+ mpack_print_data(data, size, &print, 0);
+ mpack_print_flush(&print);
+}
+
+void mpack_print_data_to_file(const char* data, size_t len, FILE* file) {
+ mpack_assert(data != NULL, "data is NULL");
+ mpack_assert(file != NULL, "file is NULL");
+
+ char buffer[1024];
+ mpack_print_t print;
+ mpack_memset(&print, 0, sizeof(print));
+ print.buffer = buffer;
+ print.size = sizeof(buffer);
+ print.callback = &mpack_print_file_callback;
+ print.context = file;
+
+ mpack_print_data(data, len, &print, 2);
+ mpack_print_append_cstr(&print, "\n");
+ mpack_print_flush(&print);
+}
+
+void mpack_print_stdfile_to_callback(FILE* file, mpack_print_callback_t callback, void* context) {
+ char buffer[1024];
+ mpack_print_t print;
+ mpack_memset(&print, 0, sizeof(print));
+ print.buffer = buffer;
+ print.size = sizeof(buffer);
+ print.callback = callback;
+ print.context = context;
+
+ mpack_reader_t reader;
+ mpack_reader_init_stdfile(&reader, file, false);
+ mpack_print_and_destroy(&reader, &print, 0);
+ mpack_print_flush(&print);
+}
+#endif
+
+#endif
+
+/* mpack/mpack-expect.c.c */
+
+#define MPACK_INTERNAL 1
+
+/* #include "mpack-expect.h" */
+
+#if MPACK_EXPECT
+
+
+// Helpers
+
+MPACK_STATIC_INLINE uint8_t mpack_expect_native_u8(mpack_reader_t* reader) {
+ if (mpack_reader_error(reader) != mpack_ok)
+ return 0;
+ uint8_t type;
+ if (!mpack_reader_ensure(reader, sizeof(type)))
+ return 0;
+ type = mpack_load_u8(reader->data);
+ reader->data += sizeof(type);
+ return type;
+}
+
+#if !MPACK_OPTIMIZE_FOR_SIZE
+MPACK_STATIC_INLINE uint16_t mpack_expect_native_u16(mpack_reader_t* reader) {
+ if (mpack_reader_error(reader) != mpack_ok)
+ return 0;
+ uint16_t type;
+ if (!mpack_reader_ensure(reader, sizeof(type)))
+ return 0;
+ type = mpack_load_u16(reader->data);
+ reader->data += sizeof(type);
+ return type;
+}
+
+MPACK_STATIC_INLINE uint32_t mpack_expect_native_u32(mpack_reader_t* reader) {
+ if (mpack_reader_error(reader) != mpack_ok)
+ return 0;
+ uint32_t type;
+ if (!mpack_reader_ensure(reader, sizeof(type)))
+ return 0;
+ type = mpack_load_u32(reader->data);
+ reader->data += sizeof(type);
+ return type;
+}
+#endif
+
+MPACK_STATIC_INLINE uint8_t mpack_expect_type_byte(mpack_reader_t* reader) {
+ mpack_reader_track_element(reader);
+ return mpack_expect_native_u8(reader);
+}
+
+
+// Basic Number Functions
+
+uint8_t mpack_expect_u8(mpack_reader_t* reader) {
+ mpack_tag_t var = mpack_read_tag(reader);
+ if (var.type == mpack_type_uint) {
+ if (var.v.u <= UINT8_MAX)
+ return (uint8_t)var.v.u;
+ } else if (var.type == mpack_type_int) {
+ if (var.v.i >= 0 && var.v.i <= UINT8_MAX)
+ return (uint8_t)var.v.i;
+ }
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return 0;
+}
+
+uint16_t mpack_expect_u16(mpack_reader_t* reader) {
+ mpack_tag_t var = mpack_read_tag(reader);
+ if (var.type == mpack_type_uint) {
+ if (var.v.u <= UINT16_MAX)
+ return (uint16_t)var.v.u;
+ } else if (var.type == mpack_type_int) {
+ if (var.v.i >= 0 && var.v.i <= UINT16_MAX)
+ return (uint16_t)var.v.i;
+ }
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return 0;
+}
+
+uint32_t mpack_expect_u32(mpack_reader_t* reader) {
+ mpack_tag_t var = mpack_read_tag(reader);
+ if (var.type == mpack_type_uint) {
+ if (var.v.u <= UINT32_MAX)
+ return (uint32_t)var.v.u;
+ } else if (var.type == mpack_type_int) {
+ if (var.v.i >= 0 && var.v.i <= UINT32_MAX)
+ return (uint32_t)var.v.i;
+ }
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return 0;
+}
+
+uint64_t mpack_expect_u64(mpack_reader_t* reader) {
+ mpack_tag_t var = mpack_read_tag(reader);
+ if (var.type == mpack_type_uint) {
+ return var.v.u;
+ } else if (var.type == mpack_type_int) {
+ if (var.v.i >= 0)
+ return (uint64_t)var.v.i;
+ }
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return 0;
+}
+
+int8_t mpack_expect_i8(mpack_reader_t* reader) {
+ mpack_tag_t var = mpack_read_tag(reader);
+ if (var.type == mpack_type_uint) {
+ if (var.v.u <= INT8_MAX)
+ return (int8_t)var.v.u;
+ } else if (var.type == mpack_type_int) {
+ if (var.v.i >= INT8_MIN && var.v.i <= INT8_MAX)
+ return (int8_t)var.v.i;
+ }
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return 0;
+}
+
+int16_t mpack_expect_i16(mpack_reader_t* reader) {
+ mpack_tag_t var = mpack_read_tag(reader);
+ if (var.type == mpack_type_uint) {
+ if (var.v.u <= INT16_MAX)
+ return (int16_t)var.v.u;
+ } else if (var.type == mpack_type_int) {
+ if (var.v.i >= INT16_MIN && var.v.i <= INT16_MAX)
+ return (int16_t)var.v.i;
+ }
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return 0;
+}
+
+int32_t mpack_expect_i32(mpack_reader_t* reader) {
+ mpack_tag_t var = mpack_read_tag(reader);
+ if (var.type == mpack_type_uint) {
+ if (var.v.u <= INT32_MAX)
+ return (int32_t)var.v.u;
+ } else if (var.type == mpack_type_int) {
+ if (var.v.i >= INT32_MIN && var.v.i <= INT32_MAX)
+ return (int32_t)var.v.i;
+ }
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return 0;
+}
+
+int64_t mpack_expect_i64(mpack_reader_t* reader) {
+ mpack_tag_t var = mpack_read_tag(reader);
+ if (var.type == mpack_type_uint) {
+ if (var.v.u <= INT64_MAX)
+ return (int64_t)var.v.u;
+ } else if (var.type == mpack_type_int) {
+ return var.v.i;
+ }
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return 0;
+}
+
+float mpack_expect_float(mpack_reader_t* reader) {
+ mpack_tag_t var = mpack_read_tag(reader);
+ if (var.type == mpack_type_uint)
+ return (float)var.v.u;
+ else if (var.type == mpack_type_int)
+ return (float)var.v.i;
+ else if (var.type == mpack_type_float)
+ return var.v.f;
+ else if (var.type == mpack_type_double)
+ return (float)var.v.d;
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return 0.0f;
+}
+
+double mpack_expect_double(mpack_reader_t* reader) {
+ mpack_tag_t var = mpack_read_tag(reader);
+ if (var.type == mpack_type_uint)
+ return (double)var.v.u;
+ else if (var.type == mpack_type_int)
+ return (double)var.v.i;
+ else if (var.type == mpack_type_float)
+ return (double)var.v.f;
+ else if (var.type == mpack_type_double)
+ return var.v.d;
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return 0.0;
+}
+
+float mpack_expect_float_strict(mpack_reader_t* reader) {
+ mpack_tag_t var = mpack_read_tag(reader);
+ if (var.type == mpack_type_float)
+ return var.v.f;
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return 0.0f;
+}
+
+double mpack_expect_double_strict(mpack_reader_t* reader) {
+ mpack_tag_t var = mpack_read_tag(reader);
+ if (var.type == mpack_type_float)
+ return (double)var.v.f;
+ else if (var.type == mpack_type_double)
+ return var.v.d;
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return 0.0;
+}
+
+
+// Ranged Number Functions
+//
+// All ranged functions are identical other than the type, so we
+// define their content with a macro. The prototypes are still written
+// out in full to support ctags/IDE tools.
+
+#define MPACK_EXPECT_RANGE_IMPL(name, type_t) \
+ \
+ /* make sure the range is sensible */ \
+ mpack_assert(min_value <= max_value, \
+ "min_value %i must be less than or equal to max_value %i", \
+ min_value, max_value); \
+ \
+ /* read the value */ \
+ type_t val = mpack_expect_##name(reader); \
+ if (mpack_reader_error(reader) != mpack_ok) \
+ return min_value; \
+ \
+ /* make sure it fits */ \
+ if (val < min_value || val > max_value) { \
+ mpack_reader_flag_error(reader, mpack_error_type); \
+ return min_value; \
+ } \
+ \
+ return val;
+
+uint8_t mpack_expect_u8_range(mpack_reader_t* reader, uint8_t min_value, uint8_t max_value) {MPACK_EXPECT_RANGE_IMPL(u8, uint8_t)}
+uint16_t mpack_expect_u16_range(mpack_reader_t* reader, uint16_t min_value, uint16_t max_value) {MPACK_EXPECT_RANGE_IMPL(u16, uint16_t)}
+uint32_t mpack_expect_u32_range(mpack_reader_t* reader, uint32_t min_value, uint32_t max_value) {MPACK_EXPECT_RANGE_IMPL(u32, uint32_t)}
+uint64_t mpack_expect_u64_range(mpack_reader_t* reader, uint64_t min_value, uint64_t max_value) {MPACK_EXPECT_RANGE_IMPL(u64, uint64_t)}
+
+int8_t mpack_expect_i8_range(mpack_reader_t* reader, int8_t min_value, int8_t max_value) {MPACK_EXPECT_RANGE_IMPL(i8, int8_t)}
+int16_t mpack_expect_i16_range(mpack_reader_t* reader, int16_t min_value, int16_t max_value) {MPACK_EXPECT_RANGE_IMPL(i16, int16_t)}
+int32_t mpack_expect_i32_range(mpack_reader_t* reader, int32_t min_value, int32_t max_value) {MPACK_EXPECT_RANGE_IMPL(i32, int32_t)}
+int64_t mpack_expect_i64_range(mpack_reader_t* reader, int64_t min_value, int64_t max_value) {MPACK_EXPECT_RANGE_IMPL(i64, int64_t)}
+
+float mpack_expect_float_range(mpack_reader_t* reader, float min_value, float max_value) {MPACK_EXPECT_RANGE_IMPL(float, float)}
+double mpack_expect_double_range(mpack_reader_t* reader, double min_value, double max_value) {MPACK_EXPECT_RANGE_IMPL(double, double)}
+
+uint32_t mpack_expect_map_range(mpack_reader_t* reader, uint32_t min_value, uint32_t max_value) {MPACK_EXPECT_RANGE_IMPL(map, uint32_t)}
+uint32_t mpack_expect_array_range(mpack_reader_t* reader, uint32_t min_value, uint32_t max_value) {MPACK_EXPECT_RANGE_IMPL(array, uint32_t)}
+
+
+// Matching Number Functions
+
+void mpack_expect_uint_match(mpack_reader_t* reader, uint64_t value) {
+ if (mpack_expect_u64(reader) != value)
+ mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+void mpack_expect_int_match(mpack_reader_t* reader, int64_t value) {
+ if (mpack_expect_i64(reader) != value)
+ mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+
+// Other Basic Types
+
+void mpack_expect_nil(mpack_reader_t* reader) {
+ if (mpack_expect_type_byte(reader) != 0xc0)
+ mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+bool mpack_expect_bool(mpack_reader_t* reader) {
+ uint8_t type = mpack_expect_type_byte(reader);
+ if ((type & ~1) != 0xc2)
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return (bool)(type & 1);
+}
+
+void mpack_expect_true(mpack_reader_t* reader) {
+ if (mpack_expect_bool(reader) != true)
+ mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+void mpack_expect_false(mpack_reader_t* reader) {
+ if (mpack_expect_bool(reader) != false)
+ mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+#if MPACK_EXTENSIONS
+mpack_timestamp_t mpack_expect_timestamp(mpack_reader_t* reader) {
+ mpack_timestamp_t zero = {0, 0};
+
+ mpack_tag_t tag = mpack_read_tag(reader);
+ if (tag.type != mpack_type_ext) {
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return zero;
+ }
+ if (mpack_tag_ext_exttype(&tag) != MPACK_EXTTYPE_TIMESTAMP) {
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return zero;
+ }
+
+ return mpack_read_timestamp(reader, mpack_tag_ext_length(&tag));
+}
+
+int64_t mpack_expect_timestamp_truncate(mpack_reader_t* reader) {
+ return mpack_expect_timestamp(reader).seconds;
+}
+#endif
+
+
+// Compound Types
+
+uint32_t mpack_expect_map(mpack_reader_t* reader) {
+ mpack_tag_t var = mpack_read_tag(reader);
+ if (var.type == mpack_type_map)
+ return var.v.n;
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return 0;
+}
+
+void mpack_expect_map_match(mpack_reader_t* reader, uint32_t count) {
+ if (mpack_expect_map(reader) != count)
+ mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+bool mpack_expect_map_or_nil(mpack_reader_t* reader, uint32_t* count) {
+ mpack_assert(count != NULL, "count cannot be NULL");
+
+ mpack_tag_t var = mpack_read_tag(reader);
+ if (var.type == mpack_type_nil) {
+ *count = 0;
+ return false;
+ }
+ if (var.type == mpack_type_map) {
+ *count = var.v.n;
+ return true;
+ }
+ mpack_reader_flag_error(reader, mpack_error_type);
+ *count = 0;
+ return false;
+}
+
+bool mpack_expect_map_max_or_nil(mpack_reader_t* reader, uint32_t max_count, uint32_t* count) {
+ mpack_assert(count != NULL, "count cannot be NULL");
+
+ bool has_map = mpack_expect_map_or_nil(reader, count);
+ if (has_map && *count > max_count) {
+ *count = 0;
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return false;
+ }
+ return has_map;
+}
+
+uint32_t mpack_expect_array(mpack_reader_t* reader) {
+ mpack_tag_t var = mpack_read_tag(reader);
+ if (var.type == mpack_type_array)
+ return var.v.n;
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return 0;
+}
+
+void mpack_expect_array_match(mpack_reader_t* reader, uint32_t count) {
+ if (mpack_expect_array(reader) != count)
+ mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+bool mpack_expect_array_or_nil(mpack_reader_t* reader, uint32_t* count) {
+ mpack_assert(count != NULL, "count cannot be NULL");
+
+ mpack_tag_t var = mpack_read_tag(reader);
+ if (var.type == mpack_type_nil) {
+ *count = 0;
+ return false;
+ }
+ if (var.type == mpack_type_array) {
+ *count = var.v.n;
+ return true;
+ }
+ mpack_reader_flag_error(reader, mpack_error_type);
+ *count = 0;
+ return false;
+}
+
+bool mpack_expect_array_max_or_nil(mpack_reader_t* reader, uint32_t max_count, uint32_t* count) {
+ mpack_assert(count != NULL, "count cannot be NULL");
+
+ bool has_array = mpack_expect_array_or_nil(reader, count);
+ if (has_array && *count > max_count) {
+ *count = 0;
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return false;
+ }
+ return has_array;
+}
+
+#ifdef MPACK_MALLOC
+void* mpack_expect_array_alloc_impl(mpack_reader_t* reader, size_t element_size, uint32_t max_count, uint32_t* out_count, bool allow_nil) {
+ mpack_assert(out_count != NULL, "out_count cannot be NULL");
+ *out_count = 0;
+
+ uint32_t count;
+ bool has_array = true;
+ if (allow_nil)
+ has_array = mpack_expect_array_max_or_nil(reader, max_count, &count);
+ else
+ count = mpack_expect_array_max(reader, max_count);
+ if (mpack_reader_error(reader))
+ return NULL;
+
+ // size 0 is not an error; we return NULL for no elements.
+ if (count == 0) {
+ // we call mpack_done_array() automatically ONLY if we are using
+ // the _or_nil variant. this is the only way to allow nil and empty
+ // to work the same way.
+ if (allow_nil && has_array)
+ mpack_done_array(reader);
+ return NULL;
+ }
+
+ void* p = MPACK_MALLOC(element_size * count);
+ if (p == NULL) {
+ mpack_reader_flag_error(reader, mpack_error_memory);
+ return NULL;
+ }
+
+ *out_count = count;
+ return p;
+}
+#endif
+
+
+// Str, Bin and Ext Functions
+
+uint32_t mpack_expect_str(mpack_reader_t* reader) {
+ #if MPACK_OPTIMIZE_FOR_SIZE
+ mpack_tag_t var = mpack_read_tag(reader);
+ if (var.type == mpack_type_str)
+ return var.v.l;
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return 0;
+ #else
+ uint8_t type = mpack_expect_type_byte(reader);
+ uint32_t count;
+
+ if ((type >> 5) == 5) {
+ count = type & (uint8_t)~0xe0;
+ } else if (type == 0xd9) {
+ count = mpack_expect_native_u8(reader);
+ } else if (type == 0xda) {
+ count = mpack_expect_native_u16(reader);
+ } else if (type == 0xdb) {
+ count = mpack_expect_native_u32(reader);
+ } else {
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return 0;
+ }
+
+ #if MPACK_READ_TRACKING
+ mpack_reader_flag_if_error(reader, mpack_track_push(&reader->track, mpack_type_str, count));
+ #endif
+ return count;
+ #endif
+}
+
+size_t mpack_expect_str_buf(mpack_reader_t* reader, char* buf, size_t bufsize) {
+ mpack_assert(buf != NULL, "buf cannot be NULL");
+
+ size_t length = mpack_expect_str(reader);
+ if (mpack_reader_error(reader))
+ return 0;
+
+ if (length > bufsize) {
+ mpack_reader_flag_error(reader, mpack_error_too_big);
+ return 0;
+ }
+
+ mpack_read_bytes(reader, buf, length);
+ if (mpack_reader_error(reader))
+ return 0;
+
+ mpack_done_str(reader);
+ return length;
+}
+
+size_t mpack_expect_utf8(mpack_reader_t* reader, char* buf, size_t size) {
+ mpack_assert(buf != NULL, "buf cannot be NULL");
+
+ size_t length = mpack_expect_str_buf(reader, buf, size);
+
+ if (!mpack_utf8_check(buf, length)) {
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return 0;
+ }
+
+ return length;
+}
+
+uint32_t mpack_expect_bin(mpack_reader_t* reader) {
+ mpack_tag_t var = mpack_read_tag(reader);
+ if (var.type == mpack_type_bin)
+ return var.v.l;
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return 0;
+}
+
+size_t mpack_expect_bin_buf(mpack_reader_t* reader, char* buf, size_t bufsize) {
+ mpack_assert(buf != NULL, "buf cannot be NULL");
+
+ size_t binsize = mpack_expect_bin(reader);
+ if (mpack_reader_error(reader))
+ return 0;
+ if (binsize > bufsize) {
+ mpack_reader_flag_error(reader, mpack_error_too_big);
+ return 0;
+ }
+ mpack_read_bytes(reader, buf, binsize);
+ if (mpack_reader_error(reader))
+ return 0;
+ mpack_done_bin(reader);
+ return binsize;
+}
+
+#if MPACK_EXTENSIONS
+uint32_t mpack_expect_ext(mpack_reader_t* reader, int8_t* type) {
+ mpack_tag_t var = mpack_read_tag(reader);
+ if (var.type == mpack_type_ext) {
+ *type = mpack_tag_ext_exttype(&var);
+ return mpack_tag_ext_length(&var);
+ }
+ *type = 0;
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return 0;
+}
+
+size_t mpack_expect_ext_buf(mpack_reader_t* reader, int8_t* type, char* buf, size_t bufsize) {
+ mpack_assert(buf != NULL, "buf cannot be NULL");
+
+ size_t extsize = mpack_expect_ext(reader, type);
+ if (mpack_reader_error(reader))
+ return 0;
+ if (extsize > bufsize) {
+ *type = 0;
+ mpack_reader_flag_error(reader, mpack_error_too_big);
+ return 0;
+ }
+ mpack_read_bytes(reader, buf, extsize);
+ if (mpack_reader_error(reader)) {
+ *type = 0;
+ return 0;
+ }
+ mpack_done_ext(reader);
+ return extsize;
+}
+#endif
+
+void mpack_expect_cstr(mpack_reader_t* reader, char* buf, size_t bufsize) {
+ uint32_t length = mpack_expect_str(reader);
+ mpack_read_cstr(reader, buf, bufsize, length);
+ mpack_done_str(reader);
+}
+
+void mpack_expect_utf8_cstr(mpack_reader_t* reader, char* buf, size_t bufsize) {
+ uint32_t length = mpack_expect_str(reader);
+ mpack_read_utf8_cstr(reader, buf, bufsize, length);
+ mpack_done_str(reader);
+}
+
+#ifdef MPACK_MALLOC
+static char* mpack_expect_cstr_alloc_unchecked(mpack_reader_t* reader, size_t maxsize, size_t* out_length) {
+ mpack_assert(out_length != NULL, "out_length cannot be NULL");
+ *out_length = 0;
+
+ // make sure argument makes sense
+ if (maxsize < 1) {
+ mpack_break("maxsize is zero; you must have room for at least a null-terminator");
+ mpack_reader_flag_error(reader, mpack_error_bug);
+ return NULL;
+ }
+
+ if (maxsize > UINT32_MAX)
+ maxsize = UINT32_MAX;
+
+ size_t length = mpack_expect_str_max(reader, (uint32_t)maxsize - 1);
+ char* str = mpack_read_bytes_alloc_impl(reader, length, true);
+ mpack_done_str(reader);
+
+ if (str)
+ *out_length = length;
+ return str;
+}
+
+char* mpack_expect_cstr_alloc(mpack_reader_t* reader, size_t maxsize) {
+ size_t length;
+ char* str = mpack_expect_cstr_alloc_unchecked(reader, maxsize, &length);
+
+ if (str && !mpack_str_check_no_null(str, length)) {
+ MPACK_FREE(str);
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return NULL;
+ }
+
+ return str;
+}
+
+char* mpack_expect_utf8_cstr_alloc(mpack_reader_t* reader, size_t maxsize) {
+ size_t length;
+ char* str = mpack_expect_cstr_alloc_unchecked(reader, maxsize, &length);
+
+ if (str && !mpack_utf8_check_no_null(str, length)) {
+ MPACK_FREE(str);
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return NULL;
+ }
+
+ return str;
+}
+#endif
+
+void mpack_expect_str_match(mpack_reader_t* reader, const char* str, size_t len) {
+ mpack_assert(str != NULL, "str cannot be NULL");
+
+ // expect a str the correct length
+ if (len > UINT32_MAX)
+ mpack_reader_flag_error(reader, mpack_error_type);
+ mpack_expect_str_length(reader, (uint32_t)len);
+ if (mpack_reader_error(reader))
+ return;
+ mpack_reader_track_bytes(reader, len);
+
+ // check each byte one by one (matched strings are likely to be very small)
+ for (; len > 0; --len) {
+ if (mpack_expect_native_u8(reader) != *str++) {
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return;
+ }
+ }
+
+ mpack_done_str(reader);
+}
+
+void mpack_expect_tag(mpack_reader_t* reader, mpack_tag_t expected) {
+ mpack_tag_t actual = mpack_read_tag(reader);
+ if (!mpack_tag_equal(actual, expected))
+ mpack_reader_flag_error(reader, mpack_error_type);
+}
+
+#ifdef MPACK_MALLOC
+char* mpack_expect_bin_alloc(mpack_reader_t* reader, size_t maxsize, size_t* size) {
+ mpack_assert(size != NULL, "size cannot be NULL");
+ *size = 0;
+
+ if (maxsize > UINT32_MAX)
+ maxsize = UINT32_MAX;
+
+ size_t length = mpack_expect_bin_max(reader, (uint32_t)maxsize);
+ if (mpack_reader_error(reader))
+ return NULL;
+
+ char* data = mpack_read_bytes_alloc(reader, length);
+ mpack_done_bin(reader);
+
+ if (data)
+ *size = length;
+ return data;
+}
+#endif
+
+#if MPACK_EXTENSIONS && defined(MPACK_MALLOC)
+char* mpack_expect_ext_alloc(mpack_reader_t* reader, int8_t* type, size_t maxsize, size_t* size) {
+ mpack_assert(size != NULL, "size cannot be NULL");
+ *size = 0;
+
+ if (maxsize > UINT32_MAX)
+ maxsize = UINT32_MAX;
+
+ size_t length = mpack_expect_ext_max(reader, type, (uint32_t)maxsize);
+ if (mpack_reader_error(reader))
+ return NULL;
+
+ char* data = mpack_read_bytes_alloc(reader, length);
+ mpack_done_ext(reader);
+
+ if (data) {
+ *size = length;
+ } else {
+ *type = 0;
+ }
+ return data;
+}
+#endif
+
+size_t mpack_expect_enum(mpack_reader_t* reader, const char* strings[], size_t count) {
+
+ // read the string in-place
+ size_t keylen = mpack_expect_str(reader);
+ const char* key = mpack_read_bytes_inplace(reader, keylen);
+ mpack_done_str(reader);
+ if (mpack_reader_error(reader) != mpack_ok)
+ return count;
+
+ // find what key it matches
+ for (size_t i = 0; i < count; ++i) {
+ const char* other = strings[i];
+ size_t otherlen = mpack_strlen(other);
+ if (keylen == otherlen && mpack_memcmp(key, other, keylen) == 0)
+ return i;
+ }
+
+ // no matches
+ mpack_reader_flag_error(reader, mpack_error_type);
+ return count;
+}
+
+size_t mpack_expect_enum_optional(mpack_reader_t* reader, const char* strings[], size_t count) {
+ if (mpack_reader_error(reader) != mpack_ok)
+ return count;
+
+ mpack_assert(count != 0, "count cannot be zero; no strings are valid!");
+ mpack_assert(strings != NULL, "strings cannot be NULL");
+
+ // the key is only recognized if it is a string
+ if (mpack_peek_tag(reader).type != mpack_type_str) {
+ mpack_discard(reader);
+ return count;
+ }
+
+ // read the string in-place
+ size_t keylen = mpack_expect_str(reader);
+ const char* key = mpack_read_bytes_inplace(reader, keylen);
+ mpack_done_str(reader);
+ if (mpack_reader_error(reader) != mpack_ok)
+ return count;
+
+ // find what key it matches
+ for (size_t i = 0; i < count; ++i) {
+ const char* other = strings[i];
+ size_t otherlen = mpack_strlen(other);
+ if (keylen == otherlen && mpack_memcmp(key, other, keylen) == 0)
+ return i;
+ }
+
+ // no matches
+ return count;
+}
+
+size_t mpack_expect_key_uint(mpack_reader_t* reader, bool found[], size_t count) {
+ if (mpack_reader_error(reader) != mpack_ok)
+ return count;
+
+ if (count == 0) {
+ mpack_break("count cannot be zero; no keys are valid!");
+ mpack_reader_flag_error(reader, mpack_error_bug);
+ return count;
+ }
+ mpack_assert(found != NULL, "found cannot be NULL");
+
+ // the key is only recognized if it is an unsigned int
+ if (mpack_peek_tag(reader).type != mpack_type_uint) {
+ mpack_discard(reader);
+ return count;
+ }
+
+ // read the key
+ uint64_t value = mpack_expect_u64(reader);
+ if (mpack_reader_error(reader) != mpack_ok)
+ return count;
+
+ // unrecognized keys are fine, we just return count
+ if (value >= count)
+ return count;
+
+ // check if this key is a duplicate
+ if (found[value]) {
+ mpack_reader_flag_error(reader, mpack_error_invalid);
+ return count;
+ }
+
+ found[value] = true;
+ return (size_t)value;
+}
+
+size_t mpack_expect_key_cstr(mpack_reader_t* reader, const char* keys[], bool found[], size_t count) {
+ size_t i = mpack_expect_enum_optional(reader, keys, count);
+
+ // unrecognized keys are fine, we just return count
+ if (i == count)
+ return count;
+
+ // check if this key is a duplicate
+ mpack_assert(found != NULL, "found cannot be NULL");
+ if (found[i]) {
+ mpack_reader_flag_error(reader, mpack_error_invalid);
+ return count;
+ }
+
+ found[i] = true;
+ return i;
+}
+
+#endif
+
+
+/* mpack/mpack-node.c.c */
+
+#define MPACK_INTERNAL 1
+
+/* #include "mpack-node.h" */
+
+#if MPACK_NODE
+
+MPACK_STATIC_INLINE const char* mpack_node_data_unchecked(mpack_node_t node) {
+ mpack_assert(mpack_node_error(node) == mpack_ok, "tree is in an error state!");
+
+ mpack_type_t type = node.data->type;
+ MPACK_UNUSED(type);
+ #if MPACK_EXTENSIONS
+ mpack_assert(type == mpack_type_str || type == mpack_type_bin || type == mpack_type_ext,
+ "node of type %i (%s) is not a data type!", type, mpack_type_to_string(type));
+ #else
+ mpack_assert(type == mpack_type_str || type == mpack_type_bin,
+ "node of type %i (%s) is not a data type!", type, mpack_type_to_string(type));
+ #endif
+
+ return node.tree->data + node.data->value.offset;
+}
+
+#if MPACK_EXTENSIONS
+MPACK_STATIC_INLINE int8_t mpack_node_exttype_unchecked(mpack_node_t node) {
+ mpack_assert(mpack_node_error(node) == mpack_ok, "tree is in an error state!");
+
+ mpack_type_t type = node.data->type;
+ MPACK_UNUSED(type);
+ mpack_assert(type == mpack_type_ext, "node of type %i (%s) is not an ext type!",
+ type, mpack_type_to_string(type));
+
+ // the exttype of an ext node is stored in the byte preceding the data
+ return mpack_load_i8(mpack_node_data_unchecked(node) - 1);
+}
+#endif
+
+
+
+/*
+ * Tree Parsing
+ */
+
+#ifdef MPACK_MALLOC
+
+// fix up the alloc size to make sure it exactly fits the
+// maximum number of nodes it can contain (the allocator will
+// waste it back anyway, but we round it down just in case)
+
+#define MPACK_NODES_PER_PAGE \
+ ((MPACK_NODE_PAGE_SIZE - sizeof(mpack_tree_page_t)) / sizeof(mpack_node_data_t) + 1)
+
+#define MPACK_PAGE_ALLOC_SIZE \
+ (sizeof(mpack_tree_page_t) + sizeof(mpack_node_data_t) * (MPACK_NODES_PER_PAGE - 1))
+
+#endif
+
+#ifdef MPACK_MALLOC
+/*
+ * Fills the tree until we have at least enough bytes for the current node.
+ */
+static bool mpack_tree_reserve_fill(mpack_tree_t* tree) {
+ mpack_assert(tree->parser.state == mpack_tree_parse_state_in_progress);
+
+ size_t bytes = tree->parser.current_node_reserved;
+ mpack_assert(bytes > tree->parser.possible_nodes_left,
+ "there are already enough bytes! call mpack_tree_ensure() instead.");
+ mpack_log("filling to reserve %i bytes\n", (int)bytes);
+
+ // if the necessary bytes would put us over the maximum tree
+ // size, fail right away.
+ // TODO: check for overflow?
+ if (tree->data_length + bytes > tree->max_size) {
+ mpack_tree_flag_error(tree, mpack_error_too_big);
+ return false;
+ }
+
+ // we'll need a read function to fetch more data. if there's
+ // no read function, the data should contain an entire message
+ // (or messages), so we flag it as invalid.
+ if (tree->read_fn == NULL) {
+ mpack_log("tree has no read function!\n");
+ mpack_tree_flag_error(tree, mpack_error_invalid);
+ return false;
+ }
+
+ // expand the buffer if needed
+ if (tree->data_length + bytes > tree->buffer_capacity) {
+
+ // TODO: check for overflow?
+ size_t new_capacity = (tree->buffer_capacity == 0) ? MPACK_BUFFER_SIZE : tree->buffer_capacity;
+ while (new_capacity < tree->data_length + bytes)
+ new_capacity *= 2;
+ if (new_capacity > tree->max_size)
+ new_capacity = tree->max_size;
+
+ mpack_log("expanding buffer from %i to %i\n", (int)tree->buffer_capacity, (int)new_capacity);
+
+ char* new_buffer;
+ if (tree->buffer == NULL)
+ new_buffer = (char*)MPACK_MALLOC(new_capacity);
+ else
+ new_buffer = (char*)mpack_realloc(tree->buffer, tree->data_length, new_capacity);
+
+ if (new_buffer == NULL) {
+ mpack_tree_flag_error(tree, mpack_error_memory);
+ return false;
+ }
+
+ tree->data = new_buffer;
+ tree->buffer = new_buffer;
+ tree->buffer_capacity = new_capacity;
+ }
+
+ // request as much data as possible, looping until we have
+ // all the data we need
+ do {
+ size_t read = tree->read_fn(tree, tree->buffer + tree->data_length, tree->buffer_capacity - tree->data_length);
+
+ // If the fill function encounters an error, it should flag an error on
+ // the tree.
+ if (mpack_tree_error(tree) != mpack_ok)
+ return false;
+
+ // We guard against fill functions that return -1 just in case.
+ if (read == (size_t)(-1)) {
+ mpack_tree_flag_error(tree, mpack_error_io);
+ return false;
+ }
+
+ // If the fill function returns 0, the data is not available yet. We
+ // return false to stop parsing the current node.
+ if (read == 0) {
+ mpack_log("not enough data.\n");
+ return false;
+ }
+
+ mpack_log("read %u more bytes\n", (uint32_t)read);
+ tree->data_length += read;
+ tree->parser.possible_nodes_left += read;
+ } while (tree->parser.possible_nodes_left < bytes);
+
+ return true;
+}
+#endif
+
+/*
+ * Ensures there are enough additional bytes in the tree for the current node
+ * (including reserved bytes for the children of this node, and in addition to
+ * the reserved bytes for children of previous compound nodes), reading more
+ * data if needed.
+ *
+ * extra_bytes is the number of additional bytes to reserve for the current
+ * node beyond the type byte (since one byte is already reserved for each node
+ * by its parent array or map.)
+ *
+ * This may reallocate the tree, which means the tree->data pointer may change!
+ *
+ * Returns false if not enough bytes could be read.
+ */
+MPACK_STATIC_INLINE bool mpack_tree_reserve_bytes(mpack_tree_t* tree, size_t extra_bytes) {
+ mpack_assert(tree->parser.state == mpack_tree_parse_state_in_progress);
+
+ // We guard against overflow here. A compound type could declare more than
+ // UINT32_MAX contents which overflows SIZE_MAX on 32-bit platforms. We
+ // flag mpack_error_invalid instead of mpack_error_too_big since it's far
+ // more likely that the message is corrupt than that the data is valid but
+ // not parseable on this architecture (see test_read_node_possible() in
+ // test-node.c .)
+ if ((uint64_t)tree->parser.current_node_reserved + (uint64_t)extra_bytes > SIZE_MAX) {
+ mpack_tree_flag_error(tree, mpack_error_invalid);
+ return false;
+ }
+
+ tree->parser.current_node_reserved += extra_bytes;
+
+ // Note that possible_nodes_left already accounts for reserved bytes for
+ // children of previous compound nodes. So even if there are hundreds of
+ // bytes left in the buffer, we might need to read anyway.
+ if (tree->parser.current_node_reserved <= tree->parser.possible_nodes_left)
+ return true;
+
+ #ifdef MPACK_MALLOC
+ return mpack_tree_reserve_fill(tree);
+ #else
+ return false;
+ #endif
+}
+
+MPACK_STATIC_INLINE size_t mpack_tree_parser_stack_capacity(mpack_tree_t* tree) {
+ #ifdef MPACK_MALLOC
+ return tree->parser.stack_capacity;
+ #else
+ return sizeof(tree->parser.stack) / sizeof(tree->parser.stack[0]);
+ #endif
+}
+
+static bool mpack_tree_push_stack(mpack_tree_t* tree, mpack_node_data_t* first_child, size_t total) {
+ mpack_tree_parser_t* parser = &tree->parser;
+ mpack_assert(parser->state == mpack_tree_parse_state_in_progress);
+
+ // No need to push empty containers
+ if (total == 0)
+ return true;
+
+ // Make sure we have enough room in the stack
+ if (parser->level + 1 == mpack_tree_parser_stack_capacity(tree)) {
+ #ifdef MPACK_MALLOC
+ size_t new_capacity = parser->stack_capacity * 2;
+ mpack_log("growing parse stack to capacity %i\n", (int)new_capacity);
+
+ // Replace the stack-allocated parsing stack
+ if (!parser->stack_owned) {
+ mpack_level_t* new_stack = (mpack_level_t*)MPACK_MALLOC(sizeof(mpack_level_t) * new_capacity);
+ if (!new_stack) {
+ mpack_tree_flag_error(tree, mpack_error_memory);
+ return false;
+ }
+ mpack_memcpy(new_stack, parser->stack, sizeof(mpack_level_t) * parser->stack_capacity);
+ parser->stack = new_stack;
+ parser->stack_owned = true;
+
+ // Realloc the allocated parsing stack
+ } else {
+ mpack_level_t* new_stack = (mpack_level_t*)mpack_realloc(parser->stack,
+ sizeof(mpack_level_t) * parser->stack_capacity, sizeof(mpack_level_t) * new_capacity);
+ if (!new_stack) {
+ mpack_tree_flag_error(tree, mpack_error_memory);
+ return false;
+ }
+ parser->stack = new_stack;
+ }
+ parser->stack_capacity = new_capacity;
+ #else
+ mpack_tree_flag_error(tree, mpack_error_too_big);
+ return false;
+ #endif
+ }
+
+ // Push the contents of this node onto the parsing stack
+ ++parser->level;
+ parser->stack[parser->level].child = first_child;
+ parser->stack[parser->level].left = total;
+ return true;
+}
+
+static bool mpack_tree_parse_children(mpack_tree_t* tree, mpack_node_data_t* node) {
+ mpack_tree_parser_t* parser = &tree->parser;
+ mpack_assert(parser->state == mpack_tree_parse_state_in_progress);
+
+ mpack_type_t type = node->type;
+ size_t total = node->len;
+
+ // Calculate total elements to read
+ if (type == mpack_type_map) {
+ if ((uint64_t)total * 2 > SIZE_MAX) {
+ mpack_tree_flag_error(tree, mpack_error_too_big);
+ return false;
+ }
+ total *= 2;
+ }
+
+ // Make sure we are under our total node limit (TODO can this overflow?)
+ tree->node_count += total;
+ if (tree->node_count > tree->max_nodes) {
+ mpack_tree_flag_error(tree, mpack_error_too_big);
+ return false;
+ }
+
+ // Each node is at least one byte. Count these bytes now to make
+ // sure there is enough data left.
+ if (!mpack_tree_reserve_bytes(tree, total))
+ return false;
+
+ // If there are enough nodes left in the current page, no need to grow
+ if (total <= parser->nodes_left) {
+ node->value.children = parser->nodes;
+ parser->nodes += total;
+ parser->nodes_left -= total;
+
+ } else {
+
+ #ifdef MPACK_MALLOC
+
+ // We can't grow if we're using a fixed pool (i.e. we didn't start with a page)
+ if (!tree->next) {
+ mpack_tree_flag_error(tree, mpack_error_too_big);
+ return false;
+ }
+
+ // Otherwise we need to grow, and the node's children need to be contiguous.
+ // This is a heuristic to decide whether we should waste the remaining space
+ // in the current page and start a new one, or give the children their
+ // own page. With a fraction of 1/8, this causes at most 12% additional
+ // waste. Note that reducing this too much causes less cache coherence and
+ // more malloc() overhead due to smaller allocations, so there's a tradeoff
+ // here. This heuristic could use some improvement, especially with custom
+ // page sizes.
+
+ mpack_tree_page_t* page;
+
+ if (total > MPACK_NODES_PER_PAGE || parser->nodes_left > MPACK_NODES_PER_PAGE / 8) {
+ // TODO: this should check for overflow
+ page = (mpack_tree_page_t*)MPACK_MALLOC(
+ sizeof(mpack_tree_page_t) + sizeof(mpack_node_data_t) * (total - 1));
+ if (page == NULL) {
+ mpack_tree_flag_error(tree, mpack_error_memory);
+ return false;
+ }
+ mpack_log("allocated seperate page %p for %i children, %i left in page of %i total\n",
+ page, (int)total, (int)parser->nodes_left, (int)MPACK_NODES_PER_PAGE);
+
+ node->value.children = page->nodes;
+
+ } else {
+ page = (mpack_tree_page_t*)MPACK_MALLOC(MPACK_PAGE_ALLOC_SIZE);
+ if (page == NULL) {
+ mpack_tree_flag_error(tree, mpack_error_memory);
+ return false;
+ }
+ mpack_log("allocated new page %p for %i children, wasting %i in page of %i total\n",
+ page, (int)total, (int)parser->nodes_left, (int)MPACK_NODES_PER_PAGE);
+
+ node->value.children = page->nodes;
+ parser->nodes = page->nodes + total;
+ parser->nodes_left = MPACK_NODES_PER_PAGE - total;
+ }
+
+ page->next = tree->next;
+ tree->next = page;
+
+ #else
+ // We can't grow if we don't have an allocator
+ mpack_tree_flag_error(tree, mpack_error_too_big);
+ return false;
+ #endif
+ }
+
+ return mpack_tree_push_stack(tree, node->value.children, total);
+}
+
+static bool mpack_tree_parse_bytes(mpack_tree_t* tree, mpack_node_data_t* node) {
+ node->value.offset = tree->size + tree->parser.current_node_reserved + 1;
+ return mpack_tree_reserve_bytes(tree, node->len);
+}
+
+#if MPACK_EXTENSIONS
+static bool mpack_tree_parse_ext(mpack_tree_t* tree, mpack_node_data_t* node) {
+ // reserve space for exttype
+ tree->parser.current_node_reserved += sizeof(int8_t);
+ node->type = mpack_type_ext;
+ return mpack_tree_parse_bytes(tree, node);
+}
+#endif
+
+static bool mpack_tree_parse_node_contents(mpack_tree_t* tree, mpack_node_data_t* node) {
+ mpack_assert(tree->parser.state == mpack_tree_parse_state_in_progress);
+ mpack_assert(node != NULL, "null node?");
+
+ // read the type. we've already accounted for this byte in
+ // possible_nodes_left, so we already know it is in bounds, and we don't
+ // need to reserve it for this node.
+ mpack_assert(tree->data_length > tree->size);
+ uint8_t type = mpack_load_u8(tree->data + tree->size);
+ mpack_log("node type %x\n", type);
+ tree->parser.current_node_reserved = 0;
+
+ // as with mpack_read_tag(), the fastest way to parse a node is to switch
+ // on the first byte, and to explicitly list every possible byte. we switch
+ // on the first four bits in size-optimized builds.
+
+ #if MPACK_OPTIMIZE_FOR_SIZE
+ switch (type >> 4) {
+
+ // positive fixnum
+ case 0x0: case 0x1: case 0x2: case 0x3:
+ case 0x4: case 0x5: case 0x6: case 0x7:
+ node->type = mpack_type_uint;
+ node->value.u = type;
+ return true;
+
+ // negative fixnum
+ case 0xe: case 0xf:
+ node->type = mpack_type_int;
+ node->value.i = (int8_t)type;
+ return true;
+
+ // fixmap
+ case 0x8:
+ node->type = mpack_type_map;
+ node->len = (uint32_t)(type & ~0xf0);
+ return mpack_tree_parse_children(tree, node);
+
+ // fixarray
+ case 0x9:
+ node->type = mpack_type_array;
+ node->len = (uint32_t)(type & ~0xf0);
+ return mpack_tree_parse_children(tree, node);
+
+ // fixstr
+ case 0xa: case 0xb:
+ node->type = mpack_type_str;
+ node->len = (uint32_t)(type & ~0xe0);
+ return mpack_tree_parse_bytes(tree, node);
+
+ // not one of the common infix types
+ default:
+ break;
+ }
+ #endif
+
+ switch (type) {
+
+ #if !MPACK_OPTIMIZE_FOR_SIZE
+ // positive fixnum
+ case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07:
+ case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f:
+ case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
+ case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f:
+ case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27:
+ case 0x28: case 0x29: case 0x2a: case 0x2b: case 0x2c: case 0x2d: case 0x2e: case 0x2f:
+ case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37:
+ case 0x38: case 0x39: case 0x3a: case 0x3b: case 0x3c: case 0x3d: case 0x3e: case 0x3f:
+ case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47:
+ case 0x48: case 0x49: case 0x4a: case 0x4b: case 0x4c: case 0x4d: case 0x4e: case 0x4f:
+ case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57:
+ case 0x58: case 0x59: case 0x5a: case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f:
+ case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67:
+ case 0x68: case 0x69: case 0x6a: case 0x6b: case 0x6c: case 0x6d: case 0x6e: case 0x6f:
+ case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77:
+ case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f:
+ node->type = mpack_type_uint;
+ node->value.u = type;
+ return true;
+
+ // negative fixnum
+ case 0xe0: case 0xe1: case 0xe2: case 0xe3: case 0xe4: case 0xe5: case 0xe6: case 0xe7:
+ case 0xe8: case 0xe9: case 0xea: case 0xeb: case 0xec: case 0xed: case 0xee: case 0xef:
+ case 0xf0: case 0xf1: case 0xf2: case 0xf3: case 0xf4: case 0xf5: case 0xf6: case 0xf7:
+ case 0xf8: case 0xf9: case 0xfa: case 0xfb: case 0xfc: case 0xfd: case 0xfe: case 0xff:
+ node->type = mpack_type_int;
+ node->value.i = (int8_t)type;
+ return true;
+
+ // fixmap
+ case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87:
+ case 0x88: case 0x89: case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f:
+ node->type = mpack_type_map;
+ node->len = (uint32_t)(type & ~0xf0);
+ return mpack_tree_parse_children(tree, node);
+
+ // fixarray
+ case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97:
+ case 0x98: case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d: case 0x9e: case 0x9f:
+ node->type = mpack_type_array;
+ node->len = (uint32_t)(type & ~0xf0);
+ return mpack_tree_parse_children(tree, node);
+
+ // fixstr
+ case 0xa0: case 0xa1: case 0xa2: case 0xa3: case 0xa4: case 0xa5: case 0xa6: case 0xa7:
+ case 0xa8: case 0xa9: case 0xaa: case 0xab: case 0xac: case 0xad: case 0xae: case 0xaf:
+ case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7:
+ case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf:
+ node->type = mpack_type_str;
+ node->len = (uint32_t)(type & ~0xe0);
+ return mpack_tree_parse_bytes(tree, node);
+ #endif
+
+ // nil
+ case 0xc0:
+ node->type = mpack_type_nil;
+ return true;
+
+ // bool
+ case 0xc2: case 0xc3:
+ node->type = mpack_type_bool;
+ node->value.b = type & 1;
+ return true;
+
+ // bin8
+ case 0xc4:
+ node->type = mpack_type_bin;
+ if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t)))
+ return false;
+ node->len = mpack_load_u8(tree->data + tree->size + 1);
+ return mpack_tree_parse_bytes(tree, node);
+
+ // bin16
+ case 0xc5:
+ node->type = mpack_type_bin;
+ if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t)))
+ return false;
+ node->len = mpack_load_u16(tree->data + tree->size + 1);
+ return mpack_tree_parse_bytes(tree, node);
+
+ // bin32
+ case 0xc6:
+ node->type = mpack_type_bin;
+ if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t)))
+ return false;
+ node->len = mpack_load_u32(tree->data + tree->size + 1);
+ return mpack_tree_parse_bytes(tree, node);
+
+ #if MPACK_EXTENSIONS
+ // ext8
+ case 0xc7:
+ if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t)))
+ return false;
+ node->len = mpack_load_u8(tree->data + tree->size + 1);
+ return mpack_tree_parse_ext(tree, node);
+
+ // ext16
+ case 0xc8:
+ if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t)))
+ return false;
+ node->len = mpack_load_u16(tree->data + tree->size + 1);
+ return mpack_tree_parse_ext(tree, node);
+
+ // ext32
+ case 0xc9:
+ if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t)))
+ return false;
+ node->len = mpack_load_u32(tree->data + tree->size + 1);
+ return mpack_tree_parse_ext(tree, node);
+ #endif
+
+ // float
+ case 0xca:
+ if (!mpack_tree_reserve_bytes(tree, sizeof(float)))
+ return false;
+ node->value.f = mpack_load_float(tree->data + tree->size + 1);
+ node->type = mpack_type_float;
+ return true;
+
+ // double
+ case 0xcb:
+ if (!mpack_tree_reserve_bytes(tree, sizeof(double)))
+ return false;
+ node->value.d = mpack_load_double(tree->data + tree->size + 1);
+ node->type = mpack_type_double;
+ return true;
+
+ // uint8
+ case 0xcc:
+ node->type = mpack_type_uint;
+ if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t)))
+ return false;
+ node->value.u = mpack_load_u8(tree->data + tree->size + 1);
+ return true;
+
+ // uint16
+ case 0xcd:
+ node->type = mpack_type_uint;
+ if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t)))
+ return false;
+ node->value.u = mpack_load_u16(tree->data + tree->size + 1);
+ return true;
+
+ // uint32
+ case 0xce:
+ node->type = mpack_type_uint;
+ if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t)))
+ return false;
+ node->value.u = mpack_load_u32(tree->data + tree->size + 1);
+ return true;
+
+ // uint64
+ case 0xcf:
+ node->type = mpack_type_uint;
+ if (!mpack_tree_reserve_bytes(tree, sizeof(uint64_t)))
+ return false;
+ node->value.u = mpack_load_u64(tree->data + tree->size + 1);
+ return true;
+
+ // int8
+ case 0xd0:
+ node->type = mpack_type_int;
+ if (!mpack_tree_reserve_bytes(tree, sizeof(int8_t)))
+ return false;
+ node->value.i = mpack_load_i8(tree->data + tree->size + 1);
+ return true;
+
+ // int16
+ case 0xd1:
+ node->type = mpack_type_int;
+ if (!mpack_tree_reserve_bytes(tree, sizeof(int16_t)))
+ return false;
+ node->value.i = mpack_load_i16(tree->data + tree->size + 1);
+ return true;
+
+ // int32
+ case 0xd2:
+ node->type = mpack_type_int;
+ if (!mpack_tree_reserve_bytes(tree, sizeof(int32_t)))
+ return false;
+ node->value.i = mpack_load_i32(tree->data + tree->size + 1);
+ return true;
+
+ // int64
+ case 0xd3:
+ node->type = mpack_type_int;
+ if (!mpack_tree_reserve_bytes(tree, sizeof(int64_t)))
+ return false;
+ node->value.i = mpack_load_i64(tree->data + tree->size + 1);
+ return true;
+
+ #if MPACK_EXTENSIONS
+ // fixext1
+ case 0xd4:
+ node->len = 1;
+ return mpack_tree_parse_ext(tree, node);
+
+ // fixext2
+ case 0xd5:
+ node->len = 2;
+ return mpack_tree_parse_ext(tree, node);
+
+ // fixext4
+ case 0xd6:
+ node->len = 4;
+ return mpack_tree_parse_ext(tree, node);
+
+ // fixext8
+ case 0xd7:
+ node->len = 8;
+ return mpack_tree_parse_ext(tree, node);
+
+ // fixext16
+ case 0xd8:
+ node->len = 16;
+ return mpack_tree_parse_ext(tree, node);
+ #endif
+
+ // str8
+ case 0xd9:
+ if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t)))
+ return false;
+ node->len = mpack_load_u8(tree->data + tree->size + 1);
+ node->type = mpack_type_str;
+ return mpack_tree_parse_bytes(tree, node);
+
+ // str16
+ case 0xda:
+ if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t)))
+ return false;
+ node->len = mpack_load_u16(tree->data + tree->size + 1);
+ node->type = mpack_type_str;
+ return mpack_tree_parse_bytes(tree, node);
+
+ // str32
+ case 0xdb:
+ if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t)))
+ return false;
+ node->len = mpack_load_u32(tree->data + tree->size + 1);
+ node->type = mpack_type_str;
+ return mpack_tree_parse_bytes(tree, node);
+
+ // array16
+ case 0xdc:
+ if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t)))
+ return false;
+ node->len = mpack_load_u16(tree->data + tree->size + 1);
+ node->type = mpack_type_array;
+ return mpack_tree_parse_children(tree, node);
+
+ // array32
+ case 0xdd:
+ if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t)))
+ return false;
+ node->len = mpack_load_u32(tree->data + tree->size + 1);
+ node->type = mpack_type_array;
+ return mpack_tree_parse_children(tree, node);
+
+ // map16
+ case 0xde:
+ if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t)))
+ return false;
+ node->len = mpack_load_u16(tree->data + tree->size + 1);
+ node->type = mpack_type_map;
+ return mpack_tree_parse_children(tree, node);
+
+ // map32
+ case 0xdf:
+ if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t)))
+ return false;
+ node->len = mpack_load_u32(tree->data + tree->size + 1);
+ node->type = mpack_type_map;
+ return mpack_tree_parse_children(tree, node);
+
+ // reserved
+ case 0xc1:
+ mpack_tree_flag_error(tree, mpack_error_invalid);
+ return false;
+
+ #if !MPACK_EXTENSIONS
+ // ext
+ case 0xc7: // fallthrough
+ case 0xc8: // fallthrough
+ case 0xc9: // fallthrough
+ // fixext
+ case 0xd4: // fallthrough
+ case 0xd5: // fallthrough
+ case 0xd6: // fallthrough
+ case 0xd7: // fallthrough
+ case 0xd8:
+ mpack_tree_flag_error(tree, mpack_error_unsupported);
+ return false;
+ #endif
+
+ #if MPACK_OPTIMIZE_FOR_SIZE
+ // any other bytes should have been handled by the infix switch
+ default:
+ break;
+ #endif
+ }
+
+ mpack_assert(0, "unreachable");
+ return false;
+}
+
+static bool mpack_tree_parse_node(mpack_tree_t* tree, mpack_node_data_t* node) {
+ mpack_log("parsing a node at position %i in level %i\n",
+ (int)tree->size, (int)tree->parser.level);
+
+ if (!mpack_tree_parse_node_contents(tree, node)) {
+ mpack_log("node parsing returned false\n");
+ return false;
+ }
+
+ tree->parser.possible_nodes_left -= tree->parser.current_node_reserved;
+
+ // The reserve for the current node does not include the initial byte
+ // previously reserved as part of its parent.
+ size_t node_size = tree->parser.current_node_reserved + 1;
+
+ // If the parsed type is a map or array, the reserve includes one byte for
+ // each child. We want to subtract these out of possible_nodes_left, but
+ // not out of the current size of the tree.
+ if (node->type == mpack_type_array)
+ node_size -= node->len;
+ else if (node->type == mpack_type_map)
+ node_size -= node->len * 2;
+ tree->size += node_size;
+
+ mpack_log("parsed a node of type %s of %i bytes and "
+ "%i additional bytes reserved for children.\n",
+ mpack_type_to_string(node->type), (int)node_size,
+ (int)tree->parser.current_node_reserved + 1 - (int)node_size);
+
+ return true;
+}
+
+/*
+ * We read nodes in a loop instead of recursively for maximum performance. The
+ * stack holds the amount of children left to read in each level of the tree.
+ * Parsing can pause and resume when more data becomes available.
+ */
+static bool mpack_tree_continue_parsing(mpack_tree_t* tree) {
+ if (mpack_tree_error(tree) != mpack_ok)
+ return false;
+
+ mpack_tree_parser_t* parser = &tree->parser;
+ mpack_assert(parser->state == mpack_tree_parse_state_in_progress);
+ mpack_log("parsing tree elements, %i bytes in buffer\n", (int)tree->data_length);
+
+ // we loop parsing nodes until the parse stack is empty. we break
+ // by returning out of the function.
+ while (true) {
+ mpack_node_data_t* node = parser->stack[parser->level].child;
+ size_t level = parser->level;
+ if (!mpack_tree_parse_node(tree, node))
+ return false;
+ --parser->stack[level].left;
+ ++parser->stack[level].child;
+
+ mpack_assert(mpack_tree_error(tree) == mpack_ok,
+ "mpack_tree_parse_node() should have returned false due to error!");
+
+ // pop empty stack levels, exiting the outer loop when the stack is empty.
+ // (we could tail-optimize containers by pre-emptively popping empty
+ // stack levels before reading the new element, this way we wouldn't
+ // have to loop. but we eventually want to use the parse stack to give
+ // better error messages that contain the location of the error, so
+ // it needs to be complete.)
+ while (parser->stack[parser->level].left == 0) {
+ if (parser->level == 0)
+ return true;
+ --parser->level;
+ }
+ }
+}
+
+static void mpack_tree_cleanup(mpack_tree_t* tree) {
+ MPACK_UNUSED(tree);
+
+ #ifdef MPACK_MALLOC
+ if (tree->parser.stack_owned) {
+ MPACK_FREE(tree->parser.stack);
+ tree->parser.stack = NULL;
+ tree->parser.stack_owned = false;
+ }
+
+ mpack_tree_page_t* page = tree->next;
+ while (page != NULL) {
+ mpack_tree_page_t* next = page->next;
+ mpack_log("freeing page %p\n", page);
+ MPACK_FREE(page);
+ page = next;
+ }
+ tree->next = NULL;
+ #endif
+}
+
+static bool mpack_tree_parse_start(mpack_tree_t* tree) {
+ if (mpack_tree_error(tree) != mpack_ok)
+ return false;
+
+ mpack_tree_parser_t* parser = &tree->parser;
+ mpack_assert(parser->state != mpack_tree_parse_state_in_progress,
+ "previous parsing was not finished!");
+
+ if (parser->state == mpack_tree_parse_state_parsed)
+ mpack_tree_cleanup(tree);
+
+ mpack_log("starting parse\n");
+ tree->parser.state = mpack_tree_parse_state_in_progress;
+ tree->parser.current_node_reserved = 0;
+
+ // check if we previously parsed a tree
+ if (tree->size > 0) {
+ #ifdef MPACK_MALLOC
+ // if we're buffered, move the remaining data back to the
+ // start of the buffer
+ // TODO: This is not ideal performance-wise. We should only move data
+ // when we need to call the fill function.
+ // TODO: We could consider shrinking the buffer here, especially if we
+ // determine that the fill function is providing less than a quarter of
+ // the buffer size or if messages take up less than a quarter of the
+ // buffer size. Maybe this should be configurable.
+ if (tree->buffer != NULL) {
+ mpack_memmove(tree->buffer, tree->buffer + tree->size, tree->data_length - tree->size);
+ }
+ else
+ #endif
+ // otherwise advance past the parsed data
+ {
+ tree->data += tree->size;
+ }
+ tree->data_length -= tree->size;
+ tree->size = 0;
+ tree->node_count = 0;
+ }
+
+ // make sure we have at least one byte available before allocating anything
+ parser->possible_nodes_left = tree->data_length;
+ if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t))) {
+ tree->parser.state = mpack_tree_parse_state_not_started;
+ return false;
+ }
+ mpack_log("parsing tree at %p starting with byte %x\n", tree->data, (uint8_t)tree->data[0]);
+ parser->possible_nodes_left -= 1;
+ tree->node_count = 1;
+
+ #ifdef MPACK_MALLOC
+ parser->stack = parser->stack_local;
+ parser->stack_owned = false;
+ parser->stack_capacity = sizeof(parser->stack_local) / sizeof(*parser->stack_local);
+
+ if (tree->pool == NULL) {
+
+ // allocate first page
+ mpack_tree_page_t* page = (mpack_tree_page_t*)MPACK_MALLOC(MPACK_PAGE_ALLOC_SIZE);
+ mpack_log("allocated initial page %p of size %i count %i\n",
+ page, (int)MPACK_PAGE_ALLOC_SIZE, (int)MPACK_NODES_PER_PAGE);
+ if (page == NULL) {
+ tree->error = mpack_error_memory;
+ return false;
+ }
+ page->next = NULL;
+ tree->next = page;
+
+ parser->nodes = page->nodes;
+ parser->nodes_left = MPACK_NODES_PER_PAGE;
+ }
+ else
+ #endif
+ {
+ // otherwise use the provided pool
+ mpack_assert(tree->pool != NULL, "no pool provided?");
+ parser->nodes = tree->pool;
+ parser->nodes_left = tree->pool_count;
+ }
+
+ tree->root = parser->nodes;
+ ++parser->nodes;
+ --parser->nodes_left;
+
+ parser->level = 0;
+ parser->stack[0].child = tree->root;
+ parser->stack[0].left = 1;
+
+ return true;
+}
+
+void mpack_tree_parse(mpack_tree_t* tree) {
+ if (mpack_tree_error(tree) != mpack_ok)
+ return;
+
+ if (tree->parser.state != mpack_tree_parse_state_in_progress) {
+ if (!mpack_tree_parse_start(tree)) {
+ mpack_tree_flag_error(tree, (tree->read_fn == NULL) ?
+ mpack_error_invalid : mpack_error_io);
+ return;
+ }
+ }
+
+ if (!mpack_tree_continue_parsing(tree)) {
+ if (mpack_tree_error(tree) != mpack_ok)
+ return;
+
+ // We're parsing synchronously on a blocking fill function. If we
+ // didn't completely finish parsing the tree, it's an error.
+ mpack_log("tree parsing incomplete. flagging error.\n");
+ mpack_tree_flag_error(tree, (tree->read_fn == NULL) ?
+ mpack_error_invalid : mpack_error_io);
+ return;
+ }
+
+ mpack_assert(mpack_tree_error(tree) == mpack_ok);
+ mpack_assert(tree->parser.level == 0);
+ tree->parser.state = mpack_tree_parse_state_parsed;
+ mpack_log("parsed tree of %i bytes, %i bytes left\n", (int)tree->size, (int)tree->parser.possible_nodes_left);
+ mpack_log("%i nodes in final page\n", (int)tree->parser.nodes_left);
+}
+
+bool mpack_tree_try_parse(mpack_tree_t* tree) {
+ if (mpack_tree_error(tree) != mpack_ok)
+ return false;
+
+ if (tree->parser.state != mpack_tree_parse_state_in_progress)
+ if (!mpack_tree_parse_start(tree))
+ return false;
+
+ if (!mpack_tree_continue_parsing(tree))
+ return false;
+
+ mpack_assert(mpack_tree_error(tree) == mpack_ok);
+ mpack_assert(tree->parser.level == 0);
+ tree->parser.state = mpack_tree_parse_state_parsed;
+ return true;
+}
+
+
+
+/*
+ * Tree functions
+ */
+
+mpack_node_t mpack_tree_root(mpack_tree_t* tree) {
+ if (mpack_tree_error(tree) != mpack_ok)
+ return mpack_tree_nil_node(tree);
+
+ // We check that a tree was parsed successfully and assert if not. You must
+ // call mpack_tree_parse() (or mpack_tree_try_parse() with a success
+ // result) in order to access the root node.
+ if (tree->parser.state != mpack_tree_parse_state_parsed) {
+ mpack_break("Tree has not been parsed! "
+ "Did you call mpack_tree_parse() or mpack_tree_try_parse()?");
+ mpack_tree_flag_error(tree, mpack_error_bug);
+ return mpack_tree_nil_node(tree);
+ }
+
+ return mpack_node(tree, tree->root);
+}
+
+static void mpack_tree_init_clear(mpack_tree_t* tree) {
+ mpack_memset(tree, 0, sizeof(*tree));
+ tree->nil_node.type = mpack_type_nil;
+ tree->missing_node.type = mpack_type_missing;
+ tree->max_size = SIZE_MAX;
+ tree->max_nodes = SIZE_MAX;
+}
+
+#ifdef MPACK_MALLOC
+void mpack_tree_init_data(mpack_tree_t* tree, const char* data, size_t length) {
+ mpack_tree_init_clear(tree);
+
+ MPACK_STATIC_ASSERT(MPACK_NODE_PAGE_SIZE >= sizeof(mpack_tree_page_t),
+ "MPACK_NODE_PAGE_SIZE is too small");
+
+ MPACK_STATIC_ASSERT(MPACK_PAGE_ALLOC_SIZE <= MPACK_NODE_PAGE_SIZE,
+ "incorrect page rounding?");
+
+ tree->data = data;
+ tree->data_length = length;
+ tree->pool = NULL;
+ tree->pool_count = 0;
+ tree->next = NULL;
+
+ mpack_log("===========================\n");
+ mpack_log("initializing tree with data of size %i\n", (int)length);
+}
+#endif
+
+void mpack_tree_init_pool(mpack_tree_t* tree, const char* data, size_t length,
+ mpack_node_data_t* node_pool, size_t node_pool_count)
+{
+ mpack_tree_init_clear(tree);
+ #ifdef MPACK_MALLOC
+ tree->next = NULL;
+ #endif
+
+ if (node_pool_count == 0) {
+ mpack_break("initial page has no nodes!");
+ mpack_tree_flag_error(tree, mpack_error_bug);
+ return;
+ }
+
+ tree->data = data;
+ tree->data_length = length;
+ tree->pool = node_pool;
+ tree->pool_count = node_pool_count;
+
+ mpack_log("===========================\n");
+ mpack_log("initializing tree with data of size %i and pool of count %i\n",
+ (int)length, (int)node_pool_count);
+}
+
+void mpack_tree_init_error(mpack_tree_t* tree, mpack_error_t error) {
+ mpack_tree_init_clear(tree);
+ tree->error = error;
+
+ mpack_log("===========================\n");
+ mpack_log("initializing tree error state %i\n", (int)error);
+}
+
+#ifdef MPACK_MALLOC
+void mpack_tree_init_stream(mpack_tree_t* tree, mpack_tree_read_t read_fn, void* context,
+ size_t max_message_size, size_t max_message_nodes) {
+ mpack_tree_init_clear(tree);
+
+ tree->read_fn = read_fn;
+ tree->context = context;
+
+ mpack_tree_set_limits(tree, max_message_size, max_message_nodes);
+ tree->max_size = max_message_size;
+ tree->max_nodes = max_message_nodes;
+
+ mpack_log("===========================\n");
+ mpack_log("initializing tree with stream, max size %i max nodes %i\n",
+ (int)max_message_size, (int)max_message_nodes);
+}
+#endif
+
+void mpack_tree_set_limits(mpack_tree_t* tree, size_t max_message_size, size_t max_message_nodes) {
+ mpack_assert(max_message_size > 0);
+ mpack_assert(max_message_nodes > 0);
+ tree->max_size = max_message_size;
+ tree->max_nodes = max_message_nodes;
+}
+
+#if MPACK_STDIO
+typedef struct mpack_file_tree_t {
+ char* data;
+ size_t size;
+ char buffer[MPACK_BUFFER_SIZE];
+} mpack_file_tree_t;
+
+static void mpack_file_tree_teardown(mpack_tree_t* tree) {
+ mpack_file_tree_t* file_tree = (mpack_file_tree_t*)tree->context;
+ MPACK_FREE(file_tree->data);
+ MPACK_FREE(file_tree);
+}
+
+static bool mpack_file_tree_read(mpack_tree_t* tree, mpack_file_tree_t* file_tree, FILE* file, size_t max_bytes) {
+
+ // get the file size
+ errno = 0;
+ int error = 0;
+ fseek(file, 0, SEEK_END);
+ error |= errno;
+ long size = ftell(file);
+ error |= errno;
+ fseek(file, 0, SEEK_SET);
+ error |= errno;
+
+ // check for errors
+ if (error != 0 || size < 0) {
+ mpack_tree_init_error(tree, mpack_error_io);
+ return false;
+ }
+ if (size == 0) {
+ mpack_tree_init_error(tree, mpack_error_invalid);
+ return false;
+ }
+
+ // make sure the size is less than max_bytes
+ // (this mess exists to safely convert between long and size_t regardless of their widths)
+ if (max_bytes != 0 && (((uint64_t)LONG_MAX > (uint64_t)SIZE_MAX && size > (long)SIZE_MAX) || (size_t)size > max_bytes)) {
+ mpack_tree_init_error(tree, mpack_error_too_big);
+ return false;
+ }
+
+ // allocate data
+ file_tree->data = (char*)MPACK_MALLOC((size_t)size);
+ if (file_tree->data == NULL) {
+ mpack_tree_init_error(tree, mpack_error_memory);
+ return false;
+ }
+
+ // read the file
+ long total = 0;
+ while (total < size) {
+ size_t read = fread(file_tree->data + total, 1, (size_t)(size - total), file);
+ if (read <= 0) {
+ mpack_tree_init_error(tree, mpack_error_io);
+ MPACK_FREE(file_tree->data);
+ return false;
+ }
+ total += (long)read;
+ }
+
+ file_tree->size = (size_t)size;
+ return true;
+}
+
+static bool mpack_tree_file_check_max_bytes(mpack_tree_t* tree, size_t max_bytes) {
+
+ // the C STDIO family of file functions use long (e.g. ftell)
+ if (max_bytes > LONG_MAX) {
+ mpack_break("max_bytes of %" PRIu64 " is invalid, maximum is LONG_MAX", (uint64_t)max_bytes);
+ mpack_tree_init_error(tree, mpack_error_bug);
+ return false;
+ }
+
+ return true;
+}
+
+static void mpack_tree_init_stdfile_noclose(mpack_tree_t* tree, FILE* stdfile, size_t max_bytes) {
+
+ // allocate file tree
+ mpack_file_tree_t* file_tree = (mpack_file_tree_t*) MPACK_MALLOC(sizeof(mpack_file_tree_t));
+ if (file_tree == NULL) {
+ mpack_tree_init_error(tree, mpack_error_memory);
+ return;
+ }
+
+ // read all data
+ if (!mpack_file_tree_read(tree, file_tree, stdfile, max_bytes)) {
+ MPACK_FREE(file_tree);
+ return;
+ }
+
+ mpack_tree_init_data(tree, file_tree->data, file_tree->size);
+ mpack_tree_set_context(tree, file_tree);
+ mpack_tree_set_teardown(tree, mpack_file_tree_teardown);
+}
+
+void mpack_tree_init_stdfile(mpack_tree_t* tree, FILE* stdfile, size_t max_bytes, bool close_when_done) {
+ if (!mpack_tree_file_check_max_bytes(tree, max_bytes))
+ return;
+
+ mpack_tree_init_stdfile_noclose(tree, stdfile, max_bytes);
+
+ if (close_when_done)
+ fclose(stdfile);
+}
+
+void mpack_tree_init_filename(mpack_tree_t* tree, const char* filename, size_t max_bytes) {
+ if (!mpack_tree_file_check_max_bytes(tree, max_bytes))
+ return;
+
+ // open the file
+ FILE* file = fopen(filename, "rb");
+ if (!file) {
+ mpack_tree_init_error(tree, mpack_error_io);
+ return;
+ }
+
+ mpack_tree_init_stdfile(tree, file, max_bytes, true);
+}
+#endif
+
+mpack_error_t mpack_tree_destroy(mpack_tree_t* tree) {
+ mpack_tree_cleanup(tree);
+
+ #ifdef MPACK_MALLOC
+ if (tree->buffer)
+ MPACK_FREE(tree->buffer);
+ #endif
+
+ if (tree->teardown)
+ tree->teardown(tree);
+ tree->teardown = NULL;
+
+ return tree->error;
+}
+
+void mpack_tree_flag_error(mpack_tree_t* tree, mpack_error_t error) {
+ if (tree->error == mpack_ok) {
+ mpack_log("tree %p setting error %i: %s\n", tree, (int)error, mpack_error_to_string(error));
+ tree->error = error;
+ if (tree->error_fn)
+ tree->error_fn(tree, error);
+ }
+
+}
+
+
+
+/*
+ * Node misc functions
+ */
+
+void mpack_node_flag_error(mpack_node_t node, mpack_error_t error) {
+ mpack_tree_flag_error(node.tree, error);
+}
+
+mpack_tag_t mpack_node_tag(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return mpack_tag_nil();
+
+ mpack_tag_t tag = MPACK_TAG_ZERO;
+
+ tag.type = node.data->type;
+ switch (node.data->type) {
+ case mpack_type_missing:
+ // If a node is missing, I don't know if it makes sense to ask for
+ // a tag for it. We'll return a missing tag to match the missing
+ // node I guess, but attempting to use the tag for anything (like
+ // writing it for example) will flag mpack_error_bug.
+ break;
+ case mpack_type_nil: break;
+ case mpack_type_bool: tag.v.b = node.data->value.b; break;
+ case mpack_type_float: tag.v.f = node.data->value.f; break;
+ case mpack_type_double: tag.v.d = node.data->value.d; break;
+ case mpack_type_int: tag.v.i = node.data->value.i; break;
+ case mpack_type_uint: tag.v.u = node.data->value.u; break;
+
+ case mpack_type_str: tag.v.l = node.data->len; break;
+ case mpack_type_bin: tag.v.l = node.data->len; break;
+
+ #if MPACK_EXTENSIONS
+ case mpack_type_ext:
+ tag.v.l = node.data->len;
+ tag.exttype = mpack_node_exttype_unchecked(node);
+ break;
+ #endif
+
+ case mpack_type_array: tag.v.n = node.data->len; break;
+ case mpack_type_map: tag.v.n = node.data->len; break;
+
+ default:
+ mpack_assert(0, "unrecognized type %i", (int)node.data->type);
+ break;
+ }
+ return tag;
+}
+
+#if MPACK_DEBUG && MPACK_STDIO
+static void mpack_node_print_element(mpack_node_t node, mpack_print_t* print, size_t depth) {
+ mpack_node_data_t* data = node.data;
+ switch (data->type) {
+ case mpack_type_str:
+ {
+ mpack_print_append_cstr(print, "\"");
+ const char* bytes = mpack_node_data_unchecked(node);
+ for (size_t i = 0; i < data->len; ++i) {
+ char c = bytes[i];
+ switch (c) {
+ case '\n': mpack_print_append_cstr(print, "\\n"); break;
+ case '\\': mpack_print_append_cstr(print, "\\\\"); break;
+ case '"': mpack_print_append_cstr(print, "\\\""); break;
+ default: mpack_print_append(print, &c, 1); break;
+ }
+ }
+ mpack_print_append_cstr(print, "\"");
+ }
+ break;
+
+ case mpack_type_array:
+ mpack_print_append_cstr(print, "[\n");
+ for (size_t i = 0; i < data->len; ++i) {
+ for (size_t j = 0; j < depth + 1; ++j)
+ mpack_print_append_cstr(print, " ");
+ mpack_node_print_element(mpack_node_array_at(node, i), print, depth + 1);
+ if (i != data->len - 1)
+ mpack_print_append_cstr(print, ",");
+ mpack_print_append_cstr(print, "\n");
+ }
+ for (size_t i = 0; i < depth; ++i)
+ mpack_print_append_cstr(print, " ");
+ mpack_print_append_cstr(print, "]");
+ break;
+
+ case mpack_type_map:
+ mpack_print_append_cstr(print, "{\n");
+ for (size_t i = 0; i < data->len; ++i) {
+ for (size_t j = 0; j < depth + 1; ++j)
+ mpack_print_append_cstr(print, " ");
+ mpack_node_print_element(mpack_node_map_key_at(node, i), print, depth + 1);
+ mpack_print_append_cstr(print, ": ");
+ mpack_node_print_element(mpack_node_map_value_at(node, i), print, depth + 1);
+ if (i != data->len - 1)
+ mpack_print_append_cstr(print, ",");
+ mpack_print_append_cstr(print, "\n");
+ }
+ for (size_t i = 0; i < depth; ++i)
+ mpack_print_append_cstr(print, " ");
+ mpack_print_append_cstr(print, "}");
+ break;
+
+ default:
+ {
+ const char* prefix = NULL;
+ size_t prefix_length = 0;
+ if (mpack_node_type(node) == mpack_type_bin
+ #if MPACK_EXTENSIONS
+ || mpack_node_type(node) == mpack_type_ext
+ #endif
+ ) {
+ prefix = mpack_node_data(node);
+ prefix_length = mpack_node_data_len(node);
+ }
+
+ char buf[256];
+ mpack_tag_t tag = mpack_node_tag(node);
+ mpack_tag_debug_pseudo_json(tag, buf, sizeof(buf), prefix, prefix_length);
+ mpack_print_append_cstr(print, buf);
+ }
+ break;
+ }
+}
+
+void mpack_node_print_to_buffer(mpack_node_t node, char* buffer, size_t buffer_size) {
+ if (buffer_size == 0) {
+ mpack_assert(false, "buffer size is zero!");
+ return;
+ }
+
+ mpack_print_t print;
+ mpack_memset(&print, 0, sizeof(print));
+ print.buffer = buffer;
+ print.size = buffer_size;
+ mpack_node_print_element(node, &print, 0);
+ mpack_print_append(&print, "", 1); // null-terminator
+ mpack_print_flush(&print);
+
+ // we always make sure there's a null-terminator at the end of the buffer
+ // in case we ran out of space.
+ print.buffer[print.size - 1] = '\0';
+}
+
+void mpack_node_print_to_callback(mpack_node_t node, mpack_print_callback_t callback, void* context) {
+ char buffer[1024];
+ mpack_print_t print;
+ mpack_memset(&print, 0, sizeof(print));
+ print.buffer = buffer;
+ print.size = sizeof(buffer);
+ print.callback = callback;
+ print.context = context;
+ mpack_node_print_element(node, &print, 0);
+ mpack_print_flush(&print);
+}
+
+void mpack_node_print_to_file(mpack_node_t node, FILE* file) {
+ mpack_assert(file != NULL, "file is NULL");
+
+ char buffer[1024];
+ mpack_print_t print;
+ mpack_memset(&print, 0, sizeof(print));
+ print.buffer = buffer;
+ print.size = sizeof(buffer);
+ print.callback = &mpack_print_file_callback;
+ print.context = file;
+
+ size_t depth = 2;
+ for (size_t i = 0; i < depth; ++i)
+ mpack_print_append_cstr(&print, " ");
+ mpack_node_print_element(node, &print, depth);
+ mpack_print_append_cstr(&print, "\n");
+ mpack_print_flush(&print);
+}
+#endif
+
+
+
+/*
+ * Node Value Functions
+ */
+
+#if MPACK_EXTENSIONS
+mpack_timestamp_t mpack_node_timestamp(mpack_node_t node) {
+ mpack_timestamp_t timestamp = {0, 0};
+
+ // we'll let mpack_node_exttype() do most checks
+ if (mpack_node_exttype(node) != MPACK_EXTTYPE_TIMESTAMP) {
+ mpack_log("exttype %i\n", mpack_node_exttype(node));
+ mpack_node_flag_error(node, mpack_error_type);
+ return timestamp;
+ }
+
+ const char* p = mpack_node_data_unchecked(node);
+
+ switch (node.data->len) {
+ case 4:
+ timestamp.nanoseconds = 0;
+ timestamp.seconds = mpack_load_u32(p);
+ break;
+
+ case 8: {
+ uint64_t value = mpack_load_u64(p);
+ timestamp.nanoseconds = (uint32_t)(value >> 34);
+ timestamp.seconds = value & ((UINT64_C(1) << 34) - 1);
+ break;
+ }
+
+ case 12:
+ timestamp.nanoseconds = mpack_load_u32(p);
+ timestamp.seconds = mpack_load_i64(p + 4);
+ break;
+
+ default:
+ mpack_tree_flag_error(node.tree, mpack_error_invalid);
+ return timestamp;
+ }
+
+ if (timestamp.nanoseconds > MPACK_TIMESTAMP_NANOSECONDS_MAX) {
+ mpack_tree_flag_error(node.tree, mpack_error_invalid);
+ mpack_timestamp_t zero = {0, 0};
+ return zero;
+ }
+
+ return timestamp;
+}
+
+int64_t mpack_node_timestamp_seconds(mpack_node_t node) {
+ return mpack_node_timestamp(node).seconds;
+}
+
+uint32_t mpack_node_timestamp_nanoseconds(mpack_node_t node) {
+ return mpack_node_timestamp(node).nanoseconds;
+}
+#endif
+
+
+
+/*
+ * Node Data Functions
+ */
+
+void mpack_node_check_utf8(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return;
+ mpack_node_data_t* data = node.data;
+ if (data->type != mpack_type_str || !mpack_utf8_check(mpack_node_data_unchecked(node), data->len))
+ mpack_node_flag_error(node, mpack_error_type);
+}
+
+void mpack_node_check_utf8_cstr(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return;
+ mpack_node_data_t* data = node.data;
+ if (data->type != mpack_type_str || !mpack_utf8_check_no_null(mpack_node_data_unchecked(node), data->len))
+ mpack_node_flag_error(node, mpack_error_type);
+}
+
+size_t mpack_node_copy_data(mpack_node_t node, char* buffer, size_t bufsize) {
+ if (mpack_node_error(node) != mpack_ok)
+ return 0;
+
+ mpack_assert(bufsize == 0 || buffer != NULL, "buffer is NULL for maximum of %i bytes", (int)bufsize);
+
+ mpack_type_t type = node.data->type;
+ if (type != mpack_type_str && type != mpack_type_bin
+ #if MPACK_EXTENSIONS
+ && type != mpack_type_ext
+ #endif
+ ) {
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0;
+ }
+
+ if (node.data->len > bufsize) {
+ mpack_node_flag_error(node, mpack_error_too_big);
+ return 0;
+ }
+
+ mpack_memcpy(buffer, mpack_node_data_unchecked(node), node.data->len);
+ return (size_t)node.data->len;
+}
+
+size_t mpack_node_copy_utf8(mpack_node_t node, char* buffer, size_t bufsize) {
+ if (mpack_node_error(node) != mpack_ok)
+ return 0;
+
+ mpack_assert(bufsize == 0 || buffer != NULL, "buffer is NULL for maximum of %i bytes", (int)bufsize);
+
+ mpack_type_t type = node.data->type;
+ if (type != mpack_type_str) {
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0;
+ }
+
+ if (node.data->len > bufsize) {
+ mpack_node_flag_error(node, mpack_error_too_big);
+ return 0;
+ }
+
+ if (!mpack_utf8_check(mpack_node_data_unchecked(node), node.data->len)) {
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0;
+ }
+
+ mpack_memcpy(buffer, mpack_node_data_unchecked(node), node.data->len);
+ return (size_t)node.data->len;
+}
+
+void mpack_node_copy_cstr(mpack_node_t node, char* buffer, size_t bufsize) {
+
+ // we can't break here because the error isn't recoverable; we
+ // have to add a null-terminator.
+ mpack_assert(buffer != NULL, "buffer is NULL");
+ mpack_assert(bufsize >= 1, "buffer size is zero; you must have room for at least a null-terminator");
+
+ if (mpack_node_error(node) != mpack_ok) {
+ buffer[0] = '\0';
+ return;
+ }
+
+ if (node.data->type != mpack_type_str) {
+ buffer[0] = '\0';
+ mpack_node_flag_error(node, mpack_error_type);
+ return;
+ }
+
+ if (node.data->len > bufsize - 1) {
+ buffer[0] = '\0';
+ mpack_node_flag_error(node, mpack_error_too_big);
+ return;
+ }
+
+ if (!mpack_str_check_no_null(mpack_node_data_unchecked(node), node.data->len)) {
+ buffer[0] = '\0';
+ mpack_node_flag_error(node, mpack_error_type);
+ return;
+ }
+
+ mpack_memcpy(buffer, mpack_node_data_unchecked(node), node.data->len);
+ buffer[node.data->len] = '\0';
+}
+
+void mpack_node_copy_utf8_cstr(mpack_node_t node, char* buffer, size_t bufsize) {
+
+ // we can't break here because the error isn't recoverable; we
+ // have to add a null-terminator.
+ mpack_assert(buffer != NULL, "buffer is NULL");
+ mpack_assert(bufsize >= 1, "buffer size is zero; you must have room for at least a null-terminator");
+
+ if (mpack_node_error(node) != mpack_ok) {
+ buffer[0] = '\0';
+ return;
+ }
+
+ if (node.data->type != mpack_type_str) {
+ buffer[0] = '\0';
+ mpack_node_flag_error(node, mpack_error_type);
+ return;
+ }
+
+ if (node.data->len > bufsize - 1) {
+ buffer[0] = '\0';
+ mpack_node_flag_error(node, mpack_error_too_big);
+ return;
+ }
+
+ if (!mpack_utf8_check_no_null(mpack_node_data_unchecked(node), node.data->len)) {
+ buffer[0] = '\0';
+ mpack_node_flag_error(node, mpack_error_type);
+ return;
+ }
+
+ mpack_memcpy(buffer, mpack_node_data_unchecked(node), node.data->len);
+ buffer[node.data->len] = '\0';
+}
+
+#ifdef MPACK_MALLOC
+char* mpack_node_data_alloc(mpack_node_t node, size_t maxlen) {
+ if (mpack_node_error(node) != mpack_ok)
+ return NULL;
+
+ // make sure this is a valid data type
+ mpack_type_t type = node.data->type;
+ if (type != mpack_type_str && type != mpack_type_bin
+ #if MPACK_EXTENSIONS
+ && type != mpack_type_ext
+ #endif
+ ) {
+ mpack_node_flag_error(node, mpack_error_type);
+ return NULL;
+ }
+
+ if (node.data->len > maxlen) {
+ mpack_node_flag_error(node, mpack_error_too_big);
+ return NULL;
+ }
+
+ char* ret = (char*) MPACK_MALLOC((size_t)node.data->len);
+ if (ret == NULL) {
+ mpack_node_flag_error(node, mpack_error_memory);
+ return NULL;
+ }
+
+ mpack_memcpy(ret, mpack_node_data_unchecked(node), node.data->len);
+ return ret;
+}
+
+char* mpack_node_cstr_alloc(mpack_node_t node, size_t maxlen) {
+ if (mpack_node_error(node) != mpack_ok)
+ return NULL;
+
+ // make sure maxlen makes sense
+ if (maxlen < 1) {
+ mpack_break("maxlen is zero; you must have room for at least a null-terminator");
+ mpack_node_flag_error(node, mpack_error_bug);
+ return NULL;
+ }
+
+ if (node.data->type != mpack_type_str) {
+ mpack_node_flag_error(node, mpack_error_type);
+ return NULL;
+ }
+
+ if (node.data->len > maxlen - 1) {
+ mpack_node_flag_error(node, mpack_error_too_big);
+ return NULL;
+ }
+
+ if (!mpack_str_check_no_null(mpack_node_data_unchecked(node), node.data->len)) {
+ mpack_node_flag_error(node, mpack_error_type);
+ return NULL;
+ }
+
+ char* ret = (char*) MPACK_MALLOC((size_t)(node.data->len + 1));
+ if (ret == NULL) {
+ mpack_node_flag_error(node, mpack_error_memory);
+ return NULL;
+ }
+
+ mpack_memcpy(ret, mpack_node_data_unchecked(node), node.data->len);
+ ret[node.data->len] = '\0';
+ return ret;
+}
+
+char* mpack_node_utf8_cstr_alloc(mpack_node_t node, size_t maxlen) {
+ if (mpack_node_error(node) != mpack_ok)
+ return NULL;
+
+ // make sure maxlen makes sense
+ if (maxlen < 1) {
+ mpack_break("maxlen is zero; you must have room for at least a null-terminator");
+ mpack_node_flag_error(node, mpack_error_bug);
+ return NULL;
+ }
+
+ if (node.data->type != mpack_type_str) {
+ mpack_node_flag_error(node, mpack_error_type);
+ return NULL;
+ }
+
+ if (node.data->len > maxlen - 1) {
+ mpack_node_flag_error(node, mpack_error_too_big);
+ return NULL;
+ }
+
+ if (!mpack_utf8_check_no_null(mpack_node_data_unchecked(node), node.data->len)) {
+ mpack_node_flag_error(node, mpack_error_type);
+ return NULL;
+ }
+
+ char* ret = (char*) MPACK_MALLOC((size_t)(node.data->len + 1));
+ if (ret == NULL) {
+ mpack_node_flag_error(node, mpack_error_memory);
+ return NULL;
+ }
+
+ mpack_memcpy(ret, mpack_node_data_unchecked(node), node.data->len);
+ ret[node.data->len] = '\0';
+ return ret;
+}
+#endif
+
+
+/*
+ * Compound Node Functions
+ */
+
+static mpack_node_data_t* mpack_node_map_int_impl(mpack_node_t node, int64_t num) {
+ if (mpack_node_error(node) != mpack_ok)
+ return NULL;
+
+ if (node.data->type != mpack_type_map) {
+ mpack_node_flag_error(node, mpack_error_type);
+ return NULL;
+ }
+
+ mpack_node_data_t* found = NULL;
+
+ for (size_t i = 0; i < node.data->len; ++i) {
+ mpack_node_data_t* key = mpack_node_child(node, i * 2);
+
+ if ((key->type == mpack_type_int && key->value.i == num) ||
+ (key->type == mpack_type_uint && num >= 0 && key->value.u == (uint64_t)num))
+ {
+ if (found) {
+ mpack_node_flag_error(node, mpack_error_data);
+ return NULL;
+ }
+ found = mpack_node_child(node, i * 2 + 1);
+ }
+ }
+
+ if (found)
+ return found;
+
+ return NULL;
+}
+
+static mpack_node_data_t* mpack_node_map_uint_impl(mpack_node_t node, uint64_t num) {
+ if (mpack_node_error(node) != mpack_ok)
+ return NULL;
+
+ if (node.data->type != mpack_type_map) {
+ mpack_node_flag_error(node, mpack_error_type);
+ return NULL;
+ }
+
+ mpack_node_data_t* found = NULL;
+
+ for (size_t i = 0; i < node.data->len; ++i) {
+ mpack_node_data_t* key = mpack_node_child(node, i * 2);
+
+ if ((key->type == mpack_type_uint && key->value.u == num) ||
+ (key->type == mpack_type_int && key->value.i >= 0 && (uint64_t)key->value.i == num))
+ {
+ if (found) {
+ mpack_node_flag_error(node, mpack_error_data);
+ return NULL;
+ }
+ found = mpack_node_child(node, i * 2 + 1);
+ }
+ }
+
+ if (found)
+ return found;
+
+ return NULL;
+}
+
+static mpack_node_data_t* mpack_node_map_str_impl(mpack_node_t node, const char* str, size_t length) {
+ if (mpack_node_error(node) != mpack_ok)
+ return NULL;
+
+ mpack_assert(length == 0 || str != NULL, "str of length %i is NULL", (int)length);
+
+ if (node.data->type != mpack_type_map) {
+ mpack_node_flag_error(node, mpack_error_type);
+ return NULL;
+ }
+
+ mpack_tree_t* tree = node.tree;
+ mpack_node_data_t* found = NULL;
+
+ for (size_t i = 0; i < node.data->len; ++i) {
+ mpack_node_data_t* key = mpack_node_child(node, i * 2);
+
+ if (key->type == mpack_type_str && key->len == length &&
+ mpack_memcmp(str, mpack_node_data_unchecked(mpack_node(tree, key)), length) == 0) {
+ if (found) {
+ mpack_node_flag_error(node, mpack_error_data);
+ return NULL;
+ }
+ found = mpack_node_child(node, i * 2 + 1);
+ }
+ }
+
+ if (found)
+ return found;
+
+ return NULL;
+}
+
+static mpack_node_t mpack_node_wrap_lookup(mpack_tree_t* tree, mpack_node_data_t* data) {
+ if (!data) {
+ if (tree->error == mpack_ok)
+ mpack_tree_flag_error(tree, mpack_error_data);
+ return mpack_tree_nil_node(tree);
+ }
+ return mpack_node(tree, data);
+}
+
+static mpack_node_t mpack_node_wrap_lookup_optional(mpack_tree_t* tree, mpack_node_data_t* data) {
+ if (!data) {
+ if (tree->error == mpack_ok)
+ return mpack_tree_missing_node(tree);
+ return mpack_tree_nil_node(tree);
+ }
+ return mpack_node(tree, data);
+}
+
+mpack_node_t mpack_node_map_int(mpack_node_t node, int64_t num) {
+ return mpack_node_wrap_lookup(node.tree, mpack_node_map_int_impl(node, num));
+}
+
+mpack_node_t mpack_node_map_int_optional(mpack_node_t node, int64_t num) {
+ return mpack_node_wrap_lookup_optional(node.tree, mpack_node_map_int_impl(node, num));
+}
+
+mpack_node_t mpack_node_map_uint(mpack_node_t node, uint64_t num) {
+ return mpack_node_wrap_lookup(node.tree, mpack_node_map_uint_impl(node, num));
+}
+
+mpack_node_t mpack_node_map_uint_optional(mpack_node_t node, uint64_t num) {
+ return mpack_node_wrap_lookup_optional(node.tree, mpack_node_map_uint_impl(node, num));
+}
+
+mpack_node_t mpack_node_map_str(mpack_node_t node, const char* str, size_t length) {
+ return mpack_node_wrap_lookup(node.tree, mpack_node_map_str_impl(node, str, length));
+}
+
+mpack_node_t mpack_node_map_str_optional(mpack_node_t node, const char* str, size_t length) {
+ return mpack_node_wrap_lookup_optional(node.tree, mpack_node_map_str_impl(node, str, length));
+}
+
+mpack_node_t mpack_node_map_cstr(mpack_node_t node, const char* cstr) {
+ mpack_assert(cstr != NULL, "cstr is NULL");
+ return mpack_node_map_str(node, cstr, mpack_strlen(cstr));
+}
+
+mpack_node_t mpack_node_map_cstr_optional(mpack_node_t node, const char* cstr) {
+ mpack_assert(cstr != NULL, "cstr is NULL");
+ return mpack_node_map_str_optional(node, cstr, mpack_strlen(cstr));
+}
+
+bool mpack_node_map_contains_int(mpack_node_t node, int64_t num) {
+ return mpack_node_map_int_impl(node, num) != NULL;
+}
+
+bool mpack_node_map_contains_uint(mpack_node_t node, uint64_t num) {
+ return mpack_node_map_uint_impl(node, num) != NULL;
+}
+
+bool mpack_node_map_contains_str(mpack_node_t node, const char* str, size_t length) {
+ return mpack_node_map_str_impl(node, str, length) != NULL;
+}
+
+bool mpack_node_map_contains_cstr(mpack_node_t node, const char* cstr) {
+ mpack_assert(cstr != NULL, "cstr is NULL");
+ return mpack_node_map_contains_str(node, cstr, mpack_strlen(cstr));
+}
+
+size_t mpack_node_enum_optional(mpack_node_t node, const char* strings[], size_t count) {
+ if (mpack_node_error(node) != mpack_ok)
+ return count;
+
+ // the value is only recognized if it is a string
+ if (mpack_node_type(node) != mpack_type_str)
+ return count;
+
+ // fetch the string
+ const char* key = mpack_node_str(node);
+ size_t keylen = mpack_node_strlen(node);
+ mpack_assert(mpack_node_error(node) == mpack_ok, "these should not fail");
+
+ // find what key it matches
+ for (size_t i = 0; i < count; ++i) {
+ const char* other = strings[i];
+ size_t otherlen = mpack_strlen(other);
+ if (keylen == otherlen && mpack_memcmp(key, other, keylen) == 0)
+ return i;
+ }
+
+ // no matches
+ return count;
+}
+
+size_t mpack_node_enum(mpack_node_t node, const char* strings[], size_t count) {
+ size_t value = mpack_node_enum_optional(node, strings, count);
+ if (value == count)
+ mpack_node_flag_error(node, mpack_error_type);
+ return value;
+}
+
+mpack_type_t mpack_node_type(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return mpack_type_nil;
+ return node.data->type;
+}
+
+bool mpack_node_is_nil(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok) {
+ // All nodes are treated as nil nodes when we are in error.
+ return true;
+ }
+ return node.data->type == mpack_type_nil;
+}
+
+bool mpack_node_is_missing(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok) {
+ // errors still return nil nodes, not missing nodes.
+ return false;
+ }
+ return node.data->type == mpack_type_missing;
+}
+
+void mpack_node_nil(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return;
+ if (node.data->type != mpack_type_nil)
+ mpack_node_flag_error(node, mpack_error_type);
+}
+
+void mpack_node_missing(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return;
+ if (node.data->type != mpack_type_missing)
+ mpack_node_flag_error(node, mpack_error_type);
+}
+
+bool mpack_node_bool(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return false;
+
+ if (node.data->type == mpack_type_bool)
+ return node.data->value.b;
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return false;
+}
+
+void mpack_node_true(mpack_node_t node) {
+ if (mpack_node_bool(node) != true)
+ mpack_node_flag_error(node, mpack_error_type);
+}
+
+void mpack_node_false(mpack_node_t node) {
+ if (mpack_node_bool(node) != false)
+ mpack_node_flag_error(node, mpack_error_type);
+}
+
+uint8_t mpack_node_u8(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return 0;
+
+ if (node.data->type == mpack_type_uint) {
+ if (node.data->value.u <= UINT8_MAX)
+ return (uint8_t)node.data->value.u;
+ } else if (node.data->type == mpack_type_int) {
+ if (node.data->value.i >= 0 && node.data->value.i <= UINT8_MAX)
+ return (uint8_t)node.data->value.i;
+ }
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0;
+}
+
+int8_t mpack_node_i8(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return 0;
+
+ if (node.data->type == mpack_type_uint) {
+ if (node.data->value.u <= INT8_MAX)
+ return (int8_t)node.data->value.u;
+ } else if (node.data->type == mpack_type_int) {
+ if (node.data->value.i >= INT8_MIN && node.data->value.i <= INT8_MAX)
+ return (int8_t)node.data->value.i;
+ }
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0;
+}
+
+uint16_t mpack_node_u16(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return 0;
+
+ if (node.data->type == mpack_type_uint) {
+ if (node.data->value.u <= UINT16_MAX)
+ return (uint16_t)node.data->value.u;
+ } else if (node.data->type == mpack_type_int) {
+ if (node.data->value.i >= 0 && node.data->value.i <= UINT16_MAX)
+ return (uint16_t)node.data->value.i;
+ }
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0;
+}
+
+int16_t mpack_node_i16(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return 0;
+
+ if (node.data->type == mpack_type_uint) {
+ if (node.data->value.u <= INT16_MAX)
+ return (int16_t)node.data->value.u;
+ } else if (node.data->type == mpack_type_int) {
+ if (node.data->value.i >= INT16_MIN && node.data->value.i <= INT16_MAX)
+ return (int16_t)node.data->value.i;
+ }
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0;
+}
+
+uint32_t mpack_node_u32(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return 0;
+
+ if (node.data->type == mpack_type_uint) {
+ if (node.data->value.u <= UINT32_MAX)
+ return (uint32_t)node.data->value.u;
+ } else if (node.data->type == mpack_type_int) {
+ if (node.data->value.i >= 0 && node.data->value.i <= UINT32_MAX)
+ return (uint32_t)node.data->value.i;
+ }
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0;
+}
+
+int32_t mpack_node_i32(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return 0;
+
+ if (node.data->type == mpack_type_uint) {
+ if (node.data->value.u <= INT32_MAX)
+ return (int32_t)node.data->value.u;
+ } else if (node.data->type == mpack_type_int) {
+ if (node.data->value.i >= INT32_MIN && node.data->value.i <= INT32_MAX)
+ return (int32_t)node.data->value.i;
+ }
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0;
+}
+
+uint64_t mpack_node_u64(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return 0;
+
+ if (node.data->type == mpack_type_uint) {
+ return node.data->value.u;
+ } else if (node.data->type == mpack_type_int) {
+ if (node.data->value.i >= 0)
+ return (uint64_t)node.data->value.i;
+ }
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0;
+}
+
+int64_t mpack_node_i64(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return 0;
+
+ if (node.data->type == mpack_type_uint) {
+ if (node.data->value.u <= (uint64_t)INT64_MAX)
+ return (int64_t)node.data->value.u;
+ } else if (node.data->type == mpack_type_int) {
+ return node.data->value.i;
+ }
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0;
+}
+
+unsigned int mpack_node_uint(mpack_node_t node) {
+
+ // This should be true at compile-time, so this just wraps the 32-bit function.
+ if (sizeof(unsigned int) == 4)
+ return (unsigned int)mpack_node_u32(node);
+
+ // Otherwise we use u64 and check the range.
+ uint64_t val = mpack_node_u64(node);
+ if (val <= UINT_MAX)
+ return (unsigned int)val;
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0;
+}
+
+int mpack_node_int(mpack_node_t node) {
+
+ // This should be true at compile-time, so this just wraps the 32-bit function.
+ if (sizeof(int) == 4)
+ return (int)mpack_node_i32(node);
+
+ // Otherwise we use i64 and check the range.
+ int64_t val = mpack_node_i64(node);
+ if (val >= INT_MIN && val <= INT_MAX)
+ return (int)val;
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0;
+}
+
+float mpack_node_float(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return 0.0f;
+
+ if (node.data->type == mpack_type_uint)
+ return (float)node.data->value.u;
+ else if (node.data->type == mpack_type_int)
+ return (float)node.data->value.i;
+ else if (node.data->type == mpack_type_float)
+ return node.data->value.f;
+ else if (node.data->type == mpack_type_double)
+ return (float)node.data->value.d;
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0.0f;
+}
+
+double mpack_node_double(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return 0.0;
+
+ if (node.data->type == mpack_type_uint)
+ return (double)node.data->value.u;
+ else if (node.data->type == mpack_type_int)
+ return (double)node.data->value.i;
+ else if (node.data->type == mpack_type_float)
+ return (double)node.data->value.f;
+ else if (node.data->type == mpack_type_double)
+ return node.data->value.d;
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0.0;
+}
+
+float mpack_node_float_strict(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return 0.0f;
+
+ if (node.data->type == mpack_type_float)
+ return node.data->value.f;
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0.0f;
+}
+
+double mpack_node_double_strict(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return 0.0;
+
+ if (node.data->type == mpack_type_float)
+ return (double)node.data->value.f;
+ else if (node.data->type == mpack_type_double)
+ return node.data->value.d;
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0.0;
+}
+
+#if MPACK_EXTENSIONS
+int8_t mpack_node_exttype(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return 0;
+
+ if (node.data->type == mpack_type_ext)
+ return mpack_node_exttype_unchecked(node);
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0;
+}
+#endif
+
+uint32_t mpack_node_data_len(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return 0;
+
+ mpack_type_t type = node.data->type;
+ if (type == mpack_type_str || type == mpack_type_bin
+ #if MPACK_EXTENSIONS
+ || type == mpack_type_ext
+ #endif
+ )
+ return (uint32_t)node.data->len;
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0;
+}
+
+size_t mpack_node_strlen(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return 0;
+
+ if (node.data->type == mpack_type_str)
+ return (size_t)node.data->len;
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0;
+}
+
+const char* mpack_node_str(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return NULL;
+
+ mpack_type_t type = node.data->type;
+ if (type == mpack_type_str)
+ return mpack_node_data_unchecked(node);
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return NULL;
+}
+
+const char* mpack_node_data(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return NULL;
+
+ mpack_type_t type = node.data->type;
+ if (type == mpack_type_str || type == mpack_type_bin
+ #if MPACK_EXTENSIONS
+ || type == mpack_type_ext
+ #endif
+ )
+ return mpack_node_data_unchecked(node);
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return NULL;
+}
+
+const char* mpack_node_bin_data(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return NULL;
+
+ if (node.data->type == mpack_type_bin)
+ return mpack_node_data_unchecked(node);
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return NULL;
+}
+
+size_t mpack_node_bin_size(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return 0;
+
+ if (node.data->type == mpack_type_bin)
+ return (size_t)node.data->len;
+
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0;
+}
+
+size_t mpack_node_array_length(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return 0;
+
+ if (node.data->type != mpack_type_array) {
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0;
+ }
+
+ return (size_t)node.data->len;
+}
+
+mpack_node_t mpack_node_array_at(mpack_node_t node, size_t index) {
+ if (mpack_node_error(node) != mpack_ok)
+ return mpack_tree_nil_node(node.tree);
+
+ if (node.data->type != mpack_type_array) {
+ mpack_node_flag_error(node, mpack_error_type);
+ return mpack_tree_nil_node(node.tree);
+ }
+
+ if (index >= node.data->len) {
+ mpack_node_flag_error(node, mpack_error_data);
+ return mpack_tree_nil_node(node.tree);
+ }
+
+ return mpack_node(node.tree, mpack_node_child(node, index));
+}
+
+size_t mpack_node_map_count(mpack_node_t node) {
+ if (mpack_node_error(node) != mpack_ok)
+ return 0;
+
+ if (node.data->type != mpack_type_map) {
+ mpack_node_flag_error(node, mpack_error_type);
+ return 0;
+ }
+
+ return node.data->len;
+}
+
+// internal node map lookup
+static mpack_node_t mpack_node_map_at(mpack_node_t node, size_t index, size_t offset) {
+ if (mpack_node_error(node) != mpack_ok)
+ return mpack_tree_nil_node(node.tree);
+
+ if (node.data->type != mpack_type_map) {
+ mpack_node_flag_error(node, mpack_error_type);
+ return mpack_tree_nil_node(node.tree);
+ }
+
+ if (index >= node.data->len) {
+ mpack_node_flag_error(node, mpack_error_data);
+ return mpack_tree_nil_node(node.tree);
+ }
+
+ return mpack_node(node.tree, mpack_node_child(node, index * 2 + offset));
+}
+
+mpack_node_t mpack_node_map_key_at(mpack_node_t node, size_t index) {
+ return mpack_node_map_at(node, index, 0);
+}
+
+mpack_node_t mpack_node_map_value_at(mpack_node_t node, size_t index) {
+ return mpack_node_map_at(node, index, 1);
+}
+
+#endif
diff --git a/src/lib/mpmalloc.cc b/src/lib/mpmalloc.cc
new file mode 100644
index 0000000..2483a3a
--- /dev/null
+++ b/src/lib/mpmalloc.cc
@@ -0,0 +1,42 @@
+#include <stdlib.h>
+#include "driver/stdout.h"
+#include "lib/mpmalloc.h"
+
+void* mpcalloc(size_t nmemb, size_t size)
+{
+ void* ret = calloc(nmemb, size);
+#ifdef ADDR_20BIT
+ kout << "calloc:" << dec << (uint32_t)nmemb << "x" << (uint32_t)size << "@" << ret << endl;
+#else
+ kout << "calloc:" << dec << nmemb << "x" << size << "@" << ret << endl;
+#endif
+ return ret;
+}
+
+void* mpmalloc(size_t size)
+{
+ void* ret = malloc(size);
+#ifdef ADDR_20BIT
+ kout << "malloc:" << dec << (uint32_t)size << "@" << ret << endl;
+#else
+ kout << "malloc:" << dec << size << "@" << ret << endl;
+#endif
+ return ret;
+}
+
+void* mprealloc(void* addr, size_t size)
+{
+ void* ret = realloc(addr, size);
+#ifdef ADDR_20BIT
+ kout << "realloc:" << addr << ":" << dec << (uint32_t)size << "@" << ret << endl;
+#else
+ kout << "realloc:" << addr << ":" << dec << size << "@" << ret << endl;
+#endif
+ return ret;
+}
+
+void mpfree(void* addr)
+{
+ kout << "free:" << addr << endl;
+ free(addr);
+}
diff --git a/src/lib/nanopb/pb_common.cc b/src/lib/nanopb/pb_common.cc
new file mode 100644
index 0000000..4fb7186
--- /dev/null
+++ b/src/lib/nanopb/pb_common.cc
@@ -0,0 +1,97 @@
+/* pb_common.c: Common support functions for pb_encode.c and pb_decode.c.
+ *
+ * 2014 Petteri Aimonen <jpa@kapsi.fi>
+ */
+
+#include "pb_common.h"
+
+bool pb_field_iter_begin(pb_field_iter_t *iter, const pb_field_t *fields, void *dest_struct)
+{
+ iter->start = fields;
+ iter->pos = fields;
+ iter->required_field_index = 0;
+ iter->dest_struct = dest_struct;
+ iter->pData = (char*)dest_struct + iter->pos->data_offset;
+ iter->pSize = (char*)iter->pData + iter->pos->size_offset;
+
+ return (iter->pos->tag != 0);
+}
+
+bool pb_field_iter_next(pb_field_iter_t *iter)
+{
+ const pb_field_t *prev_field = iter->pos;
+
+ if (prev_field->tag == 0)
+ {
+ /* Handle empty message types, where the first field is already the terminator.
+ * In other cases, the iter->pos never points to the terminator. */
+ return false;
+ }
+
+ iter->pos++;
+
+ if (iter->pos->tag == 0)
+ {
+ /* Wrapped back to beginning, reinitialize */
+ (void)pb_field_iter_begin(iter, iter->start, iter->dest_struct);
+ return false;
+ }
+ else
+ {
+ /* Increment the pointers based on previous field size */
+ size_t prev_size = prev_field->data_size;
+
+ if (PB_HTYPE(prev_field->type) == PB_HTYPE_ONEOF &&
+ PB_HTYPE(iter->pos->type) == PB_HTYPE_ONEOF &&
+ iter->pos->data_offset == PB_SIZE_MAX)
+ {
+ /* Don't advance pointers inside unions */
+ return true;
+ }
+ else if (PB_ATYPE(prev_field->type) == PB_ATYPE_STATIC &&
+ PB_HTYPE(prev_field->type) == PB_HTYPE_REPEATED)
+ {
+ /* In static arrays, the data_size tells the size of a single entry and
+ * array_size is the number of entries */
+ prev_size *= prev_field->array_size;
+ }
+ else if (PB_ATYPE(prev_field->type) == PB_ATYPE_POINTER)
+ {
+ /* Pointer fields always have a constant size in the main structure.
+ * The data_size only applies to the dynamically allocated area. */
+ prev_size = sizeof(void*);
+ }
+
+ if (PB_HTYPE(prev_field->type) == PB_HTYPE_REQUIRED)
+ {
+ /* Count the required fields, in order to check their presence in the
+ * decoder. */
+ iter->required_field_index++;
+ }
+
+ iter->pData = (char*)iter->pData + prev_size + iter->pos->data_offset;
+ iter->pSize = (char*)iter->pData + iter->pos->size_offset;
+ return true;
+ }
+}
+
+bool pb_field_iter_find(pb_field_iter_t *iter, uint32_t tag)
+{
+ const pb_field_t *start = iter->pos;
+
+ do {
+ if (iter->pos->tag == tag &&
+ PB_LTYPE(iter->pos->type) != PB_LTYPE_EXTENSION)
+ {
+ /* Found the wanted field */
+ return true;
+ }
+
+ (void)pb_field_iter_next(iter);
+ } while (iter->pos != start);
+
+ /* Searched all the way back to start, and found nothing. */
+ return false;
+}
+
+
diff --git a/src/lib/nanopb/pb_decode.cc b/src/lib/nanopb/pb_decode.cc
new file mode 100644
index 0000000..d08259c
--- /dev/null
+++ b/src/lib/nanopb/pb_decode.cc
@@ -0,0 +1,1528 @@
+/* pb_decode.c -- decode a protobuf using minimal resources
+ *
+ * 2011 Petteri Aimonen <jpa@kapsi.fi>
+ */
+
+/* Use the GCC warn_unused_result attribute to check that all return values
+ * are propagated correctly. On other compilers and gcc before 3.4.0 just
+ * ignore the annotation.
+ */
+#if !defined(__GNUC__) || ( __GNUC__ < 3) || (__GNUC__ == 3 && __GNUC_MINOR__ < 4)
+ #define checkreturn
+#else
+ #define checkreturn __attribute__((warn_unused_result))
+#endif
+
+#include "pb.h"
+#include "pb_decode.h"
+#include "pb_common.h"
+
+/**************************************
+ * Declarations internal to this file *
+ **************************************/
+
+typedef bool (*pb_decoder_t)(pb_istream_t *stream, const pb_field_t *field, void *dest) checkreturn;
+
+static bool checkreturn buf_read(pb_istream_t *stream, pb_byte_t *buf, size_t count);
+static bool checkreturn read_raw_value(pb_istream_t *stream, pb_wire_type_t wire_type, pb_byte_t *buf, size_t *size);
+static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *iter);
+static bool checkreturn decode_callback_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *iter);
+static bool checkreturn decode_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *iter);
+static void iter_from_extension(pb_field_iter_t *iter, pb_extension_t *extension);
+static bool checkreturn default_extension_decoder(pb_istream_t *stream, pb_extension_t *extension, uint32_t tag, pb_wire_type_t wire_type);
+static bool checkreturn decode_extension(pb_istream_t *stream, uint32_t tag, pb_wire_type_t wire_type, pb_field_iter_t *iter);
+static bool checkreturn find_extension_field(pb_field_iter_t *iter);
+static void pb_field_set_to_default(pb_field_iter_t *iter);
+static void pb_message_set_to_defaults(const pb_field_t fields[], void *dest_struct);
+static bool checkreturn pb_dec_varint(pb_istream_t *stream, const pb_field_t *field, void *dest);
+static bool checkreturn pb_decode_varint32_eof(pb_istream_t *stream, uint32_t *dest, bool *eof);
+static bool checkreturn pb_dec_uvarint(pb_istream_t *stream, const pb_field_t *field, void *dest);
+static bool checkreturn pb_dec_svarint(pb_istream_t *stream, const pb_field_t *field, void *dest);
+static bool checkreturn pb_dec_fixed32(pb_istream_t *stream, const pb_field_t *field, void *dest);
+static bool checkreturn pb_dec_fixed64(pb_istream_t *stream, const pb_field_t *field, void *dest);
+static bool checkreturn pb_dec_bytes(pb_istream_t *stream, const pb_field_t *field, void *dest);
+static bool checkreturn pb_dec_string(pb_istream_t *stream, const pb_field_t *field, void *dest);
+static bool checkreturn pb_dec_submessage(pb_istream_t *stream, const pb_field_t *field, void *dest);
+static bool checkreturn pb_dec_fixed_length_bytes(pb_istream_t *stream, const pb_field_t *field, void *dest);
+static bool checkreturn pb_skip_varint(pb_istream_t *stream);
+static bool checkreturn pb_skip_string(pb_istream_t *stream);
+
+#ifdef PB_ENABLE_MALLOC
+static bool checkreturn allocate_field(pb_istream_t *stream, void *pData, size_t data_size, size_t array_size);
+static bool checkreturn pb_release_union_field(pb_istream_t *stream, pb_field_iter_t *iter);
+static void pb_release_single_field(const pb_field_iter_t *iter);
+#endif
+
+#ifdef PB_WITHOUT_64BIT
+#define pb_int64_t int32_t
+#define pb_uint64_t uint32_t
+#else
+#define pb_int64_t int64_t
+#define pb_uint64_t uint64_t
+#endif
+
+/* --- Function pointers to field decoders ---
+ * Order in the array must match pb_action_t LTYPE numbering.
+ */
+static const pb_decoder_t PB_DECODERS[PB_LTYPES_COUNT] = {
+ &pb_dec_varint,
+ &pb_dec_uvarint,
+ &pb_dec_svarint,
+ &pb_dec_fixed32,
+ &pb_dec_fixed64,
+
+ &pb_dec_bytes,
+ &pb_dec_string,
+ &pb_dec_submessage,
+ NULL, /* extensions */
+ &pb_dec_fixed_length_bytes
+};
+
+/*******************************
+ * pb_istream_t implementation *
+ *******************************/
+
+static bool checkreturn buf_read(pb_istream_t *stream, pb_byte_t *buf, size_t count)
+{
+ size_t i;
+ const pb_byte_t *source = (const pb_byte_t*)stream->state;
+ stream->state = (pb_byte_t*)stream->state + count;
+
+ if (buf != NULL)
+ {
+ for (i = 0; i < count; i++)
+ buf[i] = source[i];
+ }
+
+ return true;
+}
+
+bool checkreturn pb_read(pb_istream_t *stream, pb_byte_t *buf, size_t count)
+{
+#ifndef PB_BUFFER_ONLY
+ if (buf == NULL && stream->callback != buf_read)
+ {
+ /* Skip input bytes */
+ pb_byte_t tmp[16];
+ while (count > 16)
+ {
+ if (!pb_read(stream, tmp, 16))
+ return false;
+
+ count -= 16;
+ }
+
+ return pb_read(stream, tmp, count);
+ }
+#endif
+
+ if (stream->bytes_left < count)
+ PB_RETURN_ERROR(stream, "end-of-stream");
+
+#ifndef PB_BUFFER_ONLY
+ if (!stream->callback(stream, buf, count))
+ PB_RETURN_ERROR(stream, "io error");
+#else
+ if (!buf_read(stream, buf, count))
+ return false;
+#endif
+
+ stream->bytes_left -= count;
+ return true;
+}
+
+/* Read a single byte from input stream. buf may not be NULL.
+ * This is an optimization for the varint decoding. */
+static bool checkreturn pb_readbyte(pb_istream_t *stream, pb_byte_t *buf)
+{
+ if (stream->bytes_left == 0)
+ PB_RETURN_ERROR(stream, "end-of-stream");
+
+#ifndef PB_BUFFER_ONLY
+ if (!stream->callback(stream, buf, 1))
+ PB_RETURN_ERROR(stream, "io error");
+#else
+ *buf = *(const pb_byte_t*)stream->state;
+ stream->state = (pb_byte_t*)stream->state + 1;
+#endif
+
+ stream->bytes_left--;
+
+ return true;
+}
+
+pb_istream_t pb_istream_from_buffer(const pb_byte_t *buf, size_t bufsize)
+{
+ pb_istream_t stream;
+ /* Cast away the const from buf without a compiler error. We are
+ * careful to use it only in a const manner in the callbacks.
+ */
+ union {
+ void *state;
+ const void *c_state;
+ } state;
+#ifdef PB_BUFFER_ONLY
+ stream.callback = NULL;
+#else
+ stream.callback = &buf_read;
+#endif
+ state.c_state = buf;
+ stream.state = state.state;
+ stream.bytes_left = bufsize;
+#ifndef PB_NO_ERRMSG
+ stream.errmsg = NULL;
+#endif
+ return stream;
+}
+
+/********************
+ * Helper functions *
+ ********************/
+
+static bool checkreturn pb_decode_varint32_eof(pb_istream_t *stream, uint32_t *dest, bool *eof)
+{
+ pb_byte_t byte;
+ uint32_t result;
+
+ if (!pb_readbyte(stream, &byte))
+ {
+ if (stream->bytes_left == 0)
+ {
+ if (eof)
+ {
+ *eof = true;
+ }
+ }
+
+ return false;
+ }
+
+ if ((byte & 0x80) == 0)
+ {
+ /* Quick case, 1 byte value */
+ result = byte;
+ }
+ else
+ {
+ /* Multibyte case */
+ uint_fast8_t bitpos = 7;
+ result = byte & 0x7F;
+
+ do
+ {
+ if (!pb_readbyte(stream, &byte))
+ return false;
+
+ if (bitpos >= 32)
+ {
+ /* Note: The varint could have trailing 0x80 bytes, or 0xFF for negative. */
+ uint8_t sign_extension = (bitpos < 63) ? 0xFF : 0x01;
+
+ if ((byte & 0x7F) != 0x00 && ((result >> 31) == 0 || byte != sign_extension))
+ {
+ PB_RETURN_ERROR(stream, "varint overflow");
+ }
+ }
+ else
+ {
+ result |= (uint32_t)(byte & 0x7F) << bitpos;
+ }
+ bitpos = (uint_fast8_t)(bitpos + 7);
+ } while (byte & 0x80);
+
+ if (bitpos == 35 && (byte & 0x70) != 0)
+ {
+ /* The last byte was at bitpos=28, so only bottom 4 bits fit. */
+ PB_RETURN_ERROR(stream, "varint overflow");
+ }
+ }
+
+ *dest = result;
+ return true;
+}
+
+bool checkreturn pb_decode_varint32(pb_istream_t *stream, uint32_t *dest)
+{
+ return pb_decode_varint32_eof(stream, dest, NULL);
+}
+
+#ifndef PB_WITHOUT_64BIT
+bool checkreturn pb_decode_varint(pb_istream_t *stream, uint64_t *dest)
+{
+ pb_byte_t byte;
+ uint_fast8_t bitpos = 0;
+ uint64_t result = 0;
+
+ do
+ {
+ if (bitpos >= 64)
+ PB_RETURN_ERROR(stream, "varint overflow");
+
+ if (!pb_readbyte(stream, &byte))
+ return false;
+
+ result |= (uint64_t)(byte & 0x7F) << bitpos;
+ bitpos = (uint_fast8_t)(bitpos + 7);
+ } while (byte & 0x80);
+
+ *dest = result;
+ return true;
+}
+#endif
+
+bool checkreturn pb_skip_varint(pb_istream_t *stream)
+{
+ pb_byte_t byte;
+ do
+ {
+ if (!pb_read(stream, &byte, 1))
+ return false;
+ } while (byte & 0x80);
+ return true;
+}
+
+bool checkreturn pb_skip_string(pb_istream_t *stream)
+{
+ uint32_t length;
+ if (!pb_decode_varint32(stream, &length))
+ return false;
+
+ return pb_read(stream, NULL, length);
+}
+
+bool checkreturn pb_decode_tag(pb_istream_t *stream, pb_wire_type_t *wire_type, uint32_t *tag, bool *eof)
+{
+ uint32_t temp;
+ *eof = false;
+ *wire_type = (pb_wire_type_t) 0;
+ *tag = 0;
+
+ if (!pb_decode_varint32_eof(stream, &temp, eof))
+ {
+ return false;
+ }
+
+ if (temp == 0)
+ {
+ *eof = true; /* Special feature: allow 0-terminated messages. */
+ return false;
+ }
+
+ *tag = temp >> 3;
+ *wire_type = (pb_wire_type_t)(temp & 7);
+ return true;
+}
+
+bool checkreturn pb_skip_field(pb_istream_t *stream, pb_wire_type_t wire_type)
+{
+ switch (wire_type)
+ {
+ case PB_WT_VARINT: return pb_skip_varint(stream);
+ case PB_WT_64BIT: return pb_read(stream, NULL, 8);
+ case PB_WT_STRING: return pb_skip_string(stream);
+ case PB_WT_32BIT: return pb_read(stream, NULL, 4);
+ default: PB_RETURN_ERROR(stream, "invalid wire_type");
+ }
+}
+
+/* Read a raw value to buffer, for the purpose of passing it to callback as
+ * a substream. Size is maximum size on call, and actual size on return.
+ */
+static bool checkreturn read_raw_value(pb_istream_t *stream, pb_wire_type_t wire_type, pb_byte_t *buf, size_t *size)
+{
+ size_t max_size = *size;
+ switch (wire_type)
+ {
+ case PB_WT_VARINT:
+ *size = 0;
+ do
+ {
+ (*size)++;
+ if (*size > max_size) return false;
+ if (!pb_read(stream, buf, 1)) return false;
+ } while (*buf++ & 0x80);
+ return true;
+
+ case PB_WT_64BIT:
+ *size = 8;
+ return pb_read(stream, buf, 8);
+
+ case PB_WT_32BIT:
+ *size = 4;
+ return pb_read(stream, buf, 4);
+
+ case PB_WT_STRING:
+ /* Calling read_raw_value with a PB_WT_STRING is an error.
+ * Explicitly handle this case and fallthrough to default to avoid
+ * compiler warnings.
+ */
+
+ default: PB_RETURN_ERROR(stream, "invalid wire_type");
+ }
+}
+
+/* Decode string length from stream and return a substream with limited length.
+ * Remember to close the substream using pb_close_string_substream().
+ */
+bool checkreturn pb_make_string_substream(pb_istream_t *stream, pb_istream_t *substream)
+{
+ uint32_t size;
+ if (!pb_decode_varint32(stream, &size))
+ return false;
+
+ *substream = *stream;
+ if (substream->bytes_left < size)
+ PB_RETURN_ERROR(stream, "parent stream too short");
+
+ substream->bytes_left = size;
+ stream->bytes_left -= size;
+ return true;
+}
+
+bool checkreturn pb_close_string_substream(pb_istream_t *stream, pb_istream_t *substream)
+{
+ if (substream->bytes_left) {
+ if (!pb_read(substream, NULL, substream->bytes_left))
+ return false;
+ }
+
+ stream->state = substream->state;
+
+#ifndef PB_NO_ERRMSG
+ stream->errmsg = substream->errmsg;
+#endif
+ return true;
+}
+
+/*************************
+ * Decode a single field *
+ *************************/
+
+static bool checkreturn decode_static_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *iter)
+{
+ pb_type_t type;
+ pb_decoder_t func;
+
+ type = iter->pos->type;
+ func = PB_DECODERS[PB_LTYPE(type)];
+
+ switch (PB_HTYPE(type))
+ {
+ case PB_HTYPE_REQUIRED:
+ return func(stream, iter->pos, iter->pData);
+
+ case PB_HTYPE_OPTIONAL:
+ if (iter->pSize != iter->pData)
+ *(bool*)iter->pSize = true;
+ return func(stream, iter->pos, iter->pData);
+
+ case PB_HTYPE_REPEATED:
+ if (wire_type == PB_WT_STRING
+ && PB_LTYPE(type) <= PB_LTYPE_LAST_PACKABLE)
+ {
+ /* Packed array */
+ bool status = true;
+ pb_size_t *size = (pb_size_t*)iter->pSize;
+
+ pb_istream_t substream;
+ if (!pb_make_string_substream(stream, &substream))
+ return false;
+
+ while (substream.bytes_left > 0 && *size < iter->pos->array_size)
+ {
+ void *pItem = (char*)iter->pData + iter->pos->data_size * (*size);
+ if (!func(&substream, iter->pos, pItem))
+ {
+ status = false;
+ break;
+ }
+ (*size)++;
+ }
+
+ if (substream.bytes_left != 0)
+ PB_RETURN_ERROR(stream, "array overflow");
+ if (!pb_close_string_substream(stream, &substream))
+ return false;
+
+ return status;
+ }
+ else
+ {
+ /* Repeated field */
+ pb_size_t *size = (pb_size_t*)iter->pSize;
+ char *pItem = (char*)iter->pData + iter->pos->data_size * (*size);
+
+ if ((*size)++ >= iter->pos->array_size)
+ PB_RETURN_ERROR(stream, "array overflow");
+
+ return func(stream, iter->pos, pItem);
+ }
+
+ case PB_HTYPE_ONEOF:
+ *(pb_size_t*)iter->pSize = iter->pos->tag;
+ if (PB_LTYPE(type) == PB_LTYPE_SUBMESSAGE)
+ {
+ /* We memset to zero so that any callbacks are set to NULL.
+ * Then set any default values. */
+ memset(iter->pData, 0, iter->pos->data_size);
+ pb_message_set_to_defaults((const pb_field_t*)iter->pos->ptr, iter->pData);
+ }
+ return func(stream, iter->pos, iter->pData);
+
+ default:
+ PB_RETURN_ERROR(stream, "invalid field type");
+ }
+}
+
+#ifdef PB_ENABLE_MALLOC
+/* Allocate storage for the field and store the pointer at iter->pData.
+ * array_size is the number of entries to reserve in an array.
+ * Zero size is not allowed, use pb_free() for releasing.
+ */
+static bool checkreturn allocate_field(pb_istream_t *stream, void *pData, size_t data_size, size_t array_size)
+{
+ void *ptr = *(void**)pData;
+
+ if (data_size == 0 || array_size == 0)
+ PB_RETURN_ERROR(stream, "invalid size");
+
+ /* Check for multiplication overflows.
+ * This code avoids the costly division if the sizes are small enough.
+ * Multiplication is safe as long as only half of bits are set
+ * in either multiplicand.
+ */
+ {
+ const size_t check_limit = (size_t)1 << (sizeof(size_t) * 4);
+ if (data_size >= check_limit || array_size >= check_limit)
+ {
+ const size_t size_max = (size_t)-1;
+ if (size_max / array_size < data_size)
+ {
+ PB_RETURN_ERROR(stream, "size too large");
+ }
+ }
+ }
+
+ /* Allocate new or expand previous allocation */
+ /* Note: on failure the old pointer will remain in the structure,
+ * the message must be freed by caller also on error return. */
+ ptr = pb_realloc(ptr, array_size * data_size);
+ if (ptr == NULL)
+ PB_RETURN_ERROR(stream, "realloc failed");
+
+ *(void**)pData = ptr;
+ return true;
+}
+
+/* Clear a newly allocated item in case it contains a pointer, or is a submessage. */
+static void initialize_pointer_field(void *pItem, pb_field_iter_t *iter)
+{
+ if (PB_LTYPE(iter->pos->type) == PB_LTYPE_STRING ||
+ PB_LTYPE(iter->pos->type) == PB_LTYPE_BYTES)
+ {
+ *(void**)pItem = NULL;
+ }
+ else if (PB_LTYPE(iter->pos->type) == PB_LTYPE_SUBMESSAGE)
+ {
+ /* We memset to zero so that any callbacks are set to NULL.
+ * Then set any default values. */
+ memset(pItem, 0, iter->pos->data_size);
+ pb_message_set_to_defaults((const pb_field_t *) iter->pos->ptr, pItem);
+ }
+}
+#endif
+
+static bool checkreturn decode_pointer_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *iter)
+{
+#ifndef PB_ENABLE_MALLOC
+ PB_UNUSED(wire_type);
+ PB_UNUSED(iter);
+ PB_RETURN_ERROR(stream, "no malloc support");
+#else
+ pb_type_t type;
+ pb_decoder_t func;
+
+ type = iter->pos->type;
+ func = PB_DECODERS[PB_LTYPE(type)];
+
+ switch (PB_HTYPE(type))
+ {
+ case PB_HTYPE_REQUIRED:
+ case PB_HTYPE_OPTIONAL:
+ case PB_HTYPE_ONEOF:
+ if (PB_LTYPE(type) == PB_LTYPE_SUBMESSAGE &&
+ *(void**)iter->pData != NULL)
+ {
+ /* Duplicate field, have to release the old allocation first. */
+ pb_release_single_field(iter);
+ }
+
+ if (PB_HTYPE(type) == PB_HTYPE_ONEOF)
+ {
+ *(pb_size_t*)iter->pSize = iter->pos->tag;
+ }
+
+ if (PB_LTYPE(type) == PB_LTYPE_STRING ||
+ PB_LTYPE(type) == PB_LTYPE_BYTES)
+ {
+ return func(stream, iter->pos, iter->pData);
+ }
+ else
+ {
+ if (!allocate_field(stream, iter->pData, iter->pos->data_size, 1))
+ return false;
+
+ initialize_pointer_field(*(void**)iter->pData, iter);
+ return func(stream, iter->pos, *(void**)iter->pData);
+ }
+
+ case PB_HTYPE_REPEATED:
+ if (wire_type == PB_WT_STRING
+ && PB_LTYPE(type) <= PB_LTYPE_LAST_PACKABLE)
+ {
+ /* Packed array, multiple items come in at once. */
+ bool status = true;
+ pb_size_t *size = (pb_size_t*)iter->pSize;
+ size_t allocated_size = *size;
+ void *pItem;
+ pb_istream_t substream;
+
+ if (!pb_make_string_substream(stream, &substream))
+ return false;
+
+ while (substream.bytes_left)
+ {
+ if ((size_t)*size + 1 > allocated_size)
+ {
+ /* Allocate more storage. This tries to guess the
+ * number of remaining entries. Round the division
+ * upwards. */
+ allocated_size += (substream.bytes_left - 1) / iter->pos->data_size + 1;
+
+ if (!allocate_field(&substream, iter->pData, iter->pos->data_size, allocated_size))
+ {
+ status = false;
+ break;
+ }
+ }
+
+ /* Decode the array entry */
+ pItem = *(char**)iter->pData + iter->pos->data_size * (*size);
+ initialize_pointer_field(pItem, iter);
+ if (!func(&substream, iter->pos, pItem))
+ {
+ status = false;
+ break;
+ }
+
+ if (*size == PB_SIZE_MAX)
+ {
+#ifndef PB_NO_ERRMSG
+ stream->errmsg = "too many array entries";
+#endif
+ status = false;
+ break;
+ }
+
+ (*size)++;
+ }
+ if (!pb_close_string_substream(stream, &substream))
+ return false;
+
+ return status;
+ }
+ else
+ {
+ /* Normal repeated field, i.e. only one item at a time. */
+ pb_size_t *size = (pb_size_t*)iter->pSize;
+ void *pItem;
+
+ if (*size == PB_SIZE_MAX)
+ PB_RETURN_ERROR(stream, "too many array entries");
+
+ (*size)++;
+ if (!allocate_field(stream, iter->pData, iter->pos->data_size, *size))
+ return false;
+
+ pItem = *(char**)iter->pData + iter->pos->data_size * (*size - 1);
+ initialize_pointer_field(pItem, iter);
+ return func(stream, iter->pos, pItem);
+ }
+
+ default:
+ PB_RETURN_ERROR(stream, "invalid field type");
+ }
+#endif
+}
+
+static bool checkreturn decode_callback_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *iter)
+{
+ pb_callback_t *pCallback = (pb_callback_t*)iter->pData;
+#ifdef PB_OLD_CALLBACK_STYLE
+ void *arg;
+#else
+ void **arg;
+#endif
+
+ if (pCallback == NULL || pCallback->funcs.decode == NULL)
+ return pb_skip_field(stream, wire_type);
+
+#ifdef PB_OLD_CALLBACK_STYLE
+ arg = pCallback->arg;
+#else
+ arg = &(pCallback->arg);
+#endif
+
+ if (wire_type == PB_WT_STRING)
+ {
+ pb_istream_t substream;
+
+ if (!pb_make_string_substream(stream, &substream))
+ return false;
+
+ do
+ {
+ if (!pCallback->funcs.decode(&substream, iter->pos, arg))
+ PB_RETURN_ERROR(stream, "callback failed");
+ } while (substream.bytes_left);
+
+ if (!pb_close_string_substream(stream, &substream))
+ return false;
+
+ return true;
+ }
+ else
+ {
+ /* Copy the single scalar value to stack.
+ * This is required so that we can limit the stream length,
+ * which in turn allows to use same callback for packed and
+ * not-packed fields. */
+ pb_istream_t substream;
+ pb_byte_t buffer[10];
+ size_t size = sizeof(buffer);
+
+ if (!read_raw_value(stream, wire_type, buffer, &size))
+ return false;
+ substream = pb_istream_from_buffer(buffer, size);
+
+ return pCallback->funcs.decode(&substream, iter->pos, arg);
+ }
+}
+
+static bool checkreturn decode_field(pb_istream_t *stream, pb_wire_type_t wire_type, pb_field_iter_t *iter)
+{
+#ifdef PB_ENABLE_MALLOC
+ /* When decoding an oneof field, check if there is old data that must be
+ * released first. */
+ if (PB_HTYPE(iter->pos->type) == PB_HTYPE_ONEOF)
+ {
+ if (!pb_release_union_field(stream, iter))
+ return false;
+ }
+#endif
+
+ switch (PB_ATYPE(iter->pos->type))
+ {
+ case PB_ATYPE_STATIC:
+ return decode_static_field(stream, wire_type, iter);
+
+ case PB_ATYPE_POINTER:
+ return decode_pointer_field(stream, wire_type, iter);
+
+ case PB_ATYPE_CALLBACK:
+ return decode_callback_field(stream, wire_type, iter);
+
+ default:
+ PB_RETURN_ERROR(stream, "invalid field type");
+ }
+}
+
+static void iter_from_extension(pb_field_iter_t *iter, pb_extension_t *extension)
+{
+ /* Fake a field iterator for the extension field.
+ * It is not actually safe to advance this iterator, but decode_field
+ * will not even try to. */
+ const pb_field_t *field = (const pb_field_t*)extension->type->arg;
+ (void)pb_field_iter_begin(iter, field, extension->dest);
+ iter->pData = extension->dest;
+ iter->pSize = &extension->found;
+
+ if (PB_ATYPE(field->type) == PB_ATYPE_POINTER)
+ {
+ /* For pointer extensions, the pointer is stored directly
+ * in the extension structure. This avoids having an extra
+ * indirection. */
+ iter->pData = &extension->dest;
+ }
+}
+
+/* Default handler for extension fields. Expects a pb_field_t structure
+ * in extension->type->arg. */
+static bool checkreturn default_extension_decoder(pb_istream_t *stream,
+ pb_extension_t *extension, uint32_t tag, pb_wire_type_t wire_type)
+{
+ const pb_field_t *field = (const pb_field_t*)extension->type->arg;
+ pb_field_iter_t iter;
+
+ if (field->tag != tag)
+ return true;
+
+ iter_from_extension(&iter, extension);
+ extension->found = true;
+ return decode_field(stream, wire_type, &iter);
+}
+
+/* Try to decode an unknown field as an extension field. Tries each extension
+ * decoder in turn, until one of them handles the field or loop ends. */
+static bool checkreturn decode_extension(pb_istream_t *stream,
+ uint32_t tag, pb_wire_type_t wire_type, pb_field_iter_t *iter)
+{
+ pb_extension_t *extension = *(pb_extension_t* const *)iter->pData;
+ size_t pos = stream->bytes_left;
+
+ while (extension != NULL && pos == stream->bytes_left)
+ {
+ bool status;
+ if (extension->type->decode)
+ status = extension->type->decode(stream, extension, tag, wire_type);
+ else
+ status = default_extension_decoder(stream, extension, tag, wire_type);
+
+ if (!status)
+ return false;
+
+ extension = extension->next;
+ }
+
+ return true;
+}
+
+/* Step through the iterator until an extension field is found or until all
+ * entries have been checked. There can be only one extension field per
+ * message. Returns false if no extension field is found. */
+static bool checkreturn find_extension_field(pb_field_iter_t *iter)
+{
+ const pb_field_t *start = iter->pos;
+
+ do {
+ if (PB_LTYPE(iter->pos->type) == PB_LTYPE_EXTENSION)
+ return true;
+ (void)pb_field_iter_next(iter);
+ } while (iter->pos != start);
+
+ return false;
+}
+
+/* Initialize message fields to default values, recursively */
+static void pb_field_set_to_default(pb_field_iter_t *iter)
+{
+ pb_type_t type;
+ type = iter->pos->type;
+
+ if (PB_LTYPE(type) == PB_LTYPE_EXTENSION)
+ {
+ pb_extension_t *ext = *(pb_extension_t* const *)iter->pData;
+ while (ext != NULL)
+ {
+ pb_field_iter_t ext_iter;
+ ext->found = false;
+ iter_from_extension(&ext_iter, ext);
+ pb_field_set_to_default(&ext_iter);
+ ext = ext->next;
+ }
+ }
+ else if (PB_ATYPE(type) == PB_ATYPE_STATIC)
+ {
+ bool init_data = true;
+ if (PB_HTYPE(type) == PB_HTYPE_OPTIONAL && iter->pSize != iter->pData)
+ {
+ /* Set has_field to false. Still initialize the optional field
+ * itself also. */
+ *(bool*)iter->pSize = false;
+ }
+ else if (PB_HTYPE(type) == PB_HTYPE_REPEATED ||
+ PB_HTYPE(type) == PB_HTYPE_ONEOF)
+ {
+ /* REPEATED: Set array count to 0, no need to initialize contents.
+ ONEOF: Set which_field to 0. */
+ *(pb_size_t*)iter->pSize = 0;
+ init_data = false;
+ }
+
+ if (init_data)
+ {
+ if (PB_LTYPE(iter->pos->type) == PB_LTYPE_SUBMESSAGE)
+ {
+ /* Initialize submessage to defaults */
+ pb_message_set_to_defaults((const pb_field_t *) iter->pos->ptr, iter->pData);
+ }
+ else if (iter->pos->ptr != NULL)
+ {
+ /* Initialize to default value */
+ memcpy(iter->pData, iter->pos->ptr, iter->pos->data_size);
+ }
+ else
+ {
+ /* Initialize to zeros */
+ memset(iter->pData, 0, iter->pos->data_size);
+ }
+ }
+ }
+ else if (PB_ATYPE(type) == PB_ATYPE_POINTER)
+ {
+ /* Initialize the pointer to NULL. */
+ *(void**)iter->pData = NULL;
+
+ /* Initialize array count to 0. */
+ if (PB_HTYPE(type) == PB_HTYPE_REPEATED ||
+ PB_HTYPE(type) == PB_HTYPE_ONEOF)
+ {
+ *(pb_size_t*)iter->pSize = 0;
+ }
+ }
+ else if (PB_ATYPE(type) == PB_ATYPE_CALLBACK)
+ {
+ /* Don't overwrite callback */
+ }
+}
+
+static void pb_message_set_to_defaults(const pb_field_t fields[], void *dest_struct)
+{
+ pb_field_iter_t iter;
+
+ if (!pb_field_iter_begin(&iter, fields, dest_struct))
+ return; /* Empty message type */
+
+ do
+ {
+ pb_field_set_to_default(&iter);
+ } while (pb_field_iter_next(&iter));
+}
+
+/*********************
+ * Decode all fields *
+ *********************/
+
+bool checkreturn pb_decode_noinit(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct)
+{
+ uint32_t fields_seen[(PB_MAX_REQUIRED_FIELDS + 31) / 32] = {0, 0};
+ const uint32_t allbits = ~(uint32_t)0;
+ uint32_t extension_range_start = 0;
+ pb_field_iter_t iter;
+
+ /* 'fixed_count_field' and 'fixed_count_size' track position of a repeated fixed
+ * count field. This can only handle _one_ repeated fixed count field that
+ * is unpacked and unordered among other (non repeated fixed count) fields.
+ */
+ const pb_field_t *fixed_count_field = NULL;
+ pb_size_t fixed_count_size = 0;
+
+ /* Return value ignored, as empty message types will be correctly handled by
+ * pb_field_iter_find() anyway. */
+ (void)pb_field_iter_begin(&iter, fields, dest_struct);
+
+ while (stream->bytes_left)
+ {
+ uint32_t tag;
+ pb_wire_type_t wire_type;
+ bool eof;
+
+ if (!pb_decode_tag(stream, &wire_type, &tag, &eof))
+ {
+ if (eof)
+ break;
+ else
+ return false;
+ }
+
+ if (!pb_field_iter_find(&iter, tag))
+ {
+ /* No match found, check if it matches an extension. */
+ if (tag >= extension_range_start)
+ {
+ if (!find_extension_field(&iter))
+ extension_range_start = (uint32_t)-1;
+ else
+ extension_range_start = iter.pos->tag;
+
+ if (tag >= extension_range_start)
+ {
+ size_t pos = stream->bytes_left;
+
+ if (!decode_extension(stream, tag, wire_type, &iter))
+ return false;
+
+ if (pos != stream->bytes_left)
+ {
+ /* The field was handled */
+ continue;
+ }
+ }
+ }
+
+ /* No match found, skip data */
+ if (!pb_skip_field(stream, wire_type))
+ return false;
+ continue;
+ }
+
+ /* If a repeated fixed count field was found, get size from
+ * 'fixed_count_field' as there is no counter contained in the struct.
+ */
+ if (PB_HTYPE(iter.pos->type) == PB_HTYPE_REPEATED
+ && iter.pSize == iter.pData)
+ {
+ if (fixed_count_field != iter.pos) {
+ /* If the new fixed count field does not match the previous one,
+ * check that the previous one is NULL or that it finished
+ * receiving all the expected data.
+ */
+ if (fixed_count_field != NULL &&
+ fixed_count_size != fixed_count_field->array_size)
+ {
+ PB_RETURN_ERROR(stream, "wrong size for fixed count field");
+ }
+
+ fixed_count_field = iter.pos;
+ fixed_count_size = 0;
+ }
+
+ iter.pSize = &fixed_count_size;
+ }
+
+ if (PB_HTYPE(iter.pos->type) == PB_HTYPE_REQUIRED
+ && iter.required_field_index < PB_MAX_REQUIRED_FIELDS)
+ {
+ uint32_t tmp = ((uint32_t)1 << (iter.required_field_index & 31));
+ fields_seen[iter.required_field_index >> 5] |= tmp;
+ }
+
+ if (!decode_field(stream, wire_type, &iter))
+ return false;
+ }
+
+ /* Check that all elements of the last decoded fixed count field were present. */
+ if (fixed_count_field != NULL &&
+ fixed_count_size != fixed_count_field->array_size)
+ {
+ PB_RETURN_ERROR(stream, "wrong size for fixed count field");
+ }
+
+ /* Check that all required fields were present. */
+ {
+ /* First figure out the number of required fields by
+ * seeking to the end of the field array. Usually we
+ * are already close to end after decoding.
+ */
+ unsigned req_field_count;
+ pb_type_t last_type;
+ unsigned i;
+ do {
+ req_field_count = iter.required_field_index;
+ last_type = iter.pos->type;
+ } while (pb_field_iter_next(&iter));
+
+ /* Fixup if last field was also required. */
+ if (PB_HTYPE(last_type) == PB_HTYPE_REQUIRED && iter.pos->tag != 0)
+ req_field_count++;
+
+ if (req_field_count > PB_MAX_REQUIRED_FIELDS)
+ req_field_count = PB_MAX_REQUIRED_FIELDS;
+
+ if (req_field_count > 0)
+ {
+ /* Check the whole words */
+ for (i = 0; i < (req_field_count >> 5); i++)
+ {
+ if (fields_seen[i] != allbits)
+ PB_RETURN_ERROR(stream, "missing required field");
+ }
+
+ /* Check the remaining bits (if any) */
+ if ((req_field_count & 31) != 0)
+ {
+ if (fields_seen[req_field_count >> 5] !=
+ (allbits >> (32 - (req_field_count & 31))))
+ {
+ PB_RETURN_ERROR(stream, "missing required field");
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+bool checkreturn pb_decode(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct)
+{
+ bool status;
+ pb_message_set_to_defaults(fields, dest_struct);
+ status = pb_decode_noinit(stream, fields, dest_struct);
+
+#ifdef PB_ENABLE_MALLOC
+ if (!status)
+ pb_release(fields, dest_struct);
+#endif
+
+ return status;
+}
+
+bool pb_decode_delimited_noinit(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct)
+{
+ pb_istream_t substream;
+ bool status;
+
+ if (!pb_make_string_substream(stream, &substream))
+ return false;
+
+ status = pb_decode_noinit(&substream, fields, dest_struct);
+
+ if (!pb_close_string_substream(stream, &substream))
+ return false;
+ return status;
+}
+
+bool pb_decode_delimited(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct)
+{
+ pb_istream_t substream;
+ bool status;
+
+ if (!pb_make_string_substream(stream, &substream))
+ return false;
+
+ status = pb_decode(&substream, fields, dest_struct);
+
+ if (!pb_close_string_substream(stream, &substream))
+ return false;
+ return status;
+}
+
+bool pb_decode_nullterminated(pb_istream_t *stream, const pb_field_t fields[], void *dest_struct)
+{
+ /* This behaviour will be separated in nanopb-0.4.0, see issue #278. */
+ return pb_decode(stream, fields, dest_struct);
+}
+
+#ifdef PB_ENABLE_MALLOC
+/* Given an oneof field, if there has already been a field inside this oneof,
+ * release it before overwriting with a different one. */
+static bool pb_release_union_field(pb_istream_t *stream, pb_field_iter_t *iter)
+{
+ pb_size_t old_tag = *(pb_size_t*)iter->pSize; /* Previous which_ value */
+ pb_size_t new_tag = iter->pos->tag; /* New which_ value */
+
+ if (old_tag == 0)
+ return true; /* Ok, no old data in union */
+
+ if (old_tag == new_tag)
+ return true; /* Ok, old data is of same type => merge */
+
+ /* Release old data. The find can fail if the message struct contains
+ * invalid data. */
+ if (!pb_field_iter_find(iter, old_tag))
+ PB_RETURN_ERROR(stream, "invalid union tag");
+
+ pb_release_single_field(iter);
+
+ /* Restore iterator to where it should be.
+ * This shouldn't fail unless the pb_field_t structure is corrupted. */
+ if (!pb_field_iter_find(iter, new_tag))
+ PB_RETURN_ERROR(stream, "iterator error");
+
+ return true;
+}
+
+static void pb_release_single_field(const pb_field_iter_t *iter)
+{
+ pb_type_t type;
+ type = iter->pos->type;
+
+ if (PB_HTYPE(type) == PB_HTYPE_ONEOF)
+ {
+ if (*(pb_size_t*)iter->pSize != iter->pos->tag)
+ return; /* This is not the current field in the union */
+ }
+
+ /* Release anything contained inside an extension or submsg.
+ * This has to be done even if the submsg itself is statically
+ * allocated. */
+ if (PB_LTYPE(type) == PB_LTYPE_EXTENSION)
+ {
+ /* Release fields from all extensions in the linked list */
+ pb_extension_t *ext = *(pb_extension_t**)iter->pData;
+ while (ext != NULL)
+ {
+ pb_field_iter_t ext_iter;
+ iter_from_extension(&ext_iter, ext);
+ pb_release_single_field(&ext_iter);
+ ext = ext->next;
+ }
+ }
+ else if (PB_LTYPE(type) == PB_LTYPE_SUBMESSAGE && PB_ATYPE(type) != PB_ATYPE_CALLBACK)
+ {
+ /* Release fields in submessage or submsg array */
+ void *pItem = iter->pData;
+ pb_size_t count = 1;
+
+ if (PB_ATYPE(type) == PB_ATYPE_POINTER)
+ {
+ pItem = *(void**)iter->pData;
+ }
+
+ if (PB_HTYPE(type) == PB_HTYPE_REPEATED)
+ {
+ if (PB_ATYPE(type) == PB_ATYPE_STATIC && iter->pSize == iter->pData) {
+ /* No _count field so use size of the array */
+ count = iter->pos->array_size;
+ } else {
+ count = *(pb_size_t*)iter->pSize;
+ }
+
+ if (PB_ATYPE(type) == PB_ATYPE_STATIC && count > iter->pos->array_size)
+ {
+ /* Protect against corrupted _count fields */
+ count = iter->pos->array_size;
+ }
+ }
+
+ if (pItem)
+ {
+ while (count--)
+ {
+ pb_release((const pb_field_t*)iter->pos->ptr, pItem);
+ pItem = (char*)pItem + iter->pos->data_size;
+ }
+ }
+ }
+
+ if (PB_ATYPE(type) == PB_ATYPE_POINTER)
+ {
+ if (PB_HTYPE(type) == PB_HTYPE_REPEATED &&
+ (PB_LTYPE(type) == PB_LTYPE_STRING ||
+ PB_LTYPE(type) == PB_LTYPE_BYTES))
+ {
+ /* Release entries in repeated string or bytes array */
+ void **pItem = *(void***)iter->pData;
+ pb_size_t count = *(pb_size_t*)iter->pSize;
+ while (count--)
+ {
+ pb_free(*pItem);
+ *pItem++ = NULL;
+ }
+ }
+
+ if (PB_HTYPE(type) == PB_HTYPE_REPEATED)
+ {
+ /* We are going to release the array, so set the size to 0 */
+ *(pb_size_t*)iter->pSize = 0;
+ }
+
+ /* Release main item */
+ pb_free(*(void**)iter->pData);
+ *(void**)iter->pData = NULL;
+ }
+}
+
+void pb_release(const pb_field_t fields[], void *dest_struct)
+{
+ pb_field_iter_t iter;
+
+ if (!dest_struct)
+ return; /* Ignore NULL pointers, similar to free() */
+
+ if (!pb_field_iter_begin(&iter, fields, dest_struct))
+ return; /* Empty message type */
+
+ do
+ {
+ pb_release_single_field(&iter);
+ } while (pb_field_iter_next(&iter));
+}
+#endif
+
+/* Field decoders */
+
+bool pb_decode_svarint(pb_istream_t *stream, pb_int64_t *dest)
+{
+ pb_uint64_t value;
+ if (!pb_decode_varint(stream, &value))
+ return false;
+
+ if (value & 1)
+ *dest = (pb_int64_t)(~(value >> 1));
+ else
+ *dest = (pb_int64_t)(value >> 1);
+
+ return true;
+}
+
+bool pb_decode_fixed32(pb_istream_t *stream, void *dest)
+{
+ union {
+ uint32_t fixed32;
+ pb_byte_t bytes[4];
+ } u;
+
+ if (!pb_read(stream, u.bytes, 4))
+ return false;
+
+#if defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN && CHAR_BIT == 8
+ /* fast path - if we know that we're on little endian, assign directly */
+ *(uint32_t*)dest = u.fixed32;
+#else
+ *(uint32_t*)dest = ((uint32_t)u.bytes[0] << 0) |
+ ((uint32_t)u.bytes[1] << 8) |
+ ((uint32_t)u.bytes[2] << 16) |
+ ((uint32_t)u.bytes[3] << 24);
+#endif
+ return true;
+}
+
+#ifndef PB_WITHOUT_64BIT
+bool pb_decode_fixed64(pb_istream_t *stream, void *dest)
+{
+ union {
+ uint64_t fixed64;
+ pb_byte_t bytes[8];
+ } u;
+
+ if (!pb_read(stream, u.bytes, 8))
+ return false;
+
+#if defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN && CHAR_BIT == 8
+ /* fast path - if we know that we're on little endian, assign directly */
+ *(uint64_t*)dest = u.fixed64;
+#else
+ *(uint64_t*)dest = ((uint64_t)u.bytes[0] << 0) |
+ ((uint64_t)u.bytes[1] << 8) |
+ ((uint64_t)u.bytes[2] << 16) |
+ ((uint64_t)u.bytes[3] << 24) |
+ ((uint64_t)u.bytes[4] << 32) |
+ ((uint64_t)u.bytes[5] << 40) |
+ ((uint64_t)u.bytes[6] << 48) |
+ ((uint64_t)u.bytes[7] << 56);
+#endif
+ return true;
+}
+#endif
+
+static bool checkreturn pb_dec_varint(pb_istream_t *stream, const pb_field_t *field, void *dest)
+{
+ pb_uint64_t value;
+ pb_int64_t svalue;
+ pb_int64_t clamped;
+ if (!pb_decode_varint(stream, &value))
+ return false;
+
+ /* See issue 97: Google's C++ protobuf allows negative varint values to
+ * be cast as int32_t, instead of the int64_t that should be used when
+ * encoding. Previous nanopb versions had a bug in encoding. In order to
+ * not break decoding of such messages, we cast <=32 bit fields to
+ * int32_t first to get the sign correct.
+ */
+ if (field->data_size == sizeof(pb_int64_t))
+ svalue = (pb_int64_t)value;
+ else
+ svalue = (int32_t)value;
+
+ /* Cast to the proper field size, while checking for overflows */
+ if (field->data_size == sizeof(pb_int64_t))
+ clamped = *(pb_int64_t*)dest = svalue;
+ else if (field->data_size == sizeof(int32_t))
+ clamped = *(int32_t*)dest = (int32_t)svalue;
+ else if (field->data_size == sizeof(int_least16_t))
+ clamped = *(int_least16_t*)dest = (int_least16_t)svalue;
+ else if (field->data_size == sizeof(int_least8_t))
+ clamped = *(int_least8_t*)dest = (int_least8_t)svalue;
+ else
+ PB_RETURN_ERROR(stream, "invalid data_size");
+
+ if (clamped != svalue)
+ PB_RETURN_ERROR(stream, "integer too large");
+
+ return true;
+}
+
+static bool checkreturn pb_dec_uvarint(pb_istream_t *stream, const pb_field_t *field, void *dest)
+{
+ pb_uint64_t value, clamped;
+ if (!pb_decode_varint(stream, &value))
+ return false;
+
+ /* Cast to the proper field size, while checking for overflows */
+ if (field->data_size == sizeof(pb_uint64_t))
+ clamped = *(pb_uint64_t*)dest = value;
+ else if (field->data_size == sizeof(uint32_t))
+ clamped = *(uint32_t*)dest = (uint32_t)value;
+ else if (field->data_size == sizeof(uint_least16_t))
+ clamped = *(uint_least16_t*)dest = (uint_least16_t)value;
+ else if (field->data_size == sizeof(uint_least8_t))
+ clamped = *(uint_least8_t*)dest = (uint_least8_t)value;
+ else
+ PB_RETURN_ERROR(stream, "invalid data_size");
+
+ if (clamped != value)
+ PB_RETURN_ERROR(stream, "integer too large");
+
+ return true;
+}
+
+static bool checkreturn pb_dec_svarint(pb_istream_t *stream, const pb_field_t *field, void *dest)
+{
+ pb_int64_t value, clamped;
+ if (!pb_decode_svarint(stream, &value))
+ return false;
+
+ /* Cast to the proper field size, while checking for overflows */
+ if (field->data_size == sizeof(pb_int64_t))
+ clamped = *(pb_int64_t*)dest = value;
+ else if (field->data_size == sizeof(int32_t))
+ clamped = *(int32_t*)dest = (int32_t)value;
+ else if (field->data_size == sizeof(int_least16_t))
+ clamped = *(int_least16_t*)dest = (int_least16_t)value;
+ else if (field->data_size == sizeof(int_least8_t))
+ clamped = *(int_least8_t*)dest = (int_least8_t)value;
+ else
+ PB_RETURN_ERROR(stream, "invalid data_size");
+
+ if (clamped != value)
+ PB_RETURN_ERROR(stream, "integer too large");
+
+ return true;
+}
+
+static bool checkreturn pb_dec_fixed32(pb_istream_t *stream, const pb_field_t *field, void *dest)
+{
+ PB_UNUSED(field);
+ return pb_decode_fixed32(stream, dest);
+}
+
+static bool checkreturn pb_dec_fixed64(pb_istream_t *stream, const pb_field_t *field, void *dest)
+{
+ PB_UNUSED(field);
+#ifndef PB_WITHOUT_64BIT
+ return pb_decode_fixed64(stream, dest);
+#else
+ PB_UNUSED(dest);
+ PB_RETURN_ERROR(stream, "no 64bit support");
+#endif
+}
+
+static bool checkreturn pb_dec_bytes(pb_istream_t *stream, const pb_field_t *field, void *dest)
+{
+ uint32_t size;
+ size_t alloc_size;
+ pb_bytes_array_t *bdest;
+
+ if (!pb_decode_varint32(stream, &size))
+ return false;
+
+ if (size > PB_SIZE_MAX)
+ PB_RETURN_ERROR(stream, "bytes overflow");
+
+ alloc_size = PB_BYTES_ARRAY_T_ALLOCSIZE(size);
+ if (size > alloc_size)
+ PB_RETURN_ERROR(stream, "size too large");
+
+ if (PB_ATYPE(field->type) == PB_ATYPE_POINTER)
+ {
+#ifndef PB_ENABLE_MALLOC
+ PB_RETURN_ERROR(stream, "no malloc support");
+#else
+ if (!allocate_field(stream, dest, alloc_size, 1))
+ return false;
+ bdest = *(pb_bytes_array_t**)dest;
+#endif
+ }
+ else
+ {
+ if (alloc_size > field->data_size)
+ PB_RETURN_ERROR(stream, "bytes overflow");
+ bdest = (pb_bytes_array_t*)dest;
+ }
+
+ bdest->size = (pb_size_t)size;
+ return pb_read(stream, bdest->bytes, size);
+}
+
+static bool checkreturn pb_dec_string(pb_istream_t *stream, const pb_field_t *field, void *dest)
+{
+ uint32_t size;
+ size_t alloc_size;
+ bool status;
+ if (!pb_decode_varint32(stream, &size))
+ return false;
+
+ /* Space for null terminator */
+ alloc_size = size + 1;
+
+ if (alloc_size < size)
+ PB_RETURN_ERROR(stream, "size too large");
+
+ if (PB_ATYPE(field->type) == PB_ATYPE_POINTER)
+ {
+#ifndef PB_ENABLE_MALLOC
+ PB_RETURN_ERROR(stream, "no malloc support");
+#else
+ if (!allocate_field(stream, dest, alloc_size, 1))
+ return false;
+ dest = *(void**)dest;
+#endif
+ }
+ else
+ {
+ if (alloc_size > field->data_size)
+ PB_RETURN_ERROR(stream, "string overflow");
+ }
+
+ status = pb_read(stream, (pb_byte_t*)dest, size);
+ *((pb_byte_t*)dest + size) = 0;
+ return status;
+}
+
+static bool checkreturn pb_dec_submessage(pb_istream_t *stream, const pb_field_t *field, void *dest)
+{
+ bool status;
+ pb_istream_t substream;
+ const pb_field_t* submsg_fields = (const pb_field_t*)field->ptr;
+
+ if (!pb_make_string_substream(stream, &substream))
+ return false;
+
+ if (field->ptr == NULL)
+ PB_RETURN_ERROR(stream, "invalid field descriptor");
+
+ /* New array entries need to be initialized, while required and optional
+ * submessages have already been initialized in the top-level pb_decode. */
+ if (PB_HTYPE(field->type) == PB_HTYPE_REPEATED)
+ status = pb_decode(&substream, submsg_fields, dest);
+ else
+ status = pb_decode_noinit(&substream, submsg_fields, dest);
+
+ if (!pb_close_string_substream(stream, &substream))
+ return false;
+ return status;
+}
+
+static bool checkreturn pb_dec_fixed_length_bytes(pb_istream_t *stream, const pb_field_t *field, void *dest)
+{
+ uint32_t size;
+
+ if (!pb_decode_varint32(stream, &size))
+ return false;
+
+ if (size > PB_SIZE_MAX)
+ PB_RETURN_ERROR(stream, "bytes overflow");
+
+ if (size == 0)
+ {
+ /* As a special case, treat empty bytes string as all zeros for fixed_length_bytes. */
+ memset(dest, 0, field->data_size);
+ return true;
+ }
+
+ if (size != field->data_size)
+ PB_RETURN_ERROR(stream, "incorrect fixed length bytes size");
+
+ return pb_read(stream, (pb_byte_t*)dest, field->data_size);
+}
diff --git a/src/lib/nanopb/pb_encode.cc b/src/lib/nanopb/pb_encode.cc
new file mode 100644
index 0000000..f37ceec
--- /dev/null
+++ b/src/lib/nanopb/pb_encode.cc
@@ -0,0 +1,893 @@
+/* pb_encode.c -- encode a protobuf using minimal resources
+ *
+ * 2011 Petteri Aimonen <jpa@kapsi.fi>
+ */
+
+#include "pb.h"
+#include "pb_encode.h"
+#include "pb_common.h"
+
+/* Use the GCC warn_unused_result attribute to check that all return values
+ * are propagated correctly. On other compilers and gcc before 3.4.0 just
+ * ignore the annotation.
+ */
+#if !defined(__GNUC__) || ( __GNUC__ < 3) || (__GNUC__ == 3 && __GNUC_MINOR__ < 4)
+ #define checkreturn
+#else
+ #define checkreturn __attribute__((warn_unused_result))
+#endif
+
+/**************************************
+ * Declarations internal to this file *
+ **************************************/
+typedef bool (*pb_encoder_t)(pb_ostream_t *stream, const pb_field_t *field, const void *src) checkreturn;
+
+static bool checkreturn buf_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count);
+static bool checkreturn encode_array(pb_ostream_t *stream, const pb_field_t *field, const void *pData, size_t count, pb_encoder_t func);
+static bool checkreturn encode_field(pb_ostream_t *stream, const pb_field_t *field, const void *pData);
+static bool checkreturn default_extension_encoder(pb_ostream_t *stream, const pb_extension_t *extension);
+static bool checkreturn encode_extension_field(pb_ostream_t *stream, const pb_field_t *field, const void *pData);
+static void *pb_const_cast(const void *p);
+static bool checkreturn pb_encode_varint_32(pb_ostream_t *stream, uint32_t low, uint32_t high);
+static bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_t *field, const void *src);
+static bool checkreturn pb_enc_uvarint(pb_ostream_t *stream, const pb_field_t *field, const void *src);
+static bool checkreturn pb_enc_svarint(pb_ostream_t *stream, const pb_field_t *field, const void *src);
+static bool checkreturn pb_enc_fixed32(pb_ostream_t *stream, const pb_field_t *field, const void *src);
+static bool checkreturn pb_enc_fixed64(pb_ostream_t *stream, const pb_field_t *field, const void *src);
+static bool checkreturn pb_enc_bytes(pb_ostream_t *stream, const pb_field_t *field, const void *src);
+static bool checkreturn pb_enc_string(pb_ostream_t *stream, const pb_field_t *field, const void *src);
+static bool checkreturn pb_enc_submessage(pb_ostream_t *stream, const pb_field_t *field, const void *src);
+static bool checkreturn pb_enc_fixed_length_bytes(pb_ostream_t *stream, const pb_field_t *field, const void *src);
+
+#ifdef PB_WITHOUT_64BIT
+#define pb_int64_t int32_t
+#define pb_uint64_t uint32_t
+#else
+#define pb_int64_t int64_t
+#define pb_uint64_t uint64_t
+#endif
+
+/* --- Function pointers to field encoders ---
+ * Order in the array must match pb_action_t LTYPE numbering.
+ */
+static const pb_encoder_t PB_ENCODERS[PB_LTYPES_COUNT] = {
+ &pb_enc_varint,
+ &pb_enc_uvarint,
+ &pb_enc_svarint,
+ &pb_enc_fixed32,
+ &pb_enc_fixed64,
+
+ &pb_enc_bytes,
+ &pb_enc_string,
+ &pb_enc_submessage,
+ NULL, /* extensions */
+ &pb_enc_fixed_length_bytes
+};
+
+/*******************************
+ * pb_ostream_t implementation *
+ *******************************/
+
+static bool checkreturn buf_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count)
+{
+ size_t i;
+ pb_byte_t *dest = (pb_byte_t*)stream->state;
+ stream->state = dest + count;
+
+ for (i = 0; i < count; i++)
+ dest[i] = buf[i];
+
+ return true;
+}
+
+pb_ostream_t pb_ostream_from_buffer(pb_byte_t *buf, size_t bufsize)
+{
+ pb_ostream_t stream;
+#ifdef PB_BUFFER_ONLY
+ stream.callback = (void*)1; /* Just a marker value */
+#else
+ stream.callback = &buf_write;
+#endif
+ stream.state = buf;
+ stream.max_size = bufsize;
+ stream.bytes_written = 0;
+#ifndef PB_NO_ERRMSG
+ stream.errmsg = NULL;
+#endif
+ return stream;
+}
+
+bool checkreturn pb_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count)
+{
+ if (stream->callback != NULL)
+ {
+ if (stream->bytes_written + count > stream->max_size)
+ PB_RETURN_ERROR(stream, "stream full");
+
+#ifdef PB_BUFFER_ONLY
+ if (!buf_write(stream, buf, count))
+ PB_RETURN_ERROR(stream, "io error");
+#else
+ if (!stream->callback(stream, buf, count))
+ PB_RETURN_ERROR(stream, "io error");
+#endif
+ }
+
+ stream->bytes_written += count;
+ return true;
+}
+
+/*************************
+ * Encode a single field *
+ *************************/
+
+/* Encode a static array. Handles the size calculations and possible packing. */
+static bool checkreturn encode_array(pb_ostream_t *stream, const pb_field_t *field,
+ const void *pData, size_t count, pb_encoder_t func)
+{
+ size_t i;
+ const void *p;
+ size_t size;
+
+ if (count == 0)
+ return true;
+
+ if (PB_ATYPE(field->type) != PB_ATYPE_POINTER && count > field->array_size)
+ PB_RETURN_ERROR(stream, "array max size exceeded");
+
+ /* We always pack arrays if the datatype allows it. */
+ if (PB_LTYPE(field->type) <= PB_LTYPE_LAST_PACKABLE)
+ {
+ if (!pb_encode_tag(stream, PB_WT_STRING, field->tag))
+ return false;
+
+ /* Determine the total size of packed array. */
+ if (PB_LTYPE(field->type) == PB_LTYPE_FIXED32)
+ {
+ size = 4 * count;
+ }
+ else if (PB_LTYPE(field->type) == PB_LTYPE_FIXED64)
+ {
+ size = 8 * count;
+ }
+ else
+ {
+ pb_ostream_t sizestream = PB_OSTREAM_SIZING;
+ p = pData;
+ for (i = 0; i < count; i++)
+ {
+ if (!func(&sizestream, field, p))
+ return false;
+ p = (const char*)p + field->data_size;
+ }
+ size = sizestream.bytes_written;
+ }
+
+ if (!pb_encode_varint(stream, (pb_uint64_t)size))
+ return false;
+
+ if (stream->callback == NULL)
+ return pb_write(stream, NULL, size); /* Just sizing.. */
+
+ /* Write the data */
+ p = pData;
+ for (i = 0; i < count; i++)
+ {
+ if (!func(stream, field, p))
+ return false;
+ p = (const char*)p + field->data_size;
+ }
+ }
+ else
+ {
+ p = pData;
+ for (i = 0; i < count; i++)
+ {
+ if (!pb_encode_tag_for_field(stream, field))
+ return false;
+
+ /* Normally the data is stored directly in the array entries, but
+ * for pointer-type string and bytes fields, the array entries are
+ * actually pointers themselves also. So we have to dereference once
+ * more to get to the actual data. */
+ if (PB_ATYPE(field->type) == PB_ATYPE_POINTER &&
+ (PB_LTYPE(field->type) == PB_LTYPE_STRING ||
+ PB_LTYPE(field->type) == PB_LTYPE_BYTES))
+ {
+ if (!func(stream, field, *(const void* const*)p))
+ return false;
+ }
+ else
+ {
+ if (!func(stream, field, p))
+ return false;
+ }
+ p = (const char*)p + field->data_size;
+ }
+ }
+
+ return true;
+}
+
+/* In proto3, all fields are optional and are only encoded if their value is "non-zero".
+ * This function implements the check for the zero value. */
+static bool pb_check_proto3_default_value(const pb_field_t *field, const void *pData)
+{
+ pb_type_t type = field->type;
+ const void *pSize = (const char*)pData + field->size_offset;
+
+ if (PB_HTYPE(type) == PB_HTYPE_REQUIRED)
+ {
+ /* Required proto2 fields inside proto3 submessage, pretty rare case */
+ return false;
+ }
+ else if (PB_HTYPE(type) == PB_HTYPE_REPEATED)
+ {
+ /* Repeated fields inside proto3 submessage: present if count != 0 */
+ return *(const pb_size_t*)pSize == 0;
+ }
+ else if (PB_HTYPE(type) == PB_HTYPE_ONEOF)
+ {
+ /* Oneof fields */
+ return *(const pb_size_t*)pSize == 0;
+ }
+ else if (PB_HTYPE(type) == PB_HTYPE_OPTIONAL && field->size_offset != 0)
+ {
+ /* Proto2 optional fields inside proto3 submessage */
+ return *(const bool*)pSize == false;
+ }
+
+ /* Rest is proto3 singular fields */
+
+ if (PB_ATYPE(type) == PB_ATYPE_STATIC)
+ {
+ if (PB_LTYPE(type) == PB_LTYPE_BYTES)
+ {
+ const pb_bytes_array_t *bytes = (const pb_bytes_array_t*)pData;
+ return bytes->size == 0;
+ }
+ else if (PB_LTYPE(type) == PB_LTYPE_STRING)
+ {
+ return *(const char*)pData == '\0';
+ }
+ else if (PB_LTYPE(type) == PB_LTYPE_FIXED_LENGTH_BYTES)
+ {
+ /* Fixed length bytes is only empty if its length is fixed
+ * as 0. Which would be pretty strange, but we can check
+ * it anyway. */
+ return field->data_size == 0;
+ }
+ else if (PB_LTYPE(type) == PB_LTYPE_SUBMESSAGE)
+ {
+ /* Check all fields in the submessage to find if any of them
+ * are non-zero. The comparison cannot be done byte-per-byte
+ * because the C struct may contain padding bytes that must
+ * be skipped.
+ */
+ pb_field_iter_t iter;
+ if (pb_field_iter_begin(&iter, (const pb_field_t*)field->ptr, pb_const_cast(pData)))
+ {
+ do
+ {
+ if (!pb_check_proto3_default_value(iter.pos, iter.pData))
+ {
+ return false;
+ }
+ } while (pb_field_iter_next(&iter));
+ }
+ return true;
+ }
+ }
+
+ {
+ /* Catch-all branch that does byte-per-byte comparison for zero value.
+ *
+ * This is for all pointer fields, and for static PB_LTYPE_VARINT,
+ * UVARINT, SVARINT, FIXED32, FIXED64, EXTENSION fields, and also
+ * callback fields. These all have integer or pointer value which
+ * can be compared with 0.
+ */
+ pb_size_t i;
+ const char *p = (const char*)pData;
+ for (i = 0; i < field->data_size; i++)
+ {
+ if (p[i] != 0)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
+
+/* Encode a field with static or pointer allocation, i.e. one whose data
+ * is available to the encoder directly. */
+static bool checkreturn encode_basic_field(pb_ostream_t *stream,
+ const pb_field_t *field, const void *pData)
+{
+ pb_encoder_t func;
+ bool implicit_has;
+ const void *pSize = &implicit_has;
+
+ func = PB_ENCODERS[PB_LTYPE(field->type)];
+
+ if (field->size_offset)
+ {
+ /* Static optional, repeated or oneof field */
+ pSize = (const char*)pData + field->size_offset;
+ }
+ else if (PB_HTYPE(field->type) == PB_HTYPE_OPTIONAL)
+ {
+ /* Proto3 style field, optional but without explicit has_ field. */
+ implicit_has = !pb_check_proto3_default_value(field, pData);
+ }
+ else
+ {
+ /* Required field, always present */
+ implicit_has = true;
+ }
+
+ if (PB_ATYPE(field->type) == PB_ATYPE_POINTER)
+ {
+ /* pData is a pointer to the field, which contains pointer to
+ * the data. If the 2nd pointer is NULL, it is interpreted as if
+ * the has_field was false.
+ */
+ pData = *(const void* const*)pData;
+ implicit_has = (pData != NULL);
+ }
+
+ switch (PB_HTYPE(field->type))
+ {
+ case PB_HTYPE_REQUIRED:
+ if (!pData)
+ PB_RETURN_ERROR(stream, "missing required field");
+ if (!pb_encode_tag_for_field(stream, field))
+ return false;
+ if (!func(stream, field, pData))
+ return false;
+ break;
+
+ case PB_HTYPE_OPTIONAL:
+ if (*(const bool*)pSize)
+ {
+ if (!pb_encode_tag_for_field(stream, field))
+ return false;
+
+ if (!func(stream, field, pData))
+ return false;
+ }
+ break;
+
+ case PB_HTYPE_REPEATED: {
+ pb_size_t count;
+ if (field->size_offset != 0) {
+ count = *(const pb_size_t*)pSize;
+ } else {
+ count = field->array_size;
+ }
+ if (!encode_array(stream, field, pData, count, func))
+ return false;
+ break;
+ }
+
+ case PB_HTYPE_ONEOF:
+ if (*(const pb_size_t*)pSize == field->tag)
+ {
+ if (!pb_encode_tag_for_field(stream, field))
+ return false;
+
+ if (!func(stream, field, pData))
+ return false;
+ }
+ break;
+
+ default:
+ PB_RETURN_ERROR(stream, "invalid field type");
+ }
+
+ return true;
+}
+
+/* Encode a field with callback semantics. This means that a user function is
+ * called to provide and encode the actual data. */
+static bool checkreturn encode_callback_field(pb_ostream_t *stream,
+ const pb_field_t *field, const void *pData)
+{
+ const pb_callback_t *callback = (const pb_callback_t*)pData;
+
+#ifdef PB_OLD_CALLBACK_STYLE
+ const void *arg = callback->arg;
+#else
+ void * const *arg = &(callback->arg);
+#endif
+
+ if (callback->funcs.encode != NULL)
+ {
+ if (!callback->funcs.encode(stream, field, arg))
+ PB_RETURN_ERROR(stream, "callback error");
+ }
+ return true;
+}
+
+/* Encode a single field of any callback or static type. */
+static bool checkreturn encode_field(pb_ostream_t *stream,
+ const pb_field_t *field, const void *pData)
+{
+ switch (PB_ATYPE(field->type))
+ {
+ case PB_ATYPE_STATIC:
+ case PB_ATYPE_POINTER:
+ return encode_basic_field(stream, field, pData);
+
+ case PB_ATYPE_CALLBACK:
+ return encode_callback_field(stream, field, pData);
+
+ default:
+ PB_RETURN_ERROR(stream, "invalid field type");
+ }
+}
+
+/* Default handler for extension fields. Expects to have a pb_field_t
+ * pointer in the extension->type->arg field. */
+static bool checkreturn default_extension_encoder(pb_ostream_t *stream,
+ const pb_extension_t *extension)
+{
+ const pb_field_t *field = (const pb_field_t*)extension->type->arg;
+
+ if (PB_ATYPE(field->type) == PB_ATYPE_POINTER)
+ {
+ /* For pointer extensions, the pointer is stored directly
+ * in the extension structure. This avoids having an extra
+ * indirection. */
+ return encode_field(stream, field, &extension->dest);
+ }
+ else
+ {
+ return encode_field(stream, field, extension->dest);
+ }
+}
+
+/* Walk through all the registered extensions and give them a chance
+ * to encode themselves. */
+static bool checkreturn encode_extension_field(pb_ostream_t *stream,
+ const pb_field_t *field, const void *pData)
+{
+ const pb_extension_t *extension = *(const pb_extension_t* const *)pData;
+ PB_UNUSED(field);
+
+ while (extension)
+ {
+ bool status;
+ if (extension->type->encode)
+ status = extension->type->encode(stream, extension);
+ else
+ status = default_extension_encoder(stream, extension);
+
+ if (!status)
+ return false;
+
+ extension = extension->next;
+ }
+
+ return true;
+}
+
+/*********************
+ * Encode all fields *
+ *********************/
+
+static void *pb_const_cast(const void *p)
+{
+ /* Note: this casts away const, in order to use the common field iterator
+ * logic for both encoding and decoding. */
+ union {
+ void *p1;
+ const void *p2;
+ } t;
+ t.p2 = p;
+ return t.p1;
+}
+
+bool checkreturn pb_encode(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct)
+{
+ pb_field_iter_t iter;
+ if (!pb_field_iter_begin(&iter, fields, pb_const_cast(src_struct)))
+ return true; /* Empty message type */
+
+ do {
+ if (PB_LTYPE(iter.pos->type) == PB_LTYPE_EXTENSION)
+ {
+ /* Special case for the extension field placeholder */
+ if (!encode_extension_field(stream, iter.pos, iter.pData))
+ return false;
+ }
+ else
+ {
+ /* Regular field */
+ if (!encode_field(stream, iter.pos, iter.pData))
+ return false;
+ }
+ } while (pb_field_iter_next(&iter));
+
+ return true;
+}
+
+bool pb_encode_delimited(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct)
+{
+ return pb_encode_submessage(stream, fields, src_struct);
+}
+
+bool pb_encode_nullterminated(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct)
+{
+ const pb_byte_t zero = 0;
+
+ if (!pb_encode(stream, fields, src_struct))
+ return false;
+
+ return pb_write(stream, &zero, 1);
+}
+
+bool pb_get_encoded_size(size_t *size, const pb_field_t fields[], const void *src_struct)
+{
+ pb_ostream_t stream = PB_OSTREAM_SIZING;
+
+ if (!pb_encode(&stream, fields, src_struct))
+ return false;
+
+ *size = stream.bytes_written;
+ return true;
+}
+
+/********************
+ * Helper functions *
+ ********************/
+
+/* This function avoids 64-bit shifts as they are quite slow on many platforms. */
+static bool checkreturn pb_encode_varint_32(pb_ostream_t *stream, uint32_t low, uint32_t high)
+{
+ size_t i = 0;
+ pb_byte_t buffer[10];
+ pb_byte_t byte = (pb_byte_t)(low & 0x7F);
+ low >>= 7;
+
+ while (i < 4 && (low != 0 || high != 0))
+ {
+ byte |= 0x80;
+ buffer[i++] = byte;
+ byte = (pb_byte_t)(low & 0x7F);
+ low >>= 7;
+ }
+
+ if (high)
+ {
+ byte = (pb_byte_t)(byte | ((high & 0x07) << 4));
+ high >>= 3;
+
+ while (high)
+ {
+ byte |= 0x80;
+ buffer[i++] = byte;
+ byte = (pb_byte_t)(high & 0x7F);
+ high >>= 7;
+ }
+ }
+
+ buffer[i++] = byte;
+
+ return pb_write(stream, buffer, i);
+}
+
+bool checkreturn pb_encode_varint(pb_ostream_t *stream, pb_uint64_t value)
+{
+ if (value <= 0x7F)
+ {
+ /* Fast path: single byte */
+ pb_byte_t byte = (pb_byte_t)value;
+ return pb_write(stream, &byte, 1);
+ }
+ else
+ {
+#ifdef PB_WITHOUT_64BIT
+ return pb_encode_varint_32(stream, value, 0);
+#else
+ return pb_encode_varint_32(stream, (uint32_t)value, (uint32_t)(value >> 32));
+#endif
+ }
+}
+
+bool checkreturn pb_encode_svarint(pb_ostream_t *stream, pb_int64_t value)
+{
+ pb_uint64_t zigzagged;
+ if (value < 0)
+ zigzagged = ~((pb_uint64_t)value << 1);
+ else
+ zigzagged = (pb_uint64_t)value << 1;
+
+ return pb_encode_varint(stream, zigzagged);
+}
+
+bool checkreturn pb_encode_fixed32(pb_ostream_t *stream, const void *value)
+{
+ uint32_t val = *(const uint32_t*)value;
+ pb_byte_t bytes[4];
+ bytes[0] = (pb_byte_t)(val & 0xFF);
+ bytes[1] = (pb_byte_t)((val >> 8) & 0xFF);
+ bytes[2] = (pb_byte_t)((val >> 16) & 0xFF);
+ bytes[3] = (pb_byte_t)((val >> 24) & 0xFF);
+ return pb_write(stream, bytes, 4);
+}
+
+#ifndef PB_WITHOUT_64BIT
+bool checkreturn pb_encode_fixed64(pb_ostream_t *stream, const void *value)
+{
+ uint64_t val = *(const uint64_t*)value;
+ pb_byte_t bytes[8];
+ bytes[0] = (pb_byte_t)(val & 0xFF);
+ bytes[1] = (pb_byte_t)((val >> 8) & 0xFF);
+ bytes[2] = (pb_byte_t)((val >> 16) & 0xFF);
+ bytes[3] = (pb_byte_t)((val >> 24) & 0xFF);
+ bytes[4] = (pb_byte_t)((val >> 32) & 0xFF);
+ bytes[5] = (pb_byte_t)((val >> 40) & 0xFF);
+ bytes[6] = (pb_byte_t)((val >> 48) & 0xFF);
+ bytes[7] = (pb_byte_t)((val >> 56) & 0xFF);
+ return pb_write(stream, bytes, 8);
+}
+#endif
+
+bool checkreturn pb_encode_tag(pb_ostream_t *stream, pb_wire_type_t wiretype, uint32_t field_number)
+{
+ pb_uint64_t tag = ((pb_uint64_t)field_number << 3) | wiretype;
+ return pb_encode_varint(stream, tag);
+}
+
+bool checkreturn pb_encode_tag_for_field(pb_ostream_t *stream, const pb_field_t *field)
+{
+ pb_wire_type_t wiretype;
+ switch (PB_LTYPE(field->type))
+ {
+ case PB_LTYPE_VARINT:
+ case PB_LTYPE_UVARINT:
+ case PB_LTYPE_SVARINT:
+ wiretype = PB_WT_VARINT;
+ break;
+
+ case PB_LTYPE_FIXED32:
+ wiretype = PB_WT_32BIT;
+ break;
+
+ case PB_LTYPE_FIXED64:
+ wiretype = PB_WT_64BIT;
+ break;
+
+ case PB_LTYPE_BYTES:
+ case PB_LTYPE_STRING:
+ case PB_LTYPE_SUBMESSAGE:
+ case PB_LTYPE_FIXED_LENGTH_BYTES:
+ wiretype = PB_WT_STRING;
+ break;
+
+ default:
+ PB_RETURN_ERROR(stream, "invalid field type");
+ }
+
+ return pb_encode_tag(stream, wiretype, field->tag);
+}
+
+bool checkreturn pb_encode_string(pb_ostream_t *stream, const pb_byte_t *buffer, size_t size)
+{
+ if (!pb_encode_varint(stream, (pb_uint64_t)size))
+ return false;
+
+ return pb_write(stream, buffer, size);
+}
+
+bool checkreturn pb_encode_submessage(pb_ostream_t *stream, const pb_field_t fields[], const void *src_struct)
+{
+ /* First calculate the message size using a non-writing substream. */
+ pb_ostream_t substream = PB_OSTREAM_SIZING;
+ size_t size;
+ bool status;
+
+ if (!pb_encode(&substream, fields, src_struct))
+ {
+#ifndef PB_NO_ERRMSG
+ stream->errmsg = substream.errmsg;
+#endif
+ return false;
+ }
+
+ size = substream.bytes_written;
+
+ if (!pb_encode_varint(stream, (pb_uint64_t)size))
+ return false;
+
+ if (stream->callback == NULL)
+ return pb_write(stream, NULL, size); /* Just sizing */
+
+ if (stream->bytes_written + size > stream->max_size)
+ PB_RETURN_ERROR(stream, "stream full");
+
+ /* Use a substream to verify that a callback doesn't write more than
+ * what it did the first time. */
+ substream.callback = stream->callback;
+ substream.state = stream->state;
+ substream.max_size = size;
+ substream.bytes_written = 0;
+#ifndef PB_NO_ERRMSG
+ substream.errmsg = NULL;
+#endif
+
+ status = pb_encode(&substream, fields, src_struct);
+
+ stream->bytes_written += substream.bytes_written;
+ stream->state = substream.state;
+#ifndef PB_NO_ERRMSG
+ stream->errmsg = substream.errmsg;
+#endif
+
+ if (substream.bytes_written != size)
+ PB_RETURN_ERROR(stream, "submsg size changed");
+
+ return status;
+}
+
+/* Field encoders */
+
+static bool checkreturn pb_enc_varint(pb_ostream_t *stream, const pb_field_t *field, const void *src)
+{
+ pb_int64_t value = 0;
+
+ if (field->data_size == sizeof(int_least8_t))
+ value = *(const int_least8_t*)src;
+ else if (field->data_size == sizeof(int_least16_t))
+ value = *(const int_least16_t*)src;
+ else if (field->data_size == sizeof(int32_t))
+ value = *(const int32_t*)src;
+ else if (field->data_size == sizeof(pb_int64_t))
+ value = *(const pb_int64_t*)src;
+ else
+ PB_RETURN_ERROR(stream, "invalid data_size");
+
+#ifdef PB_WITHOUT_64BIT
+ if (value < 0)
+ return pb_encode_varint_32(stream, (uint32_t)value, (uint32_t)-1);
+ else
+#endif
+ return pb_encode_varint(stream, (pb_uint64_t)value);
+}
+
+static bool checkreturn pb_enc_uvarint(pb_ostream_t *stream, const pb_field_t *field, const void *src)
+{
+ pb_uint64_t value = 0;
+
+ if (field->data_size == sizeof(uint_least8_t))
+ value = *(const uint_least8_t*)src;
+ else if (field->data_size == sizeof(uint_least16_t))
+ value = *(const uint_least16_t*)src;
+ else if (field->data_size == sizeof(uint32_t))
+ value = *(const uint32_t*)src;
+ else if (field->data_size == sizeof(pb_uint64_t))
+ value = *(const pb_uint64_t*)src;
+ else
+ PB_RETURN_ERROR(stream, "invalid data_size");
+
+ return pb_encode_varint(stream, value);
+}
+
+static bool checkreturn pb_enc_svarint(pb_ostream_t *stream, const pb_field_t *field, const void *src)
+{
+ pb_int64_t value = 0;
+
+ if (field->data_size == sizeof(int_least8_t))
+ value = *(const int_least8_t*)src;
+ else if (field->data_size == sizeof(int_least16_t))
+ value = *(const int_least16_t*)src;
+ else if (field->data_size == sizeof(int32_t))
+ value = *(const int32_t*)src;
+ else if (field->data_size == sizeof(pb_int64_t))
+ value = *(const pb_int64_t*)src;
+ else
+ PB_RETURN_ERROR(stream, "invalid data_size");
+
+ return pb_encode_svarint(stream, value);
+}
+
+static bool checkreturn pb_enc_fixed64(pb_ostream_t *stream, const pb_field_t *field, const void *src)
+{
+ PB_UNUSED(field);
+#ifndef PB_WITHOUT_64BIT
+ return pb_encode_fixed64(stream, src);
+#else
+ PB_UNUSED(src);
+ PB_RETURN_ERROR(stream, "no 64bit support");
+#endif
+}
+
+static bool checkreturn pb_enc_fixed32(pb_ostream_t *stream, const pb_field_t *field, const void *src)
+{
+ PB_UNUSED(field);
+ return pb_encode_fixed32(stream, src);
+}
+
+static bool checkreturn pb_enc_bytes(pb_ostream_t *stream, const pb_field_t *field, const void *src)
+{
+ const pb_bytes_array_t *bytes = NULL;
+
+ bytes = (const pb_bytes_array_t*)src;
+
+ if (src == NULL)
+ {
+ /* Treat null pointer as an empty bytes field */
+ return pb_encode_string(stream, NULL, 0);
+ }
+
+ if (PB_ATYPE(field->type) == PB_ATYPE_STATIC &&
+ PB_BYTES_ARRAY_T_ALLOCSIZE(bytes->size) > field->data_size)
+ {
+ PB_RETURN_ERROR(stream, "bytes size exceeded");
+ }
+
+ return pb_encode_string(stream, bytes->bytes, bytes->size);
+}
+
+static bool checkreturn pb_enc_string(pb_ostream_t *stream, const pb_field_t *field, const void *src)
+{
+ size_t size = 0;
+ size_t max_size = field->data_size;
+ const char *p = (const char*)src;
+
+ if (PB_ATYPE(field->type) == PB_ATYPE_POINTER)
+ {
+ max_size = (size_t)-1;
+ }
+ else
+ {
+ /* pb_dec_string() assumes string fields end with a null
+ * terminator when the type isn't PB_ATYPE_POINTER, so we
+ * shouldn't allow more than max-1 bytes to be written to
+ * allow space for the null terminator.
+ */
+ if (max_size == 0)
+ PB_RETURN_ERROR(stream, "zero-length string");
+
+ max_size -= 1;
+ }
+
+
+ if (src == NULL)
+ {
+ size = 0; /* Treat null pointer as an empty string */
+ }
+ else
+ {
+ /* strnlen() is not always available, so just use a loop */
+ while (size < max_size && *p != '\0')
+ {
+ size++;
+ p++;
+ }
+
+ if (*p != '\0')
+ {
+ PB_RETURN_ERROR(stream, "unterminated string");
+ }
+ }
+
+ return pb_encode_string(stream, (const pb_byte_t*)src, size);
+}
+
+static bool checkreturn pb_enc_submessage(pb_ostream_t *stream, const pb_field_t *field, const void *src)
+{
+ if (field->ptr == NULL)
+ PB_RETURN_ERROR(stream, "invalid field descriptor");
+
+ return pb_encode_submessage(stream, (const pb_field_t*)field->ptr, src);
+}
+
+static bool checkreturn pb_enc_fixed_length_bytes(pb_ostream_t *stream, const pb_field_t *field, const void *src)
+{
+ return pb_encode_string(stream, (const pb_byte_t*)src, field->data_size);
+}
+
diff --git a/src/lib/ubjson/ubjr.c b/src/lib/ubjson/ubjr.c
new file mode 100644
index 0000000..7bb2b2f
--- /dev/null
+++ b/src/lib/ubjson/ubjr.c
@@ -0,0 +1,525 @@
+#include "ubj.h"
+#include "ubj_internal.h"
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef MULTIPASS_TRACE_MALLOC
+#include "lib/mpmalloc.h"
+#else
+#define mpmalloc malloc
+#define mprealloc realloc
+#define mpfree free
+#endif
+
+#if _MSC_VER
+#define inline __inline
+#endif
+
+typedef struct ubjr_context_t_s
+{
+ size_t (*read_cb )(void* data, size_t size, size_t count, void* userdata);
+ int (*peek_cb )(void* userdata);
+ int (*close_cb)(void* userdata);
+ void (*error_cb)(const char* error_msg);
+
+ void* userdata;
+
+// struct _ubjr_container_t container_stack[CONTAINER_STACK_MAX];
+// struct _ubjr_container_t* head;
+
+ uint8_t ignore_container_flags;
+
+ uint16_t last_error_code;
+
+ size_t total_read;
+} ubjr_context_t;
+
+ubjr_context_t* ubjr_open_callback(void* userdata,
+ size_t(*read_cb)(void* data, size_t size, size_t count, void* userdata),
+ int(*peek_cb)(void* userdata),
+ int(*close_cb)(void* userdata),
+ void(*error_cb)(const char* error_msg)
+ )
+{
+ ubjr_context_t* ctx = (ubjr_context_t*)mpmalloc(sizeof(ubjr_context_t));
+ ctx->userdata = userdata;
+ ctx->read_cb = read_cb;
+ ctx->peek_cb = peek_cb;
+ ctx->close_cb = close_cb;
+ ctx->error_cb = error_cb;
+
+
+/* ctx->head = ctx->container_stack;
+ ctx->head->flags = 0;
+ ctx->head->type = UBJ_MIXED;
+ ctx->head->elements_remaining = 0;
+
+ ctx->ignore_container_flags = 0;*/
+
+ ctx->last_error_code = 0;
+
+ ctx->total_read = 0;
+ return ctx;
+}
+
+size_t ubjr_close_context(ubjr_context_t* ctx)
+{
+ size_t n = ctx->total_read;
+ mpfree(ctx->userdata);
+ mpfree(ctx);
+ return n;
+}
+
+static inline uint8_t priv_ubjr_context_getc(ubjr_context_t* ctx)
+{
+ uint8_t a;
+ ctx->total_read += 1;
+ ctx->read_cb(&a, 1, 1, ctx->userdata);
+ return a;
+}
+
+static int fpeek(void* fp)
+{
+ int c;
+ c = fgetc(fp);
+ ungetc(c, fp);
+
+ return c;
+}
+
+ubjr_context_t* ubjr_open_file(FILE* fd)
+{
+ return ubjr_open_callback(fd, (void*)fread,(void*)fpeek,(void*)fclose, NULL);
+}
+
+struct mem_r_fd
+{
+ const uint8_t *begin, *current, *end;
+};
+static int memclose(void* mfd)
+{
+ //mpfree(mfd);
+ return 0;
+}
+static size_t memread(void* data, size_t size, size_t count, struct mem_r_fd* fp)
+{
+ size_t n = size*count;
+ size_t lim = fp->end - fp->current;
+ if (lim < n)
+ {
+ n = lim;
+ }
+ memcpy(data, fp->current, n);
+ fp->current += n;
+ return n;
+}
+static int mempeek(struct mem_r_fd* mfd)
+{
+ return *mfd->current;
+}
+
+ubjr_context_t* ubjr_open_memory(const uint8_t* be, const uint8_t* en)
+{
+ struct mem_r_fd* mfd = (struct mem_r_fd*)mpmalloc(sizeof(struct mem_r_fd));
+ mfd->current = be;
+ mfd->begin = be;
+ mfd->end = en;
+ return ubjr_open_callback(mfd, (void*)memread, (void*)mempeek,(void*)memclose, NULL);
+}
+
+static inline int priv_ubjr_context_peek(ubjr_context_t* ctx)
+{
+ return ctx->peek_cb(ctx->userdata);
+}
+static inline size_t priv_ubjr_context_read(ubjr_context_t* ctx,uint8_t* dst,size_t n)
+{
+ size_t nr=ctx->read_cb(dst,n,1,ctx->userdata);
+ ctx->total_read+=nr;
+ return nr;
+}
+
+typedef struct priv_ubjr_sorted_key_t_s
+{
+ ubjr_string_t key;
+ const uint8_t* value;
+
+} priv_ubjr_sorted_key_t;
+
+static int _obj_key_cmp(const void* av, const void* bv)
+{
+ const priv_ubjr_sorted_key_t *a, *b;
+ a = (const priv_ubjr_sorted_key_t*)av;
+ b = (const priv_ubjr_sorted_key_t*)bv;
+ return strcmp(a->key,b->key);
+}
+
+static inline UBJ_TYPE priv_ubjr_type_from_char(uint8_t c)
+{
+ int i = 0; //TODO: Benchmark this and see if it should be a switch statement where the compiler implements fastest switch e.g. binary search (17 cases might be binary search fast)
+ for (i = 0; i < UBJ_NUM_TYPES && UBJI_TYPEC_convert[i] != c; i++);
+ return (UBJ_TYPE)i;
+}
+
+size_t ubjr_local_type_size(UBJ_TYPE typ)
+{
+ return UBJR_TYPE_localsize[typ];
+}
+
+
+static inline priv_ubjr_sorted_key_t* priv_ubjr_object_build_sorted_keys(ubjr_object_t* obj)
+{
+ priv_ubjr_sorted_key_t* sorted_keysmem = mpmalloc(obj->size*sizeof(priv_ubjr_sorted_key_t));
+ size_t i;
+ for (i = 0; i < obj->size; i++)
+ {
+ sorted_keysmem[i].key = obj->keys[i];
+ sorted_keysmem[i].value = (const uint8_t*)obj->values + i*UBJR_TYPE_localsize[obj->type];
+ }
+ qsort(sorted_keysmem, obj->size, sizeof(priv_ubjr_sorted_key_t), _obj_key_cmp);
+ return sorted_keysmem;
+}
+
+static inline uint8_t priv_ubjr_read_1b(ubjr_context_t* ctx)
+{
+ return priv_ubjr_context_getc(ctx);
+}
+static inline uint16_t priv_ubjr_read_2b(ubjr_context_t* ctx)
+{
+ return (uint16_t)priv_ubjr_read_1b(ctx) << 8 | (uint16_t)priv_ubjr_read_1b(ctx);
+}
+static inline uint32_t priv_ubjr_read_4b(ubjr_context_t* ctx)
+{
+ return (uint32_t)priv_ubjr_read_2b(ctx) << 16 | (uint32_t)priv_ubjr_read_2b(ctx);
+}
+static inline uint64_t priv_ubjr_read_8b(ubjr_context_t* ctx)
+{
+ return (uint64_t)priv_ubjr_read_4b(ctx) << 32 | (uint64_t)priv_ubjr_read_4b(ctx);
+}
+
+static inline int64_t priv_ubjw_read_integer(ubjr_context_t* ctx)
+{
+ ubjr_dynamic_t d = ubjr_read_dynamic(ctx);
+ if (d.type >= UBJ_INT8 && d.type <= UBJ_INT64)
+ return d.integer;
+ return 0;//error
+}
+
+static inline ubjr_object_t priv_ubjr_read_raw_object(ubjr_context_t* ctx);
+static inline ubjr_array_t priv_ubjr_read_raw_array(ubjr_context_t* ctx);
+static inline void priv_ubjr_read_to_ptr(ubjr_context_t* ctx, uint8_t* dst, UBJ_TYPE typ)
+{
+ int64_t n = 1;
+ char *tstr;
+ switch (typ)
+ {
+ case UBJ_MIXED:
+ {
+ *(ubjr_dynamic_t*)dst = ubjr_read_dynamic(ctx);
+ break;
+ }
+ case UBJ_STRING:
+ case UBJ_HIGH_PRECISION:
+ {
+ n = priv_ubjw_read_integer(ctx);
+ }
+ case UBJ_CHAR:
+ {
+ tstr = mpmalloc(n + 1);
+ priv_ubjr_context_read(ctx, tstr, n);
+ tstr[n] = 0;
+ *(ubjr_string_t*)dst = tstr;
+ break;
+ }
+ case UBJ_INT8:
+ case UBJ_UINT8:
+ {
+ *dst = priv_ubjr_read_1b(ctx);
+ break;
+ }
+ case UBJ_INT16:
+ {
+ *(uint16_t*)dst = priv_ubjr_read_2b(ctx);
+ break;
+ }
+ case UBJ_INT32:
+ case UBJ_FLOAT32:
+ {
+ *(uint32_t*)dst = priv_ubjr_read_4b(ctx);
+ break;
+ }
+ case UBJ_INT64:
+ case UBJ_FLOAT64:
+ {
+ *(uint64_t*)dst = priv_ubjr_read_8b(ctx);
+ break;
+ }
+ case UBJ_ARRAY:
+ {
+ *(ubjr_array_t*)dst = priv_ubjr_read_raw_array(ctx);
+ break;
+ }
+ case UBJ_OBJECT:
+ {
+ *(ubjr_object_t*)dst = priv_ubjr_read_raw_object(ctx);
+ break;
+ }
+ };
+}
+
+ubjr_dynamic_t ubjr_object_lookup(ubjr_object_t* obj, const char* key)
+{
+ if (obj->metatable == NULL)
+ {
+ //memcpy(obj->sorted_keys,obj->keys)
+ obj->metatable = priv_ubjr_object_build_sorted_keys(obj);
+ }
+ void* result=bsearch(key, obj->metatable,obj->size, sizeof(priv_ubjr_sorted_key_t),_obj_key_cmp);
+ if (result == NULL)
+ {
+ ubjr_dynamic_t nulldyn;
+ nulldyn.type = UBJ_NULLTYPE;
+ return nulldyn;
+ }
+ const priv_ubjr_sorted_key_t* result_key = (const priv_ubjr_sorted_key_t*)result;
+ return priv_ubjr_pointer_to_dynamic(obj->type,result_key->value);
+}
+
+size_t ubjr_ndarray_index(const ubjr_array_t* arr, const size_t* indices)
+{
+ //multi-dimensional array to linear array lookup
+ size_t cstride = 1;
+ size_t cdex = 0;
+ uint8_t i;
+ uint8_t nd = arr->num_dims;
+ const size_t* dims = arr->dims;
+ for (i = 0; i<nd; i++)
+ {
+ cdex += cstride*indices[i];
+ cstride *= dims[i];
+ }
+ return cdex;
+}
+
+
+
+ubjr_dynamic_t ubjr_read_dynamic(ubjr_context_t* ctx)
+{
+ ubjr_dynamic_t scratch; //scratch memory
+ UBJ_TYPE newtyp = priv_ubjr_type_from_char(priv_ubjr_context_getc(ctx));
+ priv_ubjr_read_to_ptr(ctx, (uint8_t*)&scratch, newtyp);
+ return priv_ubjr_pointer_to_dynamic(newtyp, &scratch);
+}
+
+static inline void priv_read_container_params(ubjr_context_t* ctx, UBJ_TYPE* typout, size_t* sizeout)
+{
+ int nextchar = priv_ubjr_context_peek(ctx);
+ if (nextchar == '$')
+ {
+ priv_ubjr_context_getc(ctx);
+ *typout = priv_ubjr_type_from_char(priv_ubjr_context_getc(ctx));
+ nextchar = priv_ubjr_context_peek(ctx);
+ }
+ else
+ {
+ *typout = UBJ_MIXED;
+ }
+
+ if (nextchar == '#')
+ {
+ priv_ubjr_context_getc(ctx);
+ *sizeout = priv_ubjw_read_integer(ctx);
+ }
+ else
+ {
+ *sizeout = 0;
+ }
+}
+//TODO: This can be reused for object
+
+static inline ubjr_array_t priv_ubjr_read_raw_array(ubjr_context_t* ctx)
+{
+ ubjr_array_t myarray;
+ priv_read_container_params(ctx,&myarray.type,&myarray.size);
+ myarray.num_dims = 1;
+ myarray.dims = NULL;
+ if (myarray.type != UBJ_MIXED && myarray.size==0) //params detected this is a typed array but no size was detected..possibly an N-D array?
+ {
+ if (priv_ubjr_context_peek(ctx) == '@')
+ {
+ uint8_t dselect;
+ priv_ubjr_context_getc(ctx);//skip over the '@' marker
+ myarray.num_dims = priv_ubjr_context_getc(ctx);//since max is 8, no type indicator needed...always a int7 type
+ myarray.dims = mpmalloc(sizeof(size_t)*myarray.num_dims);
+ myarray.size = 1;
+ for (dselect = 0; dselect < myarray.num_dims; dselect++)
+ {
+ size_t d = priv_ubjw_read_integer(ctx);
+ myarray.dims[dselect] = d;
+ myarray.size *= d;
+ }
+ }
+ }
+
+ size_t ls = UBJR_TYPE_localsize[myarray.type];
+ if (myarray.size == 0)
+ {
+ myarray.originally_sized = 0;
+ size_t arrpot = 0;
+ myarray.values=mpmalloc(1*ls+1); //the +1 is for memory for the 0-size elements
+ for (myarray.size = 0; priv_ubjr_context_peek(ctx) != ']'; myarray.size++)
+ {
+ if (myarray.size >= (1ULL << arrpot))
+ {
+ arrpot ++;
+ myarray.values = mprealloc(myarray.values, (1ULL << arrpot)*ls+1);
+ }
+ priv_ubjr_read_to_ptr(ctx,(uint8_t*)myarray.values + ls*myarray.size,myarray.type);
+ }
+ priv_ubjr_context_getc(ctx); // read the closing ']'
+ }
+ else
+ {
+ myarray.originally_sized = 1;
+ size_t i;
+ myarray.values = mpmalloc(ls*myarray.size+1);
+ size_t sz = UBJI_TYPE_size[myarray.type];
+
+ if (sz >= 0 && myarray.type != UBJ_STRING && myarray.type != UBJ_HIGH_PRECISION && myarray.type != UBJ_CHAR && myarray.type != UBJ_MIXED) //constant size,fastread
+ {
+ priv_ubjr_context_read(ctx, myarray.values, sz*myarray.size);
+ buf_endian_swap(myarray.values, sz, myarray.size); //do nothing for 0-sized buffers
+ }
+ else
+ {
+ for (i = 0; i < myarray.size; i++)
+ {
+ priv_ubjr_read_to_ptr(ctx, (uint8_t*)myarray.values + ls*i, myarray.type);
+ }
+ }
+ }
+ if (myarray.dims == NULL)
+ {
+ myarray.dims = mpmalloc(sizeof(size_t));
+ myarray.dims[0] = myarray.size;
+ }
+ return myarray;
+}
+
+static inline ubjr_object_t priv_ubjr_read_raw_object(ubjr_context_t* ctx)
+{
+ ubjr_object_t myobject;
+ myobject.metatable = NULL;
+ priv_read_container_params(ctx, &myobject.type, &myobject.size);
+
+ size_t ls = UBJR_TYPE_localsize[myobject.type];
+ if (myobject.size == 0)
+ {
+ myobject.originally_sized = 0;
+ size_t arrpot = 0;
+ myobject.values = mpmalloc(1 * ls + 1); //the +1 is for memory for the 0-size elements
+ myobject.keys = mpmalloc(1 * sizeof(ubjr_string_t));
+ for (myobject.size = 0; priv_ubjr_context_peek(ctx) != '}'; myobject.size++)
+ {
+ if (myobject.size >= (1ULL << arrpot))
+ {
+ arrpot++;
+ myobject.values = mprealloc(myobject.values, (1ULL << arrpot)*ls + 1);
+ myobject.keys = mprealloc((uint8_t*)myobject.keys, (1ULL << arrpot)*sizeof(ubjr_string_t));
+ }
+ priv_ubjr_read_to_ptr(ctx, (uint8_t*)(myobject.keys + myobject.size), UBJ_STRING);
+ priv_ubjr_read_to_ptr(ctx, (uint8_t*)myobject.values + ls*myobject.size, myobject.type);
+ }
+ priv_ubjr_context_getc(ctx); // read the closing '}'
+ }
+ else
+ {
+ size_t i;
+ myobject.originally_sized = 1;
+ myobject.values = mpmalloc(ls*myobject.size + 1);
+ myobject.keys = mpmalloc(myobject.size * sizeof(ubjr_string_t));
+
+ for (i = 0; i < myobject.size; i++)
+ {
+ priv_ubjr_read_to_ptr(ctx, (uint8_t*)(myobject.keys + i), UBJ_STRING);
+ priv_ubjr_read_to_ptr(ctx, (uint8_t*)myobject.values + ls*i, myobject.type);
+ }
+ }
+ return myobject;
+}
+static inline void priv_ubjr_cleanup_pointer(UBJ_TYPE typ,void* value);
+static inline priv_ubjr_cleanup_container(UBJ_TYPE type,size_t size,void* values)
+{
+ if(type == UBJ_MIXED || type == UBJ_ARRAY || type == UBJ_OBJECT || type == UBJ_STRING)
+ {
+ size_t ls=UBJR_TYPE_localsize[type];
+ uint8_t *viter,*vend;
+ viter=values;
+ vend=viter+ls*size;
+ for(;viter != vend;viter+=ls)
+ {
+ priv_ubjr_cleanup_pointer(type,(void*)viter);
+ }
+ }
+ mpfree(values);
+}
+static inline void priv_ubjr_cleanup_pointer(UBJ_TYPE typ,void* value)
+{
+ switch(typ)
+ {
+ case UBJ_MIXED:
+ {
+ ubjr_dynamic_t* dyn=(ubjr_dynamic_t*)value;
+ switch(dyn->type)
+ {
+ case UBJ_STRING:
+ priv_ubjr_cleanup_pointer(UBJ_STRING,&dyn->string);
+ break;
+ case UBJ_ARRAY:
+ priv_ubjr_cleanup_pointer(UBJ_ARRAY,&dyn->container_array);
+ break;
+ case UBJ_OBJECT:
+ priv_ubjr_cleanup_pointer(UBJ_OBJECT,&dyn->container_object);
+ break;
+ };
+ break;
+ }
+ case UBJ_STRING:
+ {
+ ubjr_string_t* st=(ubjr_string_t*)value;
+ mpfree((void*)*st);
+ break;
+ }
+ case UBJ_ARRAY:
+ {
+ ubjr_array_t* arr=(ubjr_array_t*)value;
+ priv_ubjr_cleanup_container(arr->type,arr->size,arr->values);
+ mpfree(arr->dims);
+ break;
+ }
+ case UBJ_OBJECT:
+ {
+ ubjr_object_t* obj=(ubjr_object_t*)value;
+ priv_ubjr_cleanup_container(obj->type,obj->size,obj->values);
+ priv_ubjr_cleanup_container(UBJ_STRING,obj->size,obj->keys);
+ if(obj->metatable)
+ {
+ mpfree(obj->metatable);
+ }
+ break;
+ }
+ };
+}
+
+void ubjr_cleanup_dynamic(ubjr_dynamic_t* dyn)
+{
+ priv_ubjr_cleanup_pointer(UBJ_MIXED,dyn);
+}
+void ubjr_cleanup_array(ubjr_array_t* arr)
+{
+ priv_ubjr_cleanup_pointer(UBJ_ARRAY,arr);
+}
+void ubjr_cleanup_object(ubjr_object_t* obj)
+{
+ priv_ubjr_cleanup_pointer(UBJ_OBJECT,obj);
+}
+
diff --git a/src/lib/ubjson/ubjrw.c b/src/lib/ubjson/ubjrw.c
new file mode 100644
index 0000000..ee43d70
--- /dev/null
+++ b/src/lib/ubjson/ubjrw.c
@@ -0,0 +1,169 @@
+#include "ubj_internal.h"
+
+static uint32_t compute_typemask(ubjr_dynamic_t* vals, size_t sz)
+{
+ uint32_t typemask = 0;
+ size_t i;
+ for (i = 0; i < sz; i++)
+ {
+ typemask |= 1UL << vals[i].type;
+ }
+ return typemask;
+}
+
+static inline UBJ_TYPE typemask2type(uint32_t v)
+{
+ unsigned int r = 0; // r will be lg(v)
+
+ while (v >>= 1) // unroll for more speed...
+ {
+ r++;
+ }
+ return (UBJ_TYPE)r;
+}
+static UBJ_TYPE compute_best_integer_type(ubjr_dynamic_t* vals, size_t sz)
+{
+ uint32_t typemask = 0;
+ size_t i;
+ for (i = 0; i < sz; i++)
+ {
+ typemask |= 1UL << ubjw_min_integer_type(vals[i].integer);
+ }
+ return typemask2type(typemask);
+}
+static uint32_t compute_best_string_type(ubjr_dynamic_t* vals, size_t sz)
+{
+ size_t i;
+ for (i = 0; i < sz; i++)
+ {
+ if (strlen(vals[i].string) > 1)
+ {
+ return UBJ_STRING;
+ }
+ }
+ return UBJ_CHAR;
+}
+static UBJ_TYPE optimize_type(UBJ_TYPE typein,ubjr_dynamic_t* vals, size_t sz)
+{
+ static const uint32_t intmask = (1 << UBJ_INT8) | (1 << UBJ_UINT8) | (1 << UBJ_INT16) | (1 << UBJ_INT32) | (1 << UBJ_INT64);
+ static const uint32_t stringmask = (1 << UBJ_STRING) | (1 << UBJ_CHAR);
+ if (typein != UBJ_MIXED)
+ return typein;
+ //integer optimization can be done here...
+ uint32_t tm = compute_typemask(vals, sz);
+ if ((tm & intmask) == tm) //if all values are integers
+ {
+ return compute_best_integer_type(vals,sz); //calculate the optimum type given the data
+ }
+ else if ((tm & stringmask) == tm)
+ {
+ return compute_best_string_type(vals,sz);
+ }
+ else if(tm && !(tm & (tm- 1))) //if only one bit is set in typemask
+ {
+ return typemask2type(tm); //figure out which bit is set.
+ }
+ else
+ {
+ return UBJ_MIXED;
+ }
+}
+
+void ubjrw_write_dynamic(ubjw_context_t* ctx, ubjr_dynamic_t dobj,uint8_t optimize)
+{
+ UBJ_TYPE ctyp,otyp;
+ size_t csize;
+ uint8_t* cvalues;
+ switch (dobj.type)
+ {
+ case UBJ_MIXED:
+ return;///error, can't be mixed
+ case UBJ_NULLTYPE:
+ ubjw_write_null(ctx);
+ return;
+ case UBJ_NOOP:
+ ubjw_write_noop(ctx);
+ return;
+ case UBJ_BOOL_FALSE:
+ ubjw_write_bool(ctx, 0);
+ return;
+ case UBJ_BOOL_TRUE:
+ ubjw_write_bool(ctx, 1);
+ return;
+ case UBJ_CHAR:
+ ubjw_write_char(ctx, *dobj.string);//first character of string
+ return;
+ case UBJ_STRING:
+ ubjw_write_string(ctx, dobj.string);
+ return;
+ case UBJ_HIGH_PRECISION:
+ ubjw_write_high_precision(ctx, dobj.string);
+ return;
+ case UBJ_INT8:
+ ubjw_write_int8(ctx, (int8_t)dobj.integer);
+ return;
+ case UBJ_UINT8:
+ ubjw_write_uint8(ctx, (uint8_t)dobj.integer);
+ return;
+ case UBJ_INT16:
+ ubjw_write_int16(ctx, (int16_t)dobj.integer);
+ return;
+ case UBJ_INT32:
+ ubjw_write_int32(ctx, (int32_t)dobj.integer);
+ return;
+ case UBJ_INT64:
+ ubjw_write_int64(ctx, dobj.integer);
+ return;
+ case UBJ_FLOAT32:
+ ubjw_write_float32(ctx, (float)dobj.real);
+ return;
+ case UBJ_FLOAT64:
+ ubjw_write_float64(ctx, dobj.real);
+ return;
+ case UBJ_ARRAY:
+ if ((dobj.container_array.originally_sized || optimize) //if we optimize an unsized array to a sized one or the original is sized
+ && dobj.container_array.type != UBJ_MIXED
+ && dobj.container_array.type != UBJ_OBJECT
+ && dobj.container_array.type != UBJ_ARRAY)
+ {
+ ubjw_write_buffer(ctx, dobj.container_array.values, dobj.container_array.type, dobj.container_array.size);
+ return;
+ }
+ else
+ {
+ ctyp = dobj.container_array.type;
+ csize = dobj.container_array.size;
+ cvalues = dobj.container_array.values;
+ otyp = optimize ? optimize_type(ctyp,(ubjr_dynamic_t*)cvalues,csize) : ctyp;
+ ubjw_begin_array(ctx, otyp, (dobj.container_array.originally_sized || optimize) ? csize : 0);
+ break;
+ }
+ case UBJ_OBJECT:
+ {
+ ctyp = dobj.container_object.type;
+ csize = dobj.container_object.size;
+ cvalues = dobj.container_object.values;
+ otyp = optimize ? optimize_type(ctyp, (ubjr_dynamic_t*)cvalues, csize) : ctyp;
+ ubjw_begin_object(ctx, otyp, (dobj.container_object.originally_sized || optimize) ? csize : 0);
+ break;
+ }
+ };
+ {
+ size_t i;
+ ubjr_dynamic_t scratch;
+ size_t ls = UBJR_TYPE_localsize[ctyp];
+
+ for (i = 0; i < csize; i++)
+ {
+ if (dobj.type == UBJ_OBJECT)
+ {
+ ubjw_write_key(ctx, dobj.container_object.keys[i]);
+ }
+ scratch = priv_ubjr_pointer_to_dynamic(ctyp, cvalues + ls*i);
+ scratch.type = (otyp == UBJ_MIXED ? scratch.type : otyp);
+ ubjrw_write_dynamic(ctx, scratch,optimize);
+ }
+ ubjw_end(ctx);
+ }
+
+}
diff --git a/src/lib/ubjson/ubjw.c b/src/lib/ubjson/ubjw.c
new file mode 100644
index 0000000..9fce397
--- /dev/null
+++ b/src/lib/ubjson/ubjw.c
@@ -0,0 +1,626 @@
+#include "ubj.h"
+#include "ubj_internal.h"
+
+#ifdef MULTIPASS_TRACE_MALLOC
+#include "lib/mpmalloc.h"
+#else
+#define mpmalloc malloc
+#define mpfree free
+#endif
+
+#define CONTAINER_IS_SIZED 0x1
+#define CONTAINER_IS_TYPED 0x2
+#define CONTAINER_IS_UBJ_ARRAY 0x4
+#define CONTAINER_IS_UBJ_OBJECT 0x8
+
+#define CONTAINER_EXPECTS_KEY 0x10
+
+#define CONTAINER_STACK_MAX 16
+#define BUFFER_OUT_SIZE 1024
+
+#define MAX_DIMS 8
+
+
+struct priv_ubjw_container_t
+{
+ uint8_t flags;
+ UBJ_TYPE type;
+ size_t elements_remaining;
+};
+
+struct ubjw_context_t_s
+{
+ size_t(*write_cb)(const void* data, size_t size, size_t count, void* userdata);
+ int(*close_cb)(void* userdata);
+ void (*error_cb)(const char* error_msg);
+
+ void* userdata;
+
+ struct priv_ubjw_container_t container_stack[CONTAINER_STACK_MAX];
+ struct priv_ubjw_container_t* head;
+
+ uint8_t ignore_container_flags;
+
+ uint16_t last_error_code;
+
+ size_t total_written;
+};
+
+
+
+ubjw_context_t* ubjw_open_callback(void* userdata,
+ size_t(*write_cb)(const void* data, size_t size, size_t count, void* userdata),
+ int(*close_cb)(void* userdata),
+ void (*error_cb)(const char* error_msg)
+ )
+{
+ ubjw_context_t* ctx = (ubjw_context_t*)mpmalloc(sizeof(ubjw_context_t));
+ ctx->userdata = userdata;
+ ctx->write_cb = write_cb;
+ ctx->close_cb = close_cb;
+ ctx->error_cb = error_cb;
+
+ ctx->head = ctx->container_stack;
+ ctx->head->flags = 0;
+ ctx->head->type = UBJ_MIXED;
+ ctx->head->elements_remaining = 0;
+ //ctx->head->num_dims=1;
+
+ ctx->ignore_container_flags = 0;
+
+ ctx->last_error_code = 0;
+
+ ctx->total_written = 0;
+ return ctx;
+}
+ubjw_context_t* ubjw_open_file(FILE* fd)
+{
+ return ubjw_open_callback(fd, (void*)fwrite,(void*)fclose,NULL);
+}
+
+struct mem_w_fd
+{
+ uint8_t *begin,*current, *end;
+};
+
+static int memclose(void* mfd)
+{
+ mpfree(mfd);
+ return 0;
+}
+static size_t memwrite(const void* data, size_t size, size_t count, struct mem_w_fd* fp)
+{
+ size_t n = size*count;
+ size_t lim = fp->end - fp->current;
+ if (lim < n)
+ {
+ n = lim;
+ }
+ memcpy(fp->current, data, n);
+ fp->current += n;
+ return n;
+}
+
+ubjw_context_t* ubjw_open_memory(uint8_t* be, uint8_t* en)
+{
+ struct mem_w_fd* mfd = (struct mem_w_fd*)mpmalloc(sizeof(struct mem_w_fd));
+ mfd->current = be;
+ mfd->begin = be;
+ mfd->end = en;
+ return ubjw_open_callback(mfd, (void*)memwrite, (void*)memclose,NULL);
+}
+
+static inline void priv_ubjw_context_append(ubjw_context_t* ctx, uint8_t a)
+{
+ ctx->total_written += 1;
+ ctx->write_cb(&a, 1, 1, ctx->userdata);
+}
+
+static inline void priv_disassembly_begin(ubjw_context_t* ctx)
+{
+#ifdef UBJW_DISASSEMBLY_MODE
+ priv_ubjw_context_append(ctx, (uint8_t)'[');
+#endif
+}
+static inline void priv_disassembly_end(ubjw_context_t* ctx)
+{
+#ifdef UBJW_DISASSEMBLY_MODE
+ priv_ubjw_context_append(ctx, (uint8_t)']');
+#endif
+}
+static inline void priv_disassembly_indent(ubjw_context_t* ctx)
+{
+#ifdef UBJW_DISASSEMBLY_MODE
+ int n = ctx->head - ctx->container_stack;
+ int i;
+ priv_ubjw_context_append(ctx, (uint8_t)'\n');
+ for (i = 0; i < n; i++)
+ {
+ priv_ubjw_context_append(ctx, (uint8_t)'\t');
+ }
+#endif
+}
+
+static inline void priv_ubjw_context_finish_container(ubjw_context_t* ctx, struct priv_ubjw_container_t* head)
+{
+ if (head->flags & CONTAINER_IS_SIZED)
+ {
+ if (head->elements_remaining > 0)
+ {
+ //error not all elements written
+ }
+ }
+ else
+ {
+ priv_disassembly_begin(ctx);
+ if (head->flags & CONTAINER_IS_UBJ_ARRAY)
+ {
+ priv_ubjw_context_append(ctx, (uint8_t)']');
+ }
+ else if (head->flags & CONTAINER_IS_UBJ_OBJECT)
+ {
+ priv_ubjw_context_append(ctx, (uint8_t)'}');
+ }
+ priv_disassembly_end(ctx);
+ }
+}
+
+static inline priv_ubjw_container_stack_push(ubjw_context_t* ctx, const struct priv_ubjw_container_t* cnt)
+{
+ size_t height = ctx->head-ctx->container_stack+1;
+ if(height < CONTAINER_STACK_MAX)
+ {
+ *(++(ctx->head))=*cnt;
+ }
+ else
+ {
+ //todo::error
+ }
+}
+static inline struct priv_ubjw_container_t priv_ubjw_container_stack_pop(ubjw_context_t* ctx)
+{
+ return *ctx->head--;
+}
+
+size_t ubjw_close_context(ubjw_context_t* ctx)
+{
+ while (ctx->head > ctx->container_stack)
+ {
+ struct priv_ubjw_container_t cnt = priv_ubjw_container_stack_pop(ctx);
+ priv_ubjw_context_finish_container(ctx, &cnt);
+ };
+ size_t n = ctx->total_written;
+ if (ctx->close_cb)
+ ctx->close_cb(ctx->userdata);
+ mpfree(ctx);
+ return n;
+}
+
+
+static inline size_t priv_ubjw_context_write(ubjw_context_t* ctx, const uint8_t* data, size_t sz)
+{
+ ctx->total_written += sz;
+ return ctx->write_cb(data, 1, sz, ctx->userdata);
+}
+
+static inline void priv_ubjw_tag_public(ubjw_context_t* ctx, UBJ_TYPE tid)
+{
+ struct priv_ubjw_container_t* ch = ctx->head;
+ if (!ctx->ignore_container_flags)
+ {
+
+ /*if (
+ (!(ch->flags & (CONTAINER_IS_UBJ_ARRAY | CONTAINER_IS_UBJ_OBJECT))) &&
+ (tid != UBJ_ARRAY && tid !=UBJ_OBJECT))
+ {
+ //error, only array and object can be first written
+ }*/
+
+ if (ch->flags & CONTAINER_IS_UBJ_OBJECT)
+ {
+ if (ch->flags & CONTAINER_EXPECTS_KEY)
+ {
+ //error,a key expected
+ return;
+ }
+ ch->flags |= CONTAINER_EXPECTS_KEY; //set key expected next time in this context
+ }
+ else
+ {
+ priv_disassembly_indent(ctx);
+ }
+
+ if (ch->flags & CONTAINER_IS_SIZED)
+ {
+ ch->elements_remaining--; //todo: error if elements remaining is 0;
+ }
+
+ if ((ch->flags & CONTAINER_IS_TYPED) && ch->type == tid)
+ {
+ return;
+ }
+ }
+ priv_disassembly_begin(ctx);
+ priv_ubjw_context_append(ctx, UBJI_TYPEC_convert[tid]);
+ priv_disassembly_end(ctx);
+}
+
+static inline void priv_ubjw_write_raw_string(ubjw_context_t* ctx, const char* out)//TODO: possibly use a safe string
+{
+ size_t n = strlen(out);
+ ctx->ignore_container_flags = 1;
+ ubjw_write_integer(ctx, (int64_t)n);
+ ctx->ignore_container_flags = 0;
+ priv_disassembly_begin(ctx);
+ priv_ubjw_context_write(ctx, (const uint8_t*)out, n);
+ priv_disassembly_end(ctx);
+}
+void ubjw_write_string(ubjw_context_t* ctx, const char* out)
+{
+ priv_ubjw_tag_public(ctx,UBJ_STRING);
+ priv_ubjw_write_raw_string(ctx, out);
+}
+
+static inline void priv_ubjw_write_raw_char(ubjw_context_t* ctx, char out)
+{
+ priv_disassembly_begin(ctx);
+ priv_ubjw_context_append(ctx, (uint8_t)out);
+ priv_disassembly_end(ctx);
+}
+void ubjw_write_char(ubjw_context_t* ctx, char out)
+{
+ priv_ubjw_tag_public(ctx,UBJ_CHAR);
+ priv_ubjw_write_raw_char(ctx, out);
+}
+
+#ifndef min
+static inline size_t min(size_t x,size_t y)
+{
+ return x < y ? x : y;
+}
+#endif
+
+#ifdef UBJW_DISASSEMBLY_MODE
+#include <stdarg.h>
+#define DISASSEMBLY_PRINT_BUFFER_SIZE 1024
+
+static inline priv_disassembly_print(ubjw_context_t* ctx, const char* format,...)
+{
+ char buffer[DISASSEMBLY_PRINT_BUFFER_SIZE];
+ va_list args;
+ va_start(args, format);
+ int n=vsnprintf(buffer, DISASSEMBLY_PRINT_BUFFER_SIZE, format, args);
+ n = min(n, DISASSEMBLY_PRINT_BUFFER_SIZE);
+ priv_ubjw_context_write(ctx, buffer,n);
+ va_end(args);
+}
+#endif
+
+static inline void priv_ubjw_write_raw_uint8(ubjw_context_t* ctx, uint8_t out)
+{
+ priv_disassembly_begin(ctx);
+#ifndef UBJW_DISASSEMBLY_MODE
+ priv_ubjw_context_append(ctx, out);
+#else
+ priv_disassembly_print(ctx, "%hhu", out);
+#endif
+ priv_disassembly_end(ctx);
+}
+void ubjw_write_uint8(ubjw_context_t* ctx, uint8_t out)
+{
+ priv_ubjw_tag_public(ctx,UBJ_UINT8);
+ priv_ubjw_write_raw_uint8(ctx, out);
+}
+
+static inline void priv_ubjw_write_raw_int8(ubjw_context_t* ctx, int8_t out)
+{
+ priv_disassembly_begin(ctx);
+#ifndef UBJW_DISASSEMBLY_MODE
+ priv_ubjw_context_append(ctx, *(uint8_t*)&out);
+#else
+ priv_disassembly_print(ctx, "%hhd", out);
+#endif
+ priv_disassembly_end(ctx);
+}
+void ubjw_write_int8(ubjw_context_t* ctx, int8_t out)
+{
+ priv_ubjw_tag_public(ctx,UBJ_INT8);
+ priv_ubjw_write_raw_int8(ctx, out);
+}
+
+static inline void priv_ubjw_write_raw_int16(ubjw_context_t* ctx, int16_t out)
+{
+ priv_disassembly_begin(ctx);
+#ifndef UBJW_DISASSEMBLY_MODE
+ uint8_t buf[2];
+ _to_bigendian16(buf, *(uint16_t*)&out);
+ priv_ubjw_context_write(ctx, buf, 2);
+#else
+ priv_disassembly_print(ctx, "%hd", out);
+#endif
+ priv_disassembly_end(ctx);
+}
+void ubjw_write_int16(ubjw_context_t* ctx, int16_t out)
+{
+ priv_ubjw_tag_public(ctx,UBJ_INT16);
+ priv_ubjw_write_raw_int16(ctx, out);
+}
+static inline void priv_ubjw_write_raw_int32(ubjw_context_t* ctx, int32_t out)
+{
+ priv_disassembly_begin(ctx);
+#ifndef UBJW_DISASSEMBLY_MODE
+ uint8_t buf[4];
+ _to_bigendian32(buf, *(uint32_t*)&out);
+ priv_ubjw_context_write(ctx, buf, 4);
+#else
+ priv_disassembly_print(ctx, "%ld", out);
+#endif
+ priv_disassembly_end(ctx);
+}
+void ubjw_write_int32(ubjw_context_t* ctx, int32_t out)
+{
+ priv_ubjw_tag_public(ctx,UBJ_INT32);
+ priv_ubjw_write_raw_int32(ctx, out);
+}
+static inline void priv_ubjw_write_raw_int64(ubjw_context_t* ctx, int64_t out)
+{
+ priv_disassembly_begin(ctx);
+#ifndef UBJW_DISASSEMBLY_MODE
+ uint8_t buf[8];
+ _to_bigendian64(buf, *(uint64_t*)&out);
+ priv_ubjw_context_write(ctx, buf, 8);
+#else
+ priv_disassembly_print(ctx, "%lld", out);
+#endif
+ priv_disassembly_end(ctx);
+}
+void ubjw_write_int64(ubjw_context_t* ctx, int64_t out)
+{
+ priv_ubjw_tag_public(ctx,UBJ_INT64);
+ priv_ubjw_write_raw_int64(ctx, out);
+}
+
+void ubjw_write_high_precision(ubjw_context_t* ctx, const char* hp)
+{
+ priv_ubjw_tag_public(ctx,UBJ_HIGH_PRECISION);
+ priv_ubjw_write_raw_string(ctx, hp);
+}
+UBJ_TYPE ubjw_min_integer_type(int64_t in)
+{
+ uint64_t mc = llabs(in);
+ if (mc < 0x80)
+ {
+ return UBJ_INT8;
+ }
+ else if (in > 0 && mc < 0x100)
+ {
+ return UBJ_UINT8;
+ }
+ else if (mc < 0x8000)
+ {
+ return UBJ_INT16;
+ }
+ else if (mc < 0x80000000)
+ {
+ return UBJ_INT32;
+ }
+ else
+ {
+ return UBJ_INT64;
+ }
+}
+
+void ubjw_write_integer(ubjw_context_t* ctx, int64_t out)
+{
+ switch (ubjw_min_integer_type(out))
+ {
+ case UBJ_INT8:
+ ubjw_write_int8(ctx, (int8_t)out);
+ break;
+ case UBJ_UINT8:
+ ubjw_write_uint8(ctx, (uint8_t)out);
+ break;
+ case UBJ_INT16:
+ ubjw_write_int16(ctx, (int16_t)out);
+ break;
+ case UBJ_INT32:
+ ubjw_write_int32(ctx, (int32_t)out);
+ break;
+ default:
+ ubjw_write_int64(ctx, out);
+ break;
+ };
+}
+
+static inline void priv_ubjw_write_raw_float32(ubjw_context_t* ctx, float out)
+{
+ priv_disassembly_begin(ctx);
+#ifndef UBJW_DISASSEMBLY_MODE
+ uint32_t fout = *(uint32_t*)&out;
+ uint8_t outbuf[4];
+ _to_bigendian32(outbuf, fout);
+ priv_ubjw_context_write(ctx, outbuf, 4);
+#else
+ priv_disassembly_print(ctx, "%g", out);
+#endif
+ priv_disassembly_end(ctx);
+
+}
+void ubjw_write_float32(ubjw_context_t* ctx, float out)
+{
+ priv_ubjw_tag_public(ctx,UBJ_FLOAT32);
+ priv_ubjw_write_raw_float32(ctx, out);
+}
+static inline void priv_ubjw_write_raw_float64(ubjw_context_t* ctx, double out)
+{
+ priv_disassembly_begin(ctx);
+#ifndef UBJW_DISASSEMBLY_MODE
+ uint64_t fout = *(uint64_t*)&out;
+ uint8_t outbuf[8];
+ _to_bigendian64(outbuf, fout);
+ priv_ubjw_context_write(ctx, outbuf, 8);
+#else
+ priv_disassembly_print(ctx, "%g", out);
+#endif
+ priv_disassembly_end(ctx);
+}
+void ubjw_write_float64(ubjw_context_t* ctx, double out)
+{
+ priv_ubjw_tag_public(ctx,UBJ_FLOAT64);
+ priv_ubjw_write_raw_float64(ctx, out);
+}
+
+void ubjw_write_floating_point(ubjw_context_t* ctx, double out)
+{
+ //this may not be possible to implement correctly...for now we just write it as a float64'
+ ubjw_write_float64(ctx,out);
+}
+
+void ubjw_write_noop(ubjw_context_t* ctx)
+{
+ priv_ubjw_tag_public(ctx,UBJ_NOOP);
+}
+void ubjw_write_null(ubjw_context_t* ctx)
+{
+ priv_ubjw_tag_public(ctx,UBJ_NULLTYPE);
+}
+void ubjw_write_bool(ubjw_context_t* ctx, uint8_t out)
+{
+ priv_ubjw_tag_public(ctx,(out ? UBJ_BOOL_TRUE : UBJ_BOOL_FALSE));
+}
+
+void priv_ubjw_begin_container(struct priv_ubjw_container_t* cnt, ubjw_context_t* ctx, UBJ_TYPE typ, size_t count)
+{
+ cnt->flags=0;
+ cnt->elements_remaining = count;
+ cnt->type = typ;
+
+ if (typ != UBJ_MIXED)
+ {
+ if (count == 0)
+ {
+ //error and return;
+ }
+ priv_disassembly_begin(ctx);
+ priv_ubjw_context_append(ctx, '$');
+ priv_disassembly_end(ctx);
+
+ priv_disassembly_begin(ctx);
+ priv_ubjw_context_append(ctx, UBJI_TYPEC_convert[typ]);
+ priv_disassembly_end(ctx);
+
+ cnt->flags |= CONTAINER_IS_TYPED;
+ }
+ if (count != 0)
+ {
+ priv_disassembly_begin(ctx);
+ priv_ubjw_context_append(ctx, '#');
+ priv_disassembly_end(ctx);
+
+ ctx->ignore_container_flags = 1;
+ ubjw_write_integer(ctx, (int64_t)count);
+ ctx->ignore_container_flags = 0;
+
+ cnt->flags |= CONTAINER_IS_SIZED;
+ cnt->elements_remaining = count;
+ }
+}
+void ubjw_begin_array(ubjw_context_t* ctx, UBJ_TYPE type, size_t count)
+{
+ priv_ubjw_tag_public(ctx, UBJ_ARRAY); //todo: should this happen before any erro potential?
+ struct priv_ubjw_container_t ch;
+ priv_ubjw_begin_container(&ch, ctx, type, count);
+ ch.flags |= CONTAINER_IS_UBJ_ARRAY;
+ priv_ubjw_container_stack_push(ctx, &ch);
+}
+void ubjw_begin_ndarray(ubjw_context_t* dst, UBJ_TYPE type, const size_t* dims, uint8_t ndims);
+void ubjw_write_ndbuffer(ubjw_context_t* dst, const uint8_t* data, UBJ_TYPE type, const size_t* dims, uint8_t ndims);
+
+void ubjw_begin_object(ubjw_context_t* ctx, UBJ_TYPE type, size_t count)
+{
+ priv_ubjw_tag_public(ctx, UBJ_OBJECT);
+ struct priv_ubjw_container_t ch;
+ priv_ubjw_begin_container(&ch, ctx, type, count);
+ ch.flags |= CONTAINER_EXPECTS_KEY | CONTAINER_IS_UBJ_OBJECT;
+ priv_ubjw_container_stack_push(ctx, &ch);
+}
+void ubjw_write_key(ubjw_context_t* ctx, const char* key)
+{
+ if (ctx->head->flags & CONTAINER_EXPECTS_KEY && ctx->head->flags & CONTAINER_IS_UBJ_OBJECT)
+ {
+ priv_disassembly_indent(ctx);
+ priv_ubjw_write_raw_string(ctx, key);
+ ctx->head->flags ^= CONTAINER_EXPECTS_KEY; //turn off container
+ }
+ else
+ {
+ //error unexpected key
+ }
+}
+void ubjw_end(ubjw_context_t* ctx)
+{
+ struct priv_ubjw_container_t ch = priv_ubjw_container_stack_pop(ctx);
+ if ((ch.flags & CONTAINER_IS_UBJ_OBJECT) && !(ch.flags & CONTAINER_EXPECTS_KEY))
+ {
+ //error expected value
+ }
+ priv_disassembly_indent(ctx);
+ priv_ubjw_context_finish_container(ctx, &ch);
+}
+
+
+// Not used by benchmark.py -> high BUFFER_OUT_SIZE does not matter
+static inline void priv_ubjw_write_byteswap(ubjw_context_t* ctx, const uint8_t* data, int sz, size_t count)
+{
+ uint8_t buf[BUFFER_OUT_SIZE];
+
+ size_t i;
+ size_t nbytes = sz*count;
+ for (i = 0; i < nbytes; i+=BUFFER_OUT_SIZE)
+ {
+ size_t npass = min(nbytes - i, BUFFER_OUT_SIZE);
+ memcpy(buf, data + i, npass);
+ buf_endian_swap(buf, sz, npass/sz);
+ priv_ubjw_context_write(ctx, buf, npass);
+ }
+}
+void ubjw_write_buffer(ubjw_context_t* ctx, const uint8_t* data, UBJ_TYPE type, size_t count)
+{
+ int typesz = UBJI_TYPE_size[type];
+ if (typesz < 0)
+ {
+ //error cannot write an STC buffer of this type.
+ }
+ ubjw_begin_array(ctx, type, count);
+ if (type == UBJ_STRING || type == UBJ_HIGH_PRECISION)
+ {
+ const char** databufs = (const char**)data;
+ size_t i;
+ for (i = 0; i < count; i++)
+ {
+ priv_ubjw_write_raw_string(ctx, databufs[i]);
+ }
+ }
+#ifndef UBJW_DISASSEMBLY_MODE
+ else if (typesz == 1 || _is_bigendian())
+ {
+ size_t n = typesz*count;
+ priv_ubjw_context_write(ctx, data, typesz*count);
+ }
+ else if (typesz > 1) //and not big-endian
+ {
+ priv_ubjw_write_byteswap(ctx, data,typesz,count);
+ }
+#else
+ else
+ {
+ size_t i;
+ for (i = 0; i < count; i++)
+ {
+ ubjr_dynamic_t dyn = priv_ubjr_pointer_to_dynamic(type, data + i*typesz);
+ ubjrw_write_dynamic(ctx, dyn, 0);
+ }
+ }
+#endif
+ ubjw_end(ctx);
+}
diff --git a/src/lib/xdr.cc b/src/lib/xdr.cc
new file mode 100644
index 0000000..b44471f
--- /dev/null
+++ b/src/lib/xdr.cc
@@ -0,0 +1,188 @@
+#include "lib/xdr.h"
+
+void XDRWriter::put(uint32_t number)
+{
+ *buffer++ = ((number >> 24) & 0xffU);
+ *buffer++ = ((number >> 16) & 0xffU);
+ *buffer++ = ((number >> 8) & 0xffU);
+ *buffer++ = (number & 0xffU);
+ pos += 4;
+}
+
+void XDRWriter::put(int32_t number)
+{
+ *buffer++ = ((number >> 24) & 0xffU);
+ *buffer++ = ((number >> 16) & 0xffU);
+ *buffer++ = ((number >> 8) & 0xffU);
+ *buffer++ = (number & 0xffU);
+ pos += 4;
+}
+
+void XDRWriter::put(uint64_t number)
+{
+ *buffer++ = ((number >> 56) & 0xffU);
+ *buffer++ = ((number >> 48) & 0xffU);
+ *buffer++ = ((number >> 40) & 0xffU);
+ *buffer++ = ((number >> 32) & 0xffU);
+ *buffer++ = ((number >> 24) & 0xffU);
+ *buffer++ = ((number >> 16) & 0xffU);
+ *buffer++ = ((number >> 8) & 0xffU);
+ *buffer++ = (number & 0xffU);
+ pos += 8;
+}
+
+void XDRWriter::put(int64_t number)
+{
+ *buffer++ = ((number >> 56) & 0xffU);
+ *buffer++ = ((number >> 48) & 0xffU);
+ *buffer++ = ((number >> 40) & 0xffU);
+ *buffer++ = ((number >> 32) & 0xffU);
+ *buffer++ = ((number >> 24) & 0xffU);
+ *buffer++ = ((number >> 16) & 0xffU);
+ *buffer++ = ((number >> 8) & 0xffU);
+ *buffer++ = (number & 0xffU);
+ pos += 8;
+}
+
+void XDRWriter::put(float number)
+{
+ union {
+ uint32_t i;
+ float f;
+ } v;
+ // Setting one member of a struct and then reading another is undefined
+ // behaviour, but works as intended in nearly any (embedded) compiler
+ v.f = number;
+ put(v.i);
+}
+
+void XDRWriter::put(double number)
+{
+ union {
+ uint64_t i;
+ double d;
+ } v;
+ // Setting one member of a struct and then reading another is undefined
+ // behaviour, but works as intended in nearly any (embedded) compiler
+ v.d = number;
+ put(v.i);
+}
+
+void XDRWriter::put(char const *data)
+{
+ if (!is_fixed_length)
+ {
+ put(next_array_len);
+ }
+ uint32_t i;
+ for (i = 0; i < next_array_len; i++)
+ {
+ *buffer++ = data[i];
+ }
+ while ((i++) % 4 != 0)
+ {
+ *buffer++ = 0;
+ }
+ pos += i - 1;
+}
+
+template <uint32_t TSize>
+void XDRWriter::put(char const (&data)[TSize])
+{
+ if (!is_fixed_length)
+ {
+ put(TSize);
+ }
+ uint32_t i;
+ for (i = 0; i < TSize; i++)
+ {
+ *buffer++ = data[i];
+ }
+ while ((i++) % 4 != 0)
+ {
+ *buffer++ = 0;
+ }
+ pos += i - 1;
+}
+
+uint32_t XDRReader::get_uint32()
+{
+ uint32_t ret = (((uint32_t)data[pos] & 0xffU) << 24) | (((uint32_t)data[pos + 1] & 0xffU) << 16) | (((uint32_t)data[pos + 2] & 0xffU) << 8) | ((uint32_t)data[pos + 3] & 0xffU);
+ pos += 4;
+ return ret;
+}
+
+int32_t XDRReader::get_int32()
+{
+ int32_t ret = (((int32_t)data[pos] & 0xff) << 24) | (((int32_t)data[pos + 1] & 0xff) << 16) | (((int32_t)data[pos + 2] & 0xff) << 8) | ((int32_t)data[pos + 3] & 0xff);
+ pos += 4;
+ return ret;
+}
+
+uint64_t XDRReader::get_uint64()
+{
+ uint64_t ret = (((uint64_t)data[pos] & 0xffU) << 56) | (((uint64_t)data[pos + 1] & 0xffU) << 48) | (((uint64_t)data[pos + 2] & 0xffU) << 40) | (((uint64_t)data[pos + 3] & 0xffU) << 32) | (((uint64_t)data[pos + 4] & 0xffU) << 24) | (((uint64_t)data[pos + 5] & 0xffU) << 16) | (((uint64_t)data[pos + 6] & 0xffU) << 8) | ((uint64_t)data[pos + 7] & 0xffU);
+ return ret;
+}
+
+int64_t XDRReader::get_int64()
+{
+ int64_t ret = (((int64_t)data[pos] & 0xff) << 56) | (((int64_t)data[pos + 1] & 0xff) << 48) | (((int64_t)data[pos + 2] & 0xff) << 40) | (((int64_t)data[pos + 3] & 0xff) << 32) | (((int64_t)data[pos + 4] & 0xff) << 24) | (((int64_t)data[pos + 5] & 0xff) << 16) | (((int64_t)data[pos + 6] & 0xff) << 8) | ((int64_t)data[pos + 7] & 0xff);
+ return ret;
+}
+
+float XDRReader::get_float()
+{
+ union {
+ uint32_t i;
+ float f;
+ } v;
+ // Setting one member of a struct and then reading another is undefined
+ // behaviour, but works as intended in nearly any (embedded) compiler
+ v.i = get_uint32();
+ return v.f;
+}
+
+double XDRReader::get_double()
+{
+ union {
+ uint64_t i;
+ double d;
+ } v;
+ // Setting one member of a struct and then reading another is undefined
+ // behaviour, but works as intended in nearly any (embedded) compiler
+ v.i = get_uint64();
+ return v.d;
+}
+
+uint32_t XDRReader::get_opaque_length()
+{
+ return get_uint32();
+}
+
+char *XDRReader::get_opaque(uint32_t length)
+{
+ char *ret = data + pos;
+ pos += length;
+ if (length % 4)
+ {
+ pos += 4 - (length % 4);
+ }
+ return ret;
+}
+
+void XDRReader::get_string(char *target)
+{
+ uint16_t length = get_opaque_length();
+ uint16_t i;
+ for (i = 0; i < length; i++)
+ {
+ target[i] = data[pos + i];
+ }
+ target[i] = 0;
+ pos += length;
+ if (length % 4)
+ {
+ pos += 4 - (length % 4);
+ }
+}
diff --git a/src/lib/xdr16.cc b/src/lib/xdr16.cc
new file mode 100644
index 0000000..b1fb7ca
--- /dev/null
+++ b/src/lib/xdr16.cc
@@ -0,0 +1,216 @@
+#include "lib/xdr16.h"
+
+void XDRWriter::put(uint16_t number)
+{
+ *buffer++ = ((number >> 8) & 0xffU);
+ *buffer++ = (number & 0xffU);
+ pos += 2;
+}
+
+void XDRWriter::put(int16_t number)
+{
+ *buffer++ = ((number >> 8) & 0xffU);
+ *buffer++ = (number & 0xffU);
+ pos += 2;
+}
+
+void XDRWriter::put(uint32_t number)
+{
+ *buffer++ = ((number >> 24) & 0xffU);
+ *buffer++ = ((number >> 16) & 0xffU);
+ *buffer++ = ((number >> 8) & 0xffU);
+ *buffer++ = (number & 0xffU);
+ pos += 4;
+}
+
+void XDRWriter::put(int32_t number)
+{
+ *buffer++ = ((number >> 24) & 0xffU);
+ *buffer++ = ((number >> 16) & 0xffU);
+ *buffer++ = ((number >> 8) & 0xffU);
+ *buffer++ = (number & 0xffU);
+ pos += 4;
+}
+
+void XDRWriter::put(uint64_t number)
+{
+ *buffer++ = ((number >> 56) & 0xffU);
+ *buffer++ = ((number >> 48) & 0xffU);
+ *buffer++ = ((number >> 40) & 0xffU);
+ *buffer++ = ((number >> 32) & 0xffU);
+ *buffer++ = ((number >> 24) & 0xffU);
+ *buffer++ = ((number >> 16) & 0xffU);
+ *buffer++ = ((number >> 8) & 0xffU);
+ *buffer++ = (number & 0xffU);
+ pos += 8;
+}
+
+void XDRWriter::put(int64_t number)
+{
+ *buffer++ = ((number >> 56) & 0xffU);
+ *buffer++ = ((number >> 48) & 0xffU);
+ *buffer++ = ((number >> 40) & 0xffU);
+ *buffer++ = ((number >> 32) & 0xffU);
+ *buffer++ = ((number >> 24) & 0xffU);
+ *buffer++ = ((number >> 16) & 0xffU);
+ *buffer++ = ((number >> 8) & 0xffU);
+ *buffer++ = (number & 0xffU);
+ pos += 8;
+}
+
+void XDRWriter::put(float number)
+{
+ union {
+ uint32_t i;
+ float f;
+ } v;
+ // Setting one member of a struct and then reading another is undefined
+ // behaviour, but works as intended in nearly any (embedded) compiler
+ v.f = number;
+ put(v.i);
+}
+
+void XDRWriter::put(double number)
+{
+ union {
+ uint64_t i;
+ double d;
+ } v;
+ // Setting one member of a struct and then reading another is undefined
+ // behaviour, but works as intended in nearly any (embedded) compiler
+ v.d = number;
+ put(v.i);
+}
+
+void XDRWriter::put(char const *data)
+{
+ if (!is_fixed_length)
+ {
+ put(next_array_len);
+ }
+ uint16_t i;
+ for (i = 0; i < next_array_len; i++)
+ {
+ *buffer++ = data[i];
+ }
+ while ((i++) % 2 != 0)
+ {
+ *buffer++ = 0;
+ }
+ pos += i - 1;
+}
+
+template <uint16_t TSize>
+void XDRWriter::put(char const (&data)[TSize])
+{
+ if (!is_fixed_length)
+ {
+ put(TSize);
+ }
+ uint16_t i;
+ for (i = 0; i < TSize; i++)
+ {
+ *buffer++ = data[i];
+ }
+ while ((i++) % 2 != 0)
+ {
+ *buffer++ = 0;
+ }
+ pos += i - 1;
+}
+
+uint16_t XDRReader::get_uint16()
+{
+ uint16_t ret = (((uint16_t)data[pos] & 0xffU) << 8) | ((uint16_t)data[pos + 1] & 0xffU);
+ pos += 2;
+ return ret;
+}
+
+int16_t XDRReader::get_int16()
+{
+ int16_t ret = (((int16_t)data[pos] & 0xff) << 8) | ((int16_t)data[pos + 1] & 0xff);
+ pos += 2;
+ return ret;
+}
+
+uint32_t XDRReader::get_uint32()
+{
+ uint32_t ret = (((uint32_t)data[pos] & 0xffU) << 24) | (((uint32_t)data[pos + 1] & 0xffU) << 16) | (((uint32_t)data[pos + 2] & 0xffU) << 8) | ((uint32_t)data[pos + 3] & 0xffU);
+ pos += 4;
+ return ret;
+}
+
+int32_t XDRReader::get_int32()
+{
+ int32_t ret = (((int32_t)data[pos] & 0xff) << 24) | (((int32_t)data[pos + 1] & 0xff) << 16) | (((int32_t)data[pos + 2] & 0xff) << 8) | ((int32_t)data[pos + 3] & 0xff);
+ pos += 4;
+ return ret;
+}
+
+uint64_t XDRReader::get_uint64()
+{
+ uint64_t ret = (((uint64_t)data[pos] & 0xffU) << 56) | (((uint64_t)data[pos + 1] & 0xffU) << 48) | (((uint64_t)data[pos + 2] & 0xffU) << 40) | (((uint64_t)data[pos + 3] & 0xffU) << 32) | (((uint64_t)data[pos + 4] & 0xffU) << 24) | (((uint64_t)data[pos + 5] & 0xffU) << 16) | (((uint64_t)data[pos + 6] & 0xffU) << 8) | ((uint64_t)data[pos + 7] & 0xffU);
+ return ret;
+}
+
+int64_t XDRReader::get_int64()
+{
+ int64_t ret = (((int64_t)data[pos] & 0xff) << 56) | (((int64_t)data[pos + 1] & 0xff) << 48) | (((int64_t)data[pos + 2] & 0xff) << 40) | (((int64_t)data[pos + 3] & 0xff) << 32) | (((int64_t)data[pos + 4] & 0xff) << 24) | (((int64_t)data[pos + 5] & 0xff) << 16) | (((int64_t)data[pos + 6] & 0xff) << 8) | ((int64_t)data[pos + 7] & 0xff);
+ return ret;
+}
+
+float XDRReader::get_float()
+{
+ union {
+ uint32_t i;
+ float f;
+ } v;
+ // Setting one member of a struct and then reading another is undefined
+ // behaviour, but works as intended in nearly any (embedded) compiler
+ v.i = get_uint32();
+ return v.f;
+}
+
+double XDRReader::get_double()
+{
+ union {
+ uint64_t i;
+ double d;
+ } v;
+ // Setting one member of a struct and then reading another is undefined
+ // behaviour, but works as intended in nearly any (embedded) compiler
+ v.i = get_uint64();
+ return v.d;
+}
+
+uint32_t XDRReader::get_opaque_length()
+{
+ return get_uint16();
+}
+
+char *XDRReader::get_opaque(uint32_t length)
+{
+ char *ret = data + pos;
+ pos += length;
+ if (length % 2)
+ {
+ pos += 2 - (length % 2);
+ }
+ return ret;
+}
+
+void XDRReader::get_string(char *target)
+{
+ uint16_t length = get_opaque_length();
+ uint16_t i;
+ for (i = 0; i < length; i++)
+ {
+ target[i] = data[pos + i];
+ }
+ target[i] = 0;
+ pos += length;
+ if (length % 2)
+ {
+ pos += 2 - (length % 2);
+ }
+}