#include "StructLayout.hpp" #include #include #include #include #include #include "util/data_io.hpp" #include "util/stringutil.hpp" using namespace data; static_assert(sizeof(float) == sizeof(int32_t)); static_assert(sizeof(double) == sizeof(int64_t)); FieldType data::FieldType_from_string(std::string_view name) { std::map map { {"int8", FieldType::I8}, {"int16", FieldType::I16}, {"int32", FieldType::I32}, {"int64", FieldType::I64}, {"float32", FieldType::F32}, {"float64", FieldType::F64}, {"char", FieldType::CHAR}, }; return map.at(name); } FieldConvertStrategy data::FieldConvertStrategy_from_string(std::string_view name) { std::map map { {"reset", FieldConvertStrategy::RESET}, {"clamp", FieldConvertStrategy::CLAMP} }; return map.at(name); } StructLayout StructLayout::create(const std::vector& fields) { std::vector builtFields = fields; std::unordered_map indices; for (Field& field : builtFields) { field.size = sizeof_type(field.type) * field.elements; } std::sort(builtFields.begin(), builtFields.end(), [](const Field& a, const Field& b) { return a.size > b.size; } ); int offset = 0; for (int i = 0; i < builtFields.size(); i++) { auto& field = builtFields[i]; field.offset = offset; indices[field.name] = i; offset += field.size; } return StructLayout( offset, std::move(builtFields), std::move(indices)); } static inline constexpr bool is_integer_type(FieldType type) { return (type >= FieldType::I8 && type <= FieldType::I64) || type == FieldType::CHAR; } static inline constexpr bool is_floating_point_type(FieldType type) { return type == FieldType::F32 || type == FieldType::F64; } 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 { std::memset(dst, 0, totalSize); for (const Field& field : srcLayout.fields) { auto dstField = getField(field.name); if (dstField == nullptr) { continue; } 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::requireField(const std::string& name) const { auto found = indices.find(name); if (found == indices.end()) { throw std::runtime_error("field '"+name+"' does not exist"); } return *&fields.at(found->second); } template static void set_int(ubyte* dst, integer_t value) { T out_value = static_cast(value); out_value = dataio::le2h(out_value); *reinterpret_cast(dst) = out_value; } void StructLayout::setInteger( ubyte* dst, integer_t value, const Field& field, int index ) const { if (index < 0 || index >= field.elements) { throw std::out_of_range( "index out of bounds [0, "+std::to_string(field.elements)+"]"); } auto ptr = dst + field.offset + index * sizeof_type(field.type); switch (field.type) { case FieldType::I8: set_int(ptr, value); break; case FieldType::I16: set_int(ptr, value); break; case FieldType::I32: set_int(ptr, value); break; case FieldType::I64: set_int(ptr, value); break; case FieldType::CHAR: set_int(ptr, value); break; case FieldType::F32: case FieldType::F64: setNumber(dst, static_cast(value), field, index); break; default: throw std::runtime_error("type error"); } } void StructLayout::setNumber( ubyte* dst, number_t value, const Field& field, int index ) const { if (index < 0 || index >= field.elements) { throw std::out_of_range( "index out of bounds [0, "+std::to_string(field.elements)+"]"); } auto ptr = dst + field.offset + index * sizeof_type(field.type); switch (field.type) { case FieldType::F32: { float fval = static_cast(value); int32_t ival; std::memcpy(&ival, &fval, sizeof(int32_t)); set_int(ptr, ival); break; } case FieldType::F64: { double fval = static_cast(value); int64_t ival; std::memcpy(&ival, &fval, sizeof(int64_t)); set_int(ptr, ival); break; } default: throw std::runtime_error("type error"); } } size_t StructLayout::setAscii( ubyte* dst, std::string_view value, const std::string& name ) const { const auto& field = requireField(name); if (field.type != FieldType::CHAR) { throw std::runtime_error("'char' field type required"); } auto ptr = reinterpret_cast(dst + field.offset); auto size = std::min(value.size(), static_cast(field.elements)); std::memcpy(ptr, value.data(), size); if (size < field.elements) { std::memset(ptr + size, 0, field.elements - size); } return size; } size_t StructLayout::setUnicode( ubyte* dst, std::string_view value, const Field& field ) const { if (field.type != FieldType::CHAR) { throw std::runtime_error("'char' field type required"); } auto text = std::string_view(value.data(), value.size()); size_t size = util::crop_utf8(text, field.elements); auto ptr = reinterpret_cast(dst + field.offset); std::memcpy(ptr, value.data(), size); if (size < field.elements) { std::memset(ptr + size, 0, field.elements - size); } return size; } template static T get_int(const ubyte* src) { return dataio::le2h(*reinterpret_cast(src)); } integer_t StructLayout::getInteger( const ubyte* src, const Field& field, int index ) const { if (index < 0 || index >= field.elements) { throw std::out_of_range( "index out of bounds [0, "+std::to_string(field.elements)+"]"); } auto ptr = src + field.offset + index * sizeof_type(field.type); switch (field.type) { case FieldType::I8: return get_int(ptr); case FieldType::I16: return get_int(ptr); case FieldType::I32: return get_int(ptr); case FieldType::I64: return get_int(ptr); case FieldType::CHAR: return get_int(ptr); default: throw std::runtime_error("type error"); } } number_t StructLayout::getNumber( const ubyte* src, const Field& field, int index ) const { if (index < 0 || index >= field.elements) { throw std::out_of_range( "index out of bounds [0, "+std::to_string(field.elements)+"]"); } auto ptr = src + field.offset + index * sizeof_type(field.type); switch (field.type) { case FieldType::F32: { float fval; auto ival = get_int(ptr); std::memcpy(&fval, &ival, sizeof(float)); return fval; } case FieldType::F64: { double fval; auto ival = get_int(ptr); std::memcpy(&fval, &ival, sizeof(double)); return fval; } case FieldType::I8: case FieldType::I16: case FieldType::I32: case FieldType::I64: case FieldType::CHAR: return getInteger(src, field, index); default: throw std::runtime_error("type error"); } } std::string_view StructLayout::getChars( const ubyte* src, const Field& field ) const { if (field.type != FieldType::CHAR) { throw std::runtime_error("'char' field type required"); } auto ptr = reinterpret_cast(src + field.offset); return std::string_view(ptr, strnlen(ptr, field.elements)); } dv::value StructLayout::serialize() const { auto map = dv::object(); for (const auto& [name, index] : indices) { auto& fieldmap = map.object(name); const auto& field = fields[index]; fieldmap["type"] = to_string(field.type); if (field.elements != 1) { fieldmap["length"] = field.elements; } if (field.convertStrategy != FieldConvertStrategy::RESET) { fieldmap["convert-strategy"] = to_string(field.convertStrategy); } } return map; } void StructLayout::deserialize(const dv::value& src) { std::vector fields; for (const auto& [name, fieldmap] : src.asObject()) { const auto& typeName = fieldmap["type"].asString(); FieldType type = FieldType_from_string(typeName); int elements = 1; fieldmap.at("length").get(elements); if (elements <= 0) { throw std::runtime_error( "invalid field " + util::quote(name) + " length: " + std::to_string(elements) ); } auto convertStrategy = FieldConvertStrategy::RESET; if (fieldmap.has("convert-strategy")) { convertStrategy = FieldConvertStrategy_from_string( fieldmap["convert-strategy"].asString() ); } fields.push_back(Field (type, name, elements, convertStrategy)); } *this = create(fields); }