diff --git a/coding/CMakeLists.txt b/coding/CMakeLists.txt index 6278e3c1b7..d09d6002b1 100644 --- a/coding/CMakeLists.txt +++ b/coding/CMakeLists.txt @@ -90,6 +90,8 @@ set( zip_creator.hpp zip_reader.cpp zip_reader.hpp + zlib.cpp + zlib.hpp ) add_library(${PROJECT_NAME} ${SRC}) diff --git a/coding/coding.pro b/coding/coding.pro index 123247a2ff..264abb1cc5 100644 --- a/coding/coding.pro +++ b/coding/coding.pro @@ -30,6 +30,7 @@ SOURCES += \ # varint_vector.cpp \ zip_creator.cpp \ zip_reader.cpp \ + zlib.cpp \ HEADERS += \ $$ROOT_DIR/3party/expat/expat_impl.h \ @@ -93,3 +94,4 @@ HEADERS += \ writer.hpp \ zip_creator.hpp \ zip_reader.hpp \ + zlib.hpp \ diff --git a/coding/coding_tests/CMakeLists.txt b/coding/coding_tests/CMakeLists.txt index 7aa4610344..c4a2d1eb81 100644 --- a/coding/coding_tests/CMakeLists.txt +++ b/coding/coding_tests/CMakeLists.txt @@ -41,6 +41,7 @@ set( writer_test.cpp zip_creator_test.cpp zip_reader_test.cpp + zlib_test.cpp ) omim_add_test(${PROJECT_NAME} ${SRC}) diff --git a/coding/coding_tests/coding_tests.pro b/coding/coding_tests/coding_tests.pro index 899346a795..232d5eac79 100644 --- a/coding/coding_tests/coding_tests.pro +++ b/coding/coding_tests/coding_tests.pro @@ -48,6 +48,7 @@ SOURCES += ../../testing/testingmain.cpp \ writer_test.cpp \ zip_creator_test.cpp \ zip_reader_test.cpp \ + zlib_test.cpp \ HEADERS += \ coder_test.hpp \ diff --git a/coding/coding_tests/zlib_test.cpp b/coding/coding_tests/zlib_test.cpp new file mode 100644 index 0000000000..6694ad30bd --- /dev/null +++ b/coding/coding_tests/zlib_test.cpp @@ -0,0 +1,50 @@ +#include "testing/testing.hpp" + +#include "coding/zlib.hpp" + +#include "std/iterator.hpp" +#include "std/sstream.hpp" +#include "std/string.hpp" + +using namespace coding; + +namespace +{ +void TestInflateDeflate(string const & original) +{ + string compressed; + TEST(ZLib::Deflate(original, ZLib::Level::BestCompression, back_inserter(compressed)), ()); + + string decompressed; + TEST(ZLib::Inflate(compressed, back_inserter(decompressed)), ()); + + TEST_EQUAL(original, decompressed, ()); +} + +UNIT_TEST(ZLib_Smoke) +{ + { + string s; + TEST(!ZLib::Deflate(nullptr, 0, ZLib::Level::BestCompression, back_inserter(s)), ()); + TEST(!ZLib::Deflate(nullptr, 4, ZLib::Level::BestCompression, back_inserter(s)), ()); + TEST(!ZLib::Inflate(nullptr, 0, back_inserter(s)), ()); + TEST(!ZLib::Inflate(nullptr, 4, back_inserter(s)), ()); + } + + TestInflateDeflate(""); + TestInflateDeflate("Hello, World"); +} + +UNIT_TEST(ZLib_Large) +{ + string original; + { + ostringstream os; + for (size_t i = 0; i < 1000; ++i) + os << i; + original = os.str(); + } + + TestInflateDeflate(original); +} +} // namespace diff --git a/coding/zlib.cpp b/coding/zlib.cpp new file mode 100644 index 0000000000..61979359f4 --- /dev/null +++ b/coding/zlib.cpp @@ -0,0 +1,83 @@ +#include "coding/zlib.hpp" + +namespace coding +{ +namespace +{ +int LevelToInt(ZLib::Level level) +{ + switch (level) + { + case ZLib::Level::NoCompression: return Z_NO_COMPRESSION; + case ZLib::Level::BestSpeed: return Z_BEST_SPEED; + case ZLib::Level::BestCompression: return Z_BEST_COMPRESSION; + case ZLib::Level::DefaultCompression: return Z_DEFAULT_COMPRESSION; + } +} +} // namespace + +// ZLib::Processor --------------------------------------------------------------------------------- +ZLib::Processor::Processor(char const * data, size_t size) : m_init(false) +{ + m_stream.next_in = const_cast(reinterpret_cast(data)); + m_stream.avail_in = size; + + m_stream.next_out = reinterpret_cast(m_buffer); + m_stream.avail_out = kBufferSize; + + m_stream.zalloc = Z_NULL; + m_stream.zfree = Z_NULL; + m_stream.opaque = Z_NULL; +} + +bool ZLib::Processor::ConsumedAll() const +{ + ASSERT(IsInit(), ()); + return m_stream.avail_in == 0; +} + +bool ZLib::Processor::BufferIsFull() const +{ + ASSERT(IsInit(), ()); + return m_stream.avail_out == 0; +} + +// ZLib::Deflate ----------------------------------------------------------------------------------- +ZLib::DeflateProcessor::DeflateProcessor(char const * data, size_t size, ZLib::Level level) + : Processor(data, size) +{ + int const ret = deflateInit(&m_stream, LevelToInt(level)); + m_init = (ret == Z_OK); +} + +ZLib::DeflateProcessor::~DeflateProcessor() +{ + if (m_init) + deflateEnd(&m_stream); +} + +int ZLib::DeflateProcessor::Process(int flush) +{ + ASSERT(IsInit(), ()); + return deflate(&m_stream, flush); +} + +// ZLib::Inflate ----------------------------------------------------------------------------------- +ZLib::InflateProcessor::InflateProcessor(char const * data, size_t size) : Processor(data, size) +{ + int const ret = inflateInit(&m_stream); + m_init = (ret == Z_OK); +} + +ZLib::InflateProcessor::~InflateProcessor() +{ + if (m_init) + inflateEnd(&m_stream); +} + +int ZLib::InflateProcessor::Process(int flush) +{ + ASSERT(IsInit(), ()); + return inflate(&m_stream, flush); +} +} // namespace coding diff --git a/coding/zlib.hpp b/coding/zlib.hpp new file mode 100644 index 0000000000..86743bb2c0 --- /dev/null +++ b/coding/zlib.hpp @@ -0,0 +1,126 @@ +#include "base/assert.hpp" + +#include "std/algorithm.hpp" +#include "std/string.hpp" + +#include "zlib.h" + +namespace coding +{ +// Following class is a wrapper around ZLib routines. +// +// *NOTE* All Inflate() and Deflate() methods may return false in case +// of errors. In this case the output sequence may be already +// partially formed, so the user needs to implement its own roll-back +// strategy. +class ZLib +{ +public: + enum class Level + { + NoCompression, + BestSpeed, + BestCompression, + DefaultCompression + }; + + template + static bool Deflate(char const * data, size_t size, Level level, OutIt out) + { + if (!data) + return false; + DeflateProcessor processor(data, size, level); + return Process(processor, out); + } + + template + static bool Deflate(string const & s, Level level, OutIt out) + { + return Deflate(s.c_str(), s.size(), level, out); + } + + template + static bool Inflate(char const * data, size_t size, OutIt out) + { + if (!data) + return false; + InflateProcessor processor(data, size); + return Process(processor, out); + } + + template + static bool Inflate(string const & s, OutIt out) + { + return Inflate(s.c_str(), s.size(), out); + } + +private: + class Processor + { + public: + static size_t constexpr kBufferSize = 1024; + + Processor(char const * data, size_t size); + + inline bool IsInit() const { return m_init; } + bool ConsumedAll() const; + bool BufferIsFull() const; + + template + void MoveOut(OutIt out) + { + ASSERT(IsInit(), ()); + copy(m_buffer, m_buffer + kBufferSize - m_stream.avail_out, out); + m_stream.next_out = reinterpret_cast(m_buffer); + m_stream.avail_out = kBufferSize; + } + + protected: + z_stream m_stream; + bool m_init; + char m_buffer[kBufferSize]; + }; + + class DeflateProcessor : public Processor + { + public: + DeflateProcessor(char const * data, size_t size, Level level); + ~DeflateProcessor(); + + int Process(int flush); + }; + + class InflateProcessor : public Processor + { + public: + InflateProcessor(char const * data, size_t size); + ~InflateProcessor(); + + int Process(int flush); + }; + + template + static bool Process(Processor & processor, OutIt out) + { + if (!processor.IsInit()) + return false; + + while (true) + { + int const flush = processor.ConsumedAll() ? Z_FINISH : Z_NO_FLUSH; + int const ret = processor.Process(flush); + if (ret != Z_OK && ret != Z_STREAM_END) + return false; + + if (processor.BufferIsFull()) + processor.MoveOut(out); + + if (flush == Z_FINISH && ret == Z_STREAM_END) + break; + } + + processor.MoveOut(out); + return true; + } +}; +} // namespace coding