blob: bd09483fe97fce21ef097f25a493b422ba4a9d3b [file] [log] [blame]
// Copyright 2021 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_decompress_reader.h"
#include <lib/fit/result.h>
#include <string.h>
#include <cstdint>
#include <iostream>
#include <memory>
#include "src/storage/volume_image/utils/lz4_decompressor.h"
namespace storage::volume_image {
fit::result<void, std::string> Lz4DecompressReader::Initialize(uint64_t max_buffer_size) const {
context_.decompressed_data.resize(max_buffer_size, 0);
context_.decompressed_offset = offset_;
context_.decompressed_length = 0;
context_.compressed_data.resize(max_buffer_size, 0);
context_.compressed_offset = offset_;
context_.decompressor = std::make_unique<Lz4Decompressor>();
context_.hint = std::nullopt;
context_.decompressor->ProvideSizeHint(max_buffer_size);
return context_.decompressor->Prepare(
[this](auto decompressed_data) { return this->DecompressionHandler(decompressed_data); });
}
fit::result<void, std::string> Lz4DecompressReader::DecompressionHandler(
fbl::Span<const uint8_t> decompressed_data) const {
memcpy(context_.decompressed_data.data(), decompressed_data.data(), decompressed_data.size());
context_.decompressed_offset += context_.decompressed_length;
context_.decompressed_length = decompressed_data.size();
return fit::ok();
}
fit::result<void, std::string> Lz4DecompressReader::Seek(uint64_t offset) const {
// Offset in uncompressed area.
if (offset < offset_) {
return fit::ok();
}
if (offset < context_.decompressed_offset) {
if (auto result = Initialize(context_.decompressed_data.size()); result.is_error()) {
return result.take_error_result();
}
}
// Offset is in range.
auto offset_in_range = [&]() {
return context_.decompressed_length > 0 && offset >= context_.decompressed_offset &&
offset < context_.decompressed_offset + context_.decompressed_length;
};
auto end_of_compressed_data = [&]() {
return context_.compressed_offset == compressed_reader_->length();
};
auto end_of_frame = [&]() { return context_.hint.has_value() && context_.hint.value() == 0; };
// Decompress until offset is in range.
while (!offset_in_range() && !end_of_frame() && !end_of_compressed_data()) {
if (auto result = NextDecompressedChunk(); result.is_error()) {
return result;
}
}
if (!offset_in_range() && (end_of_frame() || end_of_compressed_data())) {
return fit::error("Reached end of compressed data before reaching offset.");
};
return fit::ok();
}
fit::result<void, std::string> Lz4DecompressReader::NextDecompressedChunk() const {
auto read_view = fbl::Span<uint8_t>(context_.compressed_data);
uint64_t remaining_compressed_bytes = compressed_reader_->length() - context_.compressed_offset;
if (read_view.size() > remaining_compressed_bytes) {
read_view = read_view.subspan(0, remaining_compressed_bytes);
}
if (context_.hint.has_value() && read_view.size() > context_.hint.value()) {
read_view = read_view.subspan(0, context_.hint.value());
}
if (auto result = compressed_reader_->Read(context_.compressed_offset, read_view);
result.is_error()) {
return result;
}
auto decompress_result = context_.decompressor->Decompress(read_view);
if (decompress_result.is_error()) {
return decompress_result.take_error_result();
}
auto [hint, consumed_bytes] = decompress_result.value();
context_.hint = hint;
context_.compressed_offset += consumed_bytes;
return fit::ok();
}
fit::result<void, std::string> Lz4DecompressReader::Read(uint64_t offset,
fbl::Span<uint8_t> buffer) const {
// Base recursion case.
if (buffer.empty()) {
return fit::ok();
}
// Attempting to read out of the uncompressed range.
if (offset < offset_) {
uint64_t uncompressed_bytes = offset_ - offset;
uint64_t uncompressed_bytes_to_copy =
std::min(static_cast<uint64_t>(buffer.size()), uncompressed_bytes);
if (auto result =
compressed_reader_->Read(offset, buffer.subspan(0, uncompressed_bytes_to_copy));
result.is_error()) {
return result;
}
offset += uncompressed_bytes_to_copy;
buffer = buffer.subspan(uncompressed_bytes_to_copy);
if (buffer.empty()) {
return fit::ok();
}
}
while (!buffer.empty()) {
if (auto result = Seek(offset); result.is_error()) {
return result;
}
// Now the data is in the buffer, or at least some of it.
uint64_t decompressed_buffer_offset = offset - context_.decompressed_offset;
uint64_t decompressed_buffer_bytes = context_.decompressed_length - decompressed_buffer_offset;
uint64_t decompressed_bytes_to_copy =
std::min(static_cast<uint64_t>(buffer.size()), decompressed_buffer_bytes);
memcpy(buffer.data(), context_.decompressed_data.data() + decompressed_buffer_offset,
decompressed_bytes_to_copy);
offset += decompressed_bytes_to_copy;
buffer = buffer.subspan(decompressed_bytes_to_copy);
}
return fit::ok();
}
} // namespace storage::volume_image