| /** | |
| 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. | |
| */ | |
| /** | |
| * @file GzipDeflateTransformation.cc | |
| */ | |
| #include <cstring> | |
| #include <vector> | |
| #include <zlib.h> | |
| #include <cinttypes> | |
| #include <string_view> | |
| #include "tscpp/api/TransformationPlugin.h" | |
| #include "tscpp/api/GzipDeflateTransformation.h" | |
| #include "logging_internal.h" | |
| using namespace atscppapi::transformations; | |
| using std::vector; | |
| namespace | |
| { | |
| const int GZIP_MEM_LEVEL = 8; | |
| const int WINDOW_BITS = 31; // Always use 31 for gzip. | |
| const unsigned int ONE_KB = 1024; | |
| } // namespace | |
| /** | |
| * @private | |
| */ | |
| struct atscppapi::transformations::GzipDeflateTransformationState : noncopyable { | |
| z_stream z_stream_; | |
| bool z_stream_initialized_; | |
| TransformationPlugin::Type transformation_type_; | |
| int64_t bytes_produced_; | |
| GzipDeflateTransformationState(TransformationPlugin::Type type) | |
| : z_stream_initialized_(false), transformation_type_(type), bytes_produced_(0) | |
| { | |
| memset(&z_stream_, 0, sizeof(z_stream_)); | |
| int err = deflateInit2(&z_stream_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, WINDOW_BITS, GZIP_MEM_LEVEL, Z_DEFAULT_STRATEGY); | |
| if (Z_OK != err) { | |
| LOG_ERROR("deflateInit2 failed with error code '%d'.", err); | |
| } else { | |
| z_stream_initialized_ = true; | |
| } | |
| }; | |
| ~GzipDeflateTransformationState() | |
| { | |
| if (z_stream_initialized_) { | |
| deflateEnd(&z_stream_); | |
| } | |
| }; | |
| }; | |
| GzipDeflateTransformation::GzipDeflateTransformation(Transaction &transaction, TransformationPlugin::Type type) | |
| : TransformationPlugin(transaction, type) | |
| { | |
| state_ = std::make_unique<GzipDeflateTransformationState>(type); | |
| } | |
| GzipDeflateTransformation::~GzipDeflateTransformation() {} | |
| void | |
| GzipDeflateTransformation::consume(std::string_view data) | |
| { | |
| if (data.size() == 0) { | |
| return; | |
| } | |
| if (!state_->z_stream_initialized_) { | |
| LOG_ERROR("Unable to deflate output because the z_stream was not initialized."); | |
| return; | |
| } | |
| int iteration = 0; | |
| state_->z_stream_.data_type = Z_ASCII; | |
| state_->z_stream_.next_in = reinterpret_cast<unsigned char *>(const_cast<char *>(data.data())); | |
| state_->z_stream_.avail_in = data.length(); | |
| // For small payloads the size can actually be greater than the original input | |
| // so we'll use twice the original size to avoid needless repeated calls to deflate. | |
| unsigned long buffer_size = (data.length() < ONE_KB) ? 2 * ONE_KB : data.length(); | |
| vector<unsigned char> buffer(buffer_size); | |
| do { | |
| LOG_DEBUG("Iteration %d: Deflate will compress %ld bytes", ++iteration, data.size()); | |
| state_->z_stream_.avail_out = buffer_size; | |
| // next_out needs to be set to nullptr before we return since it points to a local buffer | |
| // coverity[WRAPPER_ESCAPE: FALSE] | |
| state_->z_stream_.next_out = &buffer[0]; | |
| int err = deflate(&state_->z_stream_, Z_SYNC_FLUSH); | |
| if (Z_OK != err) { | |
| LOG_ERROR("Iteration %d: Deflate failed to compress %ld bytes with error code '%d'", iteration, data.size(), err); | |
| state_->z_stream_.next_out = nullptr; | |
| return; | |
| } | |
| int bytes_to_write = buffer_size - state_->z_stream_.avail_out; | |
| state_->bytes_produced_ += bytes_to_write; | |
| LOG_DEBUG("Iteration %d: Deflate compressed %ld bytes to %d bytes, producing output...", iteration, data.size(), | |
| bytes_to_write); | |
| produce(std::string_view(reinterpret_cast<char *>(&buffer[0]), static_cast<size_t>(bytes_to_write))); | |
| } while (state_->z_stream_.avail_out == 0); | |
| state_->z_stream_.next_out = nullptr; | |
| if (state_->z_stream_.avail_in != 0) { | |
| LOG_ERROR("Inflate finished with data still remaining in the buffer of size '%u'", state_->z_stream_.avail_in); | |
| } | |
| } | |
| void | |
| GzipDeflateTransformation::handleInputComplete() | |
| { | |
| // We will flush out anything that's remaining in the gzip buffer | |
| int status = Z_OK; | |
| int iteration = 0; | |
| const int buffer_size = 1024; // 1024 bytes is usually more than enough for the epilogue | |
| unsigned char buffer[buffer_size]; | |
| /* Deflate remaining data */ | |
| do { | |
| LOG_DEBUG("Iteration %d: Gzip deflate finalizing.", ++iteration); | |
| state_->z_stream_.data_type = Z_ASCII; | |
| state_->z_stream_.avail_out = buffer_size; | |
| state_->z_stream_.next_out = buffer; | |
| status = deflate(&state_->z_stream_, Z_FINISH); | |
| int bytes_to_write = buffer_size - state_->z_stream_.avail_out; | |
| state_->bytes_produced_ += bytes_to_write; | |
| if (status == Z_OK || status == Z_STREAM_END) { | |
| LOG_DEBUG("Iteration %d: Gzip deflate finalize had an extra %d bytes to process, status '%d'. Producing output...", iteration, | |
| bytes_to_write, status); | |
| produce(std::string_view(reinterpret_cast<char *>(buffer), static_cast<size_t>(bytes_to_write))); | |
| } else if (status != Z_STREAM_END) { | |
| LOG_ERROR("Iteration %d: Gzip deflate finalize produced an error '%d'", iteration, status); | |
| } | |
| } while (status == Z_OK); | |
| int64_t bytes_written = setOutputComplete(); | |
| if (state_->bytes_produced_ != bytes_written) { | |
| LOG_ERROR("Gzip bytes produced sanity check failed, deflated bytes = %" PRId64 " != written bytes = %" PRId64, | |
| state_->bytes_produced_, bytes_written); | |
| } | |
| } |