From 8baabf4c0daba3ae138a78cae1fd26bbaca159d5 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 31 Aug 2024 08:47:16 +0300 Subject: [PATCH] add checkCompatibility method, convertStrategy field, tests --- src/data/StructLayout.cpp | 107 +++++++++++++++++++++++++++++++++++-- src/data/StructLayout.hpp | 27 +++++++++- test/data/StructLayout.cpp | 73 ++++++++++++++++++++++++- 3 files changed, 199 insertions(+), 8 deletions(-) diff --git a/src/data/StructLayout.cpp b/src/data/StructLayout.cpp index 3cf30a4f..0f8154b7 100644 --- a/src/data/StructLayout.cpp +++ b/src/data/StructLayout.cpp @@ -1,6 +1,7 @@ #include "StructLayout.hpp" #include +#include #include #include @@ -48,22 +49,118 @@ static inline constexpr bool is_numeric_type(FieldType type) { return is_floating_point_type(type) || is_integer_type(type); } +static inline FieldIncapatibilityType checkIncapatibility( + const Field& srcField, const Field& dstField +) { + auto type = FieldIncapatibilityType::NONE; + if (dstField.elements < srcField.elements) { + type = FieldIncapatibilityType::DATA_LOSS; + } + if (srcField.type == dstField.type) { + return type; + } + if (is_numeric_type(srcField.type) && is_numeric_type(dstField.type)) { + int sizediff = + sizeof_type(dstField.type) - sizeof_type(srcField.type); + if (sizediff < 0) { + type = std::max(type, FieldIncapatibilityType::DATA_LOSS); + } + } else { + type = std::max(type, FieldIncapatibilityType::TYPE_ERROR); + } + return type; +} + +static inline integer_t clamp_value(integer_t value, FieldType type) { + auto typesize = sizeof_type(type) * CHAR_BIT; + integer_t minval = -(1 << (typesize-1)); + integer_t maxval = (1 << (typesize-1))-1; + return std::min(maxval, std::max(minval, value)); +} + +static void reset_integer( + const StructLayout& srcLayout, + const StructLayout& dstLayout, + const Field& field, + const Field& dstField, + const ubyte* src, + ubyte* dst +) { + int elements = std::min(field.elements, dstField.elements); + for (int i = 0; i < elements; i++) { + auto value = srcLayout.getInteger(src, field.name, i); + auto clamped = clamp_value(value, dstField.type); + if (dstField.convertStrategy == FieldConvertStrategy::CLAMP) { + value = clamped; + } else { + if (clamped != value) { + value = 0; + } + } + dstLayout.setInteger(dst, value, field.name, i); + } +} + +static void reset_number( + const StructLayout& srcLayout, + const StructLayout& dstLayout, + const Field& field, + const Field& dstField, + const ubyte* src, + ubyte* dst +) { + int elements = std::min(field.elements, dstField.elements); + for (int i = 0; i < elements; i++) { + auto value = srcLayout.getNumber(src, field.name, i); + dstLayout.setNumber(dst, value, field.name, i); + } +} + void StructLayout::convert( const StructLayout& srcLayout, const ubyte* src, ubyte* dst, bool allowDataLoss ) const { - for (const Field& field : fields) { - auto srcField = srcLayout.getField(field.name); - if (srcField == nullptr) { - std::memset(dst + field.offset, 0, field.size); + std::memset(dst, 0, totalSize); + for (const Field& field : srcLayout.fields) { + auto dstField = getField(field.name); + if (dstField == nullptr) { continue; } - // TODO: implement + auto type = checkIncapatibility(field, *dstField); + if (type == FieldIncapatibilityType::TYPE_ERROR) { + continue; + } + // can't just memcpy, because field type may be changed without data loss + if (is_integer_type(field.type) || + (is_floating_point_type(field.type) && + is_integer_type(dstField->type))) { + reset_integer(srcLayout, *this, field, *dstField, src, dst); + } else if (is_floating_point_type(dstField->type)) { + reset_number(srcLayout, *this, field, *dstField, src, dst); + } } } +std::vector StructLayout::checkCompatibility( + const StructLayout& dstLayout +) { + std::vector report; + for (const Field& field : fields) { + auto dstField = dstLayout.getField(field.name); + if (dstField == nullptr) { + report.push_back({field.name, FieldIncapatibilityType::MISSING}); + continue; + } + auto type = checkIncapatibility(field, *dstField); + if (type != FieldIncapatibilityType::NONE) { + report.push_back({field.name, type}); + } + } + return report; +} + const Field& StructLayout::requreField(const std::string& name) const { auto found = indices.find(name); if (found == indices.end()) { diff --git a/src/data/StructLayout.hpp b/src/data/StructLayout.hpp index e3f819af..0cb07605 100644 --- a/src/data/StructLayout.hpp +++ b/src/data/StructLayout.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "typedefs.hpp" @@ -13,6 +14,19 @@ namespace data { COUNT }; + /// @brief Sorted by severity + enum class FieldIncapatibilityType { + NONE = 0, + DATA_LOSS, + TYPE_ERROR, + MISSING, + }; + + struct FieldIncapatibility { + std::string name; + FieldIncapatibilityType type; + }; + inline constexpr int sizeof_type(FieldType type) { const int sizes[static_cast(FieldType::COUNT)] = { 1, 2, 4, 8, 4, 8, 1 @@ -25,11 +39,21 @@ namespace data { dataloss_error(const std::string& message) : std::runtime_error(message) {} }; + /// @brief Strategy will be used if value can't be left the same on conversion + enum class FieldConvertStrategy { + /// @brief Reset field value + RESET, + /// @brief Clamp field value if out of type range + CLAMP + }; + struct Field { FieldType type; std::string name; /// @brief Number of field elements (array size) int elements; + /// @brief Strategy will be used in data loss case + FieldConvertStrategy convertStrategy; /// @brief Byte offset of the field int offset; /// @brief Byte size of the field @@ -154,7 +178,8 @@ namespace data { ubyte* dst, bool allowDataLoss) const; - /// TODO: add checkCompatibility method + std::vector checkCompatibility( + const StructLayout& dstLayout); [[nodiscard]] static StructLayout create(const std::vector& fields); diff --git a/test/data/StructLayout.cpp b/test/data/StructLayout.cpp index ae5ea1d3..97738bcc 100644 --- a/test/data/StructLayout.cpp +++ b/test/data/StructLayout.cpp @@ -1,6 +1,8 @@ #include "data/StructLayout.hpp" #include +#include +#include using namespace data; @@ -40,6 +42,73 @@ TEST(StructLayout, Unicode) { EXPECT_EQ(layout.getChars(buffer, "text"), std::string(u8"пр")); } -TEST(StructLayout, Convert) { - +TEST(StructLayout, ConvertReorder) { + ubyte src[16] {}; + std::vector srcFields { + Field {FieldType::CHAR, "text", 5}, + Field {FieldType::F64, "pi", 1}, + }; + auto srcLayout = StructLayout::create(srcFields); + srcLayout.setChars(src, "truth", "text"); + srcLayout.setNumber(src, 3.141592, "pi"); + + EXPECT_EQ(srcLayout.getChars(src, "text"), "truth"); + EXPECT_DOUBLE_EQ(srcLayout.getNumber(src, "pi"), 3.141592); + + ubyte dst[32] {}; + std::vector dstFields { + Field {FieldType::I64, "arr", 2}, // an array of 2 i64 + Field {FieldType::CHAR, "text", 5}, + Field {FieldType::F64, "pi", 1}, + }; + auto dstLayout = StructLayout::create(dstFields); + EXPECT_EQ(srcLayout.checkCompatibility(dstLayout).size(), 0); + dstLayout.convert(srcLayout, src, dst, false); + + EXPECT_EQ(dstLayout.getChars(dst, "text"), "truth"); + EXPECT_DOUBLE_EQ(dstLayout.getNumber(dst, "pi"), 3.141592); +} + +TEST(StructLayout, ConvertWithLoss) { + ubyte src[32] {}; + std::vector srcFields { + Field {FieldType::CHAR, "text", 5}, + Field {FieldType::I16, "someint", 1}, + Field {FieldType::F64, "pi", 1}, + }; + auto srcLayout = StructLayout::create(srcFields); + srcLayout.setChars(src, "truth", "text"); + srcLayout.setInteger(src, 150, "someint"); + srcLayout.setNumber(src, 3.141592, "pi"); + + EXPECT_EQ(srcLayout.getChars(src, "text"), "truth"); + EXPECT_EQ(srcLayout.getInteger(src, "someint"), 150); + EXPECT_DOUBLE_EQ(srcLayout.getNumber(src, "pi"), 3.141592); + + ubyte dst[8] {}; + std::vector dstFields { + Field {FieldType::CHAR, "text", 3}, + Field {FieldType::I8, "someint", 1, FieldConvertStrategy::CLAMP}, + }; + auto dstLayout = StructLayout::create(dstFields); + auto report = srcLayout.checkCompatibility(dstLayout); + std::sort(report.begin(), report.end(), [](const auto& a, const auto& b) { + return a.name < b.name; + }); + EXPECT_EQ(report.size(), 3); + + EXPECT_EQ(report[0].name, "pi"); + EXPECT_EQ(report[0].type, FieldIncapatibilityType::MISSING); + + EXPECT_EQ(report[1].name, "someint"); + EXPECT_EQ(report[1].type, FieldIncapatibilityType::DATA_LOSS); + + EXPECT_EQ(report[2].name, "text"); + EXPECT_EQ(report[2].type, FieldIncapatibilityType::DATA_LOSS); + + // convert with losses + dstLayout.convert(srcLayout, src, dst, true); + + EXPECT_EQ(dstLayout.getChars(dst, "text"), "tru"); + EXPECT_EQ(dstLayout.getInteger(dst, "someint"), INT8_MAX); }