libsnapshot: Add support for brotli compression.

Bug: 162274240
Test: cow_api_test
Change-Id: I0b0ceec3c3041a6aea4b1e6c4d01ed0a8860d7e8
This commit is contained in:
David Anderson 2020-09-04 15:47:04 -07:00
parent ebd07cc5d0
commit a652877bd6
7 changed files with 96 additions and 7 deletions

View File

@ -152,6 +152,7 @@ cc_library_static {
"liblog",
],
static_libs: [
"libbrotli",
"libz",
],
ramdisk_available: true,
@ -362,10 +363,11 @@ cc_defaults {
static_libs: [
"libbase",
"libbrotli",
"liblog",
"libdm",
"libz",
"libsnapshot_cow",
"libz",
"libsnapshot_cow",
],
}
@ -403,6 +405,7 @@ cc_test {
"libz",
],
static_libs: [
"libbrotli",
"libgtest",
"libsnapshot_cow",
],
@ -494,11 +497,12 @@ cc_test {
shared_libs: [
"libbase",
"liblog",
"libz",
],
static_libs: [
"libbrotli",
"libgtest",
"libsnapshot_cow",
"libz",
],
header_libs: [
"libstorage_literals_headers",

View File

@ -30,12 +30,12 @@ namespace snapshot {
class CowTest : public ::testing::Test {
protected:
void SetUp() override {
virtual void SetUp() override {
cow_ = std::make_unique<TemporaryFile>();
ASSERT_GE(cow_->fd, 0) << strerror(errno);
}
void TearDown() override { cow_ = nullptr; }
virtual void TearDown() override { cow_ = nullptr; }
std::unique_ptr<TemporaryFile> cow_;
};
@ -211,9 +211,11 @@ class HorribleStringSink : public StringSink {
void* GetBuffer(size_t, size_t* actual) override { return StringSink::GetBuffer(1, actual); }
};
TEST_F(CowTest, HorribleSink) {
class CompressionTest : public CowTest, public testing::WithParamInterface<const char*> {};
TEST_P(CompressionTest, HorribleSink) {
CowOptions options;
options.compression = "gz";
options.compression = GetParam();
CowWriter writer(options);
ASSERT_TRUE(writer.Initialize(cow_->fd));
@ -239,6 +241,8 @@ TEST_F(CowTest, HorribleSink) {
ASSERT_EQ(sink.stream(), data);
}
INSTANTIATE_TEST_SUITE_P(CowApi, CompressionTest, testing::Values("none", "gz", "brotli"));
TEST_F(CowTest, GetSize) {
CowOptions options;
CowWriter writer(options);

View File

@ -19,6 +19,7 @@
#include <utility>
#include <android-base/logging.h>
#include <brotli/decode.h>
#include <zlib.h>
namespace android {
@ -207,5 +208,57 @@ std::unique_ptr<IDecompressor> IDecompressor::Gz() {
return std::unique_ptr<IDecompressor>(new GzDecompressor());
}
class BrotliDecompressor final : public StreamDecompressor {
public:
~BrotliDecompressor();
bool Init() override;
bool DecompressInput(const uint8_t* data, size_t length) override;
bool Done() override { return BrotliDecoderIsFinished(decoder_); }
private:
BrotliDecoderState* decoder_ = nullptr;
};
bool BrotliDecompressor::Init() {
decoder_ = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
return true;
}
BrotliDecompressor::~BrotliDecompressor() {
if (decoder_) {
BrotliDecoderDestroyInstance(decoder_);
}
}
bool BrotliDecompressor::DecompressInput(const uint8_t* data, size_t length) {
size_t available_in = length;
const uint8_t* next_in = data;
bool needs_more_output = false;
while (available_in || needs_more_output) {
if (!output_buffer_remaining_ && !GetFreshBuffer()) {
return false;
}
auto output_buffer = output_buffer_;
auto r = BrotliDecoderDecompressStream(decoder_, &available_in, &next_in,
&output_buffer_remaining_, &output_buffer_, nullptr);
if (r == BROTLI_DECODER_RESULT_ERROR) {
LOG(ERROR) << "brotli decode failed";
return false;
}
if (!sink_->ReturnData(output_buffer, output_buffer_ - output_buffer)) {
return false;
}
needs_more_output = (r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT);
}
return true;
}
std::unique_ptr<IDecompressor> IDecompressor::Brotli() {
return std::unique_ptr<IDecompressor>(new BrotliDecompressor());
}
} // namespace snapshot
} // namespace android

View File

@ -40,6 +40,7 @@ class IDecompressor {
// Factory methods for decompression methods.
static std::unique_ptr<IDecompressor> Uncompressed();
static std::unique_ptr<IDecompressor> Gz();
static std::unique_ptr<IDecompressor> Brotli();
// |output_bytes| is the expected total number of bytes to sink.
virtual bool Decompress(size_t output_bytes) = 0;

View File

@ -233,6 +233,9 @@ bool CowReader::ReadData(const CowOperation& op, IByteSink* sink) {
case kCowCompressGz:
decompressor = IDecompressor::Gz();
break;
case kCowCompressBrotli:
decompressor = IDecompressor::Brotli();
break;
default:
LOG(ERROR) << "Unknown compression type: " << op.compression;
return false;

View File

@ -22,6 +22,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <brotli/encode.h>
#include <libsnapshot/cow_writer.h>
#include <zlib.h>
@ -63,6 +64,10 @@ bool CowWriter::Initialize(android::base::borrowed_fd fd) {
if (options_.compression == "gz") {
compression_ = kCowCompressGz;
} else if (options_.compression == "brotli") {
compression_ = kCowCompressBrotli;
} else if (options_.compression == "none") {
compression_ = kCowCompressNone;
} else if (!options_.compression.empty()) {
LOG(ERROR) << "unrecognized compression: " << options_.compression;
return false;
@ -171,6 +176,24 @@ std::basic_string<uint8_t> CowWriter::Compress(const void* data, size_t length)
}
return std::basic_string<uint8_t>(buffer.get(), dest_len);
}
case kCowCompressBrotli: {
auto bound = BrotliEncoderMaxCompressedSize(length);
if (!bound) {
LOG(ERROR) << "BrotliEncoderMaxCompressedSize returned 0";
return {};
}
auto buffer = std::make_unique<uint8_t[]>(bound);
size_t encoded_size = bound;
auto rv = BrotliEncoderCompress(
BROTLI_DEFAULT_QUALITY, BROTLI_DEFAULT_WINDOW, BROTLI_DEFAULT_MODE, length,
reinterpret_cast<const uint8_t*>(data), &encoded_size, buffer.get());
if (!rv) {
LOG(ERROR) << "BrotliEncoderCompress failed";
return {};
}
return std::basic_string<uint8_t>(buffer.get(), encoded_size);
}
default:
LOG(ERROR) << "unhandled compression type: " << compression_;
break;

View File

@ -98,6 +98,7 @@ static constexpr uint8_t kCowZeroOp = 3;
static constexpr uint8_t kCowCompressNone = 0;
static constexpr uint8_t kCowCompressGz = 1;
static constexpr uint8_t kCowCompressBrotli = 2;
} // namespace snapshot
} // namespace android