Add zucchini support in update_engine

Support zucchini as a diff algorithm in delta generator.
Note we won't update the max support version until the
installation support is in place.

Bug: 197361113
Test: TH, generate a diff package
Change-Id: I92db1e7ab2ac4aef104a715c089b136f138064e9
diff --git a/payload_consumer/payload_constants.cc b/payload_consumer/payload_constants.cc
index d62a0ec..ad20f1e 100644
--- a/payload_consumer/payload_constants.cc
+++ b/payload_consumer/payload_constants.cc
@@ -34,6 +34,7 @@
 const uint32_t kPuffdiffMinorPayloadVersion = 5;
 const uint32_t kVerityMinorPayloadVersion = 6;
 const uint32_t kPartialUpdateMinorPayloadVersion = 7;
+const uint32_t kZucchiniMinorPayloadVersion = 8;
 
 const uint32_t kMinSupportedMinorPayloadVersion = kSourceMinorPayloadVersion;
 const uint32_t kMaxSupportedMinorPayloadVersion =
@@ -66,7 +67,8 @@
       return "PUFFDIFF";
     case InstallOperation::BROTLI_BSDIFF:
       return "BROTLI_BSDIFF";
-
+    case InstallOperation::ZUCCHINI:
+      return "ZUCCHINI";
     case InstallOperation::BSDIFF:
     case InstallOperation::MOVE:
       NOTREACHED();
diff --git a/payload_consumer/payload_constants.h b/payload_consumer/payload_constants.h
index 03647ee..28edf4e 100644
--- a/payload_consumer/payload_constants.h
+++ b/payload_consumer/payload_constants.h
@@ -59,6 +59,9 @@
 // The minor version that allows partial update, e.g. kernel only update.
 extern const uint32_t kPartialUpdateMinorPayloadVersion;
 
+// The minor version that allows ZUCCHINI operation.
+extern const uint32_t kZucchiniMinorPayloadVersion;
+
 // The minimum and maximum supported minor version.
 extern const uint32_t kMinSupportedMinorPayloadVersion;
 extern const uint32_t kMaxSupportedMinorPayloadVersion;
diff --git a/payload_generator/cow_size_estimator.cc b/payload_generator/cow_size_estimator.cc
index 01e9965..0578ada 100644
--- a/payload_generator/cow_size_estimator.cc
+++ b/payload_generator/cow_size_estimator.cc
@@ -150,6 +150,7 @@
       case InstallOperation::BROTLI_BSDIFF:
       case InstallOperation::PUFFDIFF:
       case InstallOperation::BSDIFF:
+      case InstallOperation::ZUCCHINI:
         // We might do something special by adding CowBsdiff to CowWriter.
         // For now proceed the same way as normal REPLACE operation.
         TEST_AND_RETURN_FALSE(
diff --git a/payload_generator/delta_diff_utils.cc b/payload_generator/delta_diff_utils.cc
index 7d36bec..46bf70e 100644
--- a/payload_generator/delta_diff_utils.cc
+++ b/payload_generator/delta_diff_utils.cc
@@ -47,7 +47,11 @@
 #include <bsdiff/control_entry.h>
 #include <bsdiff/patch_reader.h>
 #include <bsdiff/patch_writer_factory.h>
+#include <puffin/brotli_util.h>
 #include <puffin/utils.h>
+#include <zucchini/buffer_view.h>
+#include <zucchini/patch_writer.h>
+#include <zucchini/zucchini.h>
 
 #include "update_engine/common/hash_calculator.h"
 #include "update_engine/common/subprocess.h"
@@ -84,6 +88,10 @@
 // memory intensive, so we limit these operations to 150 MiB.
 const uint64_t kMaxPuffdiffDestinationSize = 150 * 1024 * 1024;  // bytes
 
+// The maximum destination size allowed for zucchini. We are conservative here
+// as zucchini tends to use more peak memory.
+const uint64_t kMaxZucchiniDestinationSize = 150 * 1024 * 1024;  // bytes
+
 const int kBrotliCompressionQuality = 11;
 
 // Storing a diff operation has more overhead over replace operation in the
@@ -181,6 +189,190 @@
 }  // namespace
 
 namespace diff_utils {
+bool BestDiffGenerator::GenerateBestDiffOperation(AnnotatedOperation* aop,
+                                                  brillo::Blob* data_blob) {
+  std::vector<std::pair<InstallOperation_Type, size_t>> diff_candidates = {
+      {InstallOperation::SOURCE_BSDIFF, kMaxBsdiffDestinationSize},
+      {InstallOperation::PUFFDIFF, kMaxPuffdiffDestinationSize},
+      {InstallOperation::ZUCCHINI, kMaxZucchiniDestinationSize},
+  };
+
+  return GenerateBestDiffOperation(diff_candidates, aop, data_blob);
+}
+
+bool BestDiffGenerator::GenerateBestDiffOperation(
+    const std::vector<std::pair<InstallOperation_Type, size_t>>&
+        diff_candidates,
+    AnnotatedOperation* aop,
+    brillo::Blob* data_blob) {
+  CHECK(aop);
+  CHECK(data_blob);
+
+  const auto& version = config_.version;
+  uint64_t input_bytes = utils::BlocksInExtents(src_extents_) * kBlockSize;
+
+  for (auto [op_type, limit] : diff_candidates) {
+    if (!version.OperationAllowed(op_type)) {
+      continue;
+    }
+
+    // Disable the specific diff algorithm when the data is too big.
+    if (input_bytes > limit) {
+      LOG(INFO) << op_type << " ignored, data too big: " << input_bytes
+                << " bytes";
+      continue;
+    }
+
+    // Prefer BROTLI_BSDIFF as it gives smaller patch size.
+    if (op_type == InstallOperation::SOURCE_BSDIFF &&
+        version.OperationAllowed(InstallOperation::BROTLI_BSDIFF)) {
+      op_type = InstallOperation::BROTLI_BSDIFF;
+    }
+
+    switch (op_type) {
+      case InstallOperation::SOURCE_BSDIFF:
+      case InstallOperation::BROTLI_BSDIFF:
+        TEST_AND_RETURN_FALSE(
+            TryBsdiffAndUpdateOperation(op_type, aop, data_blob));
+        break;
+      case InstallOperation::PUFFDIFF:
+        TEST_AND_RETURN_FALSE(TryPuffdiffAndUpdateOperation(aop, data_blob));
+        break;
+      case InstallOperation::ZUCCHINI:
+        TEST_AND_RETURN_FALSE(TryZucchiniAndUpdateOperation(aop, data_blob));
+        break;
+      default:
+        NOTREACHED();
+    }
+  }
+
+  return true;
+}
+
+bool BestDiffGenerator::TryBsdiffAndUpdateOperation(
+    InstallOperation_Type operation_type,
+    AnnotatedOperation* aop,
+    brillo::Blob* data_blob) {
+  base::FilePath patch;
+  TEST_AND_RETURN_FALSE(base::CreateTemporaryFile(&patch));
+  ScopedPathUnlinker unlinker(patch.value());
+
+  std::unique_ptr<bsdiff::PatchWriterInterface> bsdiff_patch_writer;
+  if (operation_type == InstallOperation::BROTLI_BSDIFF) {
+    bsdiff_patch_writer =
+        bsdiff::CreateBSDF2PatchWriter(patch.value(),
+                                       bsdiff::CompressorType::kBrotli,
+                                       kBrotliCompressionQuality);
+  } else {
+    bsdiff_patch_writer = bsdiff::CreateBsdiffPatchWriter(patch.value());
+  }
+
+  brillo::Blob bsdiff_delta;
+  TEST_AND_RETURN_FALSE(0 == bsdiff::bsdiff(old_data_.data(),
+                                            old_data_.size(),
+                                            new_data_.data(),
+                                            new_data_.size(),
+                                            bsdiff_patch_writer.get(),
+                                            nullptr));
+
+  TEST_AND_RETURN_FALSE(utils::ReadFile(patch.value(), &bsdiff_delta));
+  TEST_AND_RETURN_FALSE(!bsdiff_delta.empty());
+
+  InstallOperation& operation = aop->op;
+  if (IsDiffOperationBetter(operation,
+                            data_blob->size(),
+                            bsdiff_delta.size(),
+                            src_extents_.size())) {
+    if (config_.enable_vabc_xor) {
+      StoreExtents(src_extents_, operation.mutable_src_extents());
+      diff_utils::PopulateXorOps(aop, bsdiff_delta);
+    }
+    operation.set_type(operation_type);
+    *data_blob = std::move(bsdiff_delta);
+  }
+  return true;
+}
+
+bool BestDiffGenerator::TryPuffdiffAndUpdateOperation(AnnotatedOperation* aop,
+                                                      brillo::Blob* data_blob) {
+  // Find all deflate positions inside the given extents and then put all
+  // deflates together because we have already read all the extents into
+  // one buffer.
+  vector<puffin::BitExtent> src_deflates;
+  TEST_AND_RETURN_FALSE(deflate_utils::FindAndCompactDeflates(
+      src_extents_, old_deflates_, &src_deflates));
+
+  vector<puffin::BitExtent> dst_deflates;
+  TEST_AND_RETURN_FALSE(deflate_utils::FindAndCompactDeflates(
+      dst_extents_, new_deflates_, &dst_deflates));
+
+  puffin::RemoveEqualBitExtents(
+      old_data_, new_data_, &src_deflates, &dst_deflates);
+
+  // See crbug.com/915559.
+  if (config_.version.minor <= kPuffdiffMinorPayloadVersion) {
+    TEST_AND_RETURN_FALSE(
+        puffin::RemoveDeflatesWithBadDistanceCaches(old_data_, &src_deflates));
+
+    TEST_AND_RETURN_FALSE(
+        puffin::RemoveDeflatesWithBadDistanceCaches(new_data_, &dst_deflates));
+  }
+
+  // Only Puffdiff if both files have at least one deflate left.
+  if (!src_deflates.empty() && !dst_deflates.empty()) {
+    brillo::Blob puffdiff_delta;
+    ScopedTempFile temp_file("puffdiff-delta.XXXXXX");
+    // Perform PuffDiff operation.
+    TEST_AND_RETURN_FALSE(puffin::PuffDiff(old_data_,
+                                           new_data_,
+                                           src_deflates,
+                                           dst_deflates,
+                                           temp_file.path(),
+                                           &puffdiff_delta));
+    TEST_AND_RETURN_FALSE(!puffdiff_delta.empty());
+
+    InstallOperation& operation = aop->op;
+    if (IsDiffOperationBetter(operation,
+                              data_blob->size(),
+                              puffdiff_delta.size(),
+                              src_extents_.size())) {
+      operation.set_type(InstallOperation::PUFFDIFF);
+      *data_blob = std::move(puffdiff_delta);
+    }
+  }
+  return true;
+}
+
+bool BestDiffGenerator::TryZucchiniAndUpdateOperation(AnnotatedOperation* aop,
+                                                      brillo::Blob* data_blob) {
+  zucchini::ConstBufferView src_bytes(old_data_.data(), old_data_.size());
+  zucchini::ConstBufferView dst_bytes(new_data_.data(), new_data_.size());
+
+  zucchini::EnsemblePatchWriter patch_writer(src_bytes, dst_bytes);
+  auto status = zucchini::GenerateBuffer(src_bytes, dst_bytes, &patch_writer);
+  TEST_AND_RETURN_FALSE(status == zucchini::status::kStatusSuccess);
+
+  brillo::Blob zucchini_delta(patch_writer.SerializedSize());
+  patch_writer.SerializeInto({zucchini_delta.data(), zucchini_delta.size()});
+
+  // Compress the delta with brotli.
+  // TODO(197361113) support compressing the delta with different algorithms,
+  // similar to the usage in puffin.
+  brillo::Blob compressed_delta;
+  TEST_AND_RETURN_FALSE(puffin::BrotliEncode(
+      zucchini_delta.data(), zucchini_delta.size(), &compressed_delta));
+
+  InstallOperation& operation = aop->op;
+  if (IsDiffOperationBetter(operation,
+                            data_blob->size(),
+                            zucchini_delta.size(),
+                            src_extents_.size())) {
+    operation.set_type(InstallOperation::ZUCCHINI);
+    *data_blob = std::move(zucchini_delta);
+  }
+
+  return true;
+}
 
 // This class encapsulates a file delta processing thread work. The
 // processor computes the delta between the source and target files;
diff --git a/payload_generator/delta_diff_utils.h b/payload_generator/delta_diff_utils.h
index f284530..b335e2a 100644
--- a/payload_generator/delta_diff_utils.h
+++ b/payload_generator/delta_diff_utils.h
@@ -19,6 +19,7 @@
 
 #include <map>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <brillo/secure_blob.h>
@@ -99,16 +100,17 @@
 // fills in |out_op|. If there's no change in old and new files, it creates a
 // MOVE or SOURCE_COPY operation. If there is a change, the smallest of the
 // operations allowed in the given |version| (REPLACE, REPLACE_BZ, BSDIFF,
-// SOURCE_BSDIFF, or PUFFDIFF) wins.
+// SOURCE_BSDIFF, PUFFDIFF or ZUCCHINI) wins.
 // |new_extents| must not be empty. |old_deflates| and |new_deflates| are all
 // the deflate locations in |old_part| and |new_part|. Returns true on success.
+// TODO(197361113) Move logic to calculate deflates inside puffin.
 bool ReadExtentsToDiff(const std::string& old_part,
                        const std::string& new_part,
                        const std::vector<Extent>& old_extents,
                        const std::vector<Extent>& new_extents,
                        const std::vector<puffin::BitExtent>& old_deflates,
                        const std::vector<puffin::BitExtent>& new_deflates,
-                       const PayloadGenerationConfig& version,
+                       const PayloadGenerationConfig& config,
                        brillo::Blob* out_data,
                        AnnotatedOperation* out_op);
 
@@ -158,6 +160,55 @@
   return PopulateXorOps(aop, patch_data.data(), patch_data.size());
 }
 
+// A utility class that tries different algorithms and pick the patch with the
+// smallest size.
+class BestDiffGenerator {
+ public:
+  BestDiffGenerator(const brillo::Blob& old_data,
+                    const brillo::Blob& new_data,
+                    const std::vector<Extent>& src_extents,
+                    const std::vector<Extent>& dst_extents,
+                    const std::vector<puffin::BitExtent>& old_deflates,
+                    const std::vector<puffin::BitExtent>& new_deflates,
+                    const PayloadGenerationConfig& config)
+      : old_data_(old_data),
+        new_data_(new_data),
+        src_extents_(src_extents),
+        dst_extents_(dst_extents),
+        old_deflates_(old_deflates),
+        new_deflates_(new_deflates),
+        config_(config) {}
+
+  // Tries different algorithms and compares their patch sizes with the
+  // compressed full operation data in |data_blob|. If the size is smaller,
+  // updates the operation type in |aop| and bytes in |data_blob|.
+  bool GenerateBestDiffOperation(AnnotatedOperation* aop,
+                                 brillo::Blob* data_blob);
+
+  bool GenerateBestDiffOperation(
+      const std::vector<std::pair<InstallOperation_Type, size_t>>&
+          diff_candidates,
+      AnnotatedOperation* aop,
+      brillo::Blob* data_blob);
+
+ private:
+  bool TryBsdiffAndUpdateOperation(InstallOperation_Type operation_type,
+                                   AnnotatedOperation* aop,
+                                   brillo::Blob* data_blob);
+  bool TryPuffdiffAndUpdateOperation(AnnotatedOperation* aop,
+                                     brillo::Blob* data_blob);
+  bool TryZucchiniAndUpdateOperation(AnnotatedOperation* aop,
+                                     brillo::Blob* data_blob);
+
+  const brillo::Blob& old_data_;
+  const brillo::Blob& new_data_;
+  const std::vector<Extent>& src_extents_;
+  const std::vector<Extent>& dst_extents_;
+  const std::vector<puffin::BitExtent>& old_deflates_;
+  const std::vector<puffin::BitExtent>& new_deflates_;
+  const PayloadGenerationConfig& config_;
+};
+
 }  // namespace diff_utils
 
 }  // namespace chromeos_update_engine
diff --git a/payload_generator/delta_diff_utils_unittest.cc b/payload_generator/delta_diff_utils_unittest.cc
index 5e7d6f0..ede6408 100644
--- a/payload_generator/delta_diff_utils_unittest.cc
+++ b/payload_generator/delta_diff_utils_unittest.cc
@@ -306,6 +306,123 @@
   ASSERT_EQ(InstallOperation::SOURCE_BSDIFF, op.type());
 }
 
+TEST_F(DeltaDiffUtilsTest, BrotliBsdiffTest) {
+  // Makes sure SOURCE_BSDIFF operations are emitted whenever src_ops_allowed
+  // is true. It is the same setup as BsdiffSmallTest, which checks
+  // that the operation is well-formed.
+  brillo::Blob data_blob(kBlockSize);
+  test_utils::FillWithData(&data_blob);
+
+  // The old file is on a different block than the new one.
+  vector<Extent> old_extents = {ExtentForRange(1, 1)};
+  vector<Extent> new_extents = {ExtentForRange(2, 1)};
+
+  ASSERT_TRUE(WriteExtents(old_part_.path, old_extents, kBlockSize, data_blob));
+  // Modify one byte in the new file.
+  data_blob[0]++;
+  ASSERT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, data_blob));
+
+  brillo::Blob data;
+  AnnotatedOperation aop;
+  ASSERT_TRUE(diff_utils::ReadExtentsToDiff(
+      old_part_.path,
+      new_part_.path,
+      old_extents,
+      new_extents,
+      {},  // old_deflates
+      {},  // new_deflates
+      {.version = PayloadVersion(kBrilloMajorPayloadVersion,
+                                 kBrotliBsdiffMinorPayloadVersion)},
+      &data,
+      &aop));
+  auto& op = aop.op;
+  ASSERT_FALSE(data.empty());
+  ASSERT_TRUE(op.has_type());
+  ASSERT_EQ(InstallOperation::BROTLI_BSDIFF, op.type());
+}
+
+TEST_F(DeltaDiffUtilsTest, GenerateBestDiffOperation_Zucchini) {
+  // Makes sure SOURCE_BSDIFF operations are emitted whenever src_ops_allowed
+  // is true. It is the same setup as BsdiffSmallTest, which checks
+  // that the operation is well-formed.
+  brillo::Blob dst_data_blob(kBlockSize);
+  test_utils::FillWithData(&dst_data_blob);
+
+  // The old file is on a different block than the new one.
+  vector<Extent> old_extents = {ExtentForRange(1, 1)};
+  vector<Extent> new_extents = {ExtentForRange(2, 1)};
+
+  ASSERT_TRUE(
+      WriteExtents(old_part_.path, old_extents, kBlockSize, dst_data_blob));
+  // Modify one byte in the new file.
+  brillo::Blob src_data_blob = dst_data_blob;
+  src_data_blob[0]++;
+  ASSERT_TRUE(
+      WriteExtents(new_part_.path, new_extents, kBlockSize, src_data_blob));
+
+  brillo::Blob data = dst_data_blob;  // Fake the full operation
+  AnnotatedOperation aop;
+
+  diff_utils::BestDiffGenerator best_diff_generator(
+      src_data_blob,
+      dst_data_blob,
+      old_extents,
+      new_extents,
+      {},
+      {},
+      {.version = PayloadVersion(kBrilloMajorPayloadVersion,
+                                 kZucchiniMinorPayloadVersion)});
+  ASSERT_TRUE(best_diff_generator.GenerateBestDiffOperation(
+      {{InstallOperation::ZUCCHINI, 1024 * 1024}}, &aop, &data));
+
+  auto& op = aop.op;
+  ASSERT_FALSE(data.empty());
+  ASSERT_TRUE(op.has_type());
+  ASSERT_EQ(InstallOperation::ZUCCHINI, op.type());
+}
+
+TEST_F(DeltaDiffUtilsTest, GenerateBestDiffOperation_FullOperationBetter) {
+  // Makes sure SOURCE_BSDIFF operations are emitted whenever src_ops_allowed
+  // is true. It is the same setup as BsdiffSmallTest, which checks
+  // that the operation is well-formed.
+  brillo::Blob dst_data_blob(kBlockSize);
+  test_utils::FillWithData(&dst_data_blob);
+
+  // The old file is on a different block than the new one.
+  vector<Extent> old_extents = {ExtentForRange(1, 1)};
+  vector<Extent> new_extents = {ExtentForRange(2, 1)};
+
+  ASSERT_TRUE(
+      WriteExtents(old_part_.path, old_extents, kBlockSize, dst_data_blob));
+  // Modify one byte in the new file.
+  brillo::Blob src_data_blob = dst_data_blob;
+  src_data_blob[0]++;
+  ASSERT_TRUE(
+      WriteExtents(new_part_.path, new_extents, kBlockSize, src_data_blob));
+
+  brillo::Blob data(1);
+  test_utils::FillWithData(&data);  // Fake the full operation
+  AnnotatedOperation aop;
+  aop.op.set_type(InstallOperation::REPLACE_XZ);
+
+  diff_utils::BestDiffGenerator best_diff_generator(
+      src_data_blob,
+      dst_data_blob,
+      old_extents,
+      new_extents,
+      {},
+      {},
+      {.version = PayloadVersion(kBrilloMajorPayloadVersion,
+                                 kZucchiniMinorPayloadVersion)});
+  ASSERT_TRUE(best_diff_generator.GenerateBestDiffOperation(
+      {{InstallOperation::ZUCCHINI, 1024 * 1024}}, &aop, &data));
+
+  auto& op = aop.op;
+  ASSERT_EQ(1u, data.size());
+  ASSERT_TRUE(op.has_type());
+  ASSERT_EQ(InstallOperation::REPLACE_XZ, op.type());
+}
+
 TEST_F(DeltaDiffUtilsTest, PreferReplaceTest) {
   brillo::Blob data_blob(kBlockSize);
   vector<Extent> extents = {ExtentForRange(1, 1)};
diff --git a/payload_generator/payload_generation_config.cc b/payload_generator/payload_generation_config.cc
index 2cd2ebc..7484755 100644
--- a/payload_generator/payload_generation_config.cc
+++ b/payload_generator/payload_generation_config.cc
@@ -238,7 +238,8 @@
                         minor == kBrotliBsdiffMinorPayloadVersion ||
                         minor == kPuffdiffMinorPayloadVersion ||
                         minor == kVerityMinorPayloadVersion ||
-                        minor == kPartialUpdateMinorPayloadVersion);
+                        minor == kPartialUpdateMinorPayloadVersion ||
+                        minor == kZucchiniMinorPayloadVersion);
   return true;
 }
 
@@ -270,6 +271,9 @@
     case InstallOperation::PUFFDIFF:
       return minor >= kPuffdiffMinorPayloadVersion;
 
+    case InstallOperation::ZUCCHINI:
+      return minor >= kZucchiniMinorPayloadVersion;
+
     case InstallOperation::MOVE:
     case InstallOperation::BSDIFF:
       NOTREACHED();
diff --git a/scripts/update_payload/common.py b/scripts/update_payload/common.py
index b934cf8..7c6ec8f 100644
--- a/scripts/update_payload/common.py
+++ b/scripts/update_payload/common.py
@@ -62,8 +62,9 @@
   REPLACE_XZ = _CLASS.REPLACE_XZ
   PUFFDIFF = _CLASS.PUFFDIFF
   BROTLI_BSDIFF = _CLASS.BROTLI_BSDIFF
+  ZUCCHINI = _CLASS.ZUCCHINI
   ALL = (REPLACE, REPLACE_BZ, SOURCE_COPY, SOURCE_BSDIFF, ZERO,
-         DISCARD, REPLACE_XZ, PUFFDIFF, BROTLI_BSDIFF)
+         DISCARD, REPLACE_XZ, PUFFDIFF, BROTLI_BSDIFF, ZUCCHINI)
   NAMES = {
       REPLACE: 'REPLACE',
       REPLACE_BZ: 'REPLACE_BZ',
@@ -74,6 +75,7 @@
       REPLACE_XZ: 'REPLACE_XZ',
       PUFFDIFF: 'PUFFDIFF',
       BROTLI_BSDIFF: 'BROTLI_BSDIFF',
+      ZUCCHINI: 'ZUCCHINI',
   }
 
   def __init__(self):
diff --git a/scripts/update_payload/update_metadata_pb2.py b/scripts/update_payload/update_metadata_pb2.py
index a8f1bff..b62a67a 100644
--- a/scripts/update_payload/update_metadata_pb2.py
+++ b/scripts/update_payload/update_metadata_pb2.py
@@ -20,7 +20,7 @@
   package='chromeos_update_engine',
   syntax='proto2',
   serialized_options=_b('H\003'),
-  serialized_pb=_b('\n\x15update_metadata.proto\x12\x16\x63hromeos_update_engine\"1\n\x06\x45xtent\x12\x13\n\x0bstart_block\x18\x01 \x01(\x04\x12\x12\n\nnum_blocks\x18\x02 \x01(\x04\"\x9f\x01\n\nSignatures\x12@\n\nsignatures\x18\x01 \x03(\x0b\x32,.chromeos_update_engine.Signatures.Signature\x1aO\n\tSignature\x12\x13\n\x07version\x18\x01 \x01(\rB\x02\x18\x01\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x1f\n\x17unpadded_signature_size\x18\x03 \x01(\x07\"+\n\rPartitionInfo\x12\x0c\n\x04size\x18\x01 \x01(\x04\x12\x0c\n\x04hash\x18\x02 \x01(\x0c\"\x8f\x01\n\tImageInfo\x12\x11\n\x05\x62oard\x18\x01 \x01(\tB\x02\x18\x01\x12\x0f\n\x03key\x18\x02 \x01(\tB\x02\x18\x01\x12\x13\n\x07\x63hannel\x18\x03 \x01(\tB\x02\x18\x01\x12\x13\n\x07version\x18\x04 \x01(\tB\x02\x18\x01\x12\x19\n\rbuild_channel\x18\x05 \x01(\tB\x02\x18\x01\x12\x19\n\rbuild_version\x18\x06 \x01(\tB\x02\x18\x01\"\xee\x03\n\x10InstallOperation\x12;\n\x04type\x18\x01 \x02(\x0e\x32-.chromeos_update_engine.InstallOperation.Type\x12\x13\n\x0b\x64\x61ta_offset\x18\x02 \x01(\x04\x12\x13\n\x0b\x64\x61ta_length\x18\x03 \x01(\x04\x12\x33\n\x0bsrc_extents\x18\x04 \x03(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x12\n\nsrc_length\x18\x05 \x01(\x04\x12\x33\n\x0b\x64st_extents\x18\x06 \x03(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x12\n\ndst_length\x18\x07 \x01(\x04\x12\x18\n\x10\x64\x61ta_sha256_hash\x18\x08 \x01(\x0c\x12\x17\n\x0fsrc_sha256_hash\x18\t \x01(\x0c\"\xad\x01\n\x04Type\x12\x0b\n\x07REPLACE\x10\x00\x12\x0e\n\nREPLACE_BZ\x10\x01\x12\x0c\n\x04MOVE\x10\x02\x1a\x02\x08\x01\x12\x0e\n\x06\x42SDIFF\x10\x03\x1a\x02\x08\x01\x12\x0f\n\x0bSOURCE_COPY\x10\x04\x12\x11\n\rSOURCE_BSDIFF\x10\x05\x12\x0e\n\nREPLACE_XZ\x10\x08\x12\x08\n\x04ZERO\x10\x06\x12\x0b\n\x07\x44ISCARD\x10\x07\x12\x11\n\rBROTLI_BSDIFF\x10\n\x12\x0c\n\x08PUFFDIFF\x10\t\"\x81\x02\n\x11\x43owMergeOperation\x12<\n\x04type\x18\x01 \x01(\x0e\x32..chromeos_update_engine.CowMergeOperation.Type\x12\x32\n\nsrc_extent\x18\x02 \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x32\n\ndst_extent\x18\x03 \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x12\n\nsrc_offset\x18\x04 \x01(\x04\"2\n\x04Type\x12\x0c\n\x08\x43OW_COPY\x10\x00\x12\x0b\n\x07\x43OW_XOR\x10\x01\x12\x0f\n\x0b\x43OW_REPLACE\x10\x02\"\xc8\x06\n\x0fPartitionUpdate\x12\x16\n\x0epartition_name\x18\x01 \x02(\t\x12\x17\n\x0frun_postinstall\x18\x02 \x01(\x08\x12\x18\n\x10postinstall_path\x18\x03 \x01(\t\x12\x17\n\x0f\x66ilesystem_type\x18\x04 \x01(\t\x12M\n\x17new_partition_signature\x18\x05 \x03(\x0b\x32,.chromeos_update_engine.Signatures.Signature\x12\x41\n\x12old_partition_info\x18\x06 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12\x41\n\x12new_partition_info\x18\x07 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12<\n\noperations\x18\x08 \x03(\x0b\x32(.chromeos_update_engine.InstallOperation\x12\x1c\n\x14postinstall_optional\x18\t \x01(\x08\x12=\n\x15hash_tree_data_extent\x18\n \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x38\n\x10hash_tree_extent\x18\x0b \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x1b\n\x13hash_tree_algorithm\x18\x0c \x01(\t\x12\x16\n\x0ehash_tree_salt\x18\r \x01(\x0c\x12\x37\n\x0f\x66\x65\x63_data_extent\x18\x0e \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x32\n\nfec_extent\x18\x0f \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x14\n\tfec_roots\x18\x10 \x01(\r:\x01\x32\x12\x0f\n\x07version\x18\x11 \x01(\t\x12\x43\n\x10merge_operations\x18\x12 \x03(\x0b\x32).chromeos_update_engine.CowMergeOperation\x12\x19\n\x11\x65stimate_cow_size\x18\x13 \x01(\x04\"L\n\x15\x44ynamicPartitionGroup\x12\x0c\n\x04name\x18\x01 \x02(\t\x12\x0c\n\x04size\x18\x02 \x01(\x04\x12\x17\n\x0fpartition_names\x18\x03 \x03(\t\"\xd5\x01\n\x18\x44ynamicPartitionMetadata\x12=\n\x06groups\x18\x01 \x03(\x0b\x32-.chromeos_update_engine.DynamicPartitionGroup\x12\x18\n\x10snapshot_enabled\x18\x02 \x01(\x08\x12\x14\n\x0cvabc_enabled\x18\x03 \x01(\x08\x12\x1e\n\x16vabc_compression_param\x18\x04 \x01(\t\x12\x13\n\x0b\x63ow_version\x18\x05 \x01(\r\x12\x15\n\rvabc_optional\x18\x06 \x01(\x08\"c\n\x08\x41pexInfo\x12\x14\n\x0cpackage_name\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\x03\x12\x15\n\ris_compressed\x18\x03 \x01(\x08\x12\x19\n\x11\x64\x65\x63ompressed_size\x18\x04 \x01(\x03\"C\n\x0c\x41pexMetadata\x12\x33\n\tapex_info\x18\x01 \x03(\x0b\x32 .chromeos_update_engine.ApexInfo\"\x9e\x07\n\x14\x44\x65ltaArchiveManifest\x12H\n\x12install_operations\x18\x01 \x03(\x0b\x32(.chromeos_update_engine.InstallOperationB\x02\x18\x01\x12O\n\x19kernel_install_operations\x18\x02 \x03(\x0b\x32(.chromeos_update_engine.InstallOperationB\x02\x18\x01\x12\x18\n\nblock_size\x18\x03 \x01(\r:\x04\x34\x30\x39\x36\x12\x19\n\x11signatures_offset\x18\x04 \x01(\x04\x12\x17\n\x0fsignatures_size\x18\x05 \x01(\x04\x12\x42\n\x0fold_kernel_info\x18\x06 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12\x42\n\x0fnew_kernel_info\x18\x07 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12\x42\n\x0fold_rootfs_info\x18\x08 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12\x42\n\x0fnew_rootfs_info\x18\t \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12=\n\x0eold_image_info\x18\n \x01(\x0b\x32!.chromeos_update_engine.ImageInfoB\x02\x18\x01\x12=\n\x0enew_image_info\x18\x0b \x01(\x0b\x32!.chromeos_update_engine.ImageInfoB\x02\x18\x01\x12\x18\n\rminor_version\x18\x0c \x01(\r:\x01\x30\x12;\n\npartitions\x18\r \x03(\x0b\x32\'.chromeos_update_engine.PartitionUpdate\x12\x15\n\rmax_timestamp\x18\x0e \x01(\x03\x12T\n\x1a\x64ynamic_partition_metadata\x18\x0f \x01(\x0b\x32\x30.chromeos_update_engine.DynamicPartitionMetadata\x12\x16\n\x0epartial_update\x18\x10 \x01(\x08\x12\x33\n\tapex_info\x18\x11 \x03(\x0b\x32 .chromeos_update_engine.ApexInfoB\x02H\x03')
+  serialized_pb=_b('\n\x15update_metadata.proto\x12\x16\x63hromeos_update_engine\"1\n\x06\x45xtent\x12\x13\n\x0bstart_block\x18\x01 \x01(\x04\x12\x12\n\nnum_blocks\x18\x02 \x01(\x04\"\x9f\x01\n\nSignatures\x12@\n\nsignatures\x18\x01 \x03(\x0b\x32,.chromeos_update_engine.Signatures.Signature\x1aO\n\tSignature\x12\x13\n\x07version\x18\x01 \x01(\rB\x02\x18\x01\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x1f\n\x17unpadded_signature_size\x18\x03 \x01(\x07\"+\n\rPartitionInfo\x12\x0c\n\x04size\x18\x01 \x01(\x04\x12\x0c\n\x04hash\x18\x02 \x01(\x0c\"\x8f\x01\n\tImageInfo\x12\x11\n\x05\x62oard\x18\x01 \x01(\tB\x02\x18\x01\x12\x0f\n\x03key\x18\x02 \x01(\tB\x02\x18\x01\x12\x13\n\x07\x63hannel\x18\x03 \x01(\tB\x02\x18\x01\x12\x13\n\x07version\x18\x04 \x01(\tB\x02\x18\x01\x12\x19\n\rbuild_channel\x18\x05 \x01(\tB\x02\x18\x01\x12\x19\n\rbuild_version\x18\x06 \x01(\tB\x02\x18\x01\"\xfc\x03\n\x10InstallOperation\x12;\n\x04type\x18\x01 \x02(\x0e\x32-.chromeos_update_engine.InstallOperation.Type\x12\x13\n\x0b\x64\x61ta_offset\x18\x02 \x01(\x04\x12\x13\n\x0b\x64\x61ta_length\x18\x03 \x01(\x04\x12\x33\n\x0bsrc_extents\x18\x04 \x03(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x12\n\nsrc_length\x18\x05 \x01(\x04\x12\x33\n\x0b\x64st_extents\x18\x06 \x03(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x12\n\ndst_length\x18\x07 \x01(\x04\x12\x18\n\x10\x64\x61ta_sha256_hash\x18\x08 \x01(\x0c\x12\x17\n\x0fsrc_sha256_hash\x18\t \x01(\x0c\"\xbb\x01\n\x04Type\x12\x0b\n\x07REPLACE\x10\x00\x12\x0e\n\nREPLACE_BZ\x10\x01\x12\x0c\n\x04MOVE\x10\x02\x1a\x02\x08\x01\x12\x0e\n\x06\x42SDIFF\x10\x03\x1a\x02\x08\x01\x12\x0f\n\x0bSOURCE_COPY\x10\x04\x12\x11\n\rSOURCE_BSDIFF\x10\x05\x12\x0e\n\nREPLACE_XZ\x10\x08\x12\x08\n\x04ZERO\x10\x06\x12\x0b\n\x07\x44ISCARD\x10\x07\x12\x11\n\rBROTLI_BSDIFF\x10\n\x12\x0c\n\x08PUFFDIFF\x10\t\x12\x0c\n\x08ZUCCHINI\x10\x0b\"\x81\x02\n\x11\x43owMergeOperation\x12<\n\x04type\x18\x01 \x01(\x0e\x32..chromeos_update_engine.CowMergeOperation.Type\x12\x32\n\nsrc_extent\x18\x02 \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x32\n\ndst_extent\x18\x03 \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x12\n\nsrc_offset\x18\x04 \x01(\r\"2\n\x04Type\x12\x0c\n\x08\x43OW_COPY\x10\x00\x12\x0b\n\x07\x43OW_XOR\x10\x01\x12\x0f\n\x0b\x43OW_REPLACE\x10\x02\"\xc8\x06\n\x0fPartitionUpdate\x12\x16\n\x0epartition_name\x18\x01 \x02(\t\x12\x17\n\x0frun_postinstall\x18\x02 \x01(\x08\x12\x18\n\x10postinstall_path\x18\x03 \x01(\t\x12\x17\n\x0f\x66ilesystem_type\x18\x04 \x01(\t\x12M\n\x17new_partition_signature\x18\x05 \x03(\x0b\x32,.chromeos_update_engine.Signatures.Signature\x12\x41\n\x12old_partition_info\x18\x06 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12\x41\n\x12new_partition_info\x18\x07 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12<\n\noperations\x18\x08 \x03(\x0b\x32(.chromeos_update_engine.InstallOperation\x12\x1c\n\x14postinstall_optional\x18\t \x01(\x08\x12=\n\x15hash_tree_data_extent\x18\n \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x38\n\x10hash_tree_extent\x18\x0b \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x1b\n\x13hash_tree_algorithm\x18\x0c \x01(\t\x12\x16\n\x0ehash_tree_salt\x18\r \x01(\x0c\x12\x37\n\x0f\x66\x65\x63_data_extent\x18\x0e \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x32\n\nfec_extent\x18\x0f \x01(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x14\n\tfec_roots\x18\x10 \x01(\r:\x01\x32\x12\x0f\n\x07version\x18\x11 \x01(\t\x12\x43\n\x10merge_operations\x18\x12 \x03(\x0b\x32).chromeos_update_engine.CowMergeOperation\x12\x19\n\x11\x65stimate_cow_size\x18\x13 \x01(\x04\"L\n\x15\x44ynamicPartitionGroup\x12\x0c\n\x04name\x18\x01 \x02(\t\x12\x0c\n\x04size\x18\x02 \x01(\x04\x12\x17\n\x0fpartition_names\x18\x03 \x03(\t\"\xbe\x01\n\x18\x44ynamicPartitionMetadata\x12=\n\x06groups\x18\x01 \x03(\x0b\x32-.chromeos_update_engine.DynamicPartitionGroup\x12\x18\n\x10snapshot_enabled\x18\x02 \x01(\x08\x12\x14\n\x0cvabc_enabled\x18\x03 \x01(\x08\x12\x1e\n\x16vabc_compression_param\x18\x04 \x01(\t\x12\x13\n\x0b\x63ow_version\x18\x05 \x01(\r\"c\n\x08\x41pexInfo\x12\x14\n\x0cpackage_name\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\x03\x12\x15\n\ris_compressed\x18\x03 \x01(\x08\x12\x19\n\x11\x64\x65\x63ompressed_size\x18\x04 \x01(\x03\"C\n\x0c\x41pexMetadata\x12\x33\n\tapex_info\x18\x01 \x03(\x0b\x32 .chromeos_update_engine.ApexInfo\"\x9e\x07\n\x14\x44\x65ltaArchiveManifest\x12H\n\x12install_operations\x18\x01 \x03(\x0b\x32(.chromeos_update_engine.InstallOperationB\x02\x18\x01\x12O\n\x19kernel_install_operations\x18\x02 \x03(\x0b\x32(.chromeos_update_engine.InstallOperationB\x02\x18\x01\x12\x18\n\nblock_size\x18\x03 \x01(\r:\x04\x34\x30\x39\x36\x12\x19\n\x11signatures_offset\x18\x04 \x01(\x04\x12\x17\n\x0fsignatures_size\x18\x05 \x01(\x04\x12\x42\n\x0fold_kernel_info\x18\x06 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12\x42\n\x0fnew_kernel_info\x18\x07 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12\x42\n\x0fold_rootfs_info\x18\x08 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12\x42\n\x0fnew_rootfs_info\x18\t \x01(\x0b\x32%.chromeos_update_engine.PartitionInfoB\x02\x18\x01\x12=\n\x0eold_image_info\x18\n \x01(\x0b\x32!.chromeos_update_engine.ImageInfoB\x02\x18\x01\x12=\n\x0enew_image_info\x18\x0b \x01(\x0b\x32!.chromeos_update_engine.ImageInfoB\x02\x18\x01\x12\x18\n\rminor_version\x18\x0c \x01(\r:\x01\x30\x12;\n\npartitions\x18\r \x03(\x0b\x32\'.chromeos_update_engine.PartitionUpdate\x12\x15\n\rmax_timestamp\x18\x0e \x01(\x03\x12T\n\x1a\x64ynamic_partition_metadata\x18\x0f \x01(\x0b\x32\x30.chromeos_update_engine.DynamicPartitionMetadata\x12\x16\n\x0epartial_update\x18\x10 \x01(\x08\x12\x33\n\tapex_info\x18\x11 \x03(\x0b\x32 .chromeos_update_engine.ApexInfoB\x02H\x03')
 )
 
 
@@ -75,11 +75,15 @@
       name='PUFFDIFF', index=10, number=9,
       serialized_options=None,
       type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ZUCCHINI', index=11, number=11,
+      serialized_options=None,
+      type=None),
   ],
   containing_type=None,
   serialized_options=None,
   serialized_start=775,
-  serialized_end=948,
+  serialized_end=962,
 )
 _sym_db.RegisterEnumDescriptor(_INSTALLOPERATION_TYPE)
 
@@ -104,8 +108,8 @@
   ],
   containing_type=None,
   serialized_options=None,
-  serialized_start=1158,
-  serialized_end=1208,
+  serialized_start=1172,
+  serialized_end=1222,
 )
 _sym_db.RegisterEnumDescriptor(_COWMERGEOPERATION_TYPE)
 
@@ -411,7 +415,7 @@
   oneofs=[
   ],
   serialized_start=454,
-  serialized_end=948,
+  serialized_end=962,
 )
 
 
@@ -445,7 +449,7 @@
       serialized_options=None, file=DESCRIPTOR),
     _descriptor.FieldDescriptor(
       name='src_offset', full_name='chromeos_update_engine.CowMergeOperation.src_offset', index=3,
-      number=4, type=4, cpp_type=4, label=1,
+      number=4, type=13, cpp_type=3, label=1,
       has_default_value=False, default_value=0,
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
@@ -463,8 +467,8 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=951,
-  serialized_end=1208,
+  serialized_start=965,
+  serialized_end=1222,
 )
 
 
@@ -620,8 +624,8 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=1211,
-  serialized_end=2051,
+  serialized_start=1225,
+  serialized_end=2065,
 )
 
 
@@ -665,8 +669,8 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=2053,
-  serialized_end=2129,
+  serialized_start=2067,
+  serialized_end=2143,
 )
 
 
@@ -712,13 +716,6 @@
       message_type=None, enum_type=None, containing_type=None,
       is_extension=False, extension_scope=None,
       serialized_options=None, file=DESCRIPTOR),
-    _descriptor.FieldDescriptor(
-      name='vabc_optional', full_name='chromeos_update_engine.DynamicPartitionMetadata.vabc_optional', index=5,
-      number=6, type=8, cpp_type=7, label=1,
-      has_default_value=False, default_value=False,
-      message_type=None, enum_type=None, containing_type=None,
-      is_extension=False, extension_scope=None,
-      serialized_options=None, file=DESCRIPTOR),
   ],
   extensions=[
   ],
@@ -731,8 +728,8 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=2132,
-  serialized_end=2345,
+  serialized_start=2146,
+  serialized_end=2336,
 )
 
 
@@ -783,8 +780,8 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=2347,
-  serialized_end=2446,
+  serialized_start=2338,
+  serialized_end=2437,
 )
 
 
@@ -814,8 +811,8 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=2448,
-  serialized_end=2515,
+  serialized_start=2439,
+  serialized_end=2506,
 )
 
 
@@ -957,8 +954,8 @@
   extension_ranges=[],
   oneofs=[
   ],
-  serialized_start=2518,
-  serialized_end=3444,
+  serialized_start=2509,
+  serialized_end=3435,
 )
 
 _SIGNATURES_SIGNATURE.containing_type = _SIGNATURES
diff --git a/update_metadata.proto b/update_metadata.proto
index 533f6f0..6164a4d 100644
--- a/update_metadata.proto
+++ b/update_metadata.proto
@@ -187,6 +187,9 @@
 
     // On minor version 5 or newer, these operations are supported:
     PUFFDIFF = 9;  // The data is in puffdiff format.
+
+    // On minor version 8 or newer, these operations are supported:
+    ZUCCHINI = 11;
   }
   required Type type = 1;