| // 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/brotli_compressor.h" |
| #include "bsdiff/bz2_compressor.h" |
| #include "bsdiff/logging.h" |
| |
| 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) { |
| switch (compressor_type_) { |
| case CompressorType::kNoCompression: |
| // 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); |
| break; |
| case CompressorType::kBrotli: |
| compressor_.reset(new BrotliCompressor(brotli_quality_)); |
| if (!compressor_) { |
| LOG(ERROR) << "Error creating brotli compressor."; |
| return false; |
| } |
| break; |
| case CompressorType::kBZ2: |
| compressor_.reset(new BZ2Compressor()); |
| if (!compressor_) { |
| LOG(ERROR) << "Error creating BZ2 compressor."; |
| return false; |
| } |
| break; |
| } |
| |
| // 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"; |
| return false; |
| } |
| |
| if (!diff_data_.empty() || !extra_data_.empty()) { |
| LOG(ERROR) << "Pending data to diff/extra not flushed out on Close()"; |
| return false; |
| } |
| |
| if (compressor_) { |
| if (!compressor_->Finish()) |
| return false; |
| *patch_ = compressor_->GetCompressedData(); |
| } |
| |
| 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) { |
| if (compressor_) { |
| compressor_->Write(data, size); |
| } else { |
| 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 |