diff --git a/src/plugins/intel_npu/src/common/include/intel_npu/common/igraph.hpp b/src/plugins/intel_npu/src/common/include/intel_npu/common/igraph.hpp index 93368dc3b5fa34..fc5aec9158151c 100644 --- a/src/plugins/intel_npu/src/common/include/intel_npu/common/igraph.hpp +++ b/src/plugins/intel_npu/src/common/include/intel_npu/common/igraph.hpp @@ -23,7 +23,7 @@ class IGraph : public std::enable_shared_from_this { const Config& config, std::optional> blob); - virtual void export_blob(std::ostream& stream) const = 0; + virtual size_t export_blob(std::ostream& stream) const = 0; virtual std::vector process_profiling_output(const std::vector& profData, const Config& config) const = 0; diff --git a/src/plugins/intel_npu/src/compiler_adapter/include/driver_graph.hpp b/src/plugins/intel_npu/src/compiler_adapter/include/driver_graph.hpp index 12eda1e2c1469c..cf3d54c6b363e5 100644 --- a/src/plugins/intel_npu/src/compiler_adapter/include/driver_graph.hpp +++ b/src/plugins/intel_npu/src/compiler_adapter/include/driver_graph.hpp @@ -23,7 +23,7 @@ class DriverGraph final : public IGraph { const Config& config, std::optional> blob); - void export_blob(std::ostream& stream) const override; + size_t export_blob(std::ostream& stream) const override; std::vector process_profiling_output(const std::vector& profData, const Config& config) const override; diff --git a/src/plugins/intel_npu/src/compiler_adapter/include/plugin_graph.hpp b/src/plugins/intel_npu/src/compiler_adapter/include/plugin_graph.hpp index d905517cd8f313..9c88ace1c29d23 100644 --- a/src/plugins/intel_npu/src/compiler_adapter/include/plugin_graph.hpp +++ b/src/plugins/intel_npu/src/compiler_adapter/include/plugin_graph.hpp @@ -26,7 +26,7 @@ class PluginGraph final : public IGraph { std::vector blob, const Config& config); - void export_blob(std::ostream& stream) const override; + size_t export_blob(std::ostream& stream) const override; std::vector process_profiling_output(const std::vector& profData, const Config& config) const override; diff --git a/src/plugins/intel_npu/src/compiler_adapter/src/driver_graph.cpp b/src/plugins/intel_npu/src/compiler_adapter/src/driver_graph.cpp index ced007499bdc1d..a29412075c7e39 100644 --- a/src/plugins/intel_npu/src/compiler_adapter/src/driver_graph.cpp +++ b/src/plugins/intel_npu/src/compiler_adapter/src/driver_graph.cpp @@ -32,9 +32,9 @@ DriverGraph::DriverGraph(const std::shared_ptr& zeGraphExt, initialize(config); } -void DriverGraph::export_blob(std::ostream& stream) const { +size_t DriverGraph::export_blob(std::ostream& stream) const { const uint8_t* blobPtr = nullptr; - size_t blobSize = -1; + size_t blobSize; std::vector blob; if (_blobIsReleased) { @@ -47,7 +47,7 @@ void DriverGraph::export_blob(std::ostream& stream) const { if (!stream) { _logger.error("Write blob to stream failed. Blob is broken!"); - return; + return 0; } if (_logger.level() >= ov::log::Level::INFO) { @@ -61,6 +61,7 @@ void DriverGraph::export_blob(std::ostream& stream) const { _logger.info(str.str().c_str()); } _logger.info("Write blob to stream successfully."); + return blobSize; } std::vector DriverGraph::process_profiling_output(const std::vector& profData, diff --git a/src/plugins/intel_npu/src/compiler_adapter/src/plugin_graph.cpp b/src/plugins/intel_npu/src/compiler_adapter/src/plugin_graph.cpp index ae37568a90980d..d0c24a82e03937 100644 --- a/src/plugins/intel_npu/src/compiler_adapter/src/plugin_graph.cpp +++ b/src/plugins/intel_npu/src/compiler_adapter/src/plugin_graph.cpp @@ -30,12 +30,12 @@ PluginGraph::PluginGraph(const std::shared_ptr& zeGraphExt, initialize(config); } -void PluginGraph::export_blob(std::ostream& stream) const { +size_t PluginGraph::export_blob(std::ostream& stream) const { stream.write(reinterpret_cast(_blob.data()), _blob.size()); if (!stream) { _logger.error("Write blob to stream failed. Blob is broken!"); - return; + return 0; } if (_logger.level() >= ov::log::Level::INFO) { @@ -49,6 +49,7 @@ void PluginGraph::export_blob(std::ostream& stream) const { _logger.info(str.str().c_str()); } _logger.info("Write blob to stream successfully."); + return _blob.size(); } std::vector PluginGraph::process_profiling_output(const std::vector& profData, diff --git a/src/plugins/intel_npu/src/plugin/include/metadata.hpp b/src/plugins/intel_npu/src/plugin/include/metadata.hpp new file mode 100644 index 00000000000000..f4ae25e84c9136 --- /dev/null +++ b/src/plugins/intel_npu/src/plugin/include/metadata.hpp @@ -0,0 +1,173 @@ +// Copyright (C) 2018-2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include +#include +#include +#include +#include + +namespace intel_npu { + +struct MetadataBase { +protected: + uint32_t _version; + +public: + MetadataBase(uint32_t version) : _version(version) {} + + /** + * @brief Reads metadata from a stream. + */ + virtual void read(std::istream& stream) = 0; + + /** + * @brief Writes metadata to a stream. + */ + virtual void write(std::ostream& stream) = 0; + + virtual bool is_compatible() = 0; + + virtual uint64_t get_blob_size() const = 0; + + virtual ~MetadataBase() = default; + + /** + * @brief Returns a uint32_t value which represents two uint16_t values concatenated. + * @details Convention for bumping the metadata version: + * - Increment Major in case of: removing a current field OR adding a new field in between fields. + * - Increment Minor in case of: adding a new field at the end. + * + * @return Major and minor versions concatenated into a single uint32_t value. + */ + static constexpr uint32_t make_version(uint16_t major, uint16_t minor) { + return major << 16 | (minor & 0x0000ffff); + } + + /** + * @brief Gets the major version. + * @return Major version. + */ + static constexpr uint16_t get_major(uint32_t version) { + return static_cast(version >> 16); + } + + /** + * @brief Gets the minor version. + * @return Minor version. + */ + static constexpr uint16_t get_minor(uint32_t version) { + return static_cast(version); + } +}; + +/** + * @brief Magic bytes used for identifying NPU blobs. + */ +constexpr std::string_view MAGIC_BYTES = "OVNPU"; + +/** + * @brief List of supported version formats. + */ +constexpr uint32_t METADATA_VERSION_1_0{MetadataBase::make_version(1, 0)}; + +/** + * @brief Current metadata version. + */ +constexpr uint32_t CURRENT_METADATA_VERSION{METADATA_VERSION_1_0}; + +constexpr uint16_t CURRENT_METADATA_MAJOR_VERSION{MetadataBase::get_major(CURRENT_METADATA_VERSION)}; +constexpr uint16_t CURRENT_METADATA_MINOR_VERSION{MetadataBase::get_minor(CURRENT_METADATA_VERSION)}; + +struct OpenvinoVersion { +private: + std::string _version; + uint32_t _size; + +public: + OpenvinoVersion(); + + OpenvinoVersion(std::string_view version); + + /** + * @brief Reads version data from a stream. + */ + void read(std::istream& stream); + + /** + * @brief Writes version data to a stream. + */ + void write(std::ostream& stream); + + /** + * @brief Gets the version string. + */ + std::string get_version() const; +}; + +/** + * @brief Template for metadata class handling. + */ +template +struct Metadata : public MetadataBase {}; + +/** + * @brief Template specialization for metadata version 1.0. + */ +template <> +struct Metadata : public MetadataBase { +protected: + OpenvinoVersion _ovVersion; + uint64_t _blobDataSize; + +public: + Metadata(uint64_t blobSize, std::optional ovVersion = std::nullopt); + + void read(std::istream& stream) override; + + /** + * @attention It's a must to first write metadata version in any metadata specialization. + * + * @details When importing a versioned blob, it's best to first read the metadata version field. + * This is the quickest way to handle many incompatible blob cases without needing to traverse the whole NPU + * metadata section. + */ + void write(std::ostream& stream) override; + + /** + * @brief Checks if metadata is supported. + * + * @return Returns: + * - false: + * - if blob metadata does not match current metadata. + * - if blob OpenVINO version does not match current one. + * + * - true: if all versions match. + * + * @note The version check can be disabled if the "NPU_DISABLE_VERSION_CHECK" environment variable is set to '1'. + */ + bool is_compatible() override; + + uint64_t get_blob_size() const override; +}; + +/** + * @brief Creates a Metadata object. + * + * @return Unique pointer to the created MetadataBase object if the major version is supported; otherwise, returns + * 'nullptr'. + */ +std::unique_ptr create_metadata(uint32_t version, uint64_t blobSize); + +/** + * @brief Reads metadata from a blob. + * + * @return If the blob is versioned and its major version is supported, returns an unique pointer to the read + * MetadataBase object; otherwise, returns 'nullptr'. + */ +std::unique_ptr read_metadata_from(std::istream& stream); + +} // namespace intel_npu diff --git a/src/plugins/intel_npu/src/plugin/src/compiled_model.cpp b/src/plugins/intel_npu/src/plugin/src/compiled_model.cpp index 75cf5a71f88fd8..516518f6999cd3 100644 --- a/src/plugins/intel_npu/src/plugin/src/compiled_model.cpp +++ b/src/plugins/intel_npu/src/plugin/src/compiled_model.cpp @@ -13,6 +13,7 @@ #include "intel_npu/config/compiler.hpp" #include "intel_npu/config/config.hpp" #include "intel_npu/config/runtime.hpp" +#include "metadata.hpp" #include "openvino/pass/constant_folding.hpp" #include "openvino/pass/manager.hpp" #include "openvino/runtime/properties.hpp" @@ -72,7 +73,10 @@ std::shared_ptr CompiledModel::create_sync_infer_request( void CompiledModel::export_model(std::ostream& stream) const { _logger.debug("CompiledModel::export_model"); - _graph->export_blob(stream); + size_t blobSizeBeforeVersioning = _graph->export_blob(stream); + + auto meta = Metadata(blobSizeBeforeVersioning, ov::get_openvino_version().buildNumber); + meta.write(stream); } std::shared_ptr CompiledModel::get_runtime_model() const { diff --git a/src/plugins/intel_npu/src/plugin/src/metadata.cpp b/src/plugins/intel_npu/src/plugin/src/metadata.cpp new file mode 100644 index 00000000000000..521ef5c01b96a4 --- /dev/null +++ b/src/plugins/intel_npu/src/plugin/src/metadata.cpp @@ -0,0 +1,165 @@ +// Copyright (C) 2018-2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "metadata.hpp" + +#include +#include +#include + +#include "intel_npu/config/config.hpp" +#include "intel_npu/utils/logger/logger.hpp" +#include "openvino/core/version.hpp" +#include "openvino/runtime/shared_buffer.hpp" + +namespace { + +std::streampos getFileSize(std::istream& stream) { + auto log = intel_npu::Logger::global().clone("getFileSize"); + if (!stream) { + OPENVINO_THROW("Stream is in bad status! Please check the passed stream status!"); + } + + if (dynamic_cast(stream.rdbuf()) != nullptr) { + return stream.rdbuf()->in_avail(); + } + const std::streampos streamStart = stream.tellg(); + stream.seekg(0, std::ios_base::end); + const std::streampos streamEnd = stream.tellg(); + stream.seekg(streamStart, std::ios_base::beg); + + log.debug("Read blob size: streamStart=%zu, streamEnd=%zu", streamStart, streamEnd); + + if (streamEnd < streamStart) { + OPENVINO_THROW("Invalid stream size: streamEnd (", + streamEnd, + ") is not larger than streamStart (", + streamStart, + ")!"); + } + + return streamEnd - streamStart; +} +} // anonymous namespace + +namespace intel_npu { + +OpenvinoVersion::OpenvinoVersion(std::string_view version) + : _version(version), + _size(static_cast(version.size())) {} + +void OpenvinoVersion::read(std::istream& stream) { + stream.read(reinterpret_cast(&_size), sizeof(_size)); + _version.resize(_size); + stream.read(_version.data(), _size); +} + +void OpenvinoVersion::write(std::ostream& stream) { + stream.write(reinterpret_cast(&_size), sizeof(_size)); + stream.write(_version.data(), _size); +} + +Metadata::Metadata(uint64_t blobSize, std::optional ovVersion) + : MetadataBase{METADATA_VERSION_1_0}, + _ovVersion{ovVersion.value_or(ov::get_openvino_version().buildNumber)}, + _blobDataSize{blobSize} {} + +void Metadata::read(std::istream& stream) { + _ovVersion.read(stream); +} + +void Metadata::write(std::ostream& stream) { + stream.write(reinterpret_cast(&_version), sizeof(_version)); + _ovVersion.write(stream); + stream.write(reinterpret_cast(&_blobDataSize), sizeof(_blobDataSize)); + stream.write(MAGIC_BYTES.data(), MAGIC_BYTES.size()); +} + +std::unique_ptr create_metadata(uint32_t version, uint64_t blobSize) { + if (MetadataBase::get_major(version) == CURRENT_METADATA_MAJOR_VERSION && + MetadataBase::get_minor(version) > CURRENT_METADATA_MINOR_VERSION) { + return std::make_unique>(blobSize, std::nullopt); + } + + switch (version) { + case METADATA_VERSION_1_0: + return std::make_unique>(blobSize, std::nullopt); + + default: + OPENVINO_THROW("Invalid metadata version!"); + } +} + +std::string OpenvinoVersion::get_version() const { + return _version; +} + +bool Metadata::is_compatible() { + auto logger = Logger::global().clone("NPUBlobMetadata"); + // checking if we can import the blob + if (_ovVersion.get_version() != ov::get_openvino_version().buildNumber) { + logger.error("Imported blob OpenVINO version: %s, but the current OpenVINO version is: %s", + _ovVersion.get_version().c_str(), + ov::get_openvino_version().buildNumber); + +#ifdef NPU_PLUGIN_DEVELOPER_BUILD + if (auto envVar = std::getenv("NPU_DISABLE_VERSION_CHECK")) { + if (envVarStrToBool("NPU_DISABLE_VERSION_CHECK", envVar)) { + logger.info("Blob compatibility check skipped."); + return true; + } + } +#endif + return false; + } + return true; +} + +std::unique_ptr read_metadata_from(std::istream& stream) { + size_t magicBytesSize = MAGIC_BYTES.size(); + std::string blobMagicBytes; + blobMagicBytes.resize(magicBytesSize); + + std::streampos currentStreamPos = stream.tellg(), streamSize = getFileSize(stream); + stream.seekg(streamSize - std::streampos(magicBytesSize), std::ios::cur); + stream.read(blobMagicBytes.data(), magicBytesSize); + if (MAGIC_BYTES != blobMagicBytes) { + OPENVINO_THROW("Blob is missing NPU metadata!"); + } + + uint64_t blobDataSize; + stream.seekg(-std::streampos(magicBytesSize) - sizeof(blobDataSize), std::ios::cur); + stream.read(reinterpret_cast(&blobDataSize), sizeof(blobDataSize)); + stream.seekg(-stream.tellg() + currentStreamPos + blobDataSize, std::ios::cur); + + uint32_t metaVersion; + stream.read(reinterpret_cast(&metaVersion), sizeof(metaVersion)); + + std::unique_ptr storedMeta; + try { + storedMeta = create_metadata(metaVersion, blobDataSize); + storedMeta->read(stream); + } catch (const std::exception& ex) { + OPENVINO_THROW(ex.what(), + "Imported blob metadata version: ", + MetadataBase::get_major(metaVersion), + ".", + MetadataBase::get_minor(metaVersion), + " but the current version is: ", + CURRENT_METADATA_MAJOR_VERSION, + ".", + CURRENT_METADATA_MINOR_VERSION); + } catch (...) { + OPENVINO_THROW("Unexpected exception while reading blob NPU metadata"); + } + stream.seekg(-stream.tellg() + currentStreamPos, std::ios::cur); + + return storedMeta; +} + +uint64_t Metadata::get_blob_size() const { + return _blobDataSize; +} + +} // namespace intel_npu diff --git a/src/plugins/intel_npu/src/plugin/src/plugin.cpp b/src/plugins/intel_npu/src/plugin/src/plugin.cpp index 5de2b700fe984e..5d5f666f5119ec 100644 --- a/src/plugins/intel_npu/src/plugin/src/plugin.cpp +++ b/src/plugins/intel_npu/src/plugin/src/plugin.cpp @@ -21,6 +21,7 @@ #include "intel_npu/config/npuw.hpp" #include "intel_npu/config/runtime.hpp" #include "intel_npu/utils/zero/zero_init.hpp" +#include "metadata.hpp" #include "npuw/compiled_model.hpp" #include "openvino/op/constant.hpp" #include "openvino/op/parameter.hpp" @@ -135,30 +136,6 @@ std::map any_copy(const ov::AnyMap& params) { return result; } -size_t getFileSize(std::istream& stream) { - auto log = Logger::global().clone("getFileSize"); - if (!stream) { - OPENVINO_THROW("Stream is in bad status! Please check the passed stream status!"); - } - - const size_t streamStart = stream.tellg(); - stream.seekg(0, std::ios_base::end); - const size_t streamEnd = stream.tellg(); - stream.seekg(streamStart, std::ios_base::beg); - - log.debug("Read blob size: streamStart=%zu, streamEnd=%zu", streamStart, streamEnd); - - if (streamEnd < streamStart) { - OPENVINO_THROW("Invalid stream size: streamEnd (", - streamEnd, - ") is not larger than streamStart (", - streamStart, - ")!"); - } - - return streamEnd - streamStart; -} - void update_log_level(const std::map& propertiesMap) { auto it = propertiesMap.find(std::string(LOG_LEVEL::key())); if (it != propertiesMap.end()) { @@ -773,7 +750,7 @@ std::shared_ptr Plugin::import_model(std::istream& stream, c stream.seekg(stream_start_pos); return ov::npuw::LLMCompiledModel::deserialize(stream, shared_from_this()); } - stream.seekg(stream_start_pos); + stream.seekg(-stream.tellg() + stream_start_pos, std::ios::cur); // Drop NPUW properties if there are any ov::AnyMap npu_plugin_properties; @@ -806,7 +783,12 @@ std::shared_ptr Plugin::import_model(std::istream& stream, c CompilerAdapterFactory compilerAdapterFactory; auto compiler = compilerAdapterFactory.getCompiler(_backends->getIEngineBackend(), localConfig); - auto graphSize = getFileSize(stream); + auto storedMeta = read_metadata_from(stream); + if (!storedMeta->is_compatible()) { + OPENVINO_THROW("Incompatible blob version!"); + } + + auto graphSize = storedMeta->get_blob_size(); std::vector blob(graphSize); stream.read(reinterpret_cast(blob.data()), graphSize); diff --git a/src/plugins/intel_npu/tests/unit/CMakeLists.txt b/src/plugins/intel_npu/tests/unit/CMakeLists.txt index f4e8a64ecea92b..1097e183369fe4 100644 --- a/src/plugins/intel_npu/tests/unit/CMakeLists.txt +++ b/src/plugins/intel_npu/tests/unit/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (C) 2024 Intel Corporation +# Copyright (C) 2018-2025 Intel Corporation # SPDX-License-Identifier: Apache-2.0 # @@ -29,6 +29,8 @@ ov_add_test_target( ${OpenVINO_SOURCE_DIR}/src/plugins/intel_npu/src/utils/include ${OpenVINO_SOURCE_DIR}/src/plugins/intel_npu/src/plugin/include ${OpenVINO_SOURCE_DIR}/src/plugins/intel_npu/src/al/include + OBJECT_FILES + ${OpenVINO_SOURCE_DIR}/src/plugins/intel_npu/src/plugin/src/metadata.cpp LINK_LIBRARIES ${MANDATORY_UNIT_TESTS_LIBS} LABELS diff --git a/src/plugins/intel_npu/tests/unit/npu/metadata_version.cpp b/src/plugins/intel_npu/tests/unit/npu/metadata_version.cpp new file mode 100644 index 00000000000000..0c94a1e5334b36 --- /dev/null +++ b/src/plugins/intel_npu/tests/unit/npu/metadata_version.cpp @@ -0,0 +1,201 @@ +// Copyright (C) 2018-2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +#include "common_test_utils/test_assertions.hpp" +#include "metadata.hpp" +#include "openvino/core/version.hpp" + +using namespace intel_npu; + +using MetadataUnitTests = ::testing::Test; + +struct MetadataTest : Metadata { + MetadataTest(uint64_t blobSize, std::optional ovVersion) + : Metadata(blobSize, ovVersion) {} + + void set_version(uint32_t newVersion) { + _version = newVersion; + } +}; + +TEST_F(MetadataUnitTests, readUnversionedBlob) { + std::stringstream blob("this_is an_unversioned bl0b"); + + std::unique_ptr storedMeta; + ASSERT_ANY_THROW(storedMeta = read_metadata_from(blob)); +} + +TEST_F(MetadataUnitTests, writeAndReadCurrentMetadataFromBlob) { + uint64_t blobSize = 0; + std::stringstream stream; + auto meta = MetadataTest(blobSize, ov::get_openvino_version().buildNumber); + + OV_ASSERT_NO_THROW(meta.write(stream)); + + std::unique_ptr storedMeta; + OV_ASSERT_NO_THROW(storedMeta = read_metadata_from(stream)); + ASSERT_TRUE(storedMeta->is_compatible()); +} + +TEST_F(MetadataUnitTests, writeAndReadInvalidOpenvinoVersion) { + uint64_t blobSize = 0; + std::stringstream stream; + auto meta = MetadataTest(blobSize, "just_some_wrong_ov_version"); + + OV_ASSERT_NO_THROW(meta.write(stream)); + + std::unique_ptr storedMeta; + OV_ASSERT_NO_THROW(storedMeta = read_metadata_from(stream)); + ASSERT_FALSE(storedMeta->is_compatible()); +} + +TEST_F(MetadataUnitTests, writeAndReadInvalidMetadataVersion) { + uint64_t blobSize = 0; + std::stringstream stream; + auto meta = MetadataTest(blobSize, std::nullopt); + + constexpr uint32_t dummyVersion = MetadataBase::make_version(0x00007E57, 0x0000AC3D); + meta.set_version(dummyVersion); + + OV_ASSERT_NO_THROW(meta.write(stream)); + ASSERT_ANY_THROW(auto storedMeta = read_metadata_from(stream)); +} + +TEST_F(MetadataUnitTests, writeAndReadMetadataWithNewerMinorVersion) { + uint64_t blobSize = 0; + std::stringstream stream; + auto meta = MetadataTest(blobSize, "some_ov_version"); + + constexpr uint32_t dummyVersion = + MetadataBase::make_version(CURRENT_METADATA_MAJOR_VERSION, CURRENT_METADATA_MINOR_VERSION + 1); + meta.set_version(dummyVersion); + + OV_ASSERT_NO_THROW(meta.write(stream)); + std::unique_ptr storedMeta; + OV_ASSERT_NO_THROW(storedMeta = read_metadata_from(stream)); + ASSERT_FALSE(storedMeta->is_compatible()); +} + +struct MetadataVersionTestFixture : Metadata, ::testing::TestWithParam { +public: + std::stringstream blob; + + void set_version(uint32_t newVersion) { + _version = newVersion; + } + + MetadataVersionTestFixture() : Metadata(0, std::nullopt) {} + + MetadataVersionTestFixture(uint64_t blobSize, std::optional ovVersion) + : Metadata(blobSize, ovVersion) {} + + void TestBody() override {} + + static std::string getTestCaseName(testing::TestParamInfo info); +}; + +std::string MetadataVersionTestFixture::getTestCaseName( + testing::TestParamInfo info) { + std::ostringstream result; + result << "major version=" << MetadataBase::get_major(info.param) + << ", minor version=" << MetadataBase::get_minor(info.param); + return result.str(); +} + +TEST_P(MetadataVersionTestFixture, writeAndReadInvalidMetadataVersion) { + uint32_t metaVersion = GetParam(); + if (CURRENT_METADATA_MAJOR_VERSION == MetadataBase::get_major(metaVersion) && CURRENT_METADATA_MINOR_VERSION == 0) { + GTEST_SKIP() << "Skipping single test since there is no case of lower minor version than actual."; + } + + MetadataVersionTestFixture dummyMeta = MetadataVersionTestFixture(0, "some_ov_version"); + dummyMeta.set_version(metaVersion); + + OV_ASSERT_NO_THROW(dummyMeta.write(blob)); + EXPECT_ANY_THROW(read_metadata_from(blob)); + ASSERT_FALSE(dummyMeta.is_compatible()); +} + +const std::vector badMetadataVersions = { + MetadataBase::make_version(CURRENT_METADATA_MAJOR_VERSION, CURRENT_METADATA_MINOR_VERSION - 1), + MetadataBase::make_version(CURRENT_METADATA_MAJOR_VERSION + 1, CURRENT_METADATA_MINOR_VERSION), + MetadataBase::make_version(CURRENT_METADATA_MAJOR_VERSION + 1, CURRENT_METADATA_MINOR_VERSION + 1), + MetadataBase::make_version(CURRENT_METADATA_MAJOR_VERSION + 1, CURRENT_METADATA_MINOR_VERSION - 1), + MetadataBase::make_version(CURRENT_METADATA_MAJOR_VERSION - 1, CURRENT_METADATA_MINOR_VERSION), + MetadataBase::make_version(CURRENT_METADATA_MAJOR_VERSION - 1, CURRENT_METADATA_MINOR_VERSION + 1), + MetadataBase::make_version(CURRENT_METADATA_MAJOR_VERSION - 1, CURRENT_METADATA_MINOR_VERSION - 1)}; + +INSTANTIATE_TEST_SUITE_P(MetadataUnitTests, + MetadataVersionTestFixture, + ::testing::ValuesIn(badMetadataVersions), + MetadataVersionTestFixture::getTestCaseName); + +TEST_F(MetadataUnitTests, writeAndReadMetadataWithNewerFieldAtEnd) { + uint64_t blobSize = 0; + std::stringstream stream; + auto meta = MetadataTest(blobSize, "some_ov_version"); + + constexpr uint32_t dummyVersion = + MetadataBase::make_version(CURRENT_METADATA_MAJOR_VERSION, CURRENT_METADATA_MINOR_VERSION + 1); + meta.set_version(dummyVersion); + + OV_ASSERT_NO_THROW(meta.write(stream)); + + // inserting a new field at the end of the blob, between last metadata field and blobDataSize + std::string temp = stream.str(); + size_t offset = MAGIC_BYTES.size() + sizeof(uint64_t); + temp.insert(temp.length() - offset, "new metadata field"); + stream.str(""); + stream << temp; + + std::unique_ptr storedMeta; + OV_ASSERT_NO_THROW(storedMeta = read_metadata_from(stream)); + ASSERT_FALSE(storedMeta->is_compatible()); +} + +TEST_F(MetadataUnitTests, writeAndReadMetadataWithNewerFieldAtMiddle) { + uint64_t blobSize = 0; + std::stringstream stream; + auto meta = MetadataTest(blobSize, "some_ov_version"); + + constexpr uint32_t dummyVersion = + MetadataBase::make_version(CURRENT_METADATA_MAJOR_VERSION + 1, CURRENT_METADATA_MINOR_VERSION); + meta.set_version(dummyVersion); + + OV_ASSERT_NO_THROW(meta.write(stream)); + + // inserting a new field at the middle of the blob, between metadata version and OV version size + std::string temp = stream.str(); + size_t offset = sizeof(CURRENT_METADATA_VERSION); + temp.insert(offset, "new metadata field"); + stream.str(""); + stream << temp; + + std::unique_ptr storedMeta; + EXPECT_ANY_THROW(storedMeta = read_metadata_from(stream)); +} + +TEST_F(MetadataUnitTests, writeAndReadMetadataWithRemovedField) { + uint64_t blobSize = 0; + std::stringstream stream; + auto meta = MetadataTest(blobSize, "some_ov_version"); + + constexpr uint32_t dummyVersion = + MetadataBase::make_version(CURRENT_METADATA_MAJOR_VERSION + 1, CURRENT_METADATA_MINOR_VERSION); + meta.set_version(dummyVersion); + + OV_ASSERT_NO_THROW(meta.write(stream)); + + // removing fields between metadata version and blob data size + std::string temp = stream.str(); + size_t offset = sizeof(CURRENT_METADATA_VERSION), size = offset + MAGIC_BYTES.size() + sizeof(uint64_t); + temp.replace(offset, temp.length() - size, ""); + stream.str(""); + stream << temp; + + std::unique_ptr storedMeta; + EXPECT_ANY_THROW(storedMeta = read_metadata_from(stream)); +}