192 lines
4.7 KiB
C++
192 lines
4.7 KiB
C++
//
|
|
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2017
|
|
//
|
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
//
|
|
#include "td/utils/Gzip.h"
|
|
|
|
char disable_linker_warning_about_empty_file_gzip_cpp TD_UNUSED;
|
|
|
|
#if TD_HAVE_ZLIB
|
|
#include "td/utils/logging.h"
|
|
|
|
#include <cstring>
|
|
#include <limits>
|
|
|
|
#include <zlib.h>
|
|
|
|
namespace td {
|
|
|
|
class Gzip::Impl {
|
|
public:
|
|
z_stream stream_;
|
|
|
|
// z_stream is not copyable nor movable
|
|
Impl() = default;
|
|
Impl(const Impl &other) = delete;
|
|
Impl &operator=(const Impl &other) = delete;
|
|
Impl(Impl &&other) = delete;
|
|
Impl &operator=(Impl &&other) = delete;
|
|
~Impl() = default;
|
|
};
|
|
|
|
Status Gzip::init_encode() {
|
|
CHECK(mode_ == Empty);
|
|
init_common();
|
|
mode_ = Encode;
|
|
int ret = deflateInit2(&impl_->stream_, 6, Z_DEFLATED, 15, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
|
|
if (ret != Z_OK) {
|
|
return Status::Error("zlib deflate init failed");
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
Status Gzip::init_decode() {
|
|
CHECK(mode_ == Empty);
|
|
init_common();
|
|
mode_ = Decode;
|
|
int ret = inflateInit2(&impl_->stream_, MAX_WBITS + 32);
|
|
if (ret != Z_OK) {
|
|
return Status::Error("zlib inflate init failed");
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
void Gzip::set_input(Slice input) {
|
|
CHECK(input_size_ == 0);
|
|
CHECK(!close_input_flag_);
|
|
CHECK(input.size() <= std::numeric_limits<uInt>::max());
|
|
CHECK(impl_->stream_.avail_in == 0);
|
|
input_size_ = input.size();
|
|
impl_->stream_.avail_in = static_cast<uInt>(input.size());
|
|
impl_->stream_.next_in = reinterpret_cast<Bytef *>(const_cast<char *>(input.data()));
|
|
}
|
|
|
|
void Gzip::set_output(MutableSlice output) {
|
|
CHECK(output_size_ == 0);
|
|
CHECK(output.size() <= std::numeric_limits<uInt>::max());
|
|
CHECK(impl_->stream_.avail_out == 0);
|
|
output_size_ = output.size();
|
|
impl_->stream_.avail_out = static_cast<uInt>(output.size());
|
|
impl_->stream_.next_out = reinterpret_cast<Bytef *>(output.data());
|
|
}
|
|
|
|
Result<Gzip::State> Gzip::run() {
|
|
while (true) {
|
|
int ret;
|
|
if (mode_ == Decode) {
|
|
ret = inflate(&impl_->stream_, Z_NO_FLUSH);
|
|
} else {
|
|
ret = deflate(&impl_->stream_, close_input_flag_ ? Z_FINISH : Z_NO_FLUSH);
|
|
}
|
|
|
|
if (ret == Z_OK) {
|
|
return Running;
|
|
}
|
|
if (ret == Z_STREAM_END) {
|
|
// TODO(now): fail if input is not empty;
|
|
clear();
|
|
return Done;
|
|
}
|
|
clear();
|
|
return Status::Error(PSLICE() << "zlib error " << ret);
|
|
}
|
|
}
|
|
|
|
size_t Gzip::left_input() const {
|
|
return impl_->stream_.avail_in;
|
|
}
|
|
size_t Gzip::left_output() const {
|
|
return impl_->stream_.avail_out;
|
|
}
|
|
|
|
void Gzip::init_common() {
|
|
std::memset(&impl_->stream_, 0, sizeof(impl_->stream_));
|
|
impl_->stream_.zalloc = Z_NULL;
|
|
impl_->stream_.zfree = Z_NULL;
|
|
impl_->stream_.opaque = Z_NULL;
|
|
impl_->stream_.avail_in = 0;
|
|
impl_->stream_.next_in = nullptr;
|
|
impl_->stream_.avail_out = 0;
|
|
impl_->stream_.next_out = nullptr;
|
|
|
|
input_size_ = 0;
|
|
output_size_ = 0;
|
|
|
|
close_input_flag_ = false;
|
|
}
|
|
|
|
void Gzip::clear() {
|
|
if (mode_ == Decode) {
|
|
inflateEnd(&impl_->stream_);
|
|
} else if (mode_ == Encode) {
|
|
deflateEnd(&impl_->stream_);
|
|
}
|
|
mode_ = Empty;
|
|
}
|
|
|
|
Gzip::Gzip() : impl_(make_unique<Impl>()) {
|
|
}
|
|
|
|
Gzip::Gzip(Gzip &&other) = default;
|
|
|
|
Gzip &Gzip::operator=(Gzip &&other) = default;
|
|
|
|
Gzip::~Gzip() {
|
|
clear();
|
|
}
|
|
|
|
BufferSlice gzdecode(Slice s) {
|
|
Gzip gzip;
|
|
gzip.init_decode().ensure();
|
|
auto message = ChainBufferWriter::create_empty();
|
|
gzip.set_input(s);
|
|
gzip.close_input();
|
|
double k = 2;
|
|
gzip.set_output(message.prepare_append(static_cast<size_t>(static_cast<double>(s.size()) * k)));
|
|
while (true) {
|
|
auto r_state = gzip.run();
|
|
if (r_state.is_error()) {
|
|
return BufferSlice();
|
|
}
|
|
auto state = r_state.ok();
|
|
if (state == Gzip::Done) {
|
|
message.confirm_append(gzip.flush_output());
|
|
break;
|
|
}
|
|
if (gzip.need_input()) {
|
|
return BufferSlice();
|
|
}
|
|
if (gzip.need_output()) {
|
|
message.confirm_append(gzip.flush_output());
|
|
k *= 1.5;
|
|
gzip.set_output(message.prepare_append(static_cast<size_t>(static_cast<double>(gzip.left_input()) * k)));
|
|
}
|
|
}
|
|
return message.extract_reader().move_as_buffer_slice();
|
|
}
|
|
|
|
BufferSlice gzencode(Slice s, double k) {
|
|
Gzip gzip;
|
|
gzip.init_encode().ensure();
|
|
gzip.set_input(s);
|
|
gzip.close_input();
|
|
size_t max_size = static_cast<size_t>(static_cast<double>(s.size()) * k);
|
|
BufferWriter message{max_size};
|
|
gzip.set_output(message.prepare_append());
|
|
auto r_state = gzip.run();
|
|
if (r_state.is_error()) {
|
|
return BufferSlice();
|
|
}
|
|
auto state = r_state.ok();
|
|
if (state != Gzip::Done) {
|
|
return BufferSlice();
|
|
}
|
|
message.confirm_append(gzip.flush_output());
|
|
return message.as_buffer_slice();
|
|
}
|
|
|
|
} // namespace td
|
|
#endif
|