mirror of
https://github.com/openappsec/openappsec.git
synced 2025-09-30 11:44:29 +03:00
First release of open-appsec source code
This commit is contained in:
9
core/compression/CMakeLists.txt
Executable file
9
core/compression/CMakeLists.txt
Executable file
@@ -0,0 +1,9 @@
|
||||
include_directories(${ng_module_osrc_zlib_path}/include)
|
||||
add_definitions(-DZLIB_CONST)
|
||||
|
||||
add_library(compression_utils SHARED compression_utils.cc)
|
||||
|
||||
add_subdirectory(compression_utils_ut)
|
||||
|
||||
install(TARGETS compression_utils DESTINATION lib)
|
||||
install(TARGETS compression_utils DESTINATION http_transaction_handler_service/lib)
|
384
core/compression/compression_utils.cc
Executable file
384
core/compression/compression_utils.cc
Executable file
@@ -0,0 +1,384 @@
|
||||
// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "compression_utils.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
#include <strings.h>
|
||||
#include <string.h>
|
||||
#include <zlib.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
using DebugFunction = void(*)(const char *);
|
||||
|
||||
static const int max_debug_level = static_cast<int>(CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_ASSERTION);
|
||||
|
||||
static void
|
||||
defaultPrint(const char *debug_message)
|
||||
{
|
||||
cerr << debug_message;
|
||||
};
|
||||
|
||||
class ZlibDebugStream
|
||||
{
|
||||
public:
|
||||
ZlibDebugStream(const CompressionUtilsDebugLevel _debug_level) : debug_level(_debug_level) {}
|
||||
|
||||
~ZlibDebugStream()
|
||||
{
|
||||
ZlibDebugStream::debug_funcs[debug_level](debug_message.str().c_str());
|
||||
|
||||
if (debug_level == CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_ASSERTION) abort();
|
||||
}
|
||||
|
||||
static void
|
||||
resetDebugFunctions()
|
||||
{
|
||||
for (auto &func : debug_funcs) {
|
||||
func = defaultPrint;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
setDebugFunction(const CompressionUtilsDebugLevel debug_level, DebugFunction function)
|
||||
{
|
||||
if (static_cast<int>(debug_level) > max_debug_level) return;
|
||||
debug_funcs[static_cast<int>(debug_level)] = function;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ZlibDebugStream & operator<<(const T &message) { debug_message << message; return *this; }
|
||||
|
||||
private:
|
||||
ostringstream debug_message;
|
||||
CompressionUtilsDebugLevel debug_level;
|
||||
|
||||
static array<DebugFunction, max_debug_level + 1> debug_funcs;
|
||||
};
|
||||
|
||||
array<DebugFunction, max_debug_level + 1> ZlibDebugStream::debug_funcs = {
|
||||
defaultPrint, // CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_TRACE
|
||||
defaultPrint, // CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_DEBUG
|
||||
defaultPrint, // CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_INFO
|
||||
defaultPrint, // CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_WARNING
|
||||
defaultPrint, // CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_ERROR
|
||||
defaultPrint // CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_ASSERTION
|
||||
};
|
||||
|
||||
#define zlibDbgError ZlibDebugStream(CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_ERROR)
|
||||
#define zlibDbgAssertion ZlibDebugStream(CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_ASSERTION)
|
||||
|
||||
static const int default_num_window_bits = 15; // Default used by zlib.
|
||||
static const int default_compression_level = Z_DEFAULT_COMPRESSION;
|
||||
static const int default_compression_method = Z_DEFLATED;
|
||||
static const int default_mem_level = 8; // Default recommended in zlib documentation.
|
||||
static const int default_strategy = Z_DEFAULT_STRATEGY;
|
||||
|
||||
static const int zlib_ok_return_value = Z_OK;
|
||||
static const int zlib_stream_done_return_value = Z_STREAM_END;
|
||||
static const int zlib_bad_stream_state_error = Z_STREAM_ERROR;
|
||||
static const int zlib_invalid_data_error = Z_DATA_ERROR;
|
||||
static const int zlib_out_of_memory_error = Z_MEM_ERROR;
|
||||
static const int zlib_version_mismatch_error = Z_VERSION_ERROR;
|
||||
static const int zlib_buf_error = Z_BUF_ERROR;
|
||||
|
||||
static const int zlib_finish_flush = Z_FINISH;
|
||||
static const int zlib_sync_flush = Z_SYNC_FLUSH;
|
||||
static const int zlib_no_flush = Z_NO_FLUSH;
|
||||
|
||||
struct CompressionStream
|
||||
{
|
||||
CompressionStream() { bzero(&stream, sizeof(z_stream)); }
|
||||
~CompressionStream() { fini(); }
|
||||
|
||||
tuple<basic_string<unsigned char>, bool>
|
||||
decompress(const unsigned char *data, uint32_t size)
|
||||
{
|
||||
initInflate();
|
||||
if (state != TYPE::DECOMPRESS) throw runtime_error("Could not start decompression");
|
||||
|
||||
stream.avail_in = size;
|
||||
stream.next_in = data;
|
||||
|
||||
vector<unsigned char> work_space;
|
||||
work_space.reserve(4096);
|
||||
basic_string<unsigned char> res;
|
||||
int retries = 0;
|
||||
|
||||
while (stream.avail_in != 0) {
|
||||
stream.avail_out = work_space.capacity();
|
||||
stream.next_out = work_space.data();
|
||||
|
||||
auto old_total_out = stream.total_out;
|
||||
|
||||
auto inflate_res = inflate(&stream, zlib_no_flush);
|
||||
|
||||
if (inflate_res != Z_OK && inflate_res != Z_STREAM_END) {
|
||||
fini();
|
||||
throw runtime_error("error in 'inflate': " + getZlibError(inflate_res));
|
||||
}
|
||||
|
||||
if (stream.total_out != old_total_out) {
|
||||
res.append(work_space.data(), stream.total_out - old_total_out);
|
||||
} else {
|
||||
++retries;
|
||||
if (retries > 3) {
|
||||
fini();
|
||||
throw runtime_error("No results from inflate more than three times");
|
||||
}
|
||||
}
|
||||
|
||||
if (inflate_res == Z_STREAM_END) {
|
||||
fini();
|
||||
return make_tuple(res, true);
|
||||
}
|
||||
}
|
||||
|
||||
return make_tuple(res, false);
|
||||
}
|
||||
|
||||
basic_string<unsigned char>
|
||||
compress(CompressionType type, const unsigned char *data, uint32_t size, int is_last_chunk)
|
||||
{
|
||||
initDeflate(type);
|
||||
if (state != TYPE::COMPRESS) throw runtime_error("Could not start compression");
|
||||
|
||||
stream.avail_in = size;
|
||||
stream.next_in = data;
|
||||
|
||||
vector<unsigned char> work_space;
|
||||
work_space.reserve(deflateBound(&stream, stream.avail_in));
|
||||
basic_string<unsigned char> res;
|
||||
int retries = 0;
|
||||
|
||||
while (stream.avail_in != 0) {
|
||||
stream.avail_out = work_space.capacity();
|
||||
stream.next_out = work_space.data();
|
||||
|
||||
auto old_total_out = stream.total_out;
|
||||
|
||||
int deflate_res = deflate(&stream, is_last_chunk ? zlib_finish_flush : zlib_sync_flush);
|
||||
|
||||
if (deflate_res != Z_OK && deflate_res != Z_STREAM_END) {
|
||||
fini();
|
||||
throw runtime_error("error in 'deflate': " + getZlibError(deflate_res));
|
||||
}
|
||||
if (stream.total_out != old_total_out) {
|
||||
res.append(work_space.data(), stream.total_out - old_total_out);
|
||||
} else {
|
||||
++retries;
|
||||
if (retries > 3) {
|
||||
fini();
|
||||
throw runtime_error("No results from deflate more than three times");
|
||||
}
|
||||
}
|
||||
if (deflate_res == Z_STREAM_END) {
|
||||
fini();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private:
|
||||
void
|
||||
initInflate()
|
||||
{
|
||||
if (state != TYPE::UNINITIALIZAED) return;
|
||||
|
||||
auto init_status = inflateInit2(&stream, default_num_window_bits + 32);
|
||||
if (init_status != zlib_ok_return_value) {
|
||||
throw runtime_error(
|
||||
"Failed to initialize decompression stream. Error: " + getZlibError(init_status)
|
||||
);
|
||||
}
|
||||
|
||||
state = TYPE::DECOMPRESS;
|
||||
}
|
||||
|
||||
void
|
||||
initDeflate(CompressionType type)
|
||||
{
|
||||
if (state != TYPE::UNINITIALIZAED) return;
|
||||
|
||||
int num_history_window_bits;
|
||||
switch (type) {
|
||||
case CompressionType::GZIP: {
|
||||
num_history_window_bits = default_num_window_bits + 16;
|
||||
break;
|
||||
}
|
||||
case CompressionType::ZLIB: {
|
||||
num_history_window_bits = default_num_window_bits;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
zlibDbgAssertion
|
||||
<< "Invalid compression type value: "
|
||||
<< static_cast<int>(type);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int init_status = deflateInit2(
|
||||
&stream,
|
||||
default_compression_level,
|
||||
default_compression_method,
|
||||
num_history_window_bits,
|
||||
default_mem_level,
|
||||
default_strategy
|
||||
);
|
||||
if (init_status != zlib_ok_return_value) {
|
||||
throw runtime_error(
|
||||
"Failed to initialize compression stream. Error: " + getZlibError(init_status)
|
||||
);
|
||||
}
|
||||
|
||||
state = TYPE::COMPRESS;
|
||||
}
|
||||
|
||||
void
|
||||
fini()
|
||||
{
|
||||
int end_stream_res = zlib_ok_return_value;
|
||||
|
||||
if (state == TYPE::DECOMPRESS) end_stream_res = inflateEnd(&stream);
|
||||
if (state == TYPE::COMPRESS) end_stream_res = deflateEnd(&stream);
|
||||
|
||||
if (end_stream_res != zlib_ok_return_value) {
|
||||
zlibDbgError << "Failed to clean state: " << getZlibError(end_stream_res);
|
||||
}
|
||||
|
||||
state = TYPE::UNINITIALIZAED;
|
||||
}
|
||||
|
||||
string
|
||||
getZlibError(int zlibErrorCode)
|
||||
{
|
||||
switch (zlibErrorCode) {
|
||||
case zlib_buf_error:
|
||||
return "No progress was possible (possibly no more input data or not enough output buffer space)";
|
||||
case zlib_bad_stream_state_error:
|
||||
return "Inconsistent compression stream state";
|
||||
case zlib_invalid_data_error:
|
||||
return "Invalid or corrupted stream data";
|
||||
case zlib_out_of_memory_error:
|
||||
return "Out of memory";
|
||||
case zlib_version_mismatch_error:
|
||||
return "zlib version mismatch";
|
||||
default:
|
||||
return "zlib error occurred. Error code: " + to_string(zlibErrorCode);
|
||||
}
|
||||
}
|
||||
|
||||
z_stream stream;
|
||||
enum class TYPE { UNINITIALIZAED, COMPRESS, DECOMPRESS } state = TYPE::UNINITIALIZAED;
|
||||
};
|
||||
|
||||
void
|
||||
resetCompressionDebugFunctionsToStandardError()
|
||||
{
|
||||
ZlibDebugStream::resetDebugFunctions();
|
||||
}
|
||||
|
||||
void
|
||||
setCompressionDebugFunction(const CompressionUtilsDebugLevel debug_level, void (*debug_function)(const char *))
|
||||
{
|
||||
ZlibDebugStream::setDebugFunction(debug_level, debug_function);
|
||||
}
|
||||
|
||||
CompressionStream *
|
||||
initCompressionStream()
|
||||
{
|
||||
return new CompressionStream();
|
||||
}
|
||||
|
||||
void
|
||||
finiCompressionStream(CompressionStream *compression_stream)
|
||||
{
|
||||
delete compression_stream;
|
||||
}
|
||||
|
||||
static unsigned char *
|
||||
duplicateMemory(const basic_string<unsigned char> &str)
|
||||
{
|
||||
auto res = static_cast<unsigned char *>(malloc(str.size()));
|
||||
|
||||
if (res == nullptr) throw bad_alloc();
|
||||
|
||||
memcpy(res, str.data(), str.size());
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
CompressionResult
|
||||
compressData(
|
||||
CompressionStream *compression_stream,
|
||||
const CompressionType compression_type,
|
||||
const uint32_t data_size,
|
||||
const unsigned char *uncompressed_data,
|
||||
const int is_last_chunk
|
||||
)
|
||||
{
|
||||
CompressionResult result;
|
||||
|
||||
try {
|
||||
if (compression_stream == nullptr) throw invalid_argument("Compression stream is NULL");
|
||||
if (uncompressed_data == nullptr) throw invalid_argument("Data pointer is NULL");
|
||||
|
||||
auto compress = compression_stream->compress(compression_type, uncompressed_data, data_size, is_last_chunk);
|
||||
result.output = duplicateMemory(compress);
|
||||
result.num_output_bytes = compress.size();
|
||||
result.ok = 1;
|
||||
} catch (const exception &e) {
|
||||
zlibDbgError << "Compression failed " << e.what();
|
||||
|
||||
result.ok = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DecompressionResult
|
||||
decompressData(
|
||||
CompressionStream *compression_stream,
|
||||
const uint32_t compressed_data_size,
|
||||
const unsigned char *compressed_data
|
||||
)
|
||||
{
|
||||
DecompressionResult result;
|
||||
|
||||
try {
|
||||
if (compression_stream == nullptr) throw invalid_argument("Compression stream is NULL");
|
||||
if (compressed_data == nullptr) throw invalid_argument("Data pointer is NULL");
|
||||
if (compressed_data_size == 0) throw invalid_argument("Data size is 0");
|
||||
|
||||
auto decompress = compression_stream->decompress(compressed_data, compressed_data_size);
|
||||
result.output = duplicateMemory(get<0>(decompress));
|
||||
result.num_output_bytes = get<0>(decompress).size();
|
||||
result.is_last_chunk = get<1>(decompress);
|
||||
result.ok = 1;
|
||||
} catch (const exception &e) {
|
||||
zlibDbgError << "Decompression failed " << e.what();
|
||||
|
||||
result.ok = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
5
core/compression/compression_utils_ut/CMakeLists.txt
Executable file
5
core/compression/compression_utils_ut/CMakeLists.txt
Executable file
@@ -0,0 +1,5 @@
|
||||
link_directories(${ng_module_osrc_zlib_path}/lib)
|
||||
|
||||
file(COPY test_files DESTINATION .)
|
||||
|
||||
add_unit_test(compression_utils_ut "compression_utils_ut.cc" "compression_utils;-lz")
|
459
core/compression/compression_utils_ut/compression_utils_ut.cc
Executable file
459
core/compression/compression_utils_ut/compression_utils_ut.cc
Executable file
@@ -0,0 +1,459 @@
|
||||
#include <fstream>
|
||||
|
||||
#include "cptest.h"
|
||||
#include "compression_utils.h"
|
||||
#include "buffer.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
using ErrorHook = function<void(const char *)>;
|
||||
|
||||
USE_DEBUG_FLAG(D_COMPRESSION);
|
||||
|
||||
class CompressionUtilsTest : public Test
|
||||
{
|
||||
public:
|
||||
CompressionUtilsTest()
|
||||
{
|
||||
Debug::setUnitTestFlag(D_COMPRESSION, Debug::DebugLevel::ERROR);
|
||||
Debug::setNewDefaultStdout(&capture_debug);
|
||||
|
||||
setCompressionDebugFunction(
|
||||
CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_ERROR,
|
||||
[](const char *debug_message) { dbgError(D_COMPRESSION) << debug_message; }
|
||||
);
|
||||
setCompressionDebugFunction(
|
||||
CompressionUtilsDebugLevel::COMPRESSION_DBG_LEVEL_ASSERTION,
|
||||
[](const char *assert_message) { dbgAssert(false) << assert_message; }
|
||||
);
|
||||
}
|
||||
|
||||
~CompressionUtilsTest()
|
||||
{
|
||||
resetOutputStream();
|
||||
}
|
||||
|
||||
void
|
||||
resetOutputStream()
|
||||
{
|
||||
capture_debug.str("");
|
||||
Debug::setNewDefaultStdout(&cout);
|
||||
resetCompressionDebugFunctionsToStandardError();
|
||||
}
|
||||
|
||||
string
|
||||
readTestFileContents(const string &file_name)
|
||||
{
|
||||
string file_path = cptestFnameInExeDir(test_files_dir_name + "/" + file_name);
|
||||
ifstream test_string_file(file_path);
|
||||
stringstream string_stream;
|
||||
string_stream << test_string_file.rdbuf();
|
||||
|
||||
return string_stream.str();
|
||||
}
|
||||
|
||||
Maybe<string>
|
||||
compressString(
|
||||
const CompressionType compression_type,
|
||||
const string &uncompressed_string,
|
||||
const bool last_chunk = true,
|
||||
CompressionStream *compression_stream = nullptr
|
||||
)
|
||||
{
|
||||
auto disposable_compression_stream = initCompressionStream();
|
||||
|
||||
CompressionStream *compression_stream_to_use =
|
||||
compression_stream == nullptr ?
|
||||
disposable_compression_stream :
|
||||
compression_stream;
|
||||
|
||||
unsigned char *input_data = reinterpret_cast<unsigned char *>(const_cast<char *>(uncompressed_string.c_str()));
|
||||
CompressionResult compress_data_result = compressData(
|
||||
compression_stream_to_use,
|
||||
compression_type,
|
||||
uncompressed_string.size(),
|
||||
input_data,
|
||||
last_chunk ? 1 : 0
|
||||
);
|
||||
finiCompressionStream(disposable_compression_stream);
|
||||
|
||||
if (compress_data_result.ok == 0) return genError("compressString failed");
|
||||
|
||||
auto compressed_string = string(
|
||||
reinterpret_cast<char *>(compress_data_result.output),
|
||||
compress_data_result.num_output_bytes
|
||||
);
|
||||
free(compress_data_result.output);
|
||||
|
||||
return compressed_string;
|
||||
}
|
||||
|
||||
Maybe<string>
|
||||
chunkedCompressString(const CompressionType compression_type, const string &uncompressed_string)
|
||||
{
|
||||
vector<string> input_string_chunks = splitIntoChunks(
|
||||
uncompressed_string,
|
||||
uncompressed_string.size() / chunk_size + 1
|
||||
);
|
||||
stringstream compressed_data_ss;
|
||||
auto compression_stream = initCompressionStream();
|
||||
for (uint32_t curr_chunk_index = 0; curr_chunk_index < input_string_chunks.size() - 1; curr_chunk_index++) {
|
||||
Maybe<string> compress_string_result = compressString(
|
||||
compression_type,
|
||||
input_string_chunks[curr_chunk_index],
|
||||
false,
|
||||
compression_stream
|
||||
);
|
||||
if (!compress_string_result.ok()) {
|
||||
finiCompressionStream(compression_stream);
|
||||
return genError("chunkedCompressString failed: " + compress_string_result.getErr());
|
||||
}
|
||||
|
||||
compressed_data_ss << compress_string_result.unpack();
|
||||
}
|
||||
|
||||
Maybe<string> compress_string_result = compressString(
|
||||
compression_type,
|
||||
input_string_chunks[input_string_chunks.size() - 1],
|
||||
true,
|
||||
compression_stream
|
||||
);
|
||||
finiCompressionStream(compression_stream);
|
||||
|
||||
if (!compress_string_result.ok()) {
|
||||
return genError("chunkedCompressString failed: " + compress_string_result.getErr());
|
||||
}
|
||||
|
||||
compressed_data_ss << compress_string_result.unpack();
|
||||
return compressed_data_ss.str();
|
||||
}
|
||||
|
||||
Maybe<string>
|
||||
decompressString(
|
||||
const string &compressed_string,
|
||||
int *is_last_chunk = nullptr,
|
||||
CompressionStream *compression_stream = nullptr
|
||||
)
|
||||
{
|
||||
auto disposable_compression_stream = initCompressionStream();
|
||||
CompressionStream *compression_stream_to_use =
|
||||
compression_stream == nullptr ?
|
||||
disposable_compression_stream :
|
||||
compression_stream;
|
||||
|
||||
unsigned char *compressed_data = reinterpret_cast<unsigned char *>(
|
||||
const_cast<char *>(
|
||||
compressed_string.c_str()
|
||||
)
|
||||
);
|
||||
|
||||
int disposable_is_last_chunk_indicator = 0;
|
||||
int *is_last_chunk_indicator_to_use =
|
||||
is_last_chunk == nullptr ?
|
||||
&disposable_is_last_chunk_indicator :
|
||||
is_last_chunk;
|
||||
|
||||
DecompressionResult decompress_data_result = decompressData(
|
||||
compression_stream_to_use,
|
||||
compressed_string.size(),
|
||||
compressed_data
|
||||
);
|
||||
*is_last_chunk_indicator_to_use = decompress_data_result.is_last_chunk;
|
||||
finiCompressionStream(disposable_compression_stream);
|
||||
if (decompress_data_result.ok == 0) return genError("decompressString failed");
|
||||
|
||||
auto decompressed_string = string(
|
||||
reinterpret_cast<char *>(decompress_data_result.output),
|
||||
decompress_data_result.num_output_bytes
|
||||
);
|
||||
free(decompress_data_result.output);
|
||||
|
||||
return decompressed_string;
|
||||
}
|
||||
|
||||
Maybe<string>
|
||||
chunkedDecompressString(const string &compressed_string)
|
||||
{
|
||||
auto compression_stream = initCompressionStream();
|
||||
int is_last_chunk = 0;
|
||||
stringstream decompressed_data_ss;
|
||||
|
||||
vector<string> input_string_chunks = splitIntoChunks(
|
||||
compressed_string,
|
||||
compressed_string.size() / chunk_size + 1
|
||||
);
|
||||
|
||||
for (uint32_t curr_chunk_index = 0; curr_chunk_index < input_string_chunks.size(); curr_chunk_index++) {
|
||||
Maybe<string> decompress_string_result = decompressString(
|
||||
input_string_chunks[curr_chunk_index],
|
||||
&is_last_chunk,
|
||||
compression_stream
|
||||
);
|
||||
if (!decompress_string_result.ok()) {
|
||||
finiCompressionStream(compression_stream);
|
||||
return genError("chunkedDecompress failed: " + decompress_string_result.getErr());
|
||||
}
|
||||
|
||||
decompressed_data_ss << decompress_string_result.unpack();
|
||||
}
|
||||
|
||||
finiCompressionStream(compression_stream);
|
||||
return decompressed_data_ss.str();
|
||||
}
|
||||
|
||||
bool
|
||||
performCompressionNullPointerTest()
|
||||
{
|
||||
static const vector<int> possible_last_chunk_values = { 0, 1 };
|
||||
string compress_test_string = readTestFileContents(chunk_sized_string_file_name);
|
||||
string decompress_test_string = readTestFileContents(chunk_sized_gzip_file_name);
|
||||
|
||||
for (CompressionType single_compression_type : compression_types) {
|
||||
for (int single_possible_last_chunk_value : possible_last_chunk_values) {
|
||||
CompressionResult result = compressData(
|
||||
nullptr,
|
||||
single_compression_type,
|
||||
compress_test_string.size(),
|
||||
reinterpret_cast<unsigned char *>(const_cast<char *>(compress_test_string.c_str())),
|
||||
single_possible_last_chunk_value
|
||||
);
|
||||
|
||||
if (result.ok) return false;
|
||||
}
|
||||
}
|
||||
|
||||
DecompressionResult result = decompressData(
|
||||
nullptr,
|
||||
decompress_test_string.size(),
|
||||
reinterpret_cast<unsigned char *>(const_cast<char *>(decompress_test_string.c_str()))
|
||||
);
|
||||
|
||||
if (result.ok) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
vector<string>
|
||||
splitIntoChunks(const string &data, const uint32_t num_data_chunks)
|
||||
{
|
||||
vector<string> data_chunks;
|
||||
|
||||
uint32_t num_data_chunks_to_use = min(static_cast<uint32_t>(data.size()), num_data_chunks);
|
||||
if (num_data_chunks_to_use == 1) return { data };
|
||||
|
||||
uint32_t chunk_size = data.size() / num_data_chunks;
|
||||
for (uint32_t curr_chunk_index = 0; curr_chunk_index < num_data_chunks_to_use - 1; curr_chunk_index++) {
|
||||
data_chunks.push_back(string(data.c_str() + curr_chunk_index * chunk_size, chunk_size));
|
||||
}
|
||||
|
||||
uint32_t accumulated_chunks_size = (num_data_chunks_to_use - 1) * chunk_size;
|
||||
data_chunks.push_back(string(data.c_str() + accumulated_chunks_size, data.size() - accumulated_chunks_size));
|
||||
|
||||
return data_chunks;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
calcCompressedDataSizeBound(const uint32_t compressed_data_size)
|
||||
{
|
||||
return 2 * compressed_data_size;
|
||||
}
|
||||
|
||||
ostringstream capture_debug;
|
||||
|
||||
const string simple_test_string = "Test data for compression utilities library";
|
||||
const string chunk_sized_string_file_name = "chunk_sized_string";
|
||||
const string chunk_sized_gzip_file_name = "chunk_sized_compressed_file.gz";
|
||||
const string chunk_sized_zlib_file_name = "chunk_sized_compressed_file.zz";
|
||||
const string multi_chunk_sized_string_file_name = "multiple_chunk_sized_string";
|
||||
const string multi_chunk_sized_gzip_file_name = "multiple_chunk_sized_compressed_file.gz";
|
||||
const string multi_chunk_sized_zlib_file_name = "multiple_chunk_sized_compressed_file.zz";
|
||||
const vector<string> chunk_sized_compressed_files = { chunk_sized_gzip_file_name, chunk_sized_zlib_file_name };
|
||||
const vector<string> multi_chunk_sized_compressed_files = {
|
||||
multi_chunk_sized_gzip_file_name,
|
||||
multi_chunk_sized_zlib_file_name
|
||||
};
|
||||
|
||||
const vector<CompressionType> compression_types = { CompressionType::GZIP, CompressionType::ZLIB };
|
||||
const uint32_t chunk_size = 32768;
|
||||
|
||||
private:
|
||||
const string test_files_dir_name = "test_files";
|
||||
};
|
||||
|
||||
TEST_F(CompressionUtilsTest, CompressAndDecompressSimpleString)
|
||||
{
|
||||
for (auto single_compression_type : compression_types) {
|
||||
Maybe<string> compressed_string_maybe = compressString(
|
||||
single_compression_type,
|
||||
simple_test_string
|
||||
);
|
||||
EXPECT_TRUE(compressed_string_maybe.ok());
|
||||
|
||||
Maybe<string> decompressed_string_maybe = decompressString(compressed_string_maybe.unpack());
|
||||
EXPECT_TRUE(decompressed_string_maybe.ok());
|
||||
|
||||
EXPECT_EQ(simple_test_string, decompressed_string_maybe.unpack());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompressionUtilsTest, CompressAndDecompressChunkSizedString)
|
||||
{
|
||||
string test_string = readTestFileContents(chunk_sized_string_file_name);
|
||||
|
||||
for (auto single_compression_type : compression_types) {
|
||||
Maybe<string> compressed_string_maybe = compressString(
|
||||
single_compression_type,
|
||||
test_string
|
||||
);
|
||||
EXPECT_TRUE(compressed_string_maybe.ok());
|
||||
|
||||
Maybe<string> decompressed_string_maybe = decompressString(compressed_string_maybe.unpack());
|
||||
EXPECT_TRUE(decompressed_string_maybe.ok());
|
||||
|
||||
EXPECT_EQ(test_string, decompressed_string_maybe.unpack());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompressionUtilsTest, CompressMultipleChunkSizedStringAndDecompress)
|
||||
{
|
||||
string test_string = readTestFileContents(multi_chunk_sized_string_file_name);
|
||||
for (auto single_compression_type : compression_types) {
|
||||
Maybe<string> chunked_compress_result = chunkedCompressString(single_compression_type, test_string);
|
||||
EXPECT_TRUE(chunked_compress_result.ok());
|
||||
|
||||
Maybe<string> chunked_decompress_result = chunkedDecompressString(chunked_compress_result.unpack());
|
||||
EXPECT_TRUE(chunked_decompress_result.ok());
|
||||
|
||||
EXPECT_EQ(chunked_decompress_result.unpack(), test_string);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompressionUtilsTest, DecompressChunkSizedCompressedFile)
|
||||
{
|
||||
for (const auto &single_compressed_file_name : chunk_sized_compressed_files) {
|
||||
string test_string = readTestFileContents(single_compressed_file_name);
|
||||
|
||||
string expected_decompressed_string = readTestFileContents(chunk_sized_string_file_name);
|
||||
Maybe<string> decompressed_string_result = decompressString(test_string);
|
||||
EXPECT_TRUE(decompressed_string_result.ok());
|
||||
EXPECT_EQ(decompressed_string_result.unpack(), expected_decompressed_string);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompressionUtilsTest, DecompressMultipleChunkSizedCompressedFile)
|
||||
{
|
||||
for (const auto &single_compressed_file_name : multi_chunk_sized_compressed_files) {
|
||||
string test_string = readTestFileContents(single_compressed_file_name);
|
||||
|
||||
Maybe<string> chunked_decompress_result = chunkedDecompressString(test_string);
|
||||
EXPECT_TRUE(chunked_decompress_result.ok());
|
||||
|
||||
string expected_decompressed_string = readTestFileContents(multi_chunk_sized_string_file_name);
|
||||
EXPECT_EQ(chunked_decompress_result.unpack(), expected_decompressed_string);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompressionUtilsTest, TestEmptyBuffer)
|
||||
{
|
||||
for (CompressionType compression_type : compression_types) {
|
||||
auto compression_stream = initCompressionStream();
|
||||
stringstream compressed_stream;
|
||||
|
||||
Maybe<string> compressed_string = compressString(
|
||||
compression_type,
|
||||
simple_test_string,
|
||||
false,
|
||||
compression_stream
|
||||
);
|
||||
EXPECT_TRUE(compressed_string.ok());
|
||||
compressed_stream << compressed_string.unpack();
|
||||
|
||||
compressed_string = compressString(
|
||||
compression_type,
|
||||
"",
|
||||
true,
|
||||
compression_stream
|
||||
);
|
||||
finiCompressionStream(compression_stream);
|
||||
EXPECT_TRUE(compressed_string.ok());
|
||||
compressed_stream << compressed_string.unpack();
|
||||
|
||||
Buffer compressed_buffer(compressed_stream.str());
|
||||
|
||||
int is_last_chunk;
|
||||
auto decompression_stream = initCompressionStream();
|
||||
|
||||
Maybe<string> decompressed_string = decompressString(
|
||||
compressed_stream.str(),
|
||||
&is_last_chunk,
|
||||
decompression_stream
|
||||
);
|
||||
|
||||
EXPECT_TRUE(decompressed_string.ok());
|
||||
EXPECT_EQ(decompressed_string.unpack(), simple_test_string);
|
||||
finiCompressionStream(decompression_stream);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompressionUtilsTest, CompressionStreamNullPointer)
|
||||
{
|
||||
EXPECT_TRUE(performCompressionNullPointerTest());
|
||||
EXPECT_THAT(
|
||||
capture_debug.str(),
|
||||
HasSubstr("Compression failed Compression stream is NULL")
|
||||
);
|
||||
|
||||
resetOutputStream();
|
||||
EXPECT_TRUE(performCompressionNullPointerTest());
|
||||
EXPECT_EQ(capture_debug.str(), string());
|
||||
}
|
||||
|
||||
TEST_F(CompressionUtilsTest, InputDataBufferNullPointer)
|
||||
{
|
||||
static const vector<int> possible_last_chunk_values = { 0, 1 };
|
||||
string compress_test_string = readTestFileContents(chunk_sized_string_file_name);
|
||||
auto compression_stream = initCompressionStream();
|
||||
|
||||
for (CompressionType single_compression_type : compression_types) {
|
||||
for (int single_possible_last_chunk_value : possible_last_chunk_values) {
|
||||
CompressionResult result = compressData(
|
||||
compression_stream,
|
||||
single_compression_type,
|
||||
compress_test_string.size(),
|
||||
nullptr,
|
||||
single_possible_last_chunk_value
|
||||
);
|
||||
|
||||
EXPECT_EQ(result.ok, 0);
|
||||
}
|
||||
}
|
||||
|
||||
string decompress_test_string = readTestFileContents(chunk_sized_gzip_file_name);
|
||||
finiCompressionStream(compression_stream);
|
||||
compression_stream = initCompressionStream();
|
||||
|
||||
DecompressionResult result = decompressData(
|
||||
compression_stream,
|
||||
decompress_test_string.size(),
|
||||
nullptr
|
||||
);
|
||||
|
||||
EXPECT_EQ(result.ok, 0);
|
||||
EXPECT_THAT(
|
||||
capture_debug.str(),
|
||||
HasSubstr("Compression failed Data pointer is NULL")
|
||||
);
|
||||
finiCompressionStream(compression_stream);
|
||||
}
|
||||
|
||||
TEST_F(CompressionUtilsTest, DecompressPlainText)
|
||||
{
|
||||
Maybe<string> decompress_string_result = decompressString(simple_test_string);
|
||||
|
||||
EXPECT_FALSE(decompress_string_result.ok());
|
||||
EXPECT_THAT(
|
||||
capture_debug.str(),
|
||||
HasSubstr("error in 'inflate': Invalid or corrupted stream data")
|
||||
);
|
||||
}
|
Binary file not shown.
BIN
core/compression/compression_utils_ut/test_files/chunk_sized_compressed_file.zz
Executable file
BIN
core/compression/compression_utils_ut/test_files/chunk_sized_compressed_file.zz
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
core/compression/compression_utils_ut/test_files/multiple_chunk_sized_string
Executable file
BIN
core/compression/compression_utils_ut/test_files/multiple_chunk_sized_string
Executable file
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user