paycheck: Support minor version 4.

From https://chromium-review.googlesource.com/#/c/333160/

Bug: 27156473
Test: ./scripts/update_payload/checker_unittest.py

Change-Id: I940debd5c878f622970e214fce75588f96d78407
diff --git a/scripts/update_payload/applier.py b/scripts/update_payload/applier.py
index 04791c1..e3708c7 100644
--- a/scripts/update_payload/applier.py
+++ b/scripts/update_payload/applier.py
@@ -195,13 +195,14 @@
   """
 
   def __init__(self, payload, bsdiff_in_place=True, bspatch_path=None,
-               truncate_to_expected_size=True):
+               imgpatch_path=None, truncate_to_expected_size=True):
     """Initialize the applier.
 
     Args:
       payload: the payload object to check
       bsdiff_in_place: whether to perform BSDIFF operation in-place (optional)
       bspatch_path: path to the bspatch binary (optional)
+      imgpatch_path: path to the imgpatch binary (optional)
       truncate_to_expected_size: whether to truncate the resulting partitions
                                  to their expected sizes, as specified in the
                                  payload (optional)
@@ -212,6 +213,7 @@
     self.minor_version = payload.manifest.minor_version
     self.bsdiff_in_place = bsdiff_in_place
     self.bspatch_path = bspatch_path or 'bspatch'
+    self.imgpatch_path = imgpatch_path or 'imgpatch'
     self.truncate_to_expected_size = truncate_to_expected_size
 
   def _ApplyReplaceOperation(self, op, op_name, out_data, part_file, part_size):
@@ -313,8 +315,8 @@
     """
     # Implemented using a SOURCE_BSDIFF operation with the source and target
     # partition set to the new partition.
-    self._ApplySourceBsdiffOperation(op, op_name, patch_data, new_part_file,
-                                     new_part_file)
+    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):
@@ -343,9 +345,9 @@
     _WriteExtents(new_part_file, in_data, op.dst_extents, block_size,
                   '%s.dst_extents' % op_name)
 
-  def _ApplySourceBsdiffOperation(self, op, op_name, patch_data, old_part_file,
-                                  new_part_file):
-    """Applies a SOURCE_BSDIFF operation.
+  def _ApplyDiffOperation(self, op, op_name, patch_data, old_part_file,
+                          new_part_file):
+    """Applies a SOURCE_BSDIFF or IMGDIFF operation.
 
     Args:
       op: the operation object
@@ -370,7 +372,8 @@
       patch_file.write(patch_data)
 
     if (hasattr(new_part_file, 'fileno') and
-        ((not old_part_file) or hasattr(old_part_file, 'fileno'))):
+        ((not old_part_file) or hasattr(old_part_file, 'fileno')) and
+        op.type != common.OpType.IMGDIFF):
       # Construct input and output extents argument for bspatch.
       in_extents_arg, _, _ = _ExtentsToBspatchArg(
           op.src_extents, block_size, '%s.src_extents' % op_name,
@@ -406,9 +409,11 @@
         out_file_name = out_file.name
 
       # Invoke bspatch.
-      bspatch_cmd = [self.bspatch_path, in_file_name, out_file_name,
-                     patch_file_name]
-      subprocess.check_call(bspatch_cmd)
+      patch_cmd = [self.bspatch_path, in_file_name, out_file_name,
+                   patch_file_name]
+      if op.type == common.OpType.IMGDIFF:
+        patch_cmd[0] = self.imgpatch_path
+      subprocess.check_call(patch_cmd)
 
       # Read output.
       with open(out_file_name, 'rb') as out_file:
@@ -463,9 +468,9 @@
       elif op.type == common.OpType.SOURCE_COPY:
         self._ApplySourceCopyOperation(op, op_name, old_part_file,
                                        new_part_file)
-      elif op.type == common.OpType.SOURCE_BSDIFF:
-        self._ApplySourceBsdiffOperation(op, op_name, data, old_part_file,
-                                         new_part_file)
+      elif op.type in (common.OpType.SOURCE_BSDIFF, common.OpType.IMGDIFF):
+        self._ApplyDiffOperation(op, op_name, data, old_part_file,
+                                 new_part_file)
       else:
         raise PayloadError('%s: unknown operation type (%d)' %
                            (op_name, op.type))
@@ -498,7 +503,8 @@
         # Copy the src partition to the dst one; make sure we don't truncate it.
         shutil.copyfile(old_part_file_name, new_part_file_name)
       elif (self.minor_version == common.SOURCE_MINOR_PAYLOAD_VERSION or
-            self.minor_version == common.OPSRCHASH_MINOR_PAYLOAD_VERSION):
+            self.minor_version == common.OPSRCHASH_MINOR_PAYLOAD_VERSION or
+            self.minor_version == common.IMGDIFF_MINOR_PAYLOAD_VERSION):
         # In minor version >= 2, we don't want to copy the partitions, so
         # instead just make the new partition file.
         open(new_part_file_name, 'w').close()
diff --git a/scripts/update_payload/checker.py b/scripts/update_payload/checker.py
index 7abf178..e13ea13 100644
--- a/scripts/update_payload/checker.py
+++ b/scripts/update_payload/checker.py
@@ -56,6 +56,7 @@
     1: (_TYPE_DELTA,),
     2: (_TYPE_DELTA,),
     3: (_TYPE_DELTA,),
+    4: (_TYPE_DELTA,),
 }
 
 _OLD_DELTA_USABLE_PART_SIZE = 2 * 1024 * 1024 * 1024
@@ -814,8 +815,8 @@
     if dst_extent:
       raise error.PayloadError('%s: excess dst blocks.' % op_name)
 
-  def _CheckBsdiffOperation(self, data_length, total_dst_blocks, op_name):
-    """Specific checks for BSDIFF and SOURCE_BSDIFF operations.
+  def _CheckAnyDiffOperation(self, data_length, total_dst_blocks, op_name):
+    """Specific checks for BSDIFF, SOURCE_BSDIFF and IMGDIFF operations.
 
     Args:
       data_length: The length of the data blob associated with the operation.
@@ -875,8 +876,8 @@
     if total_src_blocks == 0:
       raise error.PayloadError('%s: no src blocks in a source op.' % op_name)
 
-    # Check: src_sha256_hash present in minor version 3.
-    if self.minor_version == 3 and op.src_sha256_hash is None:
+    # Check: src_sha256_hash present in minor version >= 3.
+    if self.minor_version >= 3 and op.src_sha256_hash is None:
       raise error.PayloadError('%s: source hash missing.' % op_name)
 
   def _CheckOperation(self, op, op_name, is_last, old_block_counters,
@@ -972,14 +973,16 @@
       self._CheckMoveOperation(op, data_offset, total_src_blocks,
                                total_dst_blocks, op_name)
     elif op.type == common.OpType.BSDIFF and self.minor_version == 1:
-      self._CheckBsdiffOperation(data_length, total_dst_blocks, op_name)
-    elif op.type == common.OpType.SOURCE_COPY and self.minor_version in (2, 3):
+      self._CheckAnyDiffOperation(data_length, total_dst_blocks, op_name)
+    elif op.type == common.OpType.SOURCE_COPY and self.minor_version >= 2:
       self._CheckSourceCopyOperation(data_offset, total_src_blocks,
                                      total_dst_blocks, op_name)
       self._CheckAnySourceOperation(op, total_src_blocks, op_name)
-    elif (op.type == common.OpType.SOURCE_BSDIFF and
-          self.minor_version in (2, 3)):
-      self._CheckBsdiffOperation(data_length, total_dst_blocks, op_name)
+    elif op.type == common.OpType.SOURCE_BSDIFF and self.minor_version >= 2:
+      self._CheckAnyDiffOperation(data_length, total_dst_blocks, op_name)
+      self._CheckAnySourceOperation(op, total_src_blocks, op_name)
+    elif op.type == common.OpType.IMGDIFF and self.minor_version >= 4:
+      self._CheckAnyDiffOperation(data_length, total_dst_blocks, op_name)
       self._CheckAnySourceOperation(op, total_src_blocks, op_name)
     else:
       raise error.PayloadError(
@@ -1038,6 +1041,7 @@
         common.OpType.BSDIFF: 0,
         common.OpType.SOURCE_COPY: 0,
         common.OpType.SOURCE_BSDIFF: 0,
+        common.OpType.IMGDIFF: 0,
     }
     # Total blob sizes for each operation type.
     op_blob_totals = {
@@ -1047,6 +1051,7 @@
         common.OpType.BSDIFF: 0,
         # SOURCE_COPY operations don't have blobs.
         common.OpType.SOURCE_BSDIFF: 0,
+        common.OpType.IMGDIFF: 0,
     }
     # Counts of hashed vs unhashed operations.
     blob_hash_counts = {
diff --git a/scripts/update_payload/checker_unittest.py b/scripts/update_payload/checker_unittest.py
index b05c728..56b1a30 100755
--- a/scripts/update_payload/checker_unittest.py
+++ b/scripts/update_payload/checker_unittest.py
@@ -768,24 +768,24 @@
         payload_checker._CheckMoveOperation,
         op, None, 134, 134, 'foo')
 
-  def testCheckBsdiff(self):
-    """Tests _CheckMoveOperation()."""
+  def testCheckAnyDiff(self):
+    """Tests _CheckAnyDiffOperation()."""
     payload_checker = checker.PayloadChecker(self.MockPayload())
 
     # Pass.
     self.assertIsNone(
-        payload_checker._CheckBsdiffOperation(10000, 3, 'foo'))
+        payload_checker._CheckAnyDiffOperation(10000, 3, 'foo'))
 
     # Fail, missing data blob.
     self.assertRaises(
         update_payload.PayloadError,
-        payload_checker._CheckBsdiffOperation,
+        payload_checker._CheckAnyDiffOperation,
         None, 3, 'foo')
 
     # Fail, too big of a diff blob (unjustified).
     self.assertRaises(
         update_payload.PayloadError,
-        payload_checker._CheckBsdiffOperation,
+        payload_checker._CheckAnyDiffOperation,
         10000, 2, 'foo')
 
   def testCheckSourceCopyOperation_Pass(self):
@@ -1081,7 +1081,8 @@
         (minor_version == 0 and payload_type == checker._TYPE_FULL) or
         (minor_version == 1 and payload_type == checker._TYPE_DELTA) or
         (minor_version == 2 and payload_type == checker._TYPE_DELTA) or
-        (minor_version == 3 and payload_type == checker._TYPE_DELTA))
+        (minor_version == 3 and payload_type == checker._TYPE_DELTA) or
+        (minor_version == 4 and payload_type == checker._TYPE_DELTA))
     args = (report,)
 
     if should_succeed:
@@ -1304,7 +1305,7 @@
 
   # Add all _CheckManifestMinorVersion() test cases.
   AddParametricTests('CheckManifestMinorVersion',
-                     {'minor_version': (None, 0, 1, 2, 3, 555),
+                     {'minor_version': (None, 0, 1, 2, 3, 4, 555),
                       'payload_type': (checker._TYPE_FULL,
                                        checker._TYPE_DELTA)})
 
diff --git a/scripts/update_payload/common.py b/scripts/update_payload/common.py
index 88cd6ed..678fc5d 100644
--- a/scripts/update_payload/common.py
+++ b/scripts/update_payload/common.py
@@ -27,6 +27,7 @@
 INPLACE_MINOR_PAYLOAD_VERSION = 1
 SOURCE_MINOR_PAYLOAD_VERSION = 2
 OPSRCHASH_MINOR_PAYLOAD_VERSION = 3
+IMGDIFF_MINOR_PAYLOAD_VERSION = 4
 
 #
 # Payload operation types.