Merge "Implement Play Store's "endsley" patching format generator."

am: b2af52d1eb

Change-Id: I811a49ba1e1a6bcab7c45b99f4c37ece6151b477
This commit is contained in:
Alex Deymo authored and android-build-merger committed 2017-11-13 22:53:03 +00:00
commit 47987444f4
8 files changed
+438

No files matched your search

+2
View File
@@ -59,6 +59,7 @@ cc_library_static {
"compressor_buffer.cc",
"compressor_interface.cc",
"diff_encoder.cc",
"endsley_patch_writer.cc",
"patch_writer.cc",
"patch_writer_factory.cc",
"split_patch_writer.cc",
@@ -109,6 +110,7 @@ cc_test {
"bsdiff_unittest.cc",
"bspatch_unittest.cc",
"diff_encoder_unittest.cc",
"endsley_patch_writer_unittest.cc",
"extents_file_unittest.cc",
"extents_unittest.cc",
"patch_reader_unittest.cc",
+2
View File
@@ -39,6 +39,7 @@ bsdiff_src_files := \
compressor_buffer.cc \
compressor_interface.cc \
diff_encoder.cc \
endsley_patch_writer.cc \
patch_writer.cc \
patch_writer_factory.cc \
split_patch_writer.cc \
@@ -63,6 +64,7 @@ bsdiff_common_unittests := \
bsdiff_unittest.cc \
bspatch_unittest.cc \
diff_encoder_unittest.cc \
endsley_patch_writer_unittest.cc \
extents_file_unittest.cc \
extents_unittest.cc \
patch_reader_unittest.cc \
+2
View File
@@ -62,6 +62,7 @@
'compressor_buffer.cc',
'compressor_interface.cc',
'diff_encoder.cc',
'endsley_patch_writer.cc',
'patch_writer.cc',
'patch_writer_factory.cc',
'split_patch_writer.cc',
@@ -140,6 +141,7 @@
'bsdiff_unittest.cc',
'bspatch_unittest.cc',
'diff_encoder_unittest.cc',
'endsley_patch_writer_unittest.cc',
'extents_file_unittest.cc',
'extents_unittest.cc',
'patch_reader_unittest.cc',
+178
View File
@@ -0,0 +1,178 @@
// Copyright 2017 The Chromium OS 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 "bsdiff/endsley_patch_writer.h"
#include <string.h>
#include <algorithm>
#include "bsdiff/logging.h"
using std::endl;
namespace {
constexpr uint8_t kEndsleyMagicHeader[] = "ENDSLEY/BSDIFF43";
void EncodeInt64(int64_t x, uint8_t* buf) {
uint64_t y = x < 0 ? (1ULL << 63ULL) - x : x;
for (int i = 0; i < 8; ++i) {
buf[i] = y & 0xff;
y /= 256;
}
}
// The minimum size that we would consider flushing out.
constexpr size_t kMinimumFlushSize = 1024 * 1024; // 1 MiB
} // namespace
namespace bsdiff {
bool EndsleyPatchWriter::Init(size_t new_size) {
// The patch is uncompressed and it will need exactly:
// new_size + 24 * len(control_entries) + sizeof(header)
// We don't know the length of the control entries yet, but we can reserve
// enough space to hold at least |new_size|.
patch_->clear();
patch_->reserve(new_size);
// Header is the magic followed by the new length.
uint8_t header[24];
memcpy(header, kEndsleyMagicHeader, 16);
EncodeInt64(new_size, header + 16);
EmitBuffer(header, sizeof(header));
return true;
}
bool EndsleyPatchWriter::WriteDiffStream(const uint8_t* data, size_t size) {
if (!size)
return true;
// Speed-up the common case where the diff stream data is added right after
// the control entry that refers to it.
if (control_.empty() && pending_diff_ >= size) {
pending_diff_ -= size;
EmitBuffer(data, size);
return true;
}
diff_data_.insert(diff_data_.end(), data, data + size);
return true;
}
bool EndsleyPatchWriter::WriteExtraStream(const uint8_t* data, size_t size) {
if (!size)
return true;
// Speed-up the common case where the extra stream data is added right after
// the diff stream data and the control entry that refers to it. Note that
// the diff data comes first so we need to make sure it is all out.
if (control_.empty() && !pending_diff_ && pending_extra_ >= size) {
pending_extra_ -= size;
EmitBuffer(data, size);
return true;
}
extra_data_.insert(extra_data_.end(), data, data + size);
return true;
}
bool EndsleyPatchWriter::AddControlEntry(const ControlEntry& entry) {
// Speed-up the common case where the control entry is added when there's
// nothing else pending.
if (control_.empty() && diff_data_.empty() && extra_data_.empty() &&
!pending_diff_ && !pending_extra_) {
pending_diff_ = entry.diff_size;
pending_extra_ = entry.extra_size;
EmitControlEntry(entry);
return true;
}
control_.push_back(entry);
pending_control_data_ += entry.diff_size + entry.extra_size;
// Check whether it is worth Flushing the entries now that the we have more
// control entries. We need control entries to write enough output data, and
// we need that output data to be at least 50% of the available diff and extra
// data. This last requirement is to reduce the overhead of removing the
// flushed data.
if (pending_control_data_ > kMinimumFlushSize &&
(diff_data_.size() + extra_data_.size()) / 2 <= pending_control_data_) {
Flush();
}
return true;
}
bool EndsleyPatchWriter::Close() {
// Flush any pending data.
Flush();
if (pending_diff_ || pending_extra_ || !control_.empty()) {
LOG(ERROR) << "Insufficient data sent to diff/extra streams" << endl;
return false;
}
if (!diff_data_.empty() || !extra_data_.empty()) {
LOG(ERROR) << "Pending data to diff/extra not flushed out on Close()"
<< endl;
return false;
}
return true;
}
void EndsleyPatchWriter::EmitControlEntry(const ControlEntry& entry) {
// Generate the 24 byte control entry.
uint8_t buf[24];
EncodeInt64(entry.diff_size, buf);
EncodeInt64(entry.extra_size, buf + 8);
EncodeInt64(entry.offset_increment, buf + 16);
EmitBuffer(buf, sizeof(buf));
}
void EndsleyPatchWriter::EmitBuffer(const uint8_t* data, size_t size) {
patch_->insert(patch_->end(), data, data + size);
}
void EndsleyPatchWriter::Flush() {
size_t used_diff = 0;
size_t used_extra = 0;
size_t used_control = 0;
do {
if (!pending_diff_ && !pending_extra_ && used_control < control_.size()) {
// We can emit a control entry in these conditions.
const ControlEntry& entry = control_[used_control];
used_control++;
pending_diff_ = entry.diff_size;
pending_extra_ = entry.extra_size;
pending_control_data_ -= entry.extra_size + entry.diff_size;
EmitControlEntry(entry);
}
if (pending_diff_) {
size_t diff_size = std::min(diff_data_.size() - used_diff, pending_diff_);
EmitBuffer(diff_data_.data() + used_diff, diff_size);
pending_diff_ -= diff_size;
used_diff += diff_size;
}
if (!pending_diff_ && pending_extra_) {
size_t extra_size =
std::min(extra_data_.size() - used_extra, pending_extra_);
EmitBuffer(extra_data_.data() + used_extra, extra_size);
pending_extra_ -= extra_size;
used_extra += extra_size;
}
} while (!pending_diff_ && !pending_extra_ && used_control < control_.size());
if (used_diff)
diff_data_.erase(diff_data_.begin(), diff_data_.begin() + used_diff);
if (used_extra)
extra_data_.erase(extra_data_.begin(), extra_data_.begin() + used_extra);
if (used_control)
control_.erase(control_.begin(), control_.begin() + used_control);
}
} // namespace bsdiff
+83
View File
@@ -0,0 +1,83 @@
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef _BSDIFF_ENDSLEY_PATCH_WRITER_H_
#define _BSDIFF_ENDSLEY_PATCH_WRITER_H_
#include <string>
#include <vector>
#include "bsdiff/patch_writer_interface.h"
namespace bsdiff {
// A PatchWriterInterface class compatible with the format used by Android Play
// Store's bsdiff implementation, which is based on Matthew Endsley's bsdiff
// implementation. See https://github.com/mendsley/bsdiff for the original
// implementation of this format. See also Google's APK patch size estimator for
// more information on the file-by-file format used by Play Store:
// https://github.com/googlesamples/apk-patch-size-estimator
// This format, identified by the "ENDSLEY/BSDIFF43" magic string, uses a single
// stream with the control entries, diff data and extra data interleaved. After
// the header, each Control Entry is stored in 24 bytes followed by the diff
// stream data for that entry only, and then followed by the extra stream data
// for that entry only. The format doesn't handle the compression of the data,
// instead, the whole file (including the magic string) is compressed with any
// compression algorithm.
// This format is easier to parse and allows the patch to be streamed, but by
// mixing the diff and extra data into the same compression context offers a
// slightly worse compression ratio (about 3.5% compared to upstream's format).
class EndsleyPatchWriter : public PatchWriterInterface {
public:
// Create the patch writer that will write the data to the passed vector
// |patch|, resizing it as needed. The |patch| vector must be valid until
// Close() is called or this patch is destroyed.
explicit EndsleyPatchWriter(std::vector<uint8_t>* patch) : patch_(patch) {}
// PatchWriterInterface overrides.
bool Init(size_t new_size) override;
bool WriteDiffStream(const uint8_t* data, size_t size) override;
bool WriteExtraStream(const uint8_t* data, size_t size) override;
bool AddControlEntry(const ControlEntry& entry) override;
bool Close() override;
private:
// Emit at the end of the |patch_| vector the passed control entry.
void EmitControlEntry(const ControlEntry& entry);
// Emit at the end of the |patch_| vector the passed buffer.
void EmitBuffer(const uint8_t* data, size_t size);
// Flush as much as possible of the pending data.
void Flush();
// The vector we are writing to, owned by the caller.
std::vector<uint8_t>* patch_;
// The pending diff and extra data to be encoded in the file. These vectors
// would not be used whenever is possible to the data directly to the patch_
// vector; namely when the control, diff and extra stream data are provided in
// that order for each control entry.
std::vector<uint8_t> diff_data_;
std::vector<uint8_t> extra_data_;
std::vector<ControlEntry> control_;
// Defined as the sum of all the diff_size and extra_size values in
// |control_|. This is used to determine whether it is worth Flushing the
// pending data.
size_t pending_control_data_{0};
// Number of bytes in the diff and extra stream that are pending in the
// last control entry encoded in the |patch_|. If both are zero the last
// control entry was completely emitted.
size_t pending_diff_{0};
size_t pending_extra_{0};
};
} // namespace bsdiff
#endif // _BSDIFF_ENDSLEY_PATCH_WRITER_H_
+158
View File
@@ -0,0 +1,158 @@
// Copyright 2017 The Chromium OS 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 "bsdiff/endsley_patch_writer.h"
#include <algorithm>
#include <gtest/gtest.h>
namespace {
std::vector<uint8_t> VectorFromString(const std::string& s) {
return std::vector<uint8_t>(s.data(), s.data() + s.size());
}
} // namespace
namespace bsdiff {
class EndsleyPatchWriterTest : public testing::Test {
protected:
// Return a subvector from |data_| starting at |start| of size at most |size|.
std::vector<uint8_t> DataSubvector(size_t start, size_t size) {
if (start > data_.size())
return std::vector<uint8_t>();
size = std::min(size, data_.size() - start);
return std::vector<uint8_t>(data_.begin() + start,
data_.begin() + start + size);
}
std::vector<uint8_t> data_;
EndsleyPatchWriter patch_writer_{&data_};
};
// Smoke check that a patch includes the new_size and magic header.
TEST_F(EndsleyPatchWriterTest, CreateEmptyPatchTest) {
EXPECT_TRUE(patch_writer_.Init(0));
EXPECT_TRUE(patch_writer_.Close());
// The empty header is set to 24 bytes.
EXPECT_EQ(24U, data_.size());
std::vector<uint8_t> empty_patch = {
// Magic header.
'E', 'N', 'D', 'S', 'L', 'E', 'Y', '/', 'B', 'S', 'D', 'I', 'F', 'F', '4',
'3',
// 8 zeros for the |new_size| of zero bytes.
0, 0, 0, 0, 0, 0, 0, 0};
EXPECT_EQ(empty_patch, data_);
}
// Test we generate the right patch when the control, diff and extra stream come
// in the right order.
TEST_F(EndsleyPatchWriterTest, DataInNiceOrderTest) {
auto text = VectorFromString("abcdeFGHIJ");
EXPECT_TRUE(patch_writer_.Init(10));
EXPECT_TRUE(patch_writer_.AddControlEntry(ControlEntry(2, 3, -2)));
EXPECT_TRUE(patch_writer_.WriteDiffStream(text.data(), 2));
EXPECT_TRUE(patch_writer_.WriteExtraStream(text.data() + 2, 3));
// Check that we are actually writing to the output vector as soon as we can.
EXPECT_EQ(24U + 24U + 2U + 3U, data_.size());
EXPECT_TRUE(patch_writer_.AddControlEntry(ControlEntry(0, 5, 1024)));
EXPECT_TRUE(patch_writer_.WriteExtraStream(text.data() + 5, 5));
EXPECT_TRUE(patch_writer_.Close());
// We have a header, 2 control entries and a total of 10 bytes of data.
EXPECT_EQ(24U + 24U * 2 + 10U, data_.size());
// Verify that control entry values are encoded properly in little-endian.
EXPECT_EQ((std::vector<uint8_t>{10, 0, 0, 0, 0, 0, 0, 0}),
DataSubvector(16U, 8)); // new_size
// Negative numbers are encoded with the sign bit in the most significant bit
// of the 8-byte number.
EXPECT_EQ((std::vector<uint8_t>{2, 0, 0, 0, 0, 0, 0, 0x80}),
DataSubvector(24U + 16, 8));
// The second member on the last control entry (1024) encoded in
// little-endian.
EXPECT_EQ((std::vector<uint8_t>{0, 4, 0, 0, 0, 0, 0, 0}),
DataSubvector(24U + 24U + 5U + 16U, 8));
// Check that the diff and extra data are sent one after the other in the
// right order.
EXPECT_EQ(VectorFromString("abcde"), DataSubvector(24U + 24U, 5));
}
// When we send first the diff or extra data it shouldn't be possible to
// write it to the patch, but at the end of the patch we should be able to
// write it all.
TEST_F(EndsleyPatchWriterTest, DataInBadOrderTest) {
auto text = VectorFromString("abcdeFGHIJ");
EXPECT_TRUE(patch_writer_.Init(10));
EXPECT_TRUE(patch_writer_.WriteDiffStream(text.data(), 5));
EXPECT_TRUE(patch_writer_.WriteExtraStream(text.data() + 5, 5));
// Writ all the control entries at the end, only the header should have been
// sent so far.
EXPECT_EQ(24U, data_.size());
EXPECT_TRUE(patch_writer_.AddControlEntry(ControlEntry(2, 3, -2)));
EXPECT_TRUE(patch_writer_.AddControlEntry(ControlEntry(2, 1, 1024)));
EXPECT_TRUE(patch_writer_.AddControlEntry(ControlEntry(1, 1, 1024)));
EXPECT_TRUE(patch_writer_.Close());
// We have a header, 3 control entries and a total of 10 bytes of data.
EXPECT_EQ(24U + 24U * 3 + 10U, data_.size());
// The data from the first and second control entries:
EXPECT_EQ(VectorFromString("abFGH"), DataSubvector(24U + 24U, 5));
EXPECT_EQ(VectorFromString("cdI"), DataSubvector(24U + 24U * 2 + 5, 3));
EXPECT_EQ(VectorFromString("eJ"), DataSubvector(24U + 24U * 3 + 8, 2));
}
TEST_F(EndsleyPatchWriterTest, FlushOnlyWhenWorthItTest) {
size_t kEntrySize = 1000; // must be even for this test.
size_t kNumEntries = 3000;
size_t kNewSize = kEntrySize * kNumEntries; // 3 MB
EXPECT_TRUE(patch_writer_.Init(kNewSize));
// Write all the extra and diff data first.
std::vector<uint8_t> zeros(kNewSize / 2, 0);
EXPECT_TRUE(patch_writer_.WriteDiffStream(zeros.data(), zeros.size()));
EXPECT_TRUE(patch_writer_.WriteExtraStream(zeros.data(), zeros.size()));
// No patch data flushed so far, only the header.
EXPECT_EQ(24U, data_.size());
ControlEntry entry(kEntrySize / 2, kEntrySize / 2, -1);
for (size_t i = 0; i < 10; i++) {
EXPECT_TRUE(patch_writer_.AddControlEntry(entry));
}
// Even if all the diff and extra data is available and some control entries
// are also available no information should have been flushed yet because we
// don't want the overhead of updating the diff_data_ and extra_data_ vectors.
EXPECT_EQ(24U, data_.size());
// Write the remaining entries.
for (size_t i = 0; i < kNumEntries - 10; i++) {
EXPECT_TRUE(patch_writer_.AddControlEntry(entry));
}
// Even before Close() is called, we have enough control entries to make it
// worth it calling flush at some point.
EXPECT_LT(24U, data_.size());
EXPECT_TRUE(patch_writer_.Close());
}
} // namespace bsdiff
+7
View File
@@ -7,6 +7,7 @@
#include <memory>
#include <string>
#include <vector>
#include "bsdiff/patch_writer_interface.h"
@@ -17,6 +18,12 @@ namespace bsdiff {
std::unique_ptr<PatchWriterInterface> CreateBsdiffPatchWriter(
const std::string& patch_filename);
// Create a patch writer compatible with Android Play Store bsdiff patches,
// uncompressed. The data will be written to the passed |patch| vector, which
// must be valid until Close() is called or this patch is destroyed.
std::unique_ptr<PatchWriterInterface> CreateEndsleyPatchWriter(
std::vector<uint8_t>* patch);
} // namespace bsdiff
#endif // _BSDIFF_PATCH_WRITER_FACTORY_H_
+6
View File
@@ -4,6 +4,7 @@
#include "bsdiff/patch_writer_factory.h"
#include "bsdiff/endsley_patch_writer.h"
#include "bsdiff/patch_writer.h"
namespace bsdiff {
@@ -15,4 +16,9 @@ std::unique_ptr<PatchWriterInterface> CreateBsdiffPatchWriter(
new BsdiffPatchWriter(patch_filename, CompressorType::kBZ2));
}
std::unique_ptr<PatchWriterInterface> CreateEndsleyPatchWriter(
std::vector<uint8_t>* patch) {
return std::unique_ptr<PatchWriterInterface>(new EndsleyPatchWriter(patch));
}
} // namespace bsdiff