From 471cef5095ad577c6ea1218e53cabe539fa6f9b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 14 Feb 2020 11:37:10 +0100 Subject: [PATCH] Trade: base structures for memory-mappable serialization format. --- src/Magnum/Trade/CMakeLists.txt | 2 +- src/Magnum/Trade/Data.cpp | 94 ++++++++++++ src/Magnum/Trade/Data.h | 188 ++++++++++++++++++++++- src/Magnum/Trade/Test/CMakeLists.txt | 2 +- src/Magnum/Trade/Test/DataTest.cpp | 214 ++++++++++++++++++++++++++- src/Magnum/Trade/Trade.h | 4 + 6 files changed, 500 insertions(+), 4 deletions(-) diff --git a/src/Magnum/Trade/CMakeLists.txt b/src/Magnum/Trade/CMakeLists.txt index 27c4ac30c..e7eb5bfb1 100644 --- a/src/Magnum/Trade/CMakeLists.txt +++ b/src/Magnum/Trade/CMakeLists.txt @@ -28,7 +28,6 @@ find_package(Corrade REQUIRED PluginManager) set(MagnumTrade_SRCS AbstractMaterialData.cpp ArrayAllocator.cpp - Data.cpp LightData.cpp MeshObjectData2D.cpp MeshObjectData3D.cpp @@ -41,6 +40,7 @@ set(MagnumTrade_GracefulAssert_SRCS AbstractSceneConverter.cpp AnimationData.cpp CameraData.cpp + Data.cpp ImageData.cpp MeshData.cpp ObjectData2D.cpp diff --git a/src/Magnum/Trade/Data.cpp b/src/Magnum/Trade/Data.cpp index b7efc41c8..4f24953c4 100644 --- a/src/Magnum/Trade/Data.cpp +++ b/src/Magnum/Trade/Data.cpp @@ -25,10 +25,17 @@ #include "Data.h" +#include +#include #include namespace Magnum { namespace Trade { +static_assert(sizeof(DataChunkHeader) == (sizeof(void*) == 4 ? 20 : 24), + "DataChunkHeader has unexpected size"); +static_assert(alignof(DataChunkHeader) == sizeof(std::size_t), + "DataChunkHeader has unexpected alignment"); + Debug& operator<<(Debug& debug, const DataFlag value) { debug << "Trade::DataFlag" << Debug::nospace; @@ -50,6 +57,93 @@ Debug& operator<<(Debug& debug, const DataFlags value) { DataFlag::Mutable}); } +namespace { + Debug& printFourCC(Debug& debug, UnsignedInt value, const char* name) { + debug << name << Debug::nospace; + + for(std::size_t i = 0; i != 4; ++i) { + if(i) debug << Debug::nospace << ","; + + const int c = value & 255; + if(std::isprint(c)) { + const char data[] = {'\'', char(c), '\'', '\0'}; + debug << data; + } else { + debug << reinterpret_cast(c); + } + + value >>= 8; + } + + return debug << Debug::nospace << ")"; + } +} + +Debug& operator<<(Debug& debug, const DataChunkType value) { + return printFourCC(debug, Containers::enumCastUnderlyingType(value), "Trade::DataChunkType("); +} + +Debug& operator<<(Debug& debug, const DataChunkSignature value) { + return printFourCC(debug, Containers::enumCastUnderlyingType(value), "Trade::DataChunkSignature("); +} + +namespace { + constexpr DataChunkHeader DataChunkHeaderPrefix{ + 128, {'\x0a'}, {'\x0d', '\x0a'}, DataChunkSignature::Current, 0, 0, + /* Type and size isn't checked when validating and gets overwritten + when serializing */ + DataChunkType{}, 0 + }; + + static_assert(DataChunkHeaderPrefix.version & 0x80, + "version needs the high bit set to prevent detection as a text file"); +} + +bool isDataChunk(Containers::ArrayView data) { + return data && data.size() >= sizeof(DataChunkHeader) && + std::memcmp(data.data(), &DataChunkHeaderPrefix, 10) == 0 && + reinterpret_cast(data.data())->size <= data.size(); +} + +const DataChunkHeader* dataChunkHeaderDeserialize(const Containers::ArrayView data) { + if(data.size() < sizeof(DataChunkHeader)) { + Error{} << "Trade::dataChunkHeaderDeserialize(): expected at least" << sizeof(DataChunkHeader) << "bytes for a header but got" << data.size(); + return nullptr; + } + + const auto& header = *reinterpret_cast(data.data()); + if(header.version != DataChunkHeaderPrefix.version) { + Error{} << "Trade::dataChunkHeaderDeserialize(): expected version" << DataChunkHeaderPrefix.version << "but got" << header.version; + return nullptr; + } + if(header.signature != DataChunkSignature::Current) { + Error{} << "Trade::dataChunkHeaderDeserialize(): expected signature" << DataChunkSignature::Current << "but got" << header.signature; + return nullptr; + } + if(std::memcmp(data.data(), &DataChunkHeaderPrefix, 10) != 0) { + Error{} << "Trade::dataChunkHeaderDeserialize(): invalid header check bytes"; + return nullptr; + } + if(header.size > data.size()) { + Error{} << "Trade::dataChunkHeaderDeserialize(): expected at least" << header.size << "bytes but got" << data.size(); + return nullptr; + } + + return reinterpret_cast(data.data()); +} + +std::size_t dataChunkHeaderSerializeInto(const Containers::ArrayView out, const DataChunkType type, const UnsignedShort typeVersion) { + CORRADE_ASSERT(out.size() >= sizeof(DataChunkHeader), + "Trade::dataChunkHeaderSerializeInto(): data too small, expected at least" << sizeof(DataChunkHeader) << "bytes but got" << out.size(), {}); + + auto& header = *reinterpret_cast(out.data()); + header = DataChunkHeaderPrefix; + header.typeVersion = typeVersion; + header.type = type; + header.size = out.size(); + return sizeof(DataChunkHeader); +} + namespace Implementation { void nonOwnedArrayDeleter(char*, std::size_t) { /* does nothing */ } } diff --git a/src/Magnum/Trade/Data.h b/src/Magnum/Trade/Data.h index 4e1684ffa..b43726dea 100644 --- a/src/Magnum/Trade/Data.h +++ b/src/Magnum/Trade/Data.h @@ -26,11 +26,13 @@ */ /** @file - * @brief Enum @ref Magnum::Trade::DataFlag, enum set @ref Magnum::Trade::DataFlags + * @brief Struct @ref Magnum::Trade::DataChunkHeader, enum @ref Magnum::Trade::DataFlag, @ref Magnum::Trade::DataChunkSignature, @ref Magnum::Trade::DataChunkType, enum set @ref Magnum::Trade::DataFlags, function @ref Magnum::Trade::isDataChunk(), @ref Magnum::Trade::dataChunkHeaderDeserialize(), @ref Magnum::Trade::dataChunkHeaderSerializeInto() * @m_since_latest */ +#include #include +#include #include "Magnum/Magnum.h" #include "Magnum/Trade/visibility.h" @@ -85,6 +87,190 @@ CORRADE_ENUMSET_OPERATORS(DataFlags) */ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, DataFlags value); +/** +@brief Memory-mappable data chunk type +@m_since_latest + +A [FourCC](https://en.wikipedia.org/wiki/FourCC)-like identifier of the data +contained in the chunk. Used together with @ref DataChunkHeader::typeVersion to +identify version of a particular chunk type. + +@section Trade-DataChunkType-custom Custom data chunk types + +All identifiers starting with an uppercase leter are reserved for Magnum +itself, custom application-specific data types should use a lowercase first +letter instead. Casing of the three remaining characters doesn't have any +specified effect in the current version of the header. It doesn't need to be +alphanumeric either, but for additional versioning of a particular chunk type +it's recommended to use @ref DataChunkHeader::typeVersion, keeping the chunk +type FourCC clearly recognizable. +*/ +enum class DataChunkType: UnsignedInt { + /** + * Serialized @ref MeshData. The letters `Mesh`. + * + * Current version is @cpp 0 @ce. + */ + Mesh = Utility::Endianness::fourCC('M', 'e', 's', 'h'), + + #if 0 + /* None of these used yet, here just to lay out the naming scheme */ + Animation = Utility::Endianness::fourCC('A', 'n', 'i', 'm'), + Camera = Utility::Endianness::fourCC('C', 'a', 'm', 0), + Image1D = Utility::Endianness::fourCC('I', 'm', 'g', '1'), + Image2D = Utility::Endianness::fourCC('I', 'm', 'g', '2'), + Image3D = Utility::Endianness::fourCC('I', 'm', 'g', '3'), + Light = Utility::Endianness::fourCC('L', 'i', 'g', 't'), + Material = Utility::Endianness::fourCC('M', 't', 'l', 0), + Scene = Utility::Endianness::fourCC('S', 'c', 'n', 0), + Texture = Utility::Endianness::fourCC('T', 'e', 'x', 0) + #endif +}; + +/** +@debugoperatorenum{DataChunkType} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, DataChunkType value); + +/** +@brief Memory-mappable data chunk signature +@m_since_latest + +Reads as `BLOB` letters for a Little-Endian 64 bit data chunk. For Big-Endian +the order is reversed (thus `BOLB`), 32-bit data have the `L` letter lowercase. +@see @ref DataChunkHeader::signature +*/ +enum class DataChunkSignature: UnsignedInt { + /** Little-Endian 32-bit data. The letters `BlOB`. */ + Little32 = Utility::Endianness::fourCC('B', 'l', 'O', 'B'), + + /** Little-Endian 64-bit data. The letters `BLOB`. */ + Little64 = Utility::Endianness::fourCC('B', 'L', 'O', 'B'), + + /** Big-Endian 32-bit data. The letters `BOlB`. */ + Big32 = Utility::Endianness::fourCC('B', 'O', 'l', 'B'), + + /** Big-Endian 64-bit data. The letters `BOLB`. */ + Big64 = Utility::Endianness::fourCC('B', 'O', 'L', 'B'), + + /** Signature matching this platform. Alias to one of the above. */ + Current + #ifndef DOXYGEN_GENERATING_OUTPUT + = + #ifndef CORRADE_TARGET_BIG_ENDIAN + sizeof(std::size_t) == 8 ? Little64 : Little32 + #else + sizeof(std::size_t) == 8 ? Big64 : Big32 + #endif + #endif +}; + +/** +@debugoperatorenum{DataChunkSignature} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, DataChunkSignature value); + +/** +@brief Header for memory-mappable data chunks +@m_since_latest + +Since the goal of the serialization format is to be a direct equivalent to the +in-memory data layout, there's four different variants of the header based on +whether it's running on a 32-bit or 64-bit system and whether the machine is +Little- or Big-Endian. A 64-bit variant of the header has 24 bytes to support +data larger than 4 GB, the 32-bit variant is 20 bytes. Apart from the @ref size +member, the header is designed to contain the same amount of information on +both, and its size is chosen so the immediately following data can be aligned +to either 4 or 8 bytes without needing to add extra padding. + +The header contents are as follows, vaguely inspired by the +[PNG file header](https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_header). +All fields except @ref typeVersion and @ref size (marked with +@m_class{m-label m-primary} **E**) are stored in an endian-independent way, +otherwise the endian matches the signature field. + +@m_class{m-row m-container-inflate} + +@parblock + +@m_class{m-fullwidth} + +Byte offset | Byte size      | Member | Contents +----------- | --------- | ------------- | ------------------------------------- +0 | 1 | @ref DataChunkHeader::version "version" | Header version. Has the high bit set to prevent the file from being detected as text. Currently set to @cpp 128 @ce. +1 | 1 | @ref eolUnix | Unix EOL (LF, @cpp '\x0a' @ce), to detect unwanted Unix-to-DOS line ending conversion +2 | 2 | @ref eolDos | DOS EOL (CR+LF, @cpp '\x0d', '\x0a' @ce), to detect unwanted DOS-to-Unix line ending conversion +4 | 4 | @ref signature | File signature. Differs based on bitness and endianness, see @ref DataChunkSignature for more information. +8 | 2 | @ref zero | Two zero bytes (@cpp '\x00', '\x00' @ce), to prevent the data from being treated and copied as a null-terminated (wide) string. +10 | 2 @m_class{m-label m-primary} **E** | @ref typeVersion | Data chunk type version. Use is chunk-specific, see @ref DataChunkType for more information. +12 | 4 | @ref type | Data chunk type, see @ref DataChunkType for more information +16 | 4 or 8 @m_class{m-label m-primary} **E** | @ref size | Data chunk size, including the header size. Stored in size matching the signature field. + +@endparblock + +For a particular header variant the first 10 bytes is static and thus can be +used for file validation. After the header are directly the chunk data. For performance reasons it's recommended to have the data padded to be a multiple +of 4 or 8 bytes to ensure the immediately following chunk is correctly aligned +as well, but it's not a strict recommendation and not enforced in any way in +current version of the format. + +Current version of the header doesn't have any checksum field in order to make +it easy to modify the data in-place, this might change in the future. +@see @ref DataChunkSignature, @ref DataChunkType, @ref isDataChunk(), + @ref dataChunkHeaderDeserialize(), @ref dataChunkHeaderSerializeInto() +*/ +struct DataChunkHeader { + UnsignedByte version; /**< @brief Header version */ + char eolUnix[1]; /**< @brief Unix EOL */ + char eolDos[2]; /**< @brief Dos EOL */ + DataChunkSignature signature; /**< @brief Signature */ + UnsignedShort zero; /**< @brief Two zero bytes */ + UnsignedShort typeVersion; /**< @brief Chunk type version */ + DataChunkType type; /**< @brief Chunk type */ + std::size_t size; /**< @brief Chunk size */ +}; + +/** +@brief Check if given data blob is a valid data chunk +@m_since_latest + +Returns @cpp true @ce if @p data is a valid @ref DataChunkHeader, matches +current platform and @p data is large enough to contain the whole chunk, +@cpp false @ce otherwise. The function doesn't print any diagnostic messages on +validation failure, use @ref dataChunkHeaderDeserialize() instead if you need +to know why. +*/ +MAGNUM_TRADE_EXPORT bool isDataChunk(Containers::ArrayView data); + +/** +@brief Try to deserialize a data chunk from a memory-mappable representation +@m_since_latest + +Checks that @p data is large enough to contain a valid data chunk, validates +the header and then returns @p data reinterpreted as a @ref DataChunkHeader +pointer. On failure prints an error message and returns @cpp nullptr @ce. +@see @ref isDataChunk(), @ref dataChunkHeaderSerializeInto() +*/ +MAGNUM_TRADE_EXPORT const DataChunkHeader* dataChunkHeaderDeserialize(Containers::ArrayView data); + +/** +@brief Serialize a data chunk header into existing array +@param[out] out Where to write the output +@param[out] type Data chunk type +@param[out] typeVersion Data chunk type version +@return Number of bytes written. Same as size of @ref DataChunkHeader. +@m_since_latest + +Expects that @p data is at least the size of @ref DataChunkHeader. Fills in +@ref DataChunkHeader::typeVersion and @ref DataChunkHeader::type with passed +values used in constructor, and @ref DataChunkHeader::size with @p data size. + +@see @ref dataChunkHeaderDeserialize() +*/ +MAGNUM_TRADE_EXPORT std::size_t dataChunkHeaderSerializeInto(Containers::ArrayView out, DataChunkType type, UnsignedShort typeVersion); + namespace Implementation { /* Used internally by MeshData */ MAGNUM_TRADE_EXPORT void nonOwnedArrayDeleter(char*, std::size_t); diff --git a/src/Magnum/Trade/Test/CMakeLists.txt b/src/Magnum/Trade/Test/CMakeLists.txt index 244b018be..b05842bd8 100644 --- a/src/Magnum/Trade/Test/CMakeLists.txt +++ b/src/Magnum/Trade/Test/CMakeLists.txt @@ -48,7 +48,7 @@ target_include_directories(TradeAbstractSceneConverterTest PRIVATE ${CMAKE_CURRE corrade_add_test(TradeAnimationDataTest AnimationDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeCameraDataTest CameraDataTest.cpp LIBRARIES MagnumTradeTestLib) -corrade_add_test(TradeDataTest DataTest.cpp LIBRARIES MagnumTrade) +corrade_add_test(TradeDataTest DataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeImageDataTest ImageDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeLightDataTest LightDataTest.cpp LIBRARIES MagnumTrade) corrade_add_test(TradeMaterialDataTest MaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) diff --git a/src/Magnum/Trade/Test/DataTest.cpp b/src/Magnum/Trade/Test/DataTest.cpp index e1253d498..7810eba00 100644 --- a/src/Magnum/Trade/Test/DataTest.cpp +++ b/src/Magnum/Trade/Test/DataTest.cpp @@ -24,8 +24,12 @@ */ #include +#include #include +#include +#include #include +#include #include "Magnum/Trade/Data.h" @@ -34,13 +38,207 @@ namespace Magnum { namespace Trade { namespace Test { namespace { struct DataTest: TestSuite::Tester { explicit DataTest(); + void dataChunkHeaderDeserialize(); + void dataChunkHeaderDeserializeInvalid(); + + void dataChunkHeaderSerialize(); + void dataChunkHeaderSerializeTooShort(); + void debugDataFlag(); void debugDataFlags(); + + void debugDataChunkType(); + void debugDataChunkSignature(); +}; + +constexpr char Data32[]{ + '\x80', '\x0a', '\x0d', '\x0a', 'B', + #ifndef CORRADE_TARGET_BIG_ENDIAN + 'l', 'O', + #else + 'O', 'l', + #endif + 'B', 0, 0, + #ifndef CORRADE_TARGET_BIG_ENDIAN + 42, 0, + #else + 0, 42, + #endif + 'W', 'a', 'v', 'e', + #ifndef CORRADE_TARGET_BIG_ENDIAN + 20 + 5, 0, 0, 0, + #else + 0, 0, 0, 20 + 5, + #endif + + 'h', 'e', 'l', 'l', 'o' +}; + +constexpr char Data64[]{ + '\x80', '\x0a', '\x0d', '\x0a', 'B', + #ifndef CORRADE_TARGET_BIG_ENDIAN + 'L', 'O', + #else + 'O', 'L', + #endif + 'B', 0, 0, + #ifndef CORRADE_TARGET_BIG_ENDIAN + 42, 0, + #else + 0, 42, + #endif + 'W', 'a', 'v', 'e', + #ifndef CORRADE_TARGET_BIG_ENDIAN + 24 + 5, 0, 0, 0, 0, 0, 0, 0, + #else + 0, 0, 0, 0, 0, 0, 0, 24 + 5, + #endif + + 'h', 'e', 'l', 'l', 'o' +}; + +constexpr Containers::ArrayView Data = sizeof(void*) == 4 ? + Containers::arrayView(Data32) : Containers::arrayView(Data64); + +const struct { + const char* name; + std::size_t size; + std::size_t offset; + Containers::Array replace; + const char* message; +} DataChunkDeserializeInvalidData[] { + {"too short header", + sizeof(void*) == 4 ? 19 : 23, 0, {}, + sizeof(void*) == 4 ? + "expected at least 20 bytes for a header but got 19" : + "expected at least 24 bytes for a header but got 23"}, + {"too short chunk", + sizeof(void*) == 4 ? 24 : 28, 0, {}, + sizeof(void*) == 4 ? + "expected at least 25 bytes but got 24" : + "expected at least 29 bytes but got 28"}, + {"wrong version", + 0, 0, Containers::array({'\x7f'}), + "expected version 128 but got 127"}, + {"invalid signature", + 0, 4, + /* Using the 32-bit signature on 64-bit and vice versa */ + #ifndef CORRADE_TARGET_BIG_ENDIAN + sizeof(void*) == 4 ? + Containers::array({'B', 'L', 'O', 'B'}) : + Containers::array({'B', 'l', 'O', 'B'}), + sizeof(void*) == 4 ? + "expected signature Trade::DataChunkSignature('B', 'l', 'O', 'B') but got Trade::DataChunkSignature('B', 'L', 'O', 'B')" : + "expected signature Trade::DataChunkSignature('B', 'L', 'O', 'B') but got Trade::DataChunkSignature('B', 'l', 'O', 'B')" + #else + sizeof(void*) == 4 ? + Containers::array({'B', 'O', 'L', 'B'}) : + Containers::array({'B', 'O', 'l', 'B'}), + sizeof(void*) == 4 ? + "expected signature Trade::DataChunkSignature('B', 'O', 'l', 'B') but got Trade::DataChunkSignature('B', 'O', 'L', 'B')" : + "expected signature Trade::DataChunkSignature('B', 'O', 'L', 'B') but got Trade::DataChunkSignature('B', 'O', 'l', 'B')" + #endif + }, + {"invalid check bytes", + 0, 8, Containers::array({1, 0}), + "invalid header check bytes"}, +}; + +constexpr struct { + const char* name; + std::size_t size; +} DataChunkSerializeData[] { + {"no extra data", sizeof(DataChunkHeader)}, + {"1735 bytes extra data", sizeof(DataChunkHeader) + 1735} }; DataTest::DataTest() { + addTests({&DataTest::dataChunkHeaderDeserialize}); + + addInstancedTests({&DataTest::dataChunkHeaderDeserializeInvalid}, + Containers::arraySize(DataChunkDeserializeInvalidData)); + + addInstancedTests({&DataTest::dataChunkHeaderSerialize}, + Containers::arraySize(DataChunkSerializeData)); + + addTests({&DataTest::dataChunkHeaderSerializeTooShort}); + addTests({&DataTest::debugDataFlag, - &DataTest::debugDataFlags}); + &DataTest::debugDataFlags, + + &DataTest::debugDataChunkType, + &DataTest::debugDataChunkSignature}); +} + +void DataTest::dataChunkHeaderDeserialize() { + CORRADE_VERIFY(isDataChunk(Data)); + const DataChunkHeader* chunk = Trade::dataChunkHeaderDeserialize(Data); + CORRADE_VERIFY(chunk); +} + +void DataTest::dataChunkHeaderDeserializeInvalid() { + auto&& data = DataChunkDeserializeInvalidData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Containers::Array blob{Containers::NoInit, Data.size()}; + Utility::copy(Data, blob); + + Containers::ArrayView view = blob; + if(data.size) view = view.prefix(data.size); + if(data.replace) Utility::copy(data.replace, view.slice(data.offset, data.offset + data.replace.size())); + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!isDataChunk(view)); + CORRADE_VERIFY(!Trade::dataChunkHeaderDeserialize(view)); + CORRADE_COMPARE(out.str(), + Utility::formatString("Trade::dataChunkHeaderDeserialize(): {}\n", data.message)); +} + +void DataTest::dataChunkHeaderSerialize() { + auto&& data = DataChunkSerializeData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Containers::Array out{Containers::NoInit, data.size}; + std::size_t size = dataChunkHeaderSerializeInto(out, DataChunkType(Utility::Endianness::fourCC('r', 't', 'F', 'm')), 0xfeed); + CORRADE_COMPARE(size, sizeof(DataChunkHeader)); + #ifndef CORRADE_TARGET_BIG_ENDIAN + if(sizeof(void*) == 4) CORRADE_COMPARE_AS(out.prefix(size), + Containers::arrayView({ + '\x80', '\x0a', '\x0d', '\x0a', 'B', 'l', 'O', 'B', 0, 0, + '\xed', '\xfe', 'r', 't', 'F', 'm', + char(data.size & 0xff), char(data.size >> 8 & 0xff), 0, 0, + }), TestSuite::Compare::Container); + else CORRADE_COMPARE_AS(out.prefix(size), + Containers::arrayView({ + '\x80', '\x0a', '\x0d', '\x0a', 'B', 'L', 'O', 'B', 0, 0, + '\xed', '\xfe', 'r', 't', 'F', 'm', + char(data.size & 0xff), char(data.size >> 8 & 0xff), 0, 0, 0, 0, 0, 0 + }), TestSuite::Compare::Container); + #else + if(sizeof(void*) == 4) CORRADE_COMPARE_AS(out.prefix(size), + Containers::arrayView({ + '\x80', '\x0a', '\x0d', '\x0a', 'B', 'O', 'l', 'B', 0, 0, + '\xed', '\xfe', 'r', 't', 'F', 'm', + 0, 0, char(data.size >> 8 & 0xff), char(data.size & 0xff) + }), TestSuite::Compare::Container); + else CORRADE_COMPARE_AS(out.prefix(size), + Containers::arrayView({ + '\x80', '\x0a', '\x0d', '\x0a', 'B', 'O', 'L', 'B', 0, 0, + '\xed', '\xfe', 'r', 't', 'F', 'm', + 0, 0, 0, 0, 0, 0, char(data.size >> 8 & 0xff), char(data.size & 0xff) + }), TestSuite::Compare::Container); + #endif +} + +void DataTest::dataChunkHeaderSerializeTooShort() { + std::ostringstream out; + Error redirectError{&out}; + char data[sizeof(DataChunkHeader) - 1]; + dataChunkHeaderSerializeInto(data, DataChunkType{}, 0); + CORRADE_COMPARE(out.str(), sizeof(void*) == 4 ? + "Trade::dataChunkHeaderSerializeInto(): data too small, expected at least 20 bytes but got 19\n" : + "Trade::dataChunkHeaderSerializeInto(): data too small, expected at least 24 bytes but got 23\n"); } void DataTest::debugDataFlag() { @@ -57,6 +255,20 @@ void DataTest::debugDataFlags() { CORRADE_COMPARE(out.str(), "Trade::DataFlag::Owned|Trade::DataFlag::Mutable Trade::DataFlags{}\n"); } +void DataTest::debugDataChunkType() { + std::ostringstream out; + + Debug{&out} << DataChunkType(Utility::Endianness::fourCC('M', 's', 'h', '\xab')) << DataChunkType{}; + CORRADE_COMPARE(out.str(), "Trade::DataChunkType('M', 's', 'h', 0xab) Trade::DataChunkType(0x0, 0x0, 0x0, 0x0)\n"); +} + +void DataTest::debugDataChunkSignature() { + std::ostringstream out; + + Debug{&out} << DataChunkSignature::Little64 << DataChunkSignature{}; + CORRADE_COMPARE(out.str(), "Trade::DataChunkSignature('B', 'L', 'O', 'B') Trade::DataChunkSignature(0x0, 0x0, 0x0, 0x0)\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::Trade::Test::DataTest) diff --git a/src/Magnum/Trade/Trade.h b/src/Magnum/Trade/Trade.h index e146d5ef1..28b8233aa 100644 --- a/src/Magnum/Trade/Trade.h +++ b/src/Magnum/Trade/Trade.h @@ -62,6 +62,10 @@ class CameraData; enum class DataFlag: UnsignedByte; typedef Containers::EnumSet DataFlags; +struct DataChunkHeader; +class DataChunk; +enum class DataChunkSignature: UnsignedInt; +enum class DataChunkType: UnsignedInt; template class ImageData; typedef ImageData<1> ImageData1D;