Merge "Add an interface for bspatch reader"

am: 68540f685e

Change-Id: I5da1be9150aef4317084a177547ea97808a4c5d1
This commit is contained in:
Tianjie Xu authored and android-build-merger committed 2017-10-27 01:58:15 +00:00
commit 6cb2220909
15 files changed
+583 -186

No files matched your search

+4
View File
@@ -41,12 +41,15 @@ cc_library_static {
srcs: [
"bspatch.cc",
"bz2_decompressor.cc",
"buffer_file.cc",
"extents.cc",
"extents_file.cc",
"file.cc",
"memory_file.cc",
"patch_reader.cc",
"sink_file.cc",
"utils.cc",
],
}
@@ -105,6 +108,7 @@ cc_test {
"diff_encoder_unittest.cc",
"extents_file_unittest.cc",
"extents_unittest.cc",
"patch_reader_unittest.cc",
"patch_writer_unittest.cc",
"split_patch_writer_unittest.cc",
"suffix_array_index_unittest.cc",
+5 -1
View File
@@ -43,12 +43,15 @@ bsdiff_src_files := \
# "bspatch" program.
bspatch_src_files := \
bspatch.cc \
bz2_decompressor.cc \
buffer_file.cc \
extents.cc \
extents_file.cc \
file.cc \
memory_file.cc \
sink_file.cc
patch_reader.cc \
sink_file.cc \
utils.cc
# Unit test files.
bsdiff_common_unittests := \
@@ -57,6 +60,7 @@ bsdiff_common_unittests := \
diff_encoder_unittest.cc \
extents_file_unittest.cc \
extents_unittest.cc \
patch_reader_unittest.cc \
patch_writer_unittest.cc \
split_patch_writer_unittest.cc \
suffix_array_index_unittest.cc \
+4
View File
@@ -31,12 +31,15 @@
'variables': {
'bspatch_sources': [
'bspatch.cc',
'bz2_decompressor.cc',
'buffer_file.cc',
'extents.cc',
'extents_file.cc',
'file.cc',
'memory_file.cc',
'patch_reader.cc',
'sink_file.cc',
'utils.cc',
],
},
'targets': [
@@ -132,6 +135,7 @@
'diff_encoder_unittest.cc',
'extents_file_unittest.cc',
'extents_unittest.cc',
'patch_reader_unittest.cc',
'patch_writer_unittest.cc',
'split_patch_writer_unittest.cc',
'suffix_array_index_unittest.cc',
+66 -162
View File
@@ -30,7 +30,6 @@ __FBSDID("$FreeBSD: src/usr.bin/bsdiff/bspatch/bspatch.c,v 1.1 2005/08/06 01:59:
#include "bsdiff/bspatch.h"
#include <bzlib.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
@@ -41,75 +40,42 @@ __FBSDID("$FreeBSD: src/usr.bin/bsdiff/bspatch/bspatch.c,v 1.1 2005/08/06 01:59:
#include <unistd.h>
#include <algorithm>
#include <limits>
#include <memory>
#include <vector>
#include "bsdiff/buffer_file.h"
#include "bsdiff/control_entry.h"
#include "bsdiff/extents.h"
#include "bsdiff/extents_file.h"
#include "bsdiff/file.h"
#include "bsdiff/file_interface.h"
#include "bsdiff/memory_file.h"
#include "bsdiff/patch_reader.h"
#include "bsdiff/sink_file.h"
#include "bsdiff/utils.h"
namespace {
int64_t ParseInt64(const u_char* buf) {
int64_t y;
y = buf[7] & 0x7F;
y = y * 256;
y += buf[6];
y = y * 256;
y += buf[5];
y = y * 256;
y += buf[4];
y = y * 256;
y += buf[3];
y = y * 256;
y += buf[2];
y = y * 256;
y += buf[1];
y = y * 256;
y += buf[0];
if (buf[7] & 0x80)
y = -y;
return y;
}
bool ReadBZ2(bz_stream* stream, uint8_t* data, size_t size) {
stream->next_out = (char*)data;
// Read the data in |stream| and write |size| decompressed data to |file|;
// using the buffer in |buf| with size |buf_size|.
// Returns 0 on success, 1 on I/O error and 2 on data error.
int ReadStreamAndWriteAll(
const std::unique_ptr<bsdiff::FileInterface>& file,
size_t size,
uint8_t* buf,
size_t buf_size,
const std::function<bool(uint8_t*, size_t)>& read_func) {
while (size > 0) {
unsigned int read_size = std::min(
static_cast<size_t>(std::numeric_limits<unsigned int>::max()), size);
stream->avail_out = read_size;
int bz2err = BZ2_bzDecompress(stream);
if (bz2err != BZ_OK && bz2err != BZ_STREAM_END)
return false;
size -= read_size - stream->avail_out;
}
return true;
}
int ReadBZ2AndWriteAll(const std::unique_ptr<bsdiff::FileInterface>& file,
bz_stream* stream,
size_t size,
uint8_t* buf,
size_t buf_size) {
while (size > 0) {
size_t bytes_to_read = std::min(size, buf_size);
if (!ReadBZ2(stream, buf, bytes_to_read)) {
fprintf(stderr, "Failed to read bzip stream.\n");
size_t bytes_to_output = std::min(size, buf_size);
if (!read_func(buf, bytes_to_output)) {
fprintf(stderr, "Failed to read stream.\n");
return 2;
}
if (!WriteAll(file, buf, bytes_to_read)) {
if (!WriteAll(file, buf, bytes_to_output)) {
perror("WriteAll() failed");
return 1;
}
size -= bytes_to_read;
size -= bytes_to_output;
}
return 0;
}
@@ -282,76 +248,14 @@ int bspatch(const std::unique_ptr<FileInterface>& old_file,
const std::unique_ptr<FileInterface>& new_file,
const uint8_t* patch_data,
size_t patch_size) {
int bz2err;
u_char buf[8];
off_t ctrl[3];
// File format:
// 0 8 "BSDIFF40"
// 8 8 X
// 16 8 Y
// 24 8 sizeof(new_filename)
// 32 X bzip2(control block)
// 32+X Y bzip2(diff block)
// 32+X+Y ??? bzip2(extra block)
// with control block a set of triples (x,y,z) meaning "add x bytes
// from oldfile to x bytes from the diff block; copy y bytes from the
// extra block; seek forwards in oldfile by z bytes".
// Check for appropriate magic.
if (memcmp(patch_data, "BSDIFF40", 8) != 0) {
fprintf(stderr, "Not a bsdiff patch.\n");
BsdiffPatchReader patch_reader;
if (!patch_reader.Init(patch_data, patch_size)) {
fprintf(stderr, "Failed to initialize patch reader\n");
return 2;
}
// Read lengths from header.
uint64_t oldsize, newsize;
int64_t ctrl_len = ParseInt64(patch_data + 8);
int64_t data_len = ParseInt64(patch_data + 16);
int64_t signed_newsize = ParseInt64(patch_data + 24);
newsize = signed_newsize;
if ((ctrl_len < 0) || (data_len < 0) || (signed_newsize < 0) ||
(32 + ctrl_len + data_len > static_cast<int64_t>(patch_size))) {
fprintf(stderr, "Corrupt patch.\n");
return 2;
}
bz_stream cstream;
cstream.next_in = (char*)patch_data + 32;
cstream.avail_in = ctrl_len;
cstream.bzalloc = nullptr;
cstream.bzfree = nullptr;
cstream.opaque = nullptr;
if ((bz2err = BZ2_bzDecompressInit(&cstream, 0, 0)) != BZ_OK) {
fprintf(stderr, "Failed to bzinit control stream (%d)\n", bz2err);
return 2;
}
bz_stream dstream;
dstream.next_in = (char*)patch_data + 32 + ctrl_len;
dstream.avail_in = data_len;
dstream.bzalloc = nullptr;
dstream.bzfree = nullptr;
dstream.opaque = nullptr;
if ((bz2err = BZ2_bzDecompressInit(&dstream, 0, 0)) != BZ_OK) {
fprintf(stderr, "Failed to bzinit diff stream (%d)\n", bz2err);
return 2;
}
bz_stream estream;
estream.next_in = (char*)patch_data + 32 + ctrl_len + data_len;
estream.avail_in = patch_size - (32 + ctrl_len + data_len);
estream.bzalloc = nullptr;
estream.bzfree = nullptr;
estream.opaque = nullptr;
if ((bz2err = BZ2_bzDecompressInit(&estream, 0, 0)) != BZ_OK) {
fprintf(stderr, "Failed to bzinit extra stream (%d)\n", bz2err);
return 2;
}
uint64_t old_file_pos = 0;
if (!old_file->GetSize(&oldsize)) {
uint64_t old_file_size;
if (!old_file->GetSize(&old_file_size)) {
fprintf(stderr, "Cannot obtain the size of old file.\n");
return 1;
}
@@ -359,26 +263,18 @@ int bspatch(const std::unique_ptr<FileInterface>& old_file,
// The oldpos can be negative, but the new pos is only incremented linearly.
int64_t oldpos = 0;
uint64_t newpos = 0;
std::vector<uint8_t> old_buf(1024 * 1024), new_buf(1024 * 1024);
while (newpos < newsize) {
int64_t i;
// Read control data.
for (i = 0; i <= 2; i++) {
if (!ReadBZ2(&cstream, buf, 8)) {
fprintf(stderr, "Failed to read control stream.\n");
return 2;
}
ctrl[i] = ParseInt64(buf);
}
// Sanity-check.
if (ctrl[0] < 0 || ctrl[1] < 0) {
fprintf(stderr, "Corrupt patch.\n");
std::vector<uint8_t> old_buf(1024 * 1024);
std::vector<uint8_t> new_buf(1024 * 1024);
uint64_t old_file_pos = 0;
while (newpos < patch_reader.new_file_size()) {
ControlEntry control_entry(0, 0, 0);
if (!patch_reader.ParseControlEntry(&control_entry)) {
fprintf(stderr, "Failed to read control stream\n");
return 2;
}
// Sanity-check.
if (newpos + ctrl[0] > newsize) {
if (newpos + control_entry.diff_size > patch_reader.new_file_size()) {
fprintf(stderr, "Corrupt patch.\n");
return 2;
}
@@ -386,26 +282,30 @@ int bspatch(const std::unique_ptr<FileInterface>& old_file,
int ret = 0;
// Add old data to diff string. It is enough to fseek once, at
// the beginning of the sequence, to avoid unnecessary overhead.
if ((i = oldpos) < 0) {
int64_t seek_offset = oldpos;
if (seek_offset < 0) {
// Write diff block directly to new file without adding old data,
// because we will skip part where |oldpos| < 0.
ret = ReadBZ2AndWriteAll(new_file, &dstream, -i, new_buf.data(),
new_buf.size());
ret = ReadStreamAndWriteAll(
new_file, oldpos - old_file_size, new_buf.data(), new_buf.size(),
std::bind(&BsdiffPatchReader::ReadDiffStream, &patch_reader,
std::placeholders::_1, std::placeholders::_2));
if (ret)
return ret;
i = 0;
seek_offset = 0;
}
// We just checked that |i| is not negative.
if (static_cast<uint64_t>(i) != old_file_pos && !old_file->Seek(i)) {
fprintf(stderr, "Error seeking input file to offset %" PRId64 ": %s\n", i,
strerror(errno));
// We just checked that |seek_offset| is not negative.
if (static_cast<uint64_t>(seek_offset) != old_file_pos &&
!old_file->Seek(seek_offset)) {
fprintf(stderr, "Error seeking input file to offset %" PRId64 ": %s\n",
seek_offset, strerror(errno));
return 1;
}
if ((old_file_pos = oldpos + ctrl[0]) > oldsize)
old_file_pos = oldsize;
size_t chunk_size = old_file_pos - i;
old_file_pos =
std::min<uint64_t>(oldpos + control_entry.diff_size, old_file_size);
size_t chunk_size = old_file_pos - seek_offset;
while (chunk_size > 0) {
size_t read_bytes;
size_t bytes_to_read = std::min(chunk_size, old_buf.size());
@@ -418,7 +318,7 @@ int bspatch(const std::unique_ptr<FileInterface>& old_file,
return 2;
}
// Read same amount of bytes from diff block
if (!ReadBZ2(&dstream, new_buf.data(), read_bytes)) {
if (!patch_reader.ReadDiffStream(new_buf.data(), read_bytes)) {
fprintf(stderr, "Failed to read diff stream.\n");
return 2;
}
@@ -433,42 +333,46 @@ int bspatch(const std::unique_ptr<FileInterface>& old_file,
}
// Adjust pointers.
newpos += ctrl[0];
oldpos += ctrl[0];
newpos += control_entry.diff_size;
oldpos += control_entry.diff_size;
if (oldpos > static_cast<int64_t>(oldsize)) {
if (oldpos > static_cast<int64_t>(old_file_size)) {
// Write diff block directly to new file without adding old data,
// because we skipped part where |oldpos| > oldsize.
ret = ReadBZ2AndWriteAll(new_file, &dstream, oldpos - oldsize,
new_buf.data(), new_buf.size());
// because we skipped part where |oldpos| > old_file_size.
ret = ReadStreamAndWriteAll(
new_file, oldpos - old_file_size, new_buf.data(), new_buf.size(),
std::bind(&BsdiffPatchReader::ReadDiffStream, &patch_reader,
std::placeholders::_1, std::placeholders::_2));
if (ret)
return ret;
}
// Sanity-check.
if (newpos + ctrl[1] > newsize) {
if (newpos + control_entry.extra_size > patch_reader.new_file_size()) {
fprintf(stderr, "Corrupt patch.\n");
return 2;
}
// Read extra block.
ret = ReadBZ2AndWriteAll(new_file, &estream, ctrl[1], new_buf.data(),
new_buf.size());
ret = ReadStreamAndWriteAll(
new_file, control_entry.extra_size, new_buf.data(), new_buf.size(),
std::bind(&BsdiffPatchReader::ReadExtraStream, &patch_reader,
std::placeholders::_1, std::placeholders::_2));
if (ret)
return ret;
// Adjust pointers.
newpos += ctrl[1];
oldpos += ctrl[2];
newpos += control_entry.extra_size;
oldpos += control_entry.offset_increment;
}
// Close input file.
old_file->Close();
// Clean up the bzip2 reads.
BZ2_bzDecompressEnd(&cstream);
BZ2_bzDecompressEnd(&dstream);
BZ2_bzDecompressEnd(&estream);
if (!patch_reader.Finish()) {
fprintf(stderr, "Failed to finish the patch reader\n");
return 2;
}
if (!new_file->Close()) {
perror("Error closing new file");
+65
View File
@@ -0,0 +1,65 @@
// 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/bz2_decompressor.h"
#include <algorithm>
#include <limits>
#include <bzlib.h>
#include "bsdiff/logging.h"
using std::endl;
namespace bsdiff {
bool BZ2Decompressor::SetInputData(const uint8_t* input_data, size_t size) {
// TODO(xunchang) update the avail_in for size > 2GB.
if (size > std::numeric_limits<unsigned int>::max()) {
LOG(ERROR) << "Oversized input data" << size << endl;
return false;
}
stream_.next_in = reinterpret_cast<char*>(const_cast<uint8_t*>(input_data));
stream_.avail_in = size;
stream_.bzalloc = nullptr;
stream_.bzfree = nullptr;
stream_.opaque = nullptr;
int bz2err = BZ2_bzDecompressInit(&stream_, 0, 0);
if (bz2err != BZ_OK) {
LOG(ERROR) << "Failed to bzinit control stream: " << bz2err << endl;
return false;
}
return true;
}
bool BZ2Decompressor::Read(uint8_t* output_data, size_t bytes_to_output) {
stream_.next_out = reinterpret_cast<char*>(output_data);
while (bytes_to_output > 0) {
size_t output_size = std::min<size_t>(
std::numeric_limits<unsigned int>::max(), bytes_to_output);
stream_.avail_out = output_size;
int bz2err = BZ2_bzDecompress(&stream_);
if (bz2err != BZ_OK && bz2err != BZ_STREAM_END) {
LOG(ERROR) << "Failed to decompress " << output_size
<< " bytes of data, error: " << bz2err << endl;
return false;
}
bytes_to_output -= (output_size - stream_.avail_out);
}
return true;
}
bool BZ2Decompressor::Close() {
int bz2err = BZ2_bzDecompressEnd(&stream_);
if (bz2err != BZ_OK) {
LOG(ERROR) << "BZ2_bzDecompressEnd returns with " << bz2err << endl;
return false;
}
return true;
}
} // namespace bsdiff
+30
View File
@@ -0,0 +1,30 @@
// 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_BZ2_DECOMPRESSOR_H_
#define _BSDIFF_BZ2_DECOMPRESSOR_H_
#include <bzlib.h>
#include "bsdiff/decompressor_interface.h"
namespace bsdiff {
class BZ2Decompressor : public DecompressorInterface {
public:
BZ2Decompressor() = default;
bool SetInputData(const uint8_t* input_data, size_t size) override;
bool Read(uint8_t* output_data, size_t bytes_to_output) override;
bool Close() override;
private:
bz_stream stream_;
};
} // namespace bsdiff
#endif
+32
View File
@@ -0,0 +1,32 @@
// 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_DECOMPRESSOR_INTERFACE_H_
#define _BSDIFF_DECOMPRESSOR_INTERFACE_H_
#include <stddef.h>
#include <stdint.h>
namespace bsdiff {
class DecompressorInterface {
public:
virtual ~DecompressorInterface() {}
// Set the buffer starting from |input_data| with length |size| as the input
// compressed stream. This buffer is owned by the caller and should stay
// valid until the Close() function is called.
virtual bool SetInputData(const uint8_t* input_data, size_t size) = 0;
// Decompress the stream and output |bytes_to_output| bytes of data to
// |output_data|. Returns false if not all bytes_to_output can be
// decompressed from the stream due to a decompressor error or EOF.
virtual bool Read(uint8_t* output_data, size_t bytes_to_output) = 0;
// Close the decompression stream.
virtual bool Close() = 0;
};
} // namespace bsdiff
#endif // _BSDIFF_DECOMPRESSOR_INTERFACE_H_
+33
View File
@@ -0,0 +1,33 @@
// 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_CONTROL_ENTRY_H_
#define _BSDIFF_CONTROL_ENTRY_H_
#include <stdint.h>
struct ControlEntry {
ControlEntry(uint64_t diff_size,
uint64_t extra_size,
int64_t offset_increment)
: diff_size(diff_size),
extra_size(extra_size),
offset_increment(offset_increment) {}
// The number of bytes to copy from the source and diff stream.
uint64_t diff_size;
// The number of bytes to copy from the extra stream.
uint64_t extra_size;
// The value to add to the source pointer after patching from the diff stream.
int64_t offset_increment;
bool operator==(const ControlEntry& o) const {
return diff_size == o.diff_size && extra_size == o.extra_size &&
offset_increment == o.offset_increment;
}
};
#endif // _BSDIFF_CONTROL_ENTRY_H_
+2 -23
View File
@@ -9,31 +9,10 @@
#include <cstddef>
#include "bsdiff/control_entry.h"
namespace bsdiff {
struct ControlEntry {
ControlEntry(uint64_t diff_size,
uint64_t extra_size,
int64_t offset_increment)
: diff_size(diff_size),
extra_size(extra_size),
offset_increment(offset_increment) {}
// The number of bytes to copy from the source and diff stream.
uint64_t diff_size;
// The number of bytes to copy from the extra stream.
uint64_t extra_size;
// The value to add to the source pointer after patching from the diff stream.
int64_t offset_increment;
bool operator==(const ControlEntry& o) const {
return diff_size == o.diff_size && extra_size == o.extra_size &&
offset_increment == o.offset_increment;
}
};
class PatchWriterInterface {
public:
virtual ~PatchWriterInterface() = default;
+141
View File
@@ -0,0 +1,141 @@
// 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/patch_reader.h"
#include <string.h>
#include <limits>
#include "bsdiff/bspatch.h"
#include "bsdiff/bz2_decompressor.h"
#include "bsdiff/logging.h"
#include "bsdiff/utils.h"
using std::endl;
namespace bsdiff {
const uint8_t kMagicHeader[] = "BSDIFF40";
bool BsdiffPatchReader::Init(const uint8_t* patch_data, size_t patch_size) {
// File format:
// 0 8 "BSDIFF40"
// 8 8 X
// 16 8 Y
// 24 8 new_file_size
// 32 X compressed control block
// 32+X Y compressed diff block
// 32+X+Y ??? compressed extra block
// with control block a set of triples (x,y,z) meaning "add x bytes
// from oldfile to x bytes from the diff block; copy y bytes from the
// extra block; seek forwards in oldfile by z bytes".
// Check for appropriate magic.
if (memcmp(patch_data, kMagicHeader, 8) != 0) {
LOG(ERROR) << "Not a bsdiff patch." << endl;
return false;
}
// Read lengths from header.
int64_t ctrl_len = ParseInt64(patch_data + 8);
int64_t diff_len = ParseInt64(patch_data + 16);
int64_t signed_newsize = ParseInt64(patch_data + 24);
if ((ctrl_len < 0) || (diff_len < 0) || (signed_newsize < 0) ||
(32 + ctrl_len + diff_len > static_cast<int64_t>(patch_size))) {
LOG(ERROR) << "Corrupt patch. ctrl_len: " << ctrl_len
<< ", data_len: " << diff_len
<< ", new_file_size: " << signed_newsize
<< ", patch_size: " << patch_size;
return false;
}
new_file_size_ = signed_newsize;
// TODO(xunchang) set the correct decompressor based on the info in the
// header.
ctrl_stream_.reset(new BZ2Decompressor());
diff_stream_.reset(new BZ2Decompressor());
extra_stream_.reset(new BZ2Decompressor());
int64_t offset = 32;
if (!ctrl_stream_->SetInputData(const_cast<uint8_t*>(patch_data) + offset,
ctrl_len)) {
LOG(ERROR) << "Failed to init ctrl stream, ctrl_len: " << ctrl_len << endl;
return false;
}
offset += ctrl_len;
if (!diff_stream_->SetInputData(const_cast<uint8_t*>(patch_data) + offset,
diff_len)) {
LOG(ERROR) << "Failed to init ctrl stream, diff_len: " << diff_len << endl;
return false;
}
offset += diff_len;
if (!extra_stream_->SetInputData(const_cast<uint8_t*>(patch_data) + offset,
patch_size - offset)) {
LOG(ERROR) << "Failed to init extra stream, extra_offset: " << offset
<< ", patch_size: " << patch_size << endl;
return false;
}
return true;
}
bool BsdiffPatchReader::ParseControlEntry(ControlEntry* control_entry) {
if (!control_entry)
return false;
uint8_t buf[8];
if (!ctrl_stream_->Read(buf, 8))
return false;
int64_t diff_size = ParseInt64(buf);
if (!ctrl_stream_->Read(buf, 8))
return false;
int64_t extra_size = ParseInt64(buf);
// Sanity check.
if (diff_size < 0 || extra_size < 0) {
LOG(ERROR) << "Corrupt patch; diff_size: " << diff_size
<< ", extra_size: " << extra_size << endl;
return false;
}
control_entry->diff_size = diff_size;
control_entry->extra_size = extra_size;
if (!ctrl_stream_->Read(buf, 8))
return false;
control_entry->offset_increment = ParseInt64(buf);
return true;
}
bool BsdiffPatchReader::ReadDiffStream(uint8_t* buf, size_t size) {
return diff_stream_->Read(buf, size);
}
bool BsdiffPatchReader::ReadExtraStream(uint8_t* buf, size_t size) {
return extra_stream_->Read(buf, size);
}
bool BsdiffPatchReader::Finish() {
if (!ctrl_stream_->Close()) {
LOG(ERROR) << "Failed to close the control stream";
return false;
}
if (!diff_stream_->Close()) {
LOG(ERROR) << "Failed to close the diff stream";
return false;
}
if (!extra_stream_->Close()) {
LOG(ERROR) << "Failed to close the extra stream";
return false;
}
return true;
}
} // namespace bsdiff
+68
View File
@@ -0,0 +1,68 @@
// 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_PATCH_READER_H_
#define _BSDIFF_PATCH_READER_H_
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include "bsdiff/control_entry.h"
#include "bsdiff/decompressor_interface.h"
#include "bsdiff/file_interface.h"
namespace bsdiff {
extern const uint8_t kMagicHeader[];
// A wrapper class to read and parse the data in a bsdiff patch. The reader
// class contains the concatenated streams of control, diff, and extra data.
// After initialization, this class provides the function to read the metadata
// embedded in the control stream sequentially and decompresses the diff & extra
// stream based on this metadata.
class BsdiffPatchReader {
public:
BsdiffPatchReader() = default;
// Initialize the control stream, diff stream and extra stream from the
// corresponding offset of |patch_data|.
bool Init(const uint8_t* data_start, size_t patch_size);
// Read the control stream and parse the metadata of |diff_size_|,
// |extra_size_| and |offset_incremental_|.
bool ParseControlEntry(ControlEntry* control_entry);
// Read the data in |diff_stream_| and write |size| decompressed data to
// |buf|.
bool ReadDiffStream(uint8_t* buf, size_t size);
// Read the data in |extra_stream_| and write |size| decompressed data to
// |buf|.
bool ReadExtraStream(uint8_t* buf, size_t size);
uint64_t new_file_size() const { return new_file_size_; }
// Close the control/diff/extra stream. Return false if errors occur when
// closing any of these streams.
bool Finish();
private:
// The compressed stream that contains the control data; i.e. length of each
// diff/extra block and the corresponding offset to read in the source file.
std::unique_ptr<DecompressorInterface> ctrl_stream_{nullptr};
// The compressed stream that contains the concatenated diff blocks.
std::unique_ptr<DecompressorInterface> diff_stream_{nullptr};
// The compressed stream that contains the concatenated extra blocks.
std::unique_ptr<DecompressorInterface> extra_stream_{nullptr};
// Size of the target file.
uint64_t new_file_size_;
};
} // namespace bsdiff
#endif // _BSDIFF_PATCH_READER_H_
+96
View File
@@ -0,0 +1,96 @@
// 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 <unistd.h>
#include <algorithm>
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include "bsdiff/bz2_compressor.h"
#include "bsdiff/patch_reader.h"
#include "bsdiff/utils.h"
namespace {
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;
}
}
} // namespace
namespace bsdiff {
TEST(PatchReaderTest, BZ2PatchReaderSmoke) {
BZ2Compressor ctrl_stream;
BZ2Compressor diff_stream;
BZ2Compressor extra_stream;
// Write 3 blocks of data to diff stream & extra stream respectively.
std::vector<std::string> diff_data = {"HelloWorld", "BspatchPatchTest",
"BspatchDiffData"};
std::vector<std::string> extra_data = {"HelloWorld!", "BZ2PatchReaderSmoke",
"BspatchExtraData"};
std::vector<int64_t> offset_increment = {100, 200, 300};
for (size_t i = 0; i < diff_data.size(); i++) {
uint8_t buf[24];
EncodeInt64(diff_data[i].size(), buf);
EncodeInt64(extra_data[i].size(), buf + 8);
EncodeInt64(offset_increment[i], buf + 16);
EXPECT_TRUE(ctrl_stream.Write(buf, sizeof(buf)));
EXPECT_TRUE(
diff_stream.Write(reinterpret_cast<const uint8_t*>(diff_data[i].data()),
diff_data[i].size()));
EXPECT_TRUE(extra_stream.Write(
reinterpret_cast<const uint8_t*>(extra_data[i].data()),
extra_data[i].size()));
}
EXPECT_TRUE(ctrl_stream.Finish());
EXPECT_TRUE(diff_stream.Finish());
EXPECT_TRUE(extra_stream.Finish());
// Encode the header
uint8_t buf[24];
EncodeInt64(ctrl_stream.GetCompressedData().size(), buf);
EncodeInt64(diff_stream.GetCompressedData().size(), buf + 8);
EncodeInt64(500, buf + 16);
std::vector<uint8_t> patch_data;
std::copy(kMagicHeader, kMagicHeader + 8, std::back_inserter(patch_data));
std::copy(buf, buf + sizeof(buf), std::back_inserter(patch_data));
// Concatenate the three streams into one patch.
std::copy(ctrl_stream.GetCompressedData().begin(),
ctrl_stream.GetCompressedData().end(),
std::back_inserter(patch_data));
std::copy(diff_stream.GetCompressedData().begin(),
diff_stream.GetCompressedData().end(),
std::back_inserter(patch_data));
std::copy(extra_stream.GetCompressedData().begin(),
extra_stream.GetCompressedData().end(),
std::back_inserter(patch_data));
BsdiffPatchReader patch_reader;
EXPECT_TRUE(patch_reader.Init(patch_data.data(), patch_data.size()));
EXPECT_EQ(500ull, patch_reader.new_file_size());
// Check that the decompressed data matches what we wrote.
for (size_t i = 0; i < diff_data.size(); i++) {
ControlEntry control_entry(0, 0, 0);
EXPECT_TRUE(patch_reader.ParseControlEntry(&control_entry));
uint8_t buffer[128] = {};
EXPECT_TRUE(patch_reader.ReadDiffStream(buffer, diff_data[i].size()));
EXPECT_EQ(0, memcmp(buffer, diff_data[i].data(), diff_data[i].size()));
EXPECT_TRUE(patch_reader.ReadExtraStream(buffer, extra_data[i].size()));
EXPECT_EQ(0, memcmp(buffer, extra_data[i].data(), extra_data[i].size()));
}
EXPECT_TRUE(patch_reader.Finish());
}
} // namespace bsdiff
+1
View File
@@ -6,6 +6,7 @@
#include <string.h>
#include "bsdiff/control_entry.h"
#include "bsdiff/logging.h"
using std::endl;
+20
View File
@@ -0,0 +1,20 @@
#include <stdint.h>
#include "bsdiff/utils.h"
namespace bsdiff {
int64_t ParseInt64(const uint8_t* buf) {
int64_t result = buf[7] & 0x7F;
for (int i = 6; i >= 0; i--) {
result <<= 8;
result |= buf[i];
}
if (buf[7] & 0x80)
result = -result;
return result;
}
} // namespace bsdiff
+16
View File
@@ -0,0 +1,16 @@
// 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_UTILS_H_
#define _BSDIFF_UTILS_H_
#include <stdint.h>
namespace bsdiff {
int64_t ParseInt64(const uint8_t* buf);
} // namespace bsdiff
#endif