diff --git a/README.md b/README.md index 645f704..8ff8677 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -Gzip C++ lib for gzip compression and decompression. Extracted from [mapnik-vector-tile](https://github.com/mapbox/mapnik-vector-tile) for light-weight modularity. +Gzip C++ lib for gzip compression and decompression. + +This library is designed for **non streaming** gzip decompression and compression using C++ strings. [![Build Status](https://travis-ci.org/mapbox/gzip-hpp.svg?branch=master)](https://travis-ci.com/mapbox/gzip-hpp) [![hpp-skel badge](https://mapbox.s3.amazonaws.com/cpp-assets/hpp-skel-badge_blue.svg)](https://github.com/mapbox/hpp-skel) @@ -14,45 +16,69 @@ Gzip C++ lib for gzip compression and decompression. Extracted from [mapnik-vect // All function calls must pass in a pointer of an // immutable character sequence (aka a string in C) and its size std::string data = "hello"; -const char * pointer = data.data(); -std::size_t size = data.size(); // Check if compressed. Can check both gzip and zlib. -bool c = gzip::is_compressed(pointer, size); // false +bool c = gzip::is_compressed(data); // false // Compress returns a std::string -std::string compressed_data = gzip::compress(pointer, size); +std::string compressed_data = gzip::compress(data); // Decompress returns a std::string and decodes both zlib and gzip -const char * compressed_pointer = compressed_data.data(); -std::string decompressed_data = gzip::decompress(compressed_pointer, compressed_data.size()); +std::string decompressed_data = gzip::decompress(compressed_data); + +// Can also compress or decompress from a const char * and size +std::string compressed_data = gzip::compress(data.data(), data.size()); +std::string decompressed_data = gzip::decompress(compressed_data.data(), data.size()); + +// You can pass in an existing string as well to be modified for compression or decompression +// both of which will have data appended to the end. +std::string compressed_data; +gzip::compress(data, compressed_data); +std::string decompressed_data +gzip::decompress(compressed_data, decompressed_data); + +// This also works using pointers and sizes +std::string compressed_data; +gzip::compress(data.data(), data.size(), compressed_data); +std::string decompressed_data +gzip::decompress(compressed_data.data(), compressed_data.size(), decompressed_data); +``` +#### Compress -// Or like so -std::string compressed_data = gzip::compress(tile->data(), tile->data.size()); +All forms of compressed as shown above support the following optional arguments: + * Compression Level + * Buffering Size -// Or like so -std::string value = gzip::compress(node::Buffer::Data(obj), node::Buffer::Length(obj)); +Compression level is a number 0 to 9, based on the zlib compression levels. This increases the time spent in compression +and may result in higher levels of compression for higher compression levels. The default is the default level for the +zlib compression library of `Z_DEFAULT_COMPRESSION`. -// Or...etc +Buffering size controls the amount of memory allocated as a buffer during compression with zlib. This by default is a buffer +that is 75% of the size of the data provided. A `0` passed to buffer size is the default and forces this 75% calculation. -``` -#### Compress ```c++ -// Optionally include compression level -std::size_t size; // No default value, but what happens when not passed?? -int level = Z_DEFAULT_COMPRESSION; // Z_DEFAULT_COMPRESSION is the default if no arg is passed +const int level = Z_DEFAULT_COMPRESSION; +const std::size_t buffer_size = 1024; // 1 KB -std::string compressed_data = gzip::compress(tile->data(), size, level); +std::string compressed_data = gzip::compress(data, level, buffer_size); ``` + #### Decompress -```c++ -// No args other than the std:string -std::string data = "hello"; -std::string compressed_data = gzip::compress(data); -const char * compressed_pointer = compressed_data.data(); -std::string decompressed_data = gzip::decompress(compressed_pointer, compressed_data.size()); +All forms of decompressed as shown above support the following optional arguments: + * Maximum Uncompressed Size + * Buffering Size +Maximum uncompressed size limits the total amount of memory that may be used during decompression. This is provided to prevent heavily +compressed malicious files from causing issues in an application. By default this value is set to `0` which disables this protection. + +Buffering size controls the amount of memory allocated as a buffer during deccompression with zlib. This by default is a buffer +that is 150% of the size of the compressed data provided. A `0` passed to buffer size is the default and forces this 150% calculation. + +```c++ +const std::size_t max_decompressed_size = 1024 * 1024 * 1024; // 1 GB +const std::size_t buffer_size = 1024; // 1 kB +std::string decompressed_data = gzip::decompress(data, max_decompressed_size, buffer_size); ``` ## Test diff --git a/bench/run.cpp b/bench/run.cpp index 8f78de0..e2a8ecf 100644 --- a/bench/run.cpp +++ b/bench/run.cpp @@ -42,69 +42,63 @@ static void BM_decompress(benchmark::State& state) // NOLINT google-runtime-refe BENCHMARK(BM_decompress); -static void BM_compress_class(benchmark::State& state) // NOLINT google-runtime-references +static void BM_compress_string(benchmark::State& state) // NOLINT google-runtime-references { std::string buffer = open_file("./bench/14-4685-6265.mvt"); - gzip::Compressor comp; - for (auto _ : state) { - std::string output; - comp.compress(output, buffer.data(), buffer.size()); + std::string value = gzip::compress(buffer); + benchmark::DoNotOptimize(value.data()); } } -BENCHMARK(BM_compress_class); +BENCHMARK(BM_compress_string); -static void BM_compress_class_no_reallocations(benchmark::State& state) // NOLINT google-runtime-references +static void BM_decompress_string(benchmark::State& state) // NOLINT google-runtime-references { - std::string buffer = open_file("./bench/14-4685-6265.mvt"); - gzip::Compressor comp; - std::string output; - // Run once prior to pre-allocate - comp.compress(output, buffer.data(), buffer.size()); - + std::string buffer_uncompressed = open_file("./bench/14-4685-6265.mvt"); + std::string buffer = gzip::compress(buffer_uncompressed.data(), buffer_uncompressed.size()); for (auto _ : state) { - comp.compress(output, buffer.data(), buffer.size()); + std::string value = gzip::decompress(buffer); + benchmark::DoNotOptimize(value.data()); + benchmark::ClobberMemory(); } } -BENCHMARK(BM_compress_class_no_reallocations); +BENCHMARK(BM_decompress_string); -static void BM_decompress_class(benchmark::State& state) // NOLINT google-runtime-references +static void BM_compress_modify_string(benchmark::State& state) // NOLINT google-runtime-references { - - std::string buffer_uncompressed = open_file("./bench/14-4685-6265.mvt"); - std::string buffer = gzip::compress(buffer_uncompressed.data(), buffer_uncompressed.size()); - gzip::Decompressor decomp; + std::string buffer = open_file("./bench/14-4685-6265.mvt"); for (auto _ : state) { std::string output; - decomp.decompress(output, buffer.data(), buffer.size()); + gzip::compress(buffer, output); + benchmark::DoNotOptimize(output.data()); + benchmark::ClobberMemory(); } } -BENCHMARK(BM_decompress_class); +BENCHMARK(BM_compress_modify_string); -static void BM_decompress_class_no_reallocations(benchmark::State& state) // NOLINT google-runtime-references +static void BM_decompress_modify_string(benchmark::State& state) // NOLINT google-runtime-references { std::string buffer_uncompressed = open_file("./bench/14-4685-6265.mvt"); std::string buffer = gzip::compress(buffer_uncompressed.data(), buffer_uncompressed.size()); - gzip::Decompressor decomp; - std::string output; - // Run once prior to pre-allocate - decomp.decompress(output, buffer.data(), buffer.size()); for (auto _ : state) { - decomp.decompress(output, buffer.data(), buffer.size()); + std::string output; + gzip::decompress(buffer, output); + benchmark::DoNotOptimize(output.data()); + benchmark::ClobberMemory(); } } -BENCHMARK(BM_decompress_class_no_reallocations); +BENCHMARK(BM_decompress_modify_string); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpedantic" diff --git a/include/gzip/compress.hpp b/include/gzip/compress.hpp index 2ec56c2..7b18e0d 100644 --- a/include/gzip/compress.hpp +++ b/include/gzip/compress.hpp @@ -10,103 +10,104 @@ namespace gzip { -class Compressor +inline void compress(const char* data, + std::size_t size, + std::string& output, + int level = Z_DEFAULT_COMPRESSION, + std::size_t buffering_size = 0) { - std::size_t max_; - int level_; - - public: - Compressor(int level = Z_DEFAULT_COMPRESSION, - std::size_t max_bytes = 2000000000) // by default refuse operation if uncompressed data is > 2GB - : max_(max_bytes), - level_(level) + if (buffering_size == 0) { + buffering_size = (size * 3 / 4) + 16; } - template - void compress(InputType& output, - const char* data, - std::size_t size) const - { - -#ifdef DEBUG - // Verify if size input will fit into unsigned int, type used for zlib's avail_in - if (size > std::numeric_limits::max()) - { - throw std::runtime_error("size arg is too large to fit into unsigned int type"); - } -#endif - if (size > max_) - { - throw std::runtime_error("size may use more memory than intended when decompressing"); - } - - z_stream deflate_s; - deflate_s.zalloc = Z_NULL; - deflate_s.zfree = Z_NULL; - deflate_s.opaque = Z_NULL; - deflate_s.avail_in = 0; - deflate_s.next_in = Z_NULL; - - // The windowBits parameter is the base two logarithm of the window size (the size of the history buffer). - // It should be in the range 8..15 for this version of the library. - // Larger values of this parameter result in better compression at the expense of memory usage. - // This range of values also changes the decoding type: - // -8 to -15 for raw deflate - // 8 to 15 for zlib - // (8 to 15) + 16 for gzip - // (8 to 15) + 32 to automatically detect gzip/zlib header (decompression/inflate only) - constexpr int window_bits = 15 + 16; // gzip with windowbits of 15 - - constexpr int mem_level = 8; - // The memory requirements for deflate are (in bytes): - // (1 << (window_bits+2)) + (1 << (mem_level+9)) - // with a default value of 8 for mem_level and our window_bits of 15 - // this is 128Kb + z_stream deflate_s; + deflate_s.zalloc = Z_NULL; + deflate_s.zfree = Z_NULL; + deflate_s.opaque = Z_NULL; + deflate_s.avail_in = 0; + deflate_s.next_in = Z_NULL; + + // The windowBits parameter is the base two logarithm of the window size (the size of the history buffer). + // It should be in the range 8..15 for this version of the library. + // Larger values of this parameter result in better compression at the expense of memory usage. + // This range of values also changes the decoding type: + // -8 to -15 for raw deflate + // 8 to 15 for zlib + // (8 to 15) + 16 for gzip + // (8 to 15) + 32 to automatically detect gzip/zlib header (decompression/inflate only) + constexpr int window_bits = 15 + 16; // gzip with windowbits of 15 + + constexpr int mem_level = 8; + // The memory requirements for deflate are (in bytes): + // (1 << (window_bits+2)) + (1 << (mem_level+9)) + // with a default value of 8 for mem_level and our window_bits of 15 + // this is 128Kb #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wold-style-cast" - if (deflateInit2(&deflate_s, level_, Z_DEFLATED, window_bits, mem_level, Z_DEFAULT_STRATEGY) != Z_OK) - { - throw std::runtime_error("deflate init failed"); - } + if (deflateInit2(&deflate_s, level, Z_DEFLATED, window_bits, mem_level, Z_DEFAULT_STRATEGY) != Z_OK) + { + throw std::runtime_error("deflate init failed"); + } #pragma GCC diagnostic pop + std::string buffer; + do + { + constexpr std::size_t max_step = static_cast(std::numeric_limits::max()); + const unsigned int step_size = size > max_step ? max_step : static_cast(size); + size -= step_size; + const unsigned int buffer_size = buffering_size > step_size ? step_size : static_cast(buffering_size); + deflate_s.next_in = reinterpret_cast(data); - deflate_s.avail_in = static_cast(size); + data = data + step_size; + deflate_s.avail_in = step_size; - std::size_t size_compressed = 0; + buffer.resize(static_cast(buffer_size)); do { - size_t increase = size / 2 + 1024; - if (output.size() < (size_compressed + increase)) - { - output.resize(size_compressed + increase); - } - // There is no way we see that "increase" would not fit in an unsigned int, - // hence we use static cast here to avoid -Wshorten-64-to-32 error - deflate_s.avail_out = static_cast(increase); - deflate_s.next_out = reinterpret_cast((&output[0] + size_compressed)); + deflate_s.avail_out = buffer_size; + deflate_s.next_out = reinterpret_cast(&buffer[0]); // From http://www.zlib.net/zlib_how.html // "deflate() has a return value that can indicate errors, yet we do not check it here. // Why not? Well, it turns out that deflate() can do no wrong here." // Basically only possible error is from deflateInit not working properly deflate(&deflate_s, Z_FINISH); - size_compressed += (increase - deflate_s.avail_out); + output.append(buffer, 0, static_cast(buffer_size - deflate_s.avail_out)); } while (deflate_s.avail_out == 0); - - deflateEnd(&deflate_s); - output.resize(size_compressed); + } while (size > 0); + const int ret = deflateEnd(&deflate_s); + if (ret != Z_OK) + { + throw std::runtime_error("Unexpected gzip compression error, stream was inconsistent or freed prematurely"); } -}; +} + +inline void compress(std::string const& input, + std::string& output, + int level = Z_DEFAULT_COMPRESSION, + std::size_t buffering_size = 0) +{ + compress(input.data(), input.size(), output, level, buffering_size); +} inline std::string compress(const char* data, std::size_t size, - int level = Z_DEFAULT_COMPRESSION) + int level = Z_DEFAULT_COMPRESSION, + std::size_t buffering_size = 0) +{ + std::string output; + compress(data, size, output, level, buffering_size); + return output; +} + +inline std::string compress(std::string const& input, + int level = Z_DEFAULT_COMPRESSION, + std::size_t buffering_size = 0) { - Compressor comp(level); std::string output; - comp.compress(output, data, size); + compress(input.data(), input.size(), output, level, buffering_size); return output; } diff --git a/include/gzip/decompress.hpp b/include/gzip/decompress.hpp index b70670f..5b71942 100644 --- a/include/gzip/decompress.hpp +++ b/include/gzip/decompress.hpp @@ -10,95 +10,99 @@ namespace gzip { -class Decompressor +inline void decompress(const char* data, + std::size_t size, + std::string& output, + std::size_t max_uncompressed_size = 0, + std::size_t buffering_size = 0) { - std::size_t max_; - - public: - Decompressor(std::size_t max_bytes = 1000000000) // by default refuse operation if compressed data is > 1GB - : max_(max_bytes) + if (buffering_size == 0) { + buffering_size = (size * 2) - (size / 2) + 16; } + z_stream inflate_s; + inflate_s.zalloc = Z_NULL; + inflate_s.zfree = Z_NULL; + inflate_s.opaque = Z_NULL; + inflate_s.avail_in = 0; + inflate_s.next_in = Z_NULL; - template - void decompress(OutputType& output, - const char* data, - std::size_t size) const - { - z_stream inflate_s; - - inflate_s.zalloc = Z_NULL; - inflate_s.zfree = Z_NULL; - inflate_s.opaque = Z_NULL; - inflate_s.avail_in = 0; - inflate_s.next_in = Z_NULL; + // The windowBits parameter is the base two logarithm of the window size (the size of the history buffer). + // It should be in the range 8..15 for this version of the library. + // Larger values of this parameter result in better compression at the expense of memory usage. + // This range of values also changes the decoding type: + // -8 to -15 for raw deflate + // 8 to 15 for zlib + // (8 to 15) + 16 for gzip + // (8 to 15) + 32 to automatically detect gzip/zlib header + constexpr int window_bits = 15 + 32; // auto with windowbits of 15 - // The windowBits parameter is the base two logarithm of the window size (the size of the history buffer). - // It should be in the range 8..15 for this version of the library. - // Larger values of this parameter result in better compression at the expense of memory usage. - // This range of values also changes the decoding type: - // -8 to -15 for raw deflate - // 8 to 15 for zlib - // (8 to 15) + 16 for gzip - // (8 to 15) + 32 to automatically detect gzip/zlib header - constexpr int window_bits = 15 + 32; // auto with windowbits of 15 + constexpr unsigned int max_uint = std::numeric_limits::max(); + const unsigned int size_step = buffering_size > max_uint ? max_uint : static_cast(buffering_size); + if (max_uncompressed_size != 0 && size_step > max_uncompressed_size) + { + throw std::runtime_error("buffer size used during decoompression of gzip will use more memory then allowed, either increase the limit or reduce the buffer size"); + } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wold-style-cast" - if (inflateInit2(&inflate_s, window_bits) != Z_OK) - { - throw std::runtime_error("inflate init failed"); - } + if (inflateInit2(&inflate_s, window_bits) != Z_OK) + { + throw std::runtime_error("inflate init failed"); + } #pragma GCC diagnostic pop - inflate_s.next_in = reinterpret_cast(data); - -#ifdef DEBUG - // Verify if size (long type) input will fit into unsigned int, type used for zlib's avail_in - std::uint64_t size_64 = size * 2; - if (size_64 > std::numeric_limits::max()) + inflate_s.next_in = reinterpret_cast(data); + inflate_s.avail_in = static_cast(size); + std::string buffer(static_cast(size_step), char()); + do + { + inflate_s.avail_out = size_step; + inflate_s.next_out = reinterpret_cast(&buffer[0]); + const int ret = inflate(&inflate_s, Z_FINISH); + if (ret != Z_STREAM_END && ret != Z_OK && ret != Z_BUF_ERROR) { + std::string error_msg = inflate_s.msg; inflateEnd(&inflate_s); - throw std::runtime_error("size arg is too large to fit into unsigned int type x2"); + throw std::runtime_error(error_msg); } -#endif - if (size > max_ || (size * 2) > max_) + if (max_uncompressed_size != 0 && (output.size() + size_step - inflate_s.avail_out) > max_uncompressed_size) { inflateEnd(&inflate_s); - throw std::runtime_error("size may use more memory than intended when decompressing"); + throw std::runtime_error("size of output string will use more memory then intended when decompressing"); } - inflate_s.avail_in = static_cast(size); - std::size_t size_uncompressed = 0; - do - { - std::size_t resize_to = size_uncompressed + 2 * size; - if (resize_to > max_) - { - inflateEnd(&inflate_s); - throw std::runtime_error("size of output string will use more memory then intended when decompressing"); - } - output.resize(resize_to); - inflate_s.avail_out = static_cast(2 * size); - inflate_s.next_out = reinterpret_cast(&output[0] + size_uncompressed); - int ret = inflate(&inflate_s, Z_FINISH); - if (ret != Z_STREAM_END && ret != Z_OK && ret != Z_BUF_ERROR) - { - std::string error_msg = inflate_s.msg; - inflateEnd(&inflate_s); - throw std::runtime_error(error_msg); - } - - size_uncompressed += (2 * size - inflate_s.avail_out); - } while (inflate_s.avail_out == 0); - inflateEnd(&inflate_s); - output.resize(size_uncompressed); + output.append(buffer, 0, size_step - inflate_s.avail_out); + } while (inflate_s.avail_out == 0); + const int ret2 = inflateEnd(&inflate_s); + if (ret2 != Z_OK) + { + throw std::runtime_error("Unexpected gzip decompression error, state of stream was inconsistent"); } -}; +} + +inline void decompress(std::string const& input, + std::string& output, + std::size_t max_uncompressed_size = 0, + std::size_t buffering_size = 0) +{ + return decompress(input.data(), input.size(), output, max_uncompressed_size, buffering_size); +} + +inline std::string decompress(const char* data, + std::size_t size, + std::size_t max_uncompressed_size = 0, + std::size_t buffering_size = 0) +{ + std::string output; + decompress(data, size, output, max_uncompressed_size, buffering_size); + return output; +} -inline std::string decompress(const char* data, std::size_t size) +inline std::string decompress(std::string const& input, + std::size_t max_uncompressed_size = 0, + std::size_t buffering_size = 0) { - Decompressor decomp; std::string output; - decomp.decompress(output, data, size); + decompress(input.data(), input.size(), output, max_uncompressed_size, buffering_size); return output; } diff --git a/include/gzip/utils.hpp b/include/gzip/utils.hpp index db123d1..76455d8 100644 --- a/include/gzip/utils.hpp +++ b/include/gzip/utils.hpp @@ -1,11 +1,12 @@ #include +#include namespace gzip { // These live in gzip.hpp because it doesnt need to use deps. // Otherwise, they would need to live in impl files if these methods used // zlib structures or functions like inflate/deflate) -inline bool is_compressed(const char* data, std::size_t size) +inline bool is_compressed(const char* data, std::size_t size) noexcept { return size > 2 && ( @@ -19,4 +20,10 @@ inline bool is_compressed(const char* data, std::size_t size) // gzip (static_cast(data[0]) == 0x1F && static_cast(data[1]) == 0x8B)); } + +inline bool is_compressed(std::string const& s) noexcept +{ + return is_compressed(s.data(), s.size()); +} + } // namespace gzip diff --git a/include/gzip/version.hpp b/include/gzip/version.hpp index 47af692..24d9ab4 100644 --- a/include/gzip/version.hpp +++ b/include/gzip/version.hpp @@ -1,7 +1,7 @@ #pragma once /// The major version number -#define GZIP_VERSION_MAJOR 1 +#define GZIP_VERSION_MAJOR 2 /// The minor version number #define GZIP_VERSION_MINOR 0 @@ -13,4 +13,4 @@ #define GZIP_VERSION_CODE (GZIP_VERSION_MAJOR * 10000 + GZIP_VERSION_MINOR * 100 + GZIP_VERSION_PATCH) /// Version number as string -#define GZIP_VERSION_STRING "1.0.0" \ No newline at end of file +#define GZIP_VERSION_STRING "2.0.0" diff --git a/test/test_io.cpp b/test/test_io.cpp index 61d7341..27e4141 100644 --- a/test/test_io.cpp +++ b/test/test_io.cpp @@ -1,45 +1,40 @@ #include #include -#include #include #include #include +#include TEST_CASE("successful compress") { std::string data = "hello hello hello hello"; - SECTION("pointer") + SECTION("data/size") { - const char* pointer = data.data(); - std::string value = gzip::compress(pointer, data.size()); + std::string value = gzip::compress(data.data(), data.size()); REQUIRE(!value.empty()); } -} - -TEST_CASE("fail compress - throws max size limit") -{ - std::string data = "hello hello hello hello"; - const char* pointer = data.data(); - - std::uint64_t l = 2000000001; - CHECK_THROWS_WITH(gzip::compress(pointer, l), Catch::Contains("size may use more memory than intended when decompressing")); -} + SECTION("string") + { + std::string value = gzip::compress(data); + REQUIRE(!value.empty()); + } -#ifdef DEBUG -TEST_CASE("fail compress - pointer, debug throws int overflow") -{ - std::string data = "hello hello hello hello"; - const char* pointer = data.data(); - // numeric_limit useful for integer conversion - unsigned int i = std::numeric_limits::max(); - // turn int i into a long, so we can add to it safely without overflow - unsigned long l = static_cast(i) + 1; + SECTION("data/size and string") + { + std::string value; + gzip::compress(data.data(), data.size(), value); + REQUIRE(!value.empty()); + } - CHECK_THROWS_WITH(gzip::compress(pointer, l), Catch::Contains("size arg is too large to fit into unsigned int type")); + SECTION("string and string") + { + std::string value; + gzip::compress(data, value); + REQUIRE(!value.empty()); + } } -#endif TEST_CASE("successful decompress - pointer") { @@ -51,23 +46,6 @@ TEST_CASE("successful decompress - pointer") REQUIRE(data == value); } -#ifdef DEBUG -TEST_CASE("fail decompress - pointer, debug throws int overflow") -{ - std::string data = "hello hello hello hello"; - const char* pointer = data.data(); - std::string compressed_data = gzip::compress(pointer, data.size()); - const char* compressed_pointer = compressed_data.data(); - - // numeric_limit useful for integer conversion - unsigned int i = std::numeric_limits::max(); - // turn int i into a long, so we can add to it safely without overflow - unsigned long l = static_cast(i) + 1; - - CHECK_THROWS_WITH(gzip::decompress(compressed_pointer, l), Catch::Contains("size arg is too large to fit into unsigned int type x2")); -} -#endif - TEST_CASE("invalid decompression") { std::string data("this is a string that should be compressed data"); @@ -82,6 +60,7 @@ TEST_CASE("round trip compression - gzip") const std::string data("this is a sentence that will be compressed into something"); CHECK(!gzip::is_compressed(data.data(), data.size())); + CHECK(!gzip::is_compressed(data)); SECTION("compression level - invalid") { @@ -132,10 +111,13 @@ TEST_CASE("test decompression size limit") std::istreambuf_iterator()); stream.close(); - std::size_t limit = 20 * 1024 * 1024; // 20 Mb + const std::size_t limit = 20 * 1024 * 1024; // 20 Mb // file should be about 500 mb uncompressed - gzip::Decompressor decomp(limit); std::string output; - CHECK_THROWS(decomp.decompress(output, str_compressed.data(), str_compressed.size())); + CHECK_THROWS_WITH(gzip::decompress(str_compressed, output, limit), Catch::Contains("size of output string will use more memory then intended when decompressing")); CHECK(output.size() < limit); + // Check situation where buffer size is larger then limit + const std::size_t buffer_size = 21 * 1024 * 1024; // 21 Mb + std::string output2; + CHECK_THROWS_WITH(gzip::decompress(str_compressed, output2, limit, buffer_size), Catch::Contains("buffer size used during decoompression of gzip will use more memory then")); } diff --git a/test/test_version.cpp b/test/test_version.cpp index 342699b..abd707a 100644 --- a/test/test_version.cpp +++ b/test/test_version.cpp @@ -3,5 +3,5 @@ TEST_CASE("test version") { - REQUIRE(GZIP_VERSION_STRING == std::string("1.0.0")); + REQUIRE(GZIP_VERSION_STRING == std::string("2.0.0")); }