diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..10a39547f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "UnitTests/zlib"] + path = UnitTests/zlib + url = https://github.com/madler/zlib.git diff --git a/UnitTests/UnitTests.vcxproj b/UnitTests/UnitTests.vcxproj index 6ad2de970..ba903e304 100644 --- a/UnitTests/UnitTests.vcxproj +++ b/UnitTests/UnitTests.vcxproj @@ -79,7 +79,7 @@ Level4 true true - ..\include;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) + ..\include;$(VCInstallDir)UnitTest\include;zlib;%(AdditionalIncludeDirectories) WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions) true pch.hpp @@ -131,6 +131,48 @@ + + + NotUsing + TurnOffAllWarnings + + + NotUsing + TurnOffAllWarnings + + + NotUsing + TurnOffAllWarnings + + + NotUsing + TurnOffAllWarnings + + + NotUsing + TurnOffAllWarnings + + + NotUsing + TurnOffAllWarnings + + + NotUsing + TurnOffAllWarnings + + + NotUsing + TurnOffAllWarnings + 6385 + + + NotUsing + TurnOffAllWarnings + + + NotUsing + TurnOffAllWarnings + diff --git a/UnitTests/UnitTests.vcxproj.filters b/UnitTests/UnitTests.vcxproj.filters index 5e390340c..ae96287e0 100644 --- a/UnitTests/UnitTests.vcxproj.filters +++ b/UnitTests/UnitTests.vcxproj.filters @@ -13,6 +13,9 @@ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + {c793ce2d-1a0b-43b6-b0b4-60025ceafa56} + @@ -48,6 +51,39 @@ Source Files + + Source Files + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + diff --git a/UnitTests/pch.hpp b/UnitTests/pch.hpp index 1d0ab02f0..0aafb1134 100644 --- a/UnitTests/pch.hpp +++ b/UnitTests/pch.hpp @@ -31,6 +31,7 @@ #include #include #include +#include #include "compat.hpp" diff --git a/UnitTests/zlib b/UnitTests/zlib new file mode 160000 index 000000000..643e17b74 --- /dev/null +++ b/UnitTests/zlib @@ -0,0 +1 @@ +Subproject commit 643e17b7498d12ab8d15565662880579692f769d diff --git a/UnitTests/zlib.cpp b/UnitTests/zlib.cpp new file mode 100644 index 000000000..9e23fcdc1 --- /dev/null +++ b/UnitTests/zlib.cpp @@ -0,0 +1,40 @@ +/* + SPDX-License-Identifier: MIT + Copyright © 2024 Amebis +*/ + +#include "pch.hpp" + +using namespace std; +#ifdef _WIN32 +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +#endif + +namespace UnitTests +{ + TEST_CLASS(hash) + { + public: + TEST_METHOD(zlib) + { + static const char inflated[] = "This is a test."; + stdex::stream::memory_file dat_deflated; + { + stdex::zlib_writer zlib(dat_deflated, 9, 4); + zlib.write(inflated, sizeof(inflated) - sizeof(*inflated)); + } + static const uint8_t deflated[] = { 0x78, 0xda, 0x0b, 0xc9, 0xc8, 0x2c, 0x56, 0x00, 0xa2, 0x44, 0x85, 0x92, 0xd4, 0xe2, 0x12, 0x3d, 0x00, 0x29, 0x97, 0x05, 0x24 }; + Assert::AreEqual(sizeof(deflated), dat_deflated.size()); + Assert::AreEqual(0, memcmp(deflated, dat_deflated.data(), sizeof(deflated))); + + dat_deflated.seekbeg(0); + stdex::stream::memory_file dat_inflated; + { + stdex::zlib_reader zlib(dat_deflated, 3); + dat_inflated.write_stream(zlib); + } + Assert::AreEqual(sizeof(inflated) - sizeof(*inflated), dat_inflated.size()); + Assert::AreEqual(0, memcmp(inflated, dat_inflated.data(), sizeof(inflated) - sizeof(*inflated))); + } + }; +} diff --git a/include/stdex/zlib.hpp b/include/stdex/zlib.hpp new file mode 100644 index 000000000..6d0f05e05 --- /dev/null +++ b/include/stdex/zlib.hpp @@ -0,0 +1,169 @@ +/* + SPDX-License-Identifier: MIT + Copyright © 2016-2024 Amebis +*/ + +#pragma once + +#include "compat.hpp" +#include "stream.hpp" +#if _MSC_VER +#include +#pragma warning(push) +#pragma warning(disable: ALL_CODE_ANALYSIS_WARNINGS) +#endif +#include +#if _MSC_VER +#pragma warning(pop) +#endif +#include +#include + +namespace stdex +{ + /// \cond internal + inline void throw_on_zlib_error(int result) + { + if (result >= 0) + return; + switch (result) { + case Z_ERRNO: throw std::system_error(errno, std::system_category(), "zlib failed with errno"); + case Z_STREAM_ERROR: throw std::runtime_error("zlib stream error"); + case Z_DATA_ERROR: throw std::runtime_error("zlib data error"); + case Z_MEM_ERROR: throw std::bad_alloc(); + case Z_BUF_ERROR: throw std::runtime_error("zlib buffer error"); + case Z_VERSION_ERROR: throw std::runtime_error("zlib version error"); + default: throw std::runtime_error("zlib unknown error"); + } + } + /// \endcond + + /// + /// Compresses data when writing to a stream + /// + class zlib_writer : public stdex::stream::converter + { + public: + zlib_writer(_Inout_ stdex::stream::basic& source, _In_ int compression_level = Z_BEST_COMPRESSION, _In_ uInt block_size = 0x10000) : + stdex::stream::converter(source), + m_block_size(block_size), + m_block(new Byte[block_size]) + { + memset(&m_zlib, 0, sizeof(m_zlib)); + throw_on_zlib_error(deflateInit(&m_zlib, compression_level)); + } + + virtual ~zlib_writer() + { + m_zlib.avail_in = 0; + m_zlib.next_in = NULL; + do { + m_zlib.avail_out = m_block_size; + m_zlib.next_out = m_block.get(); + throw_on_zlib_error(deflate(&m_zlib, Z_FINISH)); + m_source->write(m_block.get(), m_block_size - m_zlib.avail_out); + if (!m_source->ok()) _Unlikely_ + throw std::system_error(sys_error(), std::system_category(), "failed to flush compressed stream"); // Data loss occured + } while (m_zlib.avail_out == 0); + // m_zlib.avail_out = m_block_size; + // m_zlib.next_out = m_block.get(); + // deflateReset(&m_zlib); + deflateEnd(&m_zlib); + } + + virtual _Success_(return != 0) size_t write( + _In_reads_bytes_opt_(length) const void* data, _In_ size_t length) + { + _Assume_(data || !length); + size_t num_written = 0; + while (length) { + uInt num_inflated = static_cast(std::min(length, UINT_MAX)); + m_zlib.avail_in = num_inflated; + m_zlib.next_in = const_cast(reinterpret_cast(data)); + do { + m_zlib.avail_out = m_block_size; + m_zlib.next_out = m_block.get(); + throw_on_zlib_error(deflate(&m_zlib, Z_NO_FLUSH)); + size_t num_deflated = m_block_size - m_zlib.avail_out; + if (num_deflated) { + m_source->write(m_block.get(), num_deflated); + if (!m_source->ok()) { + m_state = m_source->state(); + return num_written; + } + } + } while (m_zlib.avail_out == 0); + num_written += num_inflated; + reinterpret_cast(data) += num_inflated; + length -= num_inflated; + } + m_state = stdex::stream::state_t::ok; + return num_written; + } + + protected: + z_stream m_zlib; + uInt m_block_size; + std::unique_ptr m_block; + }; + + /// + /// Decompresses data when reading from a stream + /// + class zlib_reader : public stdex::stream::converter + { + public: + zlib_reader(_Inout_ stdex::stream::basic& source, _In_ uInt block_size = 0x10000) : + stdex::stream::converter(source), + m_block_size(block_size), + m_block(new Byte[block_size]) + { + memset(&m_zlib, 0, sizeof(m_zlib)); + throw_on_zlib_error(inflateInit(&m_zlib)); + } + + virtual ~zlib_reader() + { + inflateEnd(&m_zlib); + } + +#pragma warning(suppress: 6101) // See [1] below + virtual _Success_(return != 0 || length == 0) size_t read( + _Out_writes_bytes_to_opt_(length, return) void* data, _In_ size_t length) + { + _Assume_(data || !length); + size_t num_read = 0; + while (length) { + uInt num_deflated = static_cast(std::min(length, UINT_MAX)); + m_zlib.avail_out = num_deflated; + m_zlib.next_out = reinterpret_cast(data); + do { + if (m_zlib.avail_in == 0) { + m_zlib.next_in = m_block.get(); + m_zlib.avail_in = static_cast(m_source->read(m_block.get(), m_block_size)); + if (!m_zlib.avail_in) { + num_read += num_deflated - m_zlib.avail_out; // [1] Code analysis misses `num_deflated - m_zlib.avail_out` bytes were written to data in previous loop iterations. + if (num_read) { + m_state = stdex::stream::state_t::ok; + return num_read; + } + m_state = m_source->state(); + return 0; + } + } + throw_on_zlib_error(inflate(&m_zlib, Z_NO_FLUSH)); + } while (m_zlib.avail_out); + num_read += num_deflated; + reinterpret_cast(data) += num_deflated; + length -= num_deflated; + } + m_state = stdex::stream::state_t::ok; + return num_read; + } + + protected: + z_stream m_zlib; + uInt m_block_size; + std::unique_ptr m_block; + }; +}