diff --git a/CHANGELOG.md b/CHANGELOG.md index ea1220c0..a04c48bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ArduinoJson: change log ======================= * Add `ARDUINOJSON_STRING_LENGTH_SIZE` to the namespace name +* Add MsgPack bin8/bin16/bin32 support (PR #2078 by @Sanae6) v7.0.4 (2024-03-12) ------ diff --git a/extras/tests/JsonVariant/compare.cpp b/extras/tests/JsonVariant/compare.cpp index 3061cddc..de7ced71 100644 --- a/extras/tests/JsonVariant/compare.cpp +++ b/extras/tests/JsonVariant/compare.cpp @@ -113,6 +113,42 @@ TEST_CASE("Compare JsonVariant with JsonVariant") { CHECK_FALSE(a == b); } + SECTION("MsgPackBinary('abc') vs MsgPackBinary('abc')") { + a.set(MsgPackBinary("abc", 4)); + b.set(MsgPackBinary("abc", 4)); + + CHECK(a == b); + CHECK(a <= b); + CHECK(a >= b); + CHECK_FALSE(a != b); + CHECK_FALSE(a < b); + CHECK_FALSE(a > b); + } + + SECTION("MsgPackBinary('abc') vs MsgPackBinary('bcd')") { + a.set(MsgPackBinary("abc", 4)); + b.set(MsgPackBinary("bcd", 4)); + + CHECK(a != b); + CHECK(a < b); + CHECK(a <= b); + CHECK_FALSE(a == b); + CHECK_FALSE(a > b); + CHECK_FALSE(a >= b); + } + + SECTION("MsgPackBinary('bcd') vs MsgPackBinary('abc')") { + a.set(MsgPackBinary("bcd", 4)); + b.set(MsgPackBinary("abc", 4)); + + CHECK(a != b); + CHECK(a > b); + CHECK(a >= b); + CHECK_FALSE(a < b); + CHECK_FALSE(a <= b); + CHECK_FALSE(a == b); + } + SECTION("false vs true") { a.set(false); b.set(true); diff --git a/extras/tests/JsonVariant/unbound.cpp b/extras/tests/JsonVariant/unbound.cpp index fa761a8e..7b4e31f8 100644 --- a/extras/tests/JsonVariant/unbound.cpp +++ b/extras/tests/JsonVariant/unbound.cpp @@ -21,6 +21,8 @@ TEST_CASE("Unbound JsonVariant") { CHECK(variant.as().isNull()); CHECK(variant.as().isNull()); CHECK(variant.as().isNull()); + CHECK(variant.as().data() == nullptr); + CHECK(variant.as().size() == 0); } SECTION("is()") { @@ -46,6 +48,7 @@ TEST_CASE("Unbound JsonVariant") { CHECK_FALSE(variant.set(serialized("42"))); CHECK_FALSE(variant.set(serialized(std::string("42")))); CHECK_FALSE(variant.set(true)); + CHECK_FALSE(variant.set(MsgPackBinary("hello", 5))); } SECTION("add()") { diff --git a/extras/tests/MixedConfiguration/string_length_size_1.cpp b/extras/tests/MixedConfiguration/string_length_size_1.cpp index 8e17e3c9..29d6eaa0 100644 --- a/extras/tests/MixedConfiguration/string_length_size_1.cpp +++ b/extras/tests/MixedConfiguration/string_length_size_1.cpp @@ -21,6 +21,22 @@ TEST_CASE("ARDUINOJSON_STRING_LENGTH_SIZE == 1") { REQUIRE(doc.overflowed() == true); } + SECTION("set() returns true if binary has 255 characters") { + auto str = std::string(255, '?'); + auto result = doc.set(MsgPackBinary(str.data(), str.size())); + + REQUIRE(result == true); + REQUIRE(doc.overflowed() == false); + } + + SECTION("set() returns false if binary has 256 characters") { + auto str = std::string(256, '?'); + auto result = doc.set(MsgPackBinary(str.data(), str.size())); + + REQUIRE(result == false); + REQUIRE(doc.overflowed() == true); + } + SECTION("deserializeJson() returns Ok if string has 255 characters") { auto input = "\"" + std::string(255, '?') + "\""; @@ -37,7 +53,7 @@ TEST_CASE("ARDUINOJSON_STRING_LENGTH_SIZE == 1") { REQUIRE(err == DeserializationError::NoMemory); } - SECTION("deserializeMsgPack() returns Ok of string has 255 characters") { + SECTION("deserializeMsgPack() returns Ok if string has 255 characters") { auto input = "\xd9\xff" + std::string(255, '?'); auto err = deserializeMsgPack(doc, input); @@ -46,11 +62,28 @@ TEST_CASE("ARDUINOJSON_STRING_LENGTH_SIZE == 1") { } SECTION( - "deserializeMsgPack() returns NoMemory of string has 256 characters") { + "deserializeMsgPack() returns NoMemory if string has 256 characters") { auto input = std::string("\xda\x01\x00", 3) + std::string(256, '?'); auto err = deserializeMsgPack(doc, input); REQUIRE(err == DeserializationError::NoMemory); } + + SECTION("deserializeMsgPack() returns Ok if binary has 255 characters") { + auto input = "\xc4\xff" + std::string(255, '?'); + + auto err = deserializeMsgPack(doc, input); + + REQUIRE(err == DeserializationError::Ok); + } + + SECTION( + "deserializeMsgPack() returns NoMemory if binary has 256 characters") { + auto input = std::string("\xc5\x01\x00", 3) + std::string(256, '?'); + + auto err = deserializeMsgPack(doc, input); + + REQUIRE(err == DeserializationError::NoMemory); + } } diff --git a/extras/tests/MixedConfiguration/string_length_size_2.cpp b/extras/tests/MixedConfiguration/string_length_size_2.cpp index 669905ef..e6a67677 100644 --- a/extras/tests/MixedConfiguration/string_length_size_2.cpp +++ b/extras/tests/MixedConfiguration/string_length_size_2.cpp @@ -21,6 +21,22 @@ TEST_CASE("ARDUINOJSON_STRING_LENGTH_SIZE == 2") { REQUIRE(doc.overflowed() == true); } + SECTION("set() returns true if string has 65535 characters") { + auto str = std::string(65535, '?'); + auto result = doc.set(MsgPackBinary(str.data(), str.size())); + + REQUIRE(result == true); + REQUIRE(doc.overflowed() == false); + } + + SECTION("set() returns false if string has 65536 characters") { + auto str = std::string(65536, '?'); + auto result = doc.set(MsgPackBinary(str.data(), str.size())); + + REQUIRE(result == false); + REQUIRE(doc.overflowed() == true); + } + SECTION("deserializeJson() returns Ok if string has 65535 characters") { auto input = "\"" + std::string(65535, '?') + "\""; @@ -37,7 +53,7 @@ TEST_CASE("ARDUINOJSON_STRING_LENGTH_SIZE == 2") { REQUIRE(err == DeserializationError::NoMemory); } - SECTION("deserializeMsgPack() returns Ok of string has 65535 characters") { + SECTION("deserializeMsgPack() returns Ok if string has 65535 characters") { auto input = "\xda\xff\xff" + std::string(65535, '?'); auto err = deserializeMsgPack(doc, input); @@ -46,7 +62,7 @@ TEST_CASE("ARDUINOJSON_STRING_LENGTH_SIZE == 2") { } SECTION( - "deserializeMsgPack() returns NoMemory of string has 65536 characters") { + "deserializeMsgPack() returns NoMemory if string has 65536 characters") { auto input = std::string("\xdb\x00\x01\x00\x00", 5) + std::string(65536, '?'); @@ -54,4 +70,22 @@ TEST_CASE("ARDUINOJSON_STRING_LENGTH_SIZE == 2") { REQUIRE(err == DeserializationError::NoMemory); } + + SECTION("deserializeMsgPack() returns Ok if binary has 65535 characters") { + auto input = "\xc5\xff\xff" + std::string(65535, '?'); + + auto err = deserializeMsgPack(doc, input); + + REQUIRE(err == DeserializationError::Ok); + } + + SECTION( + "deserializeMsgPack() returns NoMemory of binary has 65536 characters") { + auto input = + std::string("\xc6\x00\x01\x00\x00", 5) + std::string(65536, '?'); + + auto err = deserializeMsgPack(doc, input); + + REQUIRE(err == DeserializationError::NoMemory); + } } diff --git a/extras/tests/MixedConfiguration/string_length_size_4.cpp b/extras/tests/MixedConfiguration/string_length_size_4.cpp index e448743c..d184a7de 100644 --- a/extras/tests/MixedConfiguration/string_length_size_4.cpp +++ b/extras/tests/MixedConfiguration/string_length_size_4.cpp @@ -14,6 +14,14 @@ TEST_CASE("ARDUINOJSON_STRING_LENGTH_SIZE == 4") { REQUIRE(doc.overflowed() == false); } + SECTION("set() returns true if binary has 65536 characters") { + auto str = std::string(65536, '?'); + auto result = doc.set(MsgPackBinary(str.data(), str.size())); + + REQUIRE(result == true); + REQUIRE(doc.overflowed() == false); + } + SECTION("deserializeJson() returns Ok if string has 65536 characters") { auto input = "\"" + std::string(65536, '?') + "\""; @@ -22,11 +30,45 @@ TEST_CASE("ARDUINOJSON_STRING_LENGTH_SIZE == 4") { REQUIRE(err == DeserializationError::Ok); } - SECTION("deserializeMsgPack() returns Ok of string has 65536 characters") { + SECTION("deserializeMsgPack() returns Ok if string has 65536 characters") { auto input = "\xda\xff\xff" + std::string(65536, '?'); auto err = deserializeMsgPack(doc, input); REQUIRE(err == DeserializationError::Ok); } + + SECTION("deserializeMsgPack() returns Ok if binary has 65536 characters") { + auto input = "\xc5\xff\xff" + std::string(65536, '?'); + + auto err = deserializeMsgPack(doc, input); + + REQUIRE(err == DeserializationError::Ok); + } + + SECTION("bin 32 deserialization") { + auto str = std::string(65536, '?'); + auto input = std::string("\xc6\x00\x01\x00\x00", 5) + str; + + auto err = deserializeMsgPack(doc, input); + + REQUIRE(err == DeserializationError::Ok); + REQUIRE(doc.is()); + auto binary = doc.as(); + REQUIRE(binary.size() == 65536); + REQUIRE(binary.data() != nullptr); + REQUIRE(std::string(reinterpret_cast(binary.data()), + binary.size()) == str); + } + + SECTION("bin 32 serialization") { + auto str = std::string(65536, '?'); + doc.set(MsgPackBinary(str.data(), str.size())); + + std::string output; + auto result = serializeMsgPack(doc, output); + + REQUIRE(result == 5 + str.size()); + REQUIRE(output == std::string("\xc6\x00\x01\x00\x00", 5) + str); + } } diff --git a/extras/tests/MsgPackDeserializer/deserializeVariant.cpp b/extras/tests/MsgPackDeserializer/deserializeVariant.cpp index 01361774..fbe6f3f0 100644 --- a/extras/tests/MsgPackDeserializer/deserializeVariant.cpp +++ b/extras/tests/MsgPackDeserializer/deserializeVariant.cpp @@ -3,6 +3,7 @@ // MIT License #include +#include #include #include "Allocators.hpp" @@ -139,6 +140,36 @@ TEST_CASE("deserialize MsgPack value") { SECTION("str 32") { checkValue("\xdb\x00\x00\x00\x05hello", std::string("hello")); } + + SECTION("bin 8") { + JsonDocument doc; + + DeserializationError error = deserializeMsgPack(doc, "\xc4\x01?"); + + REQUIRE(error == DeserializationError::Ok); + REQUIRE(doc.is()); + auto binary = doc.as(); + REQUIRE(binary.size() == 1); + REQUIRE(binary.data() != nullptr); + REQUIRE(reinterpret_cast(binary.data())[0] == '?'); + } + + SECTION("bin 16") { + JsonDocument doc; + + auto str = std::string(256, '?'); + auto input = std::string("\xc5\x01\x00", 3) + str; + + DeserializationError error = deserializeMsgPack(doc, input); + + REQUIRE(error == DeserializationError::Ok); + REQUIRE(doc.is()); + auto binary = doc.as(); + REQUIRE(binary.size() == 0x100); + REQUIRE(binary.data() != nullptr); + REQUIRE(std::string(reinterpret_cast(binary.data()), + binary.size()) == str); + } } TEST_CASE("deserializeMsgPack() under memory constaints") { diff --git a/extras/tests/MsgPackSerializer/serializeVariant.cpp b/extras/tests/MsgPackSerializer/serializeVariant.cpp index 65ce0bcf..e9102ff9 100644 --- a/extras/tests/MsgPackSerializer/serializeVariant.cpp +++ b/extras/tests/MsgPackSerializer/serializeVariant.cpp @@ -3,6 +3,7 @@ // MIT License #include +#include #include template @@ -146,6 +147,17 @@ TEST_CASE("serialize MsgPack value") { checkVariant(serialized("\xDB\x00\x01\x00\x00", 5), "\xDB\x00\x01\x00\x00"); } + SECTION("bin 8") { + auto str = std::string(1, 1); + checkVariant(MsgPackBinary(str.data(), str.size()), "\xC4\x01\x01"); + } + + SECTION("bin 16") { + auto str = std::string(256, 1); + checkVariant(MsgPackBinary(str.data(), str.size()), + std::string("\xC5\x01\x00", 3) + str); + } + SECTION("serialize round double as integer") { // Issue #1718 checkVariant(-32768.0, "\xD1\x80\x00"); checkVariant(-129.0, "\xD1\xFF\x7F"); diff --git a/src/ArduinoJson/Json/JsonSerializer.hpp b/src/ArduinoJson/Json/JsonSerializer.hpp index 6b02cc59..95c1094d 100644 --- a/src/ArduinoJson/Json/JsonSerializer.hpp +++ b/src/ArduinoJson/Json/JsonSerializer.hpp @@ -81,6 +81,10 @@ class JsonSerializer : public VariantDataVisitor { return bytesWritten(); } + size_t visit(MsgPackBinary) { + return visit(nullptr); + } + size_t visit(JsonInteger value) { formatter_.writeInteger(value); return bytesWritten(); diff --git a/src/ArduinoJson/MsgPack/MsgPackBinary.hpp b/src/ArduinoJson/MsgPack/MsgPackBinary.hpp new file mode 100644 index 00000000..5b6ac260 --- /dev/null +++ b/src/ArduinoJson/MsgPack/MsgPackBinary.hpp @@ -0,0 +1,23 @@ +#pragma once + +ARDUINOJSON_BEGIN_PUBLIC_NAMESPACE + +class MsgPackBinary { + public: + MsgPackBinary() : data_(nullptr), size_(0) {} + explicit MsgPackBinary(const void* c, size_t size) : data_(c), size_(size) {} + + const void* data() const { + return data_; + } + + size_t size() const { + return size_; + } + + private: + const void* data_; + size_t size_; +}; + +ARDUINOJSON_END_PUBLIC_NAMESPACE diff --git a/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp b/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp index 0475f6b3..63930dbd 100644 --- a/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp +++ b/src/ArduinoJson/MsgPack/MsgPackDeserializer.hpp @@ -69,14 +69,23 @@ class MsgPackDeserializer { variant->setBoolean(true); return DeserializationError::Ok; - case 0xc4: // bin 8 (not supported) - return skipString(); + case 0xc4: + if (allowValue) + return readBinary(variant); + else + return skipString(); - case 0xc5: // bin 16 (not supported) - return skipString(); + case 0xc5: + if (allowValue) + return readBinary(variant); + else + return skipString(); - case 0xc6: // bin 32 (not supported) - return skipString(); + case 0xc6: + if (allowValue) + return readBinary(variant); + else + return skipString(); case 0xc7: // ext 8 (not supported) return skipExt(); @@ -394,6 +403,29 @@ class MsgPackDeserializer { return DeserializationError::Ok; } + template + DeserializationError::Code readBinary(VariantData* variant) { + DeserializationError::Code err; + T size; + + err = readInteger(size); + if (err) + return err; + + return readBinary(variant, size); + } + + DeserializationError::Code readBinary(VariantData* variant, size_t n) { + DeserializationError::Code err; + + err = readString(n); + if (err) + return err; + + variant->setBinary(stringBuilder_.save()); + return DeserializationError::Ok; + } + template DeserializationError::Code readArray( VariantData* variant, TFilter filter, diff --git a/src/ArduinoJson/MsgPack/MsgPackSerializer.hpp b/src/ArduinoJson/MsgPack/MsgPackSerializer.hpp index d3aa5bbb..9aa4db39 100644 --- a/src/ArduinoJson/MsgPack/MsgPackSerializer.hpp +++ b/src/ArduinoJson/MsgPack/MsgPackSerializer.hpp @@ -123,6 +123,21 @@ class MsgPackSerializer : public VariantDataVisitor { return bytesWritten(); } + size_t visit(MsgPackBinary value) { + if (value.size() <= 0xFF) { + writeByte(0xC4); + writeInteger(uint8_t(value.size())); + } else if (value.size() <= 0xFFFF) { + writeByte(0xC5); + writeInteger(uint16_t(value.size())); + } else { + writeByte(0xC6); + writeInteger(uint32_t(value.size())); + } + writeBytes(reinterpret_cast(value.data()), value.size()); + return bytesWritten(); + } + size_t visit(JsonInteger value) { if (value > 0) { visit(static_cast(value)); diff --git a/src/ArduinoJson/Variant/ConverterImpl.hpp b/src/ArduinoJson/Variant/ConverterImpl.hpp index 8e35f72b..68d1f21b 100644 --- a/src/ArduinoJson/Variant/ConverterImpl.hpp +++ b/src/ArduinoJson/Variant/ConverterImpl.hpp @@ -192,6 +192,21 @@ struct Converter> : private detail::VariantAttorney { } }; +template <> +struct Converter : private detail::VariantAttorney { + static void toJson(MsgPackBinary src, JsonVariant dst) { + detail::VariantData::setBinary(getData(dst), src, getResourceManager(dst)); + } + static MsgPackBinary fromJson(JsonVariantConst src) { + auto data = getData(src); + return data ? data->asBinary() : MsgPackBinary(); + } + static bool checkJson(JsonVariantConst src) { + auto data = getData(src); + return data && data->isBinary(); + } +}; + template <> struct Converter : private detail::VariantAttorney { static void toJson(detail::nullptr_t, JsonVariant dst) { diff --git a/src/ArduinoJson/Variant/VariantCompare.hpp b/src/ArduinoJson/Variant/VariantCompare.hpp index 7a923080..2ca159a5 100644 --- a/src/ArduinoJson/Variant/VariantCompare.hpp +++ b/src/ArduinoJson/Variant/VariantCompare.hpp @@ -134,6 +134,25 @@ struct RawComparer : ComparerBase { using ComparerBase::visit; }; +struct MsgPackBinaryComparer : ComparerBase { + MsgPackBinary rhs_; + + explicit MsgPackBinaryComparer(MsgPackBinary rhs) : rhs_(rhs) {} + + CompareResult visit(MsgPackBinary lhs) { + size_t size = rhs_.size() < lhs.size() ? rhs_.size() : lhs.size(); + int n = memcmp(lhs.data(), rhs_.data(), size); + if (n < 0) + return COMPARE_RESULT_LESS; + else if (n > 0) + return COMPARE_RESULT_GREATER; + else + return COMPARE_RESULT_EQUAL; + } + + using ComparerBase::visit; +}; + struct VariantComparer : ComparerBase { JsonVariantConst rhs; @@ -164,6 +183,11 @@ struct VariantComparer : ComparerBase { return reverseResult(comparer); } + CompareResult visit(MsgPackBinary value) { + MsgPackBinaryComparer comparer(value); + return reverseResult(comparer); + } + CompareResult visit(JsonInteger lhs) { Comparer comparer(lhs); return reverseResult(comparer); diff --git a/src/ArduinoJson/Variant/VariantContent.hpp b/src/ArduinoJson/Variant/VariantContent.hpp index fa2cb50e..389d9d50 100644 --- a/src/ArduinoJson/Variant/VariantContent.hpp +++ b/src/ArduinoJson/Variant/VariantContent.hpp @@ -21,6 +21,7 @@ enum { VALUE_IS_RAW_STRING = 0x03, VALUE_IS_LINKED_STRING = 0x04, VALUE_IS_OWNED_STRING = 0x05, + VALUE_IS_BINARY = 0x07, // CAUTION: no OWNED_VALUE_BIT below diff --git a/src/ArduinoJson/Variant/VariantData.hpp b/src/ArduinoJson/Variant/VariantData.hpp index 5cc7de18..e13fff6b 100644 --- a/src/ArduinoJson/Variant/VariantData.hpp +++ b/src/ArduinoJson/Variant/VariantData.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -48,6 +49,10 @@ class VariantData { return visit.visit(RawString(content_.asOwnedString->data, content_.asOwnedString->length)); + case VALUE_IS_BINARY: + return visit.visit(MsgPackBinary(content_.asOwnedString->data, + content_.asOwnedString->length)); + case VALUE_IS_SIGNED_INTEGER: return visit.visit(content_.asSignedInteger); @@ -185,6 +190,16 @@ class VariantData { } } + MsgPackBinary asBinary() const { + switch (type()) { + case VALUE_IS_BINARY: + return MsgPackBinary(content_.asOwnedString->data, + content_.asOwnedString->length); + default: + return MsgPackBinary(nullptr, 0); + } + } + VariantData* getElement(size_t index, const ResourceManager* resources) const { return ArrayData::getElement(asArray(), index, resources); @@ -274,6 +289,10 @@ class VariantData { return type() == VALUE_IS_LINKED_STRING || type() == VALUE_IS_OWNED_STRING; } + bool isBinary() const { + return type() == VALUE_IS_BINARY; + } + size_t nesting(const ResourceManager* resources) const { auto collection = asCollection(); if (collection) @@ -394,6 +413,28 @@ class VariantData { var->setRawString(value, resources); } + void setBinary(StringNode* s) { + ARDUINOJSON_ASSERT(s); + setType(VALUE_IS_BINARY); + content_.asOwnedString = s; + } + + void setBinary(MsgPackBinary value, ResourceManager* resources) { + auto dup = resources->saveString( + adaptString(reinterpret_cast(value.data()), value.size())); + if (dup) + setBinary(dup); + else + setNull(); + } + + static void setBinary(VariantData* var, MsgPackBinary value, + ResourceManager* resources) { + if (!var) + return; + var->setBinary(value, resources); + } + template void setString(TAdaptedString value, ResourceManager* resources) { setNull(resources);