Implement REPLACE_XZ on the update_engine.

This patch introduces the REPLACE_XZ operation, very similar to
REPLACE_BZ but using the XzExtentWriter instead. Minor cleanup of the
growing list of operations included in this patch.

Bug: 23604708
Test: Added unittests.

Change-Id: Iefa9e2cda609fceab9311add16b7369cb88c98a2
diff --git a/delta_performer.cc b/delta_performer.cc
index 0071834..c8a00d3 100644
--- a/delta_performer.cc
+++ b/delta_performer.cc
@@ -47,6 +47,7 @@
 #include "update_engine/subprocess.h"
 #include "update_engine/terminator.h"
 #include "update_engine/update_attempter.h"
+#include "update_engine/xz_extent_writer.h"
 
 using google::protobuf::RepeatedPtrField;
 using std::min;
@@ -594,28 +595,28 @@
         ScopedTerminatorExitUnblocker();  // Avoids a compiler unused var bug.
 
     bool op_result;
-    if (op.type() == InstallOperation::REPLACE ||
-        op.type() == InstallOperation::REPLACE_BZ)
-      op_result = HandleOpResult(
-          PerformReplaceOperation(op, is_kernel_partition), "replace", error);
-    else if (op.type() == InstallOperation::MOVE)
-      op_result = HandleOpResult(
-          PerformMoveOperation(op, is_kernel_partition), "move", error);
-    else if (op.type() == InstallOperation::BSDIFF)
-      op_result = HandleOpResult(
-          PerformBsdiffOperation(op, is_kernel_partition), "bsdiff", error);
-    else if (op.type() == InstallOperation::SOURCE_COPY)
-      op_result =
-          HandleOpResult(PerformSourceCopyOperation(op, is_kernel_partition),
-                         "source_copy", error);
-    else if (op.type() == InstallOperation::SOURCE_BSDIFF)
-      op_result =
-          HandleOpResult(PerformSourceBsdiffOperation(op, is_kernel_partition),
-                         "source_bsdiff", error);
-    else
-      op_result = HandleOpResult(false, "unknown", error);
-
-    if (!op_result)
+    switch (op.type()) {
+      case InstallOperation::REPLACE:
+      case InstallOperation::REPLACE_BZ:
+      case InstallOperation::REPLACE_XZ:
+        op_result = PerformReplaceOperation(op, is_kernel_partition);
+        break;
+      case InstallOperation::MOVE:
+        op_result = PerformMoveOperation(op, is_kernel_partition);
+        break;
+      case InstallOperation::BSDIFF:
+        op_result = PerformBsdiffOperation(op, is_kernel_partition);
+        break;
+      case InstallOperation::SOURCE_COPY:
+        op_result = PerformSourceCopyOperation(op, is_kernel_partition);
+        break;
+      case InstallOperation::SOURCE_BSDIFF:
+        op_result = PerformSourceBsdiffOperation(op, is_kernel_partition);
+        break;
+      default:
+       op_result = false;
+    }
+    if (!HandleOpResult(op_result, InstallOperationTypeName(op.type()), error))
       return false;
 
     next_operation_num_++;
@@ -650,7 +651,8 @@
 bool DeltaPerformer::PerformReplaceOperation(const InstallOperation& operation,
                                              bool is_kernel_partition) {
   CHECK(operation.type() == InstallOperation::REPLACE ||
-        operation.type() == InstallOperation::REPLACE_BZ);
+        operation.type() == InstallOperation::REPLACE_BZ ||
+        operation.type() == InstallOperation::REPLACE_XZ);
 
   // Since we delete data off the beginning of the buffer as we use it,
   // the data we need should be exactly at the beginning of the buffer.
@@ -665,8 +667,11 @@
     chromeos::make_unique_ptr(new ZeroPadExtentWriter(
       chromeos::make_unique_ptr(new DirectExtentWriter())));
 
-  if (operation.type() == InstallOperation::REPLACE_BZ)
+  if (operation.type() == InstallOperation::REPLACE_BZ) {
     writer.reset(new BzipExtentWriter(std::move(writer)));
+  } else if (operation.type() == InstallOperation::REPLACE_XZ) {
+    writer.reset(new XzExtentWriter(std::move(writer)));
+  }
 
   // Create a vector of extents to pass to the ExtentWriter.
   vector<Extent> extents;
diff --git a/delta_performer_unittest.cc b/delta_performer_unittest.cc
index cc3d6e2..0b509e3 100644
--- a/delta_performer_unittest.cc
+++ b/delta_performer_unittest.cc
@@ -23,9 +23,9 @@
 
 #include <base/files/file_path.h>
 #include <base/files/file_util.h>
-#include <base/strings/stringprintf.h>
 #include <base/strings/string_number_conversions.h>
 #include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
 #include <google/protobuf/repeated_field.h>
 #include <gtest/gtest.h>
 
@@ -46,10 +46,8 @@
 
 using std::string;
 using std::vector;
-using testing::Return;
-using testing::_;
-using test_utils::kRandomString;
 using test_utils::System;
+using test_utils::kRandomString;
 
 extern const char* kUnittestPrivateKeyPath;
 extern const char* kUnittestPublicKeyPath;
@@ -72,6 +70,16 @@
   kValidMetadataSignature,
 };
 
+// Compressed data without checksum, generated with:
+// echo -n a | xz -9 --check=none | hexdump -v -e '"    " 12/1 "0x%02x, " "\n"'
+const uint8_t kXzCompressedData[] = {
+    0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x00, 0xff, 0x12, 0xd9, 0x41,
+    0x02, 0x00, 0x21, 0x01, 0x1c, 0x00, 0x00, 0x00, 0x10, 0xcf, 0x58, 0xcc,
+    0x01, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x11, 0x01,
+    0xad, 0xa6, 0x58, 0x04, 0x06, 0x72, 0x9e, 0x7a, 0x01, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x59, 0x5a,
+};
+
 }  // namespace
 
 class DeltaPerformerTest : public ::testing::Test {
@@ -315,6 +323,27 @@
   EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null"));
 }
 
+TEST_F(DeltaPerformerTest, ReplaceXzOperationTest) {
+  chromeos::Blob xz_data(std::begin(kXzCompressedData),
+                         std::end(kXzCompressedData));
+  // The compressed xz data contains only a single "a", but the operation should
+  // pad the rest of the two blocks with zeros.
+  chromeos::Blob expected_data = chromeos::Blob(4096, 0);
+  expected_data[0] = 'a';
+
+  AnnotatedOperation aop;
+  *(aop.op.add_dst_extents()) = ExtentForRange(0, 1);
+  aop.op.set_data_offset(0);
+  aop.op.set_data_length(xz_data.size());
+  aop.op.set_type(InstallOperation::REPLACE_XZ);
+  vector<AnnotatedOperation> aops = {aop};
+
+  chromeos::Blob payload_data = GeneratePayload(xz_data, aops, false,
+                                                kSourceMinorPayloadVersion);
+
+  EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null"));
+}
+
 TEST_F(DeltaPerformerTest, SourceCopyOperationTest) {
   chromeos::Blob expected_data = chromeos::Blob(std::begin(kRandomString),
                                                 std::end(kRandomString));
diff --git a/payload_constants.cc b/payload_constants.cc
index aeddf11..b28461b 100644
--- a/payload_constants.cc
+++ b/payload_constants.cc
@@ -31,4 +31,28 @@
 const char kDeltaMagic[] = "CrAU";
 const char kBspatchPath[] = "bspatch";
 
+const char* InstallOperationTypeName(InstallOperation_Type op_type) {
+  switch (op_type) {
+    case InstallOperation::BSDIFF:
+      return "BSDIFF";
+    case InstallOperation::MOVE:
+      return "MOVE";
+    case InstallOperation::REPLACE:
+      return "REPLACE";
+    case InstallOperation::REPLACE_BZ:
+      return "REPLACE_BZ";
+    case InstallOperation::SOURCE_COPY:
+      return "SOURCE_COPY";
+    case InstallOperation::SOURCE_BSDIFF:
+      return "SOURCE_BSDIFF";
+    case InstallOperation::ZERO:
+      return "ZERO";
+    case InstallOperation::DISCARD:
+      return "DISCARD";
+    case InstallOperation::REPLACE_XZ:
+      return "REPLACE_XZ";
+  }
+  return "<unknown_op>";
+}
+
 };  // namespace chromeos_update_engine
diff --git a/payload_constants.h b/payload_constants.h
index 21dab00..81bc36a 100644
--- a/payload_constants.h
+++ b/payload_constants.h
@@ -21,6 +21,8 @@
 
 #include <limits>
 
+#include "update_engine/update_metadata.pb.h"
+
 namespace chromeos_update_engine {
 
 // The major version used by Chrome OS.
@@ -52,6 +54,9 @@
 // section of blocks not present on disk on a sparse file.
 const uint64_t kSparseHole = std::numeric_limits<uint64_t>::max();
 
+// Return the name of the operation type.
+const char* InstallOperationTypeName(InstallOperation_Type op_type);
+
 }  // namespace chromeos_update_engine
 
 #endif  // UPDATE_ENGINE_PAYLOAD_CONSTANTS_H_
diff --git a/payload_generator/annotated_operation.cc b/payload_generator/annotated_operation.cc
index 3fbea2c..ca900ce 100644
--- a/payload_generator/annotated_operation.cc
+++ b/payload_generator/annotated_operation.cc
@@ -20,10 +20,9 @@
 #include <base/strings/string_number_conversions.h>
 #include <base/strings/stringprintf.h>
 
+#include "update_engine/payload_constants.h"
 #include "update_engine/utils.h"
 
-using std::string;
-
 namespace chromeos_update_engine {
 
 namespace {
@@ -46,30 +45,6 @@
   return true;
 }
 
-string InstallOperationTypeName(InstallOperation_Type op_type) {
-  switch (op_type) {
-    case InstallOperation::BSDIFF:
-      return "BSDIFF";
-    case InstallOperation::MOVE:
-      return "MOVE";
-    case InstallOperation::REPLACE:
-      return "REPLACE";
-    case InstallOperation::REPLACE_BZ:
-      return "REPLACE_BZ";
-    case InstallOperation::SOURCE_COPY:
-      return "SOURCE_COPY";
-    case InstallOperation::SOURCE_BSDIFF:
-      return "SOURCE_BSDIFF";
-    case InstallOperation::ZERO:
-      return "ZERO";
-    case InstallOperation::DISCARD:
-      return "DISCARD";
-    case InstallOperation::REPLACE_XZ:
-      return "REPLACE_XZ";
-  }
-  return "UNK";
-}
-
 std::ostream& operator<<(std::ostream& os, const AnnotatedOperation& aop) {
   // For example, this prints:
   // REPLACE_BZ 500 @3000
diff --git a/payload_generator/annotated_operation.h b/payload_generator/annotated_operation.h
index a6afbaa..08a0d0b 100644
--- a/payload_generator/annotated_operation.h
+++ b/payload_generator/annotated_operation.h
@@ -44,8 +44,6 @@
 // For logging purposes.
 std::ostream& operator<<(std::ostream& os, const AnnotatedOperation& aop);
 
-std::string InstallOperationTypeName(InstallOperation_Type op_type);
-
 }  // namespace chromeos_update_engine
 
 #endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_ANNOTATED_OPERATION_H_