blob: d87cb77d631ce91d381c999f4b55d8720471c8aa [file] [log] [blame]
/** @file
A brief file description
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you 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 <cstdint>
#include "gzip.h"
#include <zlib.h>
#include "Utils.h"
using namespace EsiLib;
using std::string;
template <typename T>
inline void
append(string &out, T data)
{
for (unsigned int i = 0; i < sizeof(data); ++i) {
out += static_cast<char>(data & 0xff);
data = data >> 8;
}
}
template <typename T>
inline void
extract(const char *in, T &data)
{
data = 0;
for (int i = (sizeof(data) - 1); i >= 0; --i) {
data = data << 8;
data = data | static_cast<unsigned char>(*(in + i));
}
}
inline int
runDeflateLoop(z_stream &zstrm, int flush, std::string &cdata)
{
char buf[BUF_SIZE];
int deflate_result = Z_OK;
do {
zstrm.next_out = reinterpret_cast<Bytef *>(buf);
zstrm.avail_out = BUF_SIZE;
deflate_result = deflate(&zstrm, flush);
if ((deflate_result == Z_OK) || (deflate_result == Z_STREAM_END)) {
cdata.append(buf, BUF_SIZE - zstrm.avail_out);
if ((deflate_result == Z_STREAM_END) || zstrm.avail_out) {
break;
}
} else {
break;
}
} while (true);
return deflate_result;
}
bool
EsiLib::gzip(const ByteBlockList &blocks, std::string &cdata)
{
cdata.assign(GZIP_HEADER_SIZE, 0); // reserving space for the header
z_stream zstrm;
zstrm.zalloc = Z_NULL;
zstrm.zfree = Z_NULL;
zstrm.opaque = Z_NULL;
if (deflateInit2(&zstrm, COMPRESSION_LEVEL, Z_DEFLATED, -MAX_WBITS, ZLIB_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) {
Utils::ERROR_LOG("[%s] deflateInit2 failed!", __FUNCTION__);
return false;
}
int total_data_len = 0;
uLong crc = crc32(0, Z_NULL, 0);
int deflate_result = Z_OK;
int in_data_size = 0;
for (auto block : blocks) {
if (block.data && (block.data_len > 0)) {
zstrm.next_in = reinterpret_cast<Bytef *>(const_cast<char *>(block.data));
zstrm.avail_in = block.data_len;
in_data_size += block.data_len;
deflate_result = runDeflateLoop(zstrm, 0, cdata);
if (deflate_result != Z_OK) {
break; // break out of the blocks iteration
}
crc = crc32(crc, reinterpret_cast<const Bytef *>(block.data), block.data_len);
total_data_len += block.data_len;
}
}
if (!in_data_size) {
zstrm.avail_in = 0; // required for the "finish" loop as no data has been given so far
}
if (deflate_result == Z_OK) {
deflate_result = runDeflateLoop(zstrm, Z_FINISH, cdata);
}
deflateEnd(&zstrm);
if (deflate_result != Z_STREAM_END) {
Utils::ERROR_LOG("[%s] Failure while deflating; error code %d", __FUNCTION__, deflate_result);
return false;
}
cdata[0] = MAGIC_BYTE_1;
cdata[1] = MAGIC_BYTE_2;
cdata[2] = Z_DEFLATED;
cdata[9] = OS_TYPE;
append(cdata, static_cast<uint32_t>(crc));
append(cdata, static_cast<int32_t>(total_data_len));
return true;
}
bool
EsiLib::gunzip(const char *data, int data_len, BufferList &buf_list)
{
if (!data || (data_len <= (GZIP_HEADER_SIZE + GZIP_TRAILER_SIZE))) {
Utils::ERROR_LOG("[%s] Invalid arguments: 0x%p, %d", __FUNCTION__, data, data_len);
return false;
}
if ((data[0] != MAGIC_BYTE_1) || (data[1] != MAGIC_BYTE_2) || (data[2] != Z_DEFLATED)) {
Utils::ERROR_LOG("[%s] Header check failed!", __FUNCTION__);
return false;
}
data += GZIP_HEADER_SIZE;
data_len -= (GZIP_HEADER_SIZE + GZIP_TRAILER_SIZE);
buf_list.clear();
z_stream zstrm;
zstrm.zalloc = Z_NULL;
zstrm.zfree = Z_NULL;
zstrm.opaque = Z_NULL;
zstrm.next_in = nullptr;
zstrm.avail_in = 0;
if (inflateInit2(&zstrm, -MAX_WBITS) != Z_OK) {
Utils::ERROR_LOG("[%s] inflateInit2 failed!", __FUNCTION__);
return false;
}
zstrm.next_in = reinterpret_cast<Bytef *>(const_cast<char *>(data));
zstrm.avail_in = data_len;
char raw_buf[BUF_SIZE];
int inflate_result;
int32_t unzipped_data_size = 0;
int32_t curr_buf_size;
uLong crc = crc32(0, Z_NULL, 0);
do {
zstrm.next_out = reinterpret_cast<Bytef *>(raw_buf);
zstrm.avail_out = BUF_SIZE;
inflate_result = inflate(&zstrm, Z_SYNC_FLUSH);
curr_buf_size = -1;
if ((inflate_result == Z_OK) || (inflate_result == Z_BUF_ERROR)) {
curr_buf_size = BUF_SIZE;
} else if (inflate_result == Z_STREAM_END) {
curr_buf_size = BUF_SIZE - zstrm.avail_out;
}
if (curr_buf_size > BUF_SIZE) {
Utils::ERROR_LOG("[%s] buf too large", __FUNCTION__);
break;
}
if (curr_buf_size < 1) {
Utils::ERROR_LOG("[%s] buf below zero", __FUNCTION__);
break;
}
unzipped_data_size += curr_buf_size;
crc = crc32(crc, reinterpret_cast<const Bytef *>(raw_buf), curr_buf_size);
// push empty object onto list and add data to in-list object to
// avoid data copy for temporary
buf_list.push_back(string());
string &curr_buf = buf_list.back();
curr_buf.assign(raw_buf, curr_buf_size);
if (inflate_result == Z_STREAM_END) {
break;
}
} while (zstrm.avail_in > 0);
inflateEnd(&zstrm);
if (inflate_result != Z_STREAM_END) {
Utils::ERROR_LOG("[%s] Failure while inflating; error code %d", __FUNCTION__, inflate_result);
return false;
}
int32_t orig_size;
uint32_t orig_crc;
extract(data + data_len, orig_crc);
extract(data + data_len + 4, orig_size);
if ((crc != orig_crc) || (unzipped_data_size != orig_size)) {
Utils::ERROR_LOG("[%s] CRC/size error. Expecting (CRC, size) (0x%x, 0x%x); computed (0x%x, 0x%x)", __FUNCTION__, orig_crc,
orig_size, crc, unzipped_data_size);
return false;
}
return true;
}