From b70ea9a34c2af689164c8aa88f6064e71126bfae Mon Sep 17 00:00:00 2001 From: Randy Jones Date: Wed, 10 Jul 2024 14:56:18 -0700 Subject: [PATCH] add Blob serialization and test --- Tests/treeTest.cpp | 7 +++-- source/app/MLSerialization.h | 52 ++++++++++++++++++++---------------- source/app/MLValue.cpp | 5 ++-- source/app/MLValue.h | 9 ++++--- 4 files changed, 42 insertions(+), 31 deletions(-) diff --git a/Tests/treeTest.cpp b/Tests/treeTest.cpp index e417fb25..015f3d51 100644 --- a/Tests/treeTest.cpp +++ b/Tests/treeTest.cpp @@ -450,17 +450,20 @@ TEST_CASE("madronalib/core/serialization", "[serialization]") v["b"] = "hello"; v["a/b/c"] = "hello again"; + std::array testArray{1, 3, 5, 7, 9}; + auto testBytes = testArray.size() * sizeof(uint8_t); + v["blobtest"] = Value(testArray.data(), testBytes); + Tree< Value > v2 = JSONToValueTree(valueTreeToJSON(v)); + REQUIRE(v == v2); // Value tree to JSON to text to JSON to value tree. auto t1 = JSONToText(valueTreeToJSON(v)); auto v3 = JSONToValueTree(textToJSON(t1)); REQUIRE(v == v3); - } - TEST_CASE("madronalib/core/floatvectors", "[floatvectors]") { const int kTestSize{100}; diff --git a/source/app/MLSerialization.h b/source/app/MLSerialization.h index 5ddc9226..a01c2ddc 100644 --- a/source/app/MLSerialization.h +++ b/source/app/MLSerialization.h @@ -156,14 +156,13 @@ inline std::unique_ptr > valueToBinary(Value v) } case Value::kBlobValue: { - char* blobData = static_cast(v.getBlobValue()); + uint8_t* blobData = v.getBlobData(); unsigned int blobSize = (unsigned int)v.getBlobSize(); outputVector.resize(headerSize + blobSize); BinaryChunkHeader* header{reinterpret_cast(outputVector.data())}; *header = BinaryChunkHeader{'B', blobSize}; auto pDest{outputVector.data() + headerSize}; - auto pSrc{blobData}; - std::copy(pSrc, pSrc + blobSize, pDest); + std::copy(blobData, blobData + blobSize, pDest); break; } } @@ -444,6 +443,8 @@ class JSONHolder cJSON* data() { return &_data; } }; +static TextFragment kBlobHeader("!BLOB!"); + // return a JSON object representing the value tree. The caller is responsible // for freeing the object. // @@ -471,28 +472,18 @@ inline JSONHolder valueTreeToJSON(const Tree& t) case Value::kTextValue: cJSON_AddStringToObject(root.data(), keyStr, v.getTextValue().getText()); break; - case Value::kMatrixValue: - { - /* TODO - cJSON* signalObj = cJSON_CreateObject(); - const MLSignal& sig = state.mValue.getSignalValue(); - cJSON_AddStringToObject(signalObj, "type", "signal"); - cJSON_AddNumberToObject(signalObj, "width", sig.getWidth()); - cJSON_AddNumberToObject(signalObj, "height", sig.getHeight()); - cJSON_AddNumberToObject(signalObj, "depth", sig.getDepth()); - int size = sig.getSize(); - float* pSignalData = sig.getBuffer(); - cJSON* data = cJSON_CreateFloatArray(pSignalData, size); - cJSON_AddItemToObject(signalObj, "data", data); - - // add signal object to state JSON - cJSON_AddItemToObject(root, keyStr, signalObj); - */ - } - break; case Value::kUnsignedLongValue: cJSON_AddNumberToObject(root.data(), keyStr, v.getUnsignedLongValue()); break; + case Value::kBlobValue: + { + uint8_t* blobData = v.getBlobData(); + size_t blobSize = v.getBlobSize(); + std::vector blobVec(blobData, blobData + blobSize); + TextFragment blobText(kBlobHeader, textUtils::base64Encode(blobVec)); + cJSON_AddStringToObject(root.data(), keyStr, blobText.getText()); + break; + } default: // debug() << "MLAppState::saveStateToStateFile(): undefined param type! \n"; break; @@ -527,7 +518,22 @@ inline Tree readJSONToValueTree(cJSON* obj, Tree& r, Path currentP } case cJSON_String: { - r.add(newObjectPath, TextFragment(obj->valuestring)); + TextFragment valueText(obj->valuestring); + if(valueText.beginsWith(kBlobHeader)) // not wonderful + { + auto headerLen = kBlobHeader.lengthInCodePoints(); + auto textLen = valueText.lengthInCodePoints(); + auto body = textUtils::subText(valueText, headerLen, textLen); + + auto blobDataVec = textUtils::base64Decode(body.getText()); + auto* pBlobData{reinterpret_cast(blobDataVec.data())}; + auto blobValue = Value{pBlobData, blobDataVec.size()}; + r.add(newObjectPath, blobValue); + } + else + { + r.add(newObjectPath, valueText); + } break; } case cJSON_Object: diff --git a/source/app/MLValue.cpp b/source/app/MLValue.cpp index 1db2d48d..a3593858 100644 --- a/source/app/MLValue.cpp +++ b/source/app/MLValue.cpp @@ -226,7 +226,8 @@ bool Value::operator==(const Value& b) const r = (getUnsignedLongValue() == b.getUnsignedLongValue()); break; case kBlobValue: - r = false; + // compare blobs by value + r = !std::memcmp(getBlobData(), b.getBlobData(), getBlobSize()); break; case kIntervalValue: r = (getIntervalValue() == b.getIntervalValue()); @@ -291,7 +292,7 @@ std::ostream& operator<<(std::ostream& out, const Value& r) out << r.getUnsignedLongValue(); break; case Value::kBlobValue: - out << "[blob]"; + out << "[blob, " << r.getBlobSize() << " bytes]"; break; case Value::kIntervalValue: out << r.getIntervalValue(); diff --git a/source/app/MLValue.h b/source/app/MLValue.h index 91c3ec28..ec062a73 100644 --- a/source/app/MLValue.h +++ b/source/app/MLValue.h @@ -59,9 +59,10 @@ class Value Value(const ml::Matrix& s); Value(Interval i); - // binary blob constructor. - // if data size > kBlobSizeBytes, this will allocate heap. + // Blob constructors. + // if data size > kBlobSizeBytes, blob values will allocate heap. explicit Value(const void* pData, size_t n); + // TODO make array-like ctor using gsl::span // matrix type constructor via initializer_list Value(std::initializer_list values) @@ -141,11 +142,11 @@ class Value return (mType == kIntervalValue) ? (mIntervalVal) : d; } - inline void* getBlobValue() const + inline uint8_t* getBlobData() const { if (mType == kBlobValue) { - return (void*)(pBlobData); + return pBlobData; } else {