Parcourir la source

Add MsgPack bin8/bin16/bin32 support

Closes #2078
Closes #922
Aubrey (Sanae) il y a 1 an
Parent
commit
18a9a5b590

+ 1 - 0
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)
 ------

+ 36 - 0
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);

+ 3 - 0
extras/tests/JsonVariant/unbound.cpp

@@ -21,6 +21,8 @@ TEST_CASE("Unbound JsonVariant") {
     CHECK(variant.as<JsonObject>().isNull());
     CHECK(variant.as<JsonObjectConst>().isNull());
     CHECK(variant.as<JsonString>().isNull());
+    CHECK(variant.as<MsgPackBinary>().data() == nullptr);
+    CHECK(variant.as<MsgPackBinary>().size() == 0);
   }
 
   SECTION("is<T>()") {
@@ -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()") {

+ 35 - 2
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);
+  }
 }

+ 36 - 2
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);
+  }
 }

+ 43 - 1
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<MsgPackBinary>());
+    auto binary = doc.as<MsgPackBinary>();
+    REQUIRE(binary.size() == 65536);
+    REQUIRE(binary.data() != nullptr);
+    REQUIRE(std::string(reinterpret_cast<const char*>(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);
+  }
 }

+ 31 - 0
extras/tests/MsgPackDeserializer/deserializeVariant.cpp

@@ -3,6 +3,7 @@
 // MIT License
 
 #include <ArduinoJson.h>
+#include <array>
 #include <catch.hpp>
 
 #include "Allocators.hpp"
@@ -139,6 +140,36 @@ TEST_CASE("deserialize MsgPack value") {
   SECTION("str 32") {
     checkValue<std::string>("\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<MsgPackBinary>());
+    auto binary = doc.as<MsgPackBinary>();
+    REQUIRE(binary.size() == 1);
+    REQUIRE(binary.data() != nullptr);
+    REQUIRE(reinterpret_cast<const char*>(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<MsgPackBinary>());
+    auto binary = doc.as<MsgPackBinary>();
+    REQUIRE(binary.size() == 0x100);
+    REQUIRE(binary.data() != nullptr);
+    REQUIRE(std::string(reinterpret_cast<const char*>(binary.data()),
+                        binary.size()) == str);
+  }
 }
 
 TEST_CASE("deserializeMsgPack() under memory constaints") {

+ 12 - 0
extras/tests/MsgPackSerializer/serializeVariant.cpp

@@ -3,6 +3,7 @@
 // MIT License
 
 #include <ArduinoJson.h>
+#include <array>
 #include <catch.hpp>
 
 template <typename T>
@@ -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");

+ 4 - 0
src/ArduinoJson/Json/JsonSerializer.hpp

@@ -81,6 +81,10 @@ class JsonSerializer : public VariantDataVisitor<size_t> {
     return bytesWritten();
   }
 
+  size_t visit(MsgPackBinary) {
+    return visit(nullptr);
+  }
+
   size_t visit(JsonInteger value) {
     formatter_.writeInteger(value);
     return bytesWritten();

+ 23 - 0
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

+ 38 - 6
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<uint8_t>();
+      case 0xc4:
+        if (allowValue)
+          return readBinary<uint8_t>(variant);
+        else
+          return skipString<uint8_t>();
 
-      case 0xc5:  // bin 16 (not supported)
-        return skipString<uint16_t>();
+      case 0xc5:
+        if (allowValue)
+          return readBinary<uint16_t>(variant);
+        else
+          return skipString<uint16_t>();
 
-      case 0xc6:  // bin 32 (not supported)
-        return skipString<uint32_t>();
+      case 0xc6:
+        if (allowValue)
+          return readBinary<uint32_t>(variant);
+        else
+          return skipString<uint32_t>();
 
       case 0xc7:  // ext 8 (not supported)
         return skipExt<uint8_t>();
@@ -394,6 +403,29 @@ class MsgPackDeserializer {
     return DeserializationError::Ok;
   }
 
+  template <typename T>
+  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 <typename TSize, typename TFilter>
   DeserializationError::Code readArray(
       VariantData* variant, TFilter filter,

+ 15 - 0
src/ArduinoJson/MsgPack/MsgPackSerializer.hpp

@@ -123,6 +123,21 @@ class MsgPackSerializer : public VariantDataVisitor<size_t> {
     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<const uint8_t*>(value.data()), value.size());
+    return bytesWritten();
+  }
+
   size_t visit(JsonInteger value) {
     if (value > 0) {
       visit(static_cast<JsonUInt>(value));

+ 15 - 0
src/ArduinoJson/Variant/ConverterImpl.hpp

@@ -192,6 +192,21 @@ struct Converter<SerializedValue<T>> : private detail::VariantAttorney {
   }
 };
 
+template <>
+struct Converter<MsgPackBinary> : 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<detail::nullptr_t> : private detail::VariantAttorney {
   static void toJson(detail::nullptr_t, JsonVariant dst) {

+ 24 - 0
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<JsonInteger> comparer(lhs);
     return reverseResult(comparer);

+ 1 - 0
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
 

+ 41 - 0
src/ArduinoJson/Variant/VariantData.hpp

@@ -6,6 +6,7 @@
 
 #include <ArduinoJson/Memory/StringNode.hpp>
 #include <ArduinoJson/Misc/SerializedValue.hpp>
+#include <ArduinoJson/MsgPack/MsgPackBinary.hpp>
 #include <ArduinoJson/Numbers/convertNumber.hpp>
 #include <ArduinoJson/Strings/JsonString.hpp>
 #include <ArduinoJson/Strings/StringAdapters.hpp>
@@ -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<const char*>(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 <typename TAdaptedString>
   void setString(TAdaptedString value, ResourceManager* resources) {
     setNull(resources);