update_payload: Add puffdiff support.

This patch adds support for applier.py to be able to apply PUFFDIFF
operation using puffin binary. It also fixes minor deficiencies for the
PUFFDIFF operation.

BUG=chromium:768461
TEST=unittests pass;
TEST=scripts/paycheck.py payload-puffin.delta new_kern.dat new_root.dat old_kern.dat old_root.dat

Change-Id: I5612ead8d8b8176e7263cfcb15403e8d36319642
Reviewed-on: https://chromium-review.googlesource.com/713540
Commit-Ready: Amin Hassani <ahassani@chromium.org>
Tested-by: Amin Hassani <ahassani@chromium.org>
Reviewed-by: Ben Chan <benchan@chromium.org>
Reviewed-by: Sen Jiang <senj@chromium.org>
diff --git a/scripts/update_payload/applier.py b/scripts/update_payload/applier.py
index cc77165..84d7e3c 100644
--- a/scripts/update_payload/applier.py
+++ b/scripts/update_payload/applier.py
@@ -213,7 +213,7 @@
     self.minor_version = payload.manifest.minor_version
     self.bsdiff_in_place = bsdiff_in_place
     self.bspatch_path = bspatch_path or 'bspatch'
-    self.puffpatch_path = puffpatch_path or 'imgpatch'
+    self.puffpatch_path = puffpatch_path or 'puffin'
     self.truncate_to_expected_size = truncate_to_expected_size
 
   def _ApplyReplaceOperation(self, op, op_name, out_data, part_file, part_size):
@@ -322,23 +322,6 @@
         part_file.seek(ex.start_block * block_size)
         part_file.write('\0' * (ex.num_blocks * block_size))
 
-  def _ApplyBsdiffOperation(self, op, op_name, patch_data, new_part_file):
-    """Applies a BSDIFF operation.
-
-    Args:
-      op: the operation object
-      op_name: name string for error reporting
-      patch_data: the binary patch content
-      new_part_file: the target partition file object
-
-    Raises:
-      PayloadError if something goes wrong.
-    """
-    # Implemented using a SOURCE_BSDIFF operation with the source and target
-    # partition set to the new partition.
-    self._ApplyDiffOperation(op, op_name, patch_data, new_part_file,
-                             new_part_file)
-
   def _ApplySourceCopyOperation(self, op, op_name, old_part_file,
                                 new_part_file):
     """Applies a SOURCE_COPY operation.
@@ -393,8 +376,7 @@
       patch_file.write(patch_data)
 
     if (hasattr(new_part_file, 'fileno') and
-        ((not old_part_file) or hasattr(old_part_file, 'fileno')) and
-        op.type != common.OpType.PUFFDIFF):
+        ((not old_part_file) or hasattr(old_part_file, 'fileno'))):
       # Construct input and output extents argument for bspatch.
       in_extents_arg, _, _ = _ExtentsToBspatchArg(
           op.src_extents, block_size, '%s.src_extents' % op_name,
@@ -407,10 +389,23 @@
       # Diff from source partition.
       old_file_name = '/dev/fd/%d' % old_part_file.fileno()
 
-      # Invoke bspatch on partition file with extents args.
-      bspatch_cmd = [self.bspatch_path, old_file_name, new_file_name,
-                     patch_file_name, in_extents_arg, out_extents_arg]
-      subprocess.check_call(bspatch_cmd)
+      if op.type in (common.OpType.BSDIFF, common.OpType.SOURCE_BSDIFF):
+        # Invoke bspatch on partition file with extents args.
+        bspatch_cmd = [self.bspatch_path, old_file_name, new_file_name,
+                       patch_file_name, in_extents_arg, out_extents_arg]
+        subprocess.check_call(bspatch_cmd)
+      elif op.type == common.OpType.PUFFDIFF:
+        # Invoke puffpatch on partition file with extents args.
+        puffpatch_cmd = [self.puffpatch_path,
+                         "--operation=puffpatch",
+                         "--src_file=%s" % old_file_name,
+                         "--dst_file=%s" % new_file_name,
+                         "--patch_file=%s" % patch_file_name,
+                         "--src_extents=%s" % in_extents_arg,
+                         "--dst_extents=%s" % out_extents_arg]
+        subprocess.check_call(puffpatch_cmd)
+      else:
+        raise PayloadError("Unknown operation %s", op.type)
 
       # Pad with zeros past the total output length.
       if pad_len:
@@ -429,12 +424,21 @@
       with tempfile.NamedTemporaryFile(delete=False) as out_file:
         out_file_name = out_file.name
 
-      # Invoke bspatch.
-      patch_cmd = [self.bspatch_path, in_file_name, out_file_name,
-                   patch_file_name]
-      if op.type == common.OpType.PUFFDIFF:
-        patch_cmd[0] = self.puffpatch_path
-      subprocess.check_call(patch_cmd)
+      if op.type in (common.OpType.BSDIFF, common.OpType.SOURCE_BSDIFF):
+        # Invoke bspatch.
+        bspatch_cmd = [self.bspatch_path, in_file_name, out_file_name,
+                       patch_file_name]
+        subprocess.check_call(bspatch_cmd)
+      elif op.type == common.OpType.PUFFDIFF:
+        # Invoke puffpatch.
+        puffpatch_cmd = [self.puffpatch_path,
+                         "--operation=puffpatch",
+                         "--src_file=%s" % in_file_name,
+                         "--dst_file=%s" % out_file_name,
+                         "--patch_file=%s" % patch_file_name]
+        subprocess.check_call(puffpatch_cmd)
+      else:
+        raise PayloadError("Unknown operation %s", op.type)
 
       # Read output.
       with open(out_file_name, 'rb') as out_file:
@@ -487,7 +491,8 @@
       elif op.type == common.OpType.ZERO:
         self._ApplyZeroOperation(op, op_name, new_part_file)
       elif op.type == common.OpType.BSDIFF:
-        self._ApplyBsdiffOperation(op, op_name, data, new_part_file)
+        self._ApplyDiffOperation(op, op_name, data, new_part_file,
+                                 new_part_file)
       elif op.type == common.OpType.SOURCE_COPY:
         self._ApplySourceCopyOperation(op, op_name, old_part_file,
                                        new_part_file)
diff --git a/scripts/update_payload/checker_unittest.py b/scripts/update_payload/checker_unittest.py
index d79b00b..eecd456 100755
--- a/scripts/update_payload/checker_unittest.py
+++ b/scripts/update_payload/checker_unittest.py
@@ -818,7 +818,7 @@
 
     Args:
       op_type_name: 'REPLACE', 'REPLACE_BZ', 'MOVE', 'BSDIFF', 'SOURCE_COPY',
-        or 'SOURCE_BSDIFF'.
+        'SOURCE_BSDIFF' or 'PUFFDIFF'.
       is_last: Whether we're testing the last operation in a sequence.
       allow_signature: Whether we're testing a signature-capable operation.
       allow_unhashed: Whether we're allowing to not hash the data.
@@ -857,7 +857,8 @@
 
     total_src_blocks = 0
     if op_type in (common.OpType.MOVE, common.OpType.BSDIFF,
-                   common.OpType.SOURCE_COPY, common.OpType.SOURCE_BSDIFF):
+                   common.OpType.SOURCE_COPY, common.OpType.SOURCE_BSDIFF,
+                   common.OpType.PUFFDIFF):
       if fail_src_extents:
         self.AddToMessage(op.src_extents,
                           self.NewExtentList((1, 0)))
@@ -872,7 +873,8 @@
       payload_checker.minor_version = 2 if fail_bad_minor_version else 1
     elif op_type in (common.OpType.SOURCE_COPY, common.OpType.SOURCE_BSDIFF):
       payload_checker.minor_version = 1 if fail_bad_minor_version else 2
-    elif op_type in (common.OpType.ZERO, common.OpType.DISCARD):
+    elif op_type in (common.OpType.ZERO, common.OpType.DISCARD,
+                     common.OpType.PUFFDIFF):
       payload_checker.minor_version = 3 if fail_bad_minor_version else 4
 
     if op_type not in (common.OpType.MOVE, common.OpType.SOURCE_COPY):
@@ -1277,7 +1279,7 @@
   AddParametricTests('CheckOperation',
                      {'op_type_name': ('REPLACE', 'REPLACE_BZ', 'MOVE',
                                        'BSDIFF', 'SOURCE_COPY',
-                                       'SOURCE_BSDIFF'),
+                                       'SOURCE_BSDIFF', 'PUFFDIFF'),
                       'is_last': (True, False),
                       'allow_signature': (True, False),
                       'allow_unhashed': (True, False),
diff --git a/update_metadata.proto b/update_metadata.proto
index f91f673..c5b2ed4 100644
--- a/update_metadata.proto
+++ b/update_metadata.proto
@@ -77,6 +77,9 @@
 // - REPLACE_XZ: Replace the dst_extents with the contents of the attached
 //   xz file after decompression. The xz file should only use crc32 or no crc at
 //   all to be compatible with xz-embedded.
+// - PUFFDIFF: Read the data in src_extents in the old partition, perform
+//   puffpatch with the attached data and write the new data to dst_extents in
+//   the new partition.
 //
 // The operations allowed in the payload (supported by the client) depend on the
 // major and minor version. See InstallOperation.Type bellow for details.