blob: 2d3be1745035fc6d83dc6b039624e2aa6d8dd086 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/storage/volume_image/utils/lz4_compressor.h"
#include "src/storage/volume_image/options.h"
#include "src/storage/volume_image/utils/lz4_result.h"
namespace storage::volume_image {
namespace {
Lz4Compressor::Preferences ConvertOptionsToPreferences(
const CompressionOptions& compression_options) {
Lz4Compressor::Preferences preferences = {};
preferences.frameInfo.blockMode = LZ4F_blockIndependent;
const auto& options = compression_options.options;
auto const_it = options.find("block_size");
int block_size_kb = 0;
if (const_it != options.end()) {
block_size_kb = static_cast<int>(const_it->second);
}
LZ4F_blockSizeID_t block_size_id = LZ4F_max64KB;
if (block_size_kb <= 64) {
block_size_id = LZ4F_max64KB;
} else if (block_size_kb <= 256) {
block_size_id = LZ4F_max256KB;
} else if (block_size_kb <= 1024) {
block_size_id = LZ4F_max1MB;
} else {
block_size_id = LZ4F_max4MB;
}
preferences.frameInfo.blockSizeID = block_size_id;
const_it = options.find("compression_level");
int compression_level = 0;
if (const_it != options.end()) {
compression_level = static_cast<int>(const_it->second);
}
preferences.compressionLevel = compression_level;
return preferences;
}
} // namespace
Lz4Compressor::Lz4Compressor()
: Lz4Compressor(
ConvertOptionsToPreferences(CompressionOptions{.schema = CompressionSchema::kLz4})) {}
Lz4Compressor::~Lz4Compressor() {
if (context_ != nullptr) {
LZ4F_freeCompressionContext(context_);
context_ = nullptr;
}
}
fpromise::result<Lz4Compressor, std::string> Lz4Compressor::Create(
const CompressionOptions& options) {
if (options.schema != CompressionSchema::kLz4) {
return fpromise::error("Lz4Compressor requires" + EnumAsString(CompressionSchema::kLz4) +
". Provided: " + EnumAsString(options.schema) + ".");
}
Preferences preferences = ConvertOptionsToPreferences(options);
return fpromise::ok(Lz4Compressor(preferences));
}
fpromise::result<void, std::string> Lz4Compressor::Prepare(Handler handler) {
if (state_ != State::kInitalized && state_ != State::kFinalized) {
return fpromise::error(
"Lz4Compressor::Prepare must be in |kInitialized| or |kFinalized| state.");
}
if (handler == nullptr) {
return fpromise::error("Lz4Compressor::Prepare requires a valid |handler|.");
}
Lz4Result result = LZ4F_createCompressionContext(&context_, LZ4F_VERSION);
if (result.is_error()) {
std::string error = "Failed to create LZ4 Compression Context. LZ4 Error: ";
error.append(result.error()).append(".");
return fpromise::error(error);
}
// Adjust buffer so it fits the header.
if (compression_buffer_.size() < LZ4F_HEADER_SIZE_MAX) {
compression_buffer_.resize(LZ4F_HEADER_SIZE_MAX, 0);
}
handler_ = std::move(handler);
result = LZ4F_compressBegin(context_, compression_buffer_.data(), compression_buffer_.size(),
&preferences_);
if (result.is_error()) {
std::string error = "Failed to emit LZ4 Frame header. LZ4 Error: ";
error.append(result.error()).append(".");
return fpromise::error(error);
}
state_ = State::kPrepared;
return handler_(cpp20::span(compression_buffer_.data(), result.byte_count()));
}
fpromise::result<void, std::string> Lz4Compressor::Compress(
cpp20::span<const uint8_t> uncompressed_data) {
if (state_ != State::kPrepared && state_ != State::kCompressed) {
return fpromise::error(
"Lz4Compressor::Compress must be in |kPrepared| or |kCompressed| state.");
}
size_t max_compressed_size = LZ4F_compressBound(uncompressed_data.size(), &preferences_);
if (compression_buffer_.size() < max_compressed_size) {
compression_buffer_.resize(max_compressed_size, 0);
}
Lz4Result result =
LZ4F_compressUpdate(context_, compression_buffer_.data(), compression_buffer_.size(),
uncompressed_data.data(), uncompressed_data.size(), nullptr);
if (result.is_error()) {
std::string error = "Failed to compress data with LZ4 compressor. LZ4 Error: ";
error.append(result.error()).append(".");
return fpromise::error(error);
}
state_ = State::kCompressed;
return handler_(cpp20::span(compression_buffer_.data(), result.byte_count()));
}
fpromise::result<void, std::string> Lz4Compressor::Finalize() {
if (state_ != State::kCompressed) {
return fpromise::error("Lz4Compressor::Finalize must be in |kCompressed| state.");
}
size_t max_compressed_size = LZ4F_compressBound(0, &preferences_);
if (compression_buffer_.size() < max_compressed_size) {
compression_buffer_.resize(max_compressed_size, 0);
}
Lz4Result result =
LZ4F_compressEnd(context_, compression_buffer_.data(), compression_buffer_.size(), nullptr);
if (result.is_error()) {
std::string error = "Failed to finalize compression with LZ4 Compressor. LZ4 Error: ";
error.append(result.error()).append(".");
return fpromise::error(error);
}
auto handler_result = handler_(cpp20::span(compression_buffer_.data(), result.byte_count()));
// Even though we can reuse compression context after compressionEnd, its preferred not to
// delegate this to the destructor, since it may error, and we wont be able to surface it.
result = LZ4F_freeCompressionContext(context_);
context_ = nullptr;
if (result.is_error()) {
std::string error = "Failed to free compression contrext in LZ4 Compressor. LZ4 Error: ";
error.append(result.error()).append(".");
return fpromise::error(error);
}
state_ = State::kFinalized;
return handler_result;
}
} // namespace storage::volume_image