paycheck: support minor version 3.

Added check for mandatory source operation hash in minor version 3.

Fixed unittest.

BUG=chromium:568473
TEST=./paycheck.py <minor v3 payload>; ./checker_unittest.py

Change-Id: Id96ddce8c59f28b3449524b786c54a6c69ca798f
Reviewed-on: https://chromium-review.googlesource.com/317573
Trybot-Ready: Sen Jiang <senj@chromium.org>
Tested-by: Sen Jiang <senj@chromium.org>
Reviewed-by: Alex Deymo <deymo@chromium.org>
Reviewed-by: Gilad Arnold <garnold@chromium.org>
Commit-Queue: Alex Deymo <deymo@chromium.org>
diff --git a/host/lib/update_payload/checker.py b/host/lib/update_payload/checker.py
index 47461af..c10ecce 100644
--- a/host/lib/update_payload/checker.py
+++ b/host/lib/update_payload/checker.py
@@ -55,6 +55,7 @@
     0: (_TYPE_FULL,),
     1: (_TYPE_DELTA,),
     2: (_TYPE_DELTA,),
+    3: (_TYPE_DELTA,),
 }
 
 _OLD_DELTA_USABLE_PART_SIZE = 2 * 1024 * 1024 * 1024
@@ -859,6 +860,25 @@
           '%s: total src blocks (%d) != total dst blocks (%d).' %
           (op_name, total_src_blocks, total_dst_blocks))
 
+  def _CheckSourceOperation(self, op, total_src_blocks, op_name):
+    """Specific checks for SOURCE_* operations.
+
+    Args:
+      op: The operation object from the manifest.
+      total_src_blocks: Total number of blocks in src_extents.
+      op_name: Operation name for error reporting.
+
+    Raises:
+      error.PayloadError if any check fails.
+    """
+    # Check: total_src_blocks != 0.
+    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:
+      raise error.PayloadError('%s: source hash missing.' % op_name)
+
   def _CheckOperation(self, op, op_name, is_last, old_block_counters,
                       new_block_counters, old_usable_size, new_usable_size,
                       prev_data_offset, allow_signature, blob_hash_counts):
@@ -953,11 +973,14 @@
                                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 == 2:
+    elif op.type == common.OpType.SOURCE_COPY and self.minor_version in (2, 3):
       self._CheckSourceCopyOperation(data_offset, total_src_blocks,
                                      total_dst_blocks, op_name)
-    elif op.type == common.OpType.SOURCE_BSDIFF and self.minor_version == 2:
+      self._CheckSourceOperation(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)
+      self._CheckSourceOperation(op, total_src_blocks, op_name)
     else:
       raise error.PayloadError(
           'Operation %s (type %d) not allowed in minor version %d' %
diff --git a/host/lib/update_payload/checker_unittest.py b/host/lib/update_payload/checker_unittest.py
index b761873..d7c6025 100755
--- a/host/lib/update_payload/checker_unittest.py
+++ b/host/lib/update_payload/checker_unittest.py
@@ -1079,7 +1079,8 @@
     should_succeed = (
         (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))
+        (minor_version == 2 and payload_type == checker._TYPE_DELTA) or
+        (minor_version == 3 and payload_type == checker._TYPE_DELTA))
     args = (report,)
 
     if should_succeed:
@@ -1302,7 +1303,7 @@
 
   # Add all _CheckManifestMinorVersion() test cases.
   AddParametricTests('CheckManifestMinorVersion',
-                     {'minor_version': (None, 0, 1, 2, 555),
+                     {'minor_version': (None, 0, 1, 2, 3, 555),
                       'payload_type': (checker._TYPE_FULL,
                                        checker._TYPE_DELTA)})
 
diff --git a/host/lib/update_payload/test_utils.py b/host/lib/update_payload/test_utils.py
index 8746fce..61a91f5 100644
--- a/host/lib/update_payload/test_utils.py
+++ b/host/lib/update_payload/test_utils.py
@@ -219,9 +219,11 @@
     """Writes a payload heaer to a file."""
     # We need to access protected members in Payload for writing the header.
     # pylint: disable=W0212
-    file_obj.write(payload.Payload._MAGIC)
-    _WriteInt(file_obj, payload.Payload._VERSION_SIZE, True, self.version)
-    _WriteInt(file_obj, payload.Payload._MANIFEST_LEN_SIZE, True, manifest_len)
+    file_obj.write(payload.Payload._PayloadHeader._MAGIC)
+    _WriteInt(file_obj, payload.Payload._PayloadHeader._VERSION_SIZE, True,
+              self.version)
+    _WriteInt(file_obj, payload.Payload._PayloadHeader._MANIFEST_LEN_SIZE, True,
+              manifest_len)
 
   def WriteToFile(self, file_obj, manifest_len=-1, data_blobs=None,
                   sigs_data=None, padding=None):