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;
+ };
+}