update_engine: Refactor OperationsGenerator into a base class.

This refactor cleans up the interface of the algorithms that generate
the list of rootfs and kernel operations removing the mention of a
Graph from it. The Graph class is only used by the in-place generator
because it requires to keep track of dependencies between operations
reading or writting the same block. The full update generator, using
only REPLACE or REPLACE_BZ doesn't need to use a graph to do that, but
in order to reuse some code, the interface was hacked that way.

This patch now uses two vectors of "AnnotatedOperations", which are
a mere InstallOperation as defined by the .proto file plus a name
used for logging purposes only. Both rootfs and kernel operations
have now the same type on the interface, allowing to share common
functions handling those.

BUG=chromium:331965
TEST=FEATURES=test emerge-link update_engine

Change-Id: I78566bbecb948634b7ecc8d086766ce67a79b43e
Reviewed-on: https://chromium-review.googlesource.com/262281
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Reviewed-by: Don Garrett <dgarrett@chromium.org>
Commit-Queue: Alex Deymo <deymo@chromium.org>
Trybot-Ready: Alex Deymo <deymo@chromium.org>
Tested-by: Alex Deymo <deymo@chromium.org>
diff --git a/update_engine/delta_performer_unittest.cc b/update_engine/delta_performer_unittest.cc
index fc2ac7e..e0bd95d 100644
--- a/update_engine/delta_performer_unittest.cc
+++ b/update_engine/delta_performer_unittest.cc
@@ -525,7 +525,7 @@
 
     EXPECT_TRUE(payload_config.Validate());
     EXPECT_TRUE(
-        DeltaDiffGenerator::GenerateDeltaUpdateFile(
+        GenerateUpdatePayloadFile(
             payload_config,
             state->delta_path,
             private_key,
diff --git a/update_engine/payload_generator/annotated_operation.cc b/update_engine/payload_generator/annotated_operation.cc
new file mode 100644
index 0000000..0e73479
--- /dev/null
+++ b/update_engine/payload_generator/annotated_operation.cc
@@ -0,0 +1,80 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "update_engine/payload_generator/annotated_operation.h"
+
+#include <base/format_macros.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/stringprintf.h>
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+// Output the list of extents as (start_block, num_blocks) in the passed output
+// stream.
+void OutputExtents(std::ostream* os,
+                   const google::protobuf::RepeatedPtrField<Extent>& extents) {
+  for (const auto& extent : extents) {
+    *os << " (" << extent.start_block() << ", " << extent.num_blocks() << ")";
+  }
+}
+}  // namespace
+
+void AnnotatedOperation::SetNameFromFileAndChunk(
+    const string& filename, off_t chunk_offset, off_t chunk_size) {
+  name = filename;
+  if (chunk_offset != 0 || chunk_size != -1) {
+    if (chunk_size != -1) {
+      base::StringAppendF(&name, " [%" PRId64 ", %" PRId64 ")",
+                          chunk_offset, chunk_offset + chunk_size);
+    } else {
+      base::StringAppendF(&name, " [%" PRId64 ", end)", chunk_offset);
+    }
+  }
+}
+
+string InstallOperationTypeName(
+    DeltaArchiveManifest_InstallOperation_Type op_type) {
+  switch (op_type) {
+    case DeltaArchiveManifest_InstallOperation_Type_BSDIFF:
+      return "BSDIFF";
+    case DeltaArchiveManifest_InstallOperation_Type_MOVE:
+      return "MOVE";
+    case DeltaArchiveManifest_InstallOperation_Type_REPLACE:
+      return "REPLACE";
+    case DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ:
+      return "REPLACE_BZ";
+    case DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY:
+      return "SOURCE_COPY";
+    case DeltaArchiveManifest_InstallOperation_Type_SOURCE_BSDIFF:
+      return "SOURCE_BSDIFF";
+  }
+  return "UNK";
+}
+
+std::ostream& operator<<(std::ostream& os, const AnnotatedOperation& aop) {
+  // For example, this prints:
+  // REPLACE_BZ 500 @3000
+  //   name: /foo/bar
+  //    dst: (123, 3) (127, 2)
+  os << InstallOperationTypeName(aop.op.type()) << " "  << aop.op.data_length();
+  if (aop.op.data_length() > 0)
+    os << " @" << aop.op.data_offset();
+  if (!aop.name.empty()) {
+    os << std::endl << "  name: " << aop.name;
+  }
+  if (aop.op.src_extents_size() != 0) {
+    os << std::endl << "   src:";
+    OutputExtents(&os, aop.op.src_extents());
+  }
+  if (aop.op.dst_extents_size() != 0) {
+    os << std::endl << "   dst:";
+    OutputExtents(&os, aop.op.dst_extents());
+  }
+  return os;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/annotated_operation.h b/update_engine/payload_generator/annotated_operation.h
new file mode 100644
index 0000000..e370cfe
--- /dev/null
+++ b/update_engine/payload_generator/annotated_operation.h
@@ -0,0 +1,36 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_ANNOTATED_OPERATION_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_ANNOTATED_OPERATION_H_
+
+#include <ostream>  // NOLINT(readability/streams)
+#include <string>
+
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+struct AnnotatedOperation {
+  // The name given to the operation, for logging and debugging purposes only.
+  // This normally includes the path to the file and the chunk used, if any.
+  std::string name;
+
+  // The InstallOperation, as defined by the protobuf.
+  DeltaArchiveManifest_InstallOperation op;
+
+  // Sets |name| to a human readable representation of a chunk in a file.
+  void SetNameFromFileAndChunk(const std::string& filename,
+                               off_t chunk_offset, off_t chunk_size);
+};
+
+// For logging purposes.
+std::ostream& operator<<(std::ostream& os, const AnnotatedOperation& aop);
+
+std::string InstallOperationTypeName(
+    DeltaArchiveManifest_InstallOperation_Type op_type);
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_ANNOTATED_OPERATION_H_
diff --git a/update_engine/payload_generator/delta_diff_generator.cc b/update_engine/payload_generator/delta_diff_generator.cc
index 6527bff..725442a 100644
--- a/update_engine/payload_generator/delta_diff_generator.cc
+++ b/update_engine/payload_generator/delta_diff_generator.cc
@@ -21,7 +21,6 @@
 #include <base/files/file_util.h>
 #include <base/logging.h>
 #include <base/strings/stringprintf.h>
-#include <base/strings/string_number_conversions.h>
 #include <base/strings/string_util.h>
 #include <bzlib.h>
 
@@ -114,47 +113,28 @@
   return true;
 }
 
-// Adds each operation from |graph| to |out_manifest| in the order specified by
-// |order| while building |out_op_name_map| with operation to name
-// mappings. Adds all |kernel_ops| to |out_manifest|. Filters out no-op
-// operations.
+// Adds each operation from |rootfs_ops| and |kernel_ops| to |out_manifest| in
+// the order they come in those vectors. reports the operations names
 void InstallOperationsToManifest(
-    const Graph& graph,
-    const vector<Vertex::Index>& order,
-    const vector<DeltaArchiveManifest_InstallOperation>& kernel_ops,
+    const vector<AnnotatedOperation>& rootfs_ops,
+    const vector<AnnotatedOperation>& kernel_ops,
     DeltaArchiveManifest* out_manifest,
     OperationNameMap* out_op_name_map) {
-  for (Vertex::Index vertex_index : order) {
-    const Vertex& vertex = graph[vertex_index];
-    const DeltaArchiveManifest_InstallOperation& add_op = vertex.op;
-    if (DeltaDiffGenerator::IsNoopOperation(add_op)) {
+  for (const AnnotatedOperation& aop : rootfs_ops) {
+    if (DeltaDiffGenerator::IsNoopOperation(aop.op))
       continue;
-    }
-    DeltaArchiveManifest_InstallOperation* op =
+    DeltaArchiveManifest_InstallOperation* new_op =
         out_manifest->add_install_operations();
-    *op = add_op;
-    string name = vertex.file_name;
-    if (vertex.chunk_offset || vertex.chunk_size != -1) {
-      string offset = base::Int64ToString(vertex.chunk_offset);
-      if (vertex.chunk_size != -1) {
-        name += " [" + offset + ", " +
-            base::Int64ToString(vertex.chunk_offset + vertex.chunk_size - 1) +
-            "]";
-      } else {
-        name += " [" + offset + ", end]";
-      }
-    }
-    (*out_op_name_map)[op] = name;
+    (*out_op_name_map)[new_op] = aop.name;
+    *new_op = aop.op;
   }
-  for (vector<DeltaArchiveManifest_InstallOperation>::const_iterator it =
-           kernel_ops.begin(); it != kernel_ops.end(); ++it) {
-    const DeltaArchiveManifest_InstallOperation& add_op = *it;
-    if (DeltaDiffGenerator::IsNoopOperation(add_op)) {
+  for (const AnnotatedOperation& aop : kernel_ops) {
+    if (DeltaDiffGenerator::IsNoopOperation(aop.op))
       continue;
-    }
-    DeltaArchiveManifest_InstallOperation* op =
+    DeltaArchiveManifest_InstallOperation* new_op =
         out_manifest->add_kernel_install_operations();
-    *op = add_op;
+    (*out_op_name_map)[new_op] = aop.name;
+    *new_op = aop.op;
   }
 }
 
@@ -317,12 +297,6 @@
 
 }  // namespace
 
-void DeltaDiffGenerator::CheckGraph(const Graph& graph) {
-  for (const Vertex& v : graph) {
-    CHECK(v.op.has_type());
-  }
-}
-
 bool DeltaDiffGenerator::DeltaReadFiles(Graph* graph,
                                         vector<Block>* blocks,
                                         const string& old_root,
@@ -634,7 +608,7 @@
 bool DeltaDiffGenerator::DeltaCompressKernelPartition(
     const string& old_kernel_part,
     const string& new_kernel_part,
-    vector<DeltaArchiveManifest_InstallOperation>* ops,
+    vector<AnnotatedOperation>* kernel_ops,
     int blobs_fd,
     off_t* blobs_length,
     bool src_ops_allowed) {
@@ -673,8 +647,10 @@
   }
 
   // Add the new install operation.
-  ops->clear();
-  ops->push_back(op);
+  kernel_ops->clear();
+  kernel_ops->emplace_back();
+  kernel_ops->back().op = op;
+  kernel_ops->back().name = "<kernel-delta-operation>";
 
   TEST_AND_RETURN_FALSE(utils::WriteAll(blobs_fd, data.data(), data.size()));
   *blobs_length += data.size();
@@ -684,6 +660,8 @@
   return true;
 }
 
+// TODO(deymo): Replace Vertex with AnnotatedOperation. This requires to move
+// out the code that adds the reader dependencies on the new vertex.
 bool DeltaDiffGenerator::ReadUnwrittenBlocks(
     const vector<Block>& blocks,
     int blobs_fd,
@@ -893,6 +871,14 @@
           ExpandExtents(op.src_extents()) == ExpandExtents(op.dst_extents()));
 }
 
+void DeltaDiffGenerator::FilterNoopOperations(vector<AnnotatedOperation>* ops) {
+  ops->erase(
+      std::remove_if(
+          ops->begin(), ops->end(),
+          [](const AnnotatedOperation& aop){return IsNoopOperation(aop.op);}),
+      ops->end());
+}
+
 bool DeltaDiffGenerator::ReorderDataBlobs(
     DeltaArchiveManifest* manifest,
     const string& data_blobs_path,
@@ -948,25 +934,22 @@
   return true;
 }
 
-vector<Vertex::Index> DeltaDiffGenerator::OrderIndices(const Graph& graph) {
-  vector<Vertex::Index> order(graph.size());
-  std::iota(order.begin(), order.end(), 0);
-  return order;
-}
-
-bool DeltaDiffGenerator::GenerateDeltaWithSourceOperations(
+bool DeltaDiffGenerator::GenerateOperations(
     const PayloadGenerationConfig& config,
     int data_file_fd,
     off_t* data_file_size,
-    Graph* graph,
-    vector<DeltaArchiveManifest_InstallOperation>* kernel_ops,
-    vector<Vertex::Index>* final_order) {
+    vector<AnnotatedOperation>* rootfs_ops,
+    vector<AnnotatedOperation>* kernel_ops) {
   // List of blocks in the target partition, with the operation that needs to
   // write it and the operation that needs to read it. This is used here to
   // keep track of the blocks that no operation is writting it.
   vector<Block> blocks(config.target.rootfs_size / config.block_size);
 
-  TEST_AND_RETURN_FALSE(DeltaReadFiles(graph,
+  // TODO(deymo): DeltaReadFiles() should not use a graph to generate the
+  // operations, either in the in-place or source uprate. Split out the
+  // graph dependency generation.
+  Graph graph;
+  TEST_AND_RETURN_FALSE(DeltaReadFiles(&graph,
                                        &blocks,
                                        config.source.rootfs_mountpt,
                                        config.target.rootfs_mountpt,
@@ -974,9 +957,15 @@
                                        data_file_fd,
                                        data_file_size,
                                        true));  // src_ops_allowed
+  rootfs_ops->clear();
+  for (const Vertex& v : graph) {
+    rootfs_ops->emplace_back();
+    AnnotatedOperation& aop = rootfs_ops->back();
+    aop.op = v.op;
+    aop.SetNameFromFileAndChunk(v.file_name, v.chunk_offset, v.chunk_size);
+  }
 
   LOG(INFO) << "done reading normal files";
-  CheckGraph(*graph);
 
   // Read kernel partition
   TEST_AND_RETURN_FALSE(
@@ -987,25 +976,25 @@
                                    data_file_size,
                                    true));  // src_ops_allowed
   LOG(INFO) << "done reading kernel";
-  CheckGraph(*graph);
 
-  graph->emplace_back();
+  Vertex unwritten_vertex;
   TEST_AND_RETURN_FALSE(ReadUnwrittenBlocks(blocks,
                                             data_file_fd,
                                             data_file_size,
                                             config.source.rootfs_part,
                                             config.target.rootfs_part,
-                                            &graph->back()));
-  if (graph->back().op.data_length() == 0) {
+                                            &unwritten_vertex));
+  if (unwritten_vertex.op.data_length() == 0) {
     LOG(INFO) << "No unwritten blocks to write, omitting operation";
-    graph->pop_back();
+  } else {
+    rootfs_ops->emplace_back();
+    rootfs_ops->back().op = unwritten_vertex.op;
+    rootfs_ops->back().name = unwritten_vertex.file_name;
   }
-
-  *final_order = OrderIndices(*graph);
   return true;
 }
 
-bool DeltaDiffGenerator::GenerateDeltaUpdateFile(
+bool GenerateUpdatePayloadFile(
     const PayloadGenerationConfig& config,
     const string& output_path,
     const string& private_key_path,
@@ -1038,29 +1027,26 @@
   DeltaArchiveManifest manifest;
   manifest.set_minor_version(config.minor_version);
 
-  vector<DeltaArchiveManifest_InstallOperation> kernel_ops;
-
-  Graph graph;
-  CheckGraph(graph);
-  vector<Vertex::Index> final_order;
+  vector<AnnotatedOperation> rootfs_ops;
+  vector<AnnotatedOperation> kernel_ops;
 
   // Select payload generation strategy based on the config.
-  OperationsGenerator* strategy = nullptr;
+  unique_ptr<OperationsGenerator> strategy;
   if (config.is_delta) {
     // We don't efficiently support deltas on squashfs. For now, we will
     // produce full operations in that case.
     if (utils::IsSquashfsFilesystem(config.target.rootfs_part)) {
       LOG(INFO) << "Using generator FullUpdateGenerator::Run for squashfs "
                    "deltas";
-      strategy = &FullUpdateGenerator::Run;
+      strategy.reset(new FullUpdateGenerator());
     } else if (utils::IsExtFilesystem(config.target.rootfs_part)) {
       // Delta update (with possibly a full kernel update).
       if (config.minor_version == kInPlaceMinorPayloadVersion) {
         LOG(INFO) << "Using generator InplaceGenerator::GenerateInplaceDelta";
-        strategy = &InplaceGenerator::GenerateInplaceDelta;
+        strategy.reset(new InplaceGenerator());
       } else if (config.minor_version == kSourceMinorPayloadVersion) {
         LOG(INFO) << "Using generator DeltaDiffGenerator::GenerateSourceDelta";
-        strategy = &DeltaDiffGenerator::GenerateDeltaWithSourceOperations;
+        strategy.reset(new DeltaDiffGenerator());
       } else {
         LOG(ERROR) << "Unsupported minor version given for delta payload: "
                    << config.minor_version;
@@ -1074,7 +1060,7 @@
   } else {
     // Full update.
     LOG(INFO) << "Using generator FullUpdateGenerator::Run";
-    strategy = &FullUpdateGenerator::Run;
+    strategy.reset(new FullUpdateGenerator());
   }
 
   {
@@ -1086,12 +1072,11 @@
     ScopedFdCloser data_file_fd_closer(&data_file_fd);
 
     // Generate the operations using the strategy we selected above.
-    TEST_AND_RETURN_FALSE((*strategy)(config,
-                                      data_file_fd,
-                                      &data_file_size,
-                                      &graph,
-                                      &kernel_ops,
-                                      &final_order));
+    TEST_AND_RETURN_FALSE(strategy->GenerateOperations(config,
+                                                       data_file_fd,
+                                                       &data_file_size,
+                                                       &rootfs_ops,
+                                                       &kernel_ops));
   }
 
   if (!config.source.ImageInfoIsEmpty())
@@ -1100,26 +1085,27 @@
   if (!config.target.ImageInfoIsEmpty())
     *(manifest.mutable_new_image_info()) = config.target.image_info;
 
+  // Filter the no-operations. OperationsGenerators should not output this kind
+  // of operations normally, but this is an extra step to fix that if
+  // happened.
+  DeltaDiffGenerator::FilterNoopOperations(&rootfs_ops);
+  DeltaDiffGenerator::FilterNoopOperations(&kernel_ops);
+
   OperationNameMap op_name_map;
-  CheckGraph(graph);
-  InstallOperationsToManifest(graph,
-                              final_order,
-                              kernel_ops,
-                              &manifest,
-                              &op_name_map);
-  CheckGraph(graph);
+  InstallOperationsToManifest(rootfs_ops, kernel_ops, &manifest, &op_name_map);
   manifest.set_block_size(config.block_size);
 
-  // Reorder the data blobs with the newly ordered manifest
+  // Reorder the data blobs with the newly ordered manifest.
   string ordered_blobs_path;
   TEST_AND_RETURN_FALSE(utils::MakeTempFile(
       "CrAU_temp_data.ordered.XXXXXX",
       &ordered_blobs_path,
       nullptr));
   ScopedPathUnlinker ordered_blobs_unlinker(ordered_blobs_path);
-  TEST_AND_RETURN_FALSE(ReorderDataBlobs(&manifest,
-                                         temp_file_path,
-                                         ordered_blobs_path));
+  TEST_AND_RETURN_FALSE(
+      DeltaDiffGenerator::ReorderDataBlobs(&manifest,
+                                           temp_file_path,
+                                           ordered_blobs_path));
   temp_file_unlinker.reset();
 
   // Check that install op blobs are in order.
@@ -1149,7 +1135,8 @@
     TEST_AND_RETURN_FALSE(
         PayloadSigner::SignatureBlobLength(vector<string>(1, private_key_path),
                                            &signature_blob_length));
-    AddSignatureOp(next_blob_offset, signature_blob_length, &manifest);
+    DeltaDiffGenerator::AddSignatureOp(
+        next_blob_offset, signature_blob_length, &manifest);
   }
 
   TEST_AND_RETURN_FALSE(InitializePartitionInfos(config, &manifest));
@@ -1157,9 +1144,7 @@
   // Serialize protobuf
   string serialized_manifest;
 
-  CheckGraph(graph);
   TEST_AND_RETURN_FALSE(manifest.AppendToString(&serialized_manifest));
-  CheckGraph(graph);
 
   LOG(INFO) << "Writing final delta file header...";
   DirectFileWriter writer;
diff --git a/update_engine/payload_generator/delta_diff_generator.h b/update_engine/payload_generator/delta_diff_generator.h
index 299b793..6004d31 100644
--- a/update_engine/payload_generator/delta_diff_generator.h
+++ b/update_engine/payload_generator/delta_diff_generator.h
@@ -15,6 +15,7 @@
 #include "update_engine/payload_constants.h"
 #include "update_engine/payload_generator/graph_types.h"
 #include "update_engine/payload_generator/graph_utils.h"
+#include "update_engine/payload_generator/operations_generator.h"
 #include "update_engine/payload_generator/payload_generation_config.h"
 #include "update_engine/update_metadata.pb.h"
 
@@ -31,34 +32,10 @@
 extern const size_t kBlockSize;
 extern const size_t kRootFSPartitionSize;
 
-// The payload generation strategy prototype. This is the function that does
-// all the work to generate the operations for the rootfs and the kernel.
-// Given the |config|, generates the payload by populating the |graph| with
-// the operations required to update the rootfs when applied in the order
-// specified by |final_order|, and populating |kernel_ops| with the list of
-// kernel operations required to update the kernel.
-// The operations returned will refer to offsets in the file |data_file_fd|,
-// where this function stores the output, but not necessarily in the same
-// order as they appear in the |final_order| and |kernel_ops|.
-// This function stores the amount of data written to |data_file_fd| in
-// |data_file_size|.
-// TODO(deymo): Replace |graph|, |kernel_ops| and |final_order| by two vectors
-// of annotated InstallOperation (InstallOperation plus a name for logging
-// purposes). At this level, there's no need to have a graph.
-// TODO(deymo): Convert this type alias into a base class, so other parameter
-// can be passed to the particular generators while keeping the same interface
-// for operation generation.
-using OperationsGenerator = bool(
-      const PayloadGenerationConfig& /* config */,
-      int /* data_file_fd */,
-      off_t* /* data_file_size */,
-      Graph* /* graph */,
-      std::vector<DeltaArchiveManifest_InstallOperation>* /* kernel_ops */,
-      std::vector<Vertex::Index>* /* final_order */);
-
-
-class DeltaDiffGenerator {
+class DeltaDiffGenerator : public OperationsGenerator {
  public:
+  DeltaDiffGenerator() = default;
+
   // Represents a disk block on the install partition.
   struct Block {
     // During install, each block on the install partition will be written
@@ -74,22 +51,6 @@
     Vertex::Index writer;
   };
 
-  // This is the only function that external users of the class should call.
-  // The |config| describes the payload generation request, describing both
-  // old and new images for delta payloads and only the new image for full
-  // payloads.
-  // For delta payloads, the images should be already mounted read-only at
-  // the respective rootfs_mountpt.
-  // |private_key_path| points to a private key used to sign the update.
-  // Pass empty string to not sign the update.
-  // |output_path| is the filename where the delta update should be written.
-  // Returns true on success. Also writes the size of the metadata into
-  // |metadata_size|.
-  static bool GenerateDeltaUpdateFile(const PayloadGenerationConfig& config,
-                                      const std::string& output_path,
-                                      const std::string& private_key_path,
-                                      uint64_t* metadata_size);
-
   // These functions are public so that the unit tests can access them:
 
   // Generate the update payload operations for the kernel and rootfs using
@@ -97,18 +58,17 @@
   // kSourceMinorPayloadVersion. This function will generate operations in the
   // rootfs that will read blocks from the source partition in random order and
   // write the new image on the target partition, also possibly in random order.
-  // The rootfs operations are stored in |graph| and should be executed in the
-  // |final_order| order. The kernel operations are stored in |kernel_ops|. All
+  // The rootfs operations are stored in |rootfs_ops| and should be executed in
+  // that order. The kernel operations are stored in |kernel_ops|. All
   // the offsets in the operations reference the data written to |data_file_fd|.
   // The total amount of data written to that file is stored in
   // |data_file_size|.
-  static bool GenerateDeltaWithSourceOperations(
+  bool GenerateOperations(
       const PayloadGenerationConfig& config,
       int data_file_fd,
       off_t* data_file_size,
-      Graph* graph,
-      std::vector<DeltaArchiveManifest_InstallOperation>* kernel_ops,
-      std::vector<Vertex::Index>* final_order);
+      std::vector<AnnotatedOperation>* rootfs_ops,
+      std::vector<AnnotatedOperation>* kernel_ops) override;
 
   // For each regular file within new_root, creates a node in the graph,
   // determines the best way to compress it (REPLACE, REPLACE_BZ, COPY, BSDIFF),
@@ -170,7 +130,7 @@
   static bool DeltaCompressKernelPartition(
       const std::string& old_kernel_part,
       const std::string& new_kernel_part,
-      std::vector<DeltaArchiveManifest_InstallOperation>* ops,
+      std::vector<AnnotatedOperation>* ops,
       int blobs_fd,
       off_t* blobs_length,
       bool src_ops_allowed);
@@ -218,6 +178,10 @@
   // (e.g., a move operation that copies blocks onto themselves).
   static bool IsNoopOperation(const DeltaArchiveManifest_InstallOperation& op);
 
+  // Filters all the operations that are no-op, maintaining the relative order
+  // of the rest of the operations.
+  static void FilterNoopOperations(std::vector<AnnotatedOperation>* ops);
+
   static bool InitializePartitionInfo(bool is_kernel,
                                       const std::string& partition,
                                       PartitionInfo* info);
@@ -234,10 +198,6 @@
                              uint64_t signature_blob_length,
                              DeltaArchiveManifest* manifest);
 
-  // Takes |graph| and returns a vertex order with the vertex indices of
-  // |graph|, in the order they appear. Returns |true| on success.
-  static std::vector<Vertex::Index> OrderIndices(const Graph& graph);
-
   // Takes a collection (vector or RepeatedPtrField) of Extent and
   // returns a vector of the blocks referenced, in order.
   template<typename T>
@@ -257,13 +217,27 @@
     return ret;
   }
 
-  static void CheckGraph(const Graph& graph);
-
  private:
-  // This should never be constructed.
-  DISALLOW_IMPLICIT_CONSTRUCTORS(DeltaDiffGenerator);
+  DISALLOW_COPY_AND_ASSIGN(DeltaDiffGenerator);
 };
 
+// This is the only function that external users of this module should call.
+// The |config| describes the payload generation request, describing both
+// old and new images for delta payloads and only the new image for full
+// payloads.
+// For delta payloads, the images should be already mounted read-only at
+// the respective rootfs_mountpt.
+// |private_key_path| points to a private key used to sign the update.
+// Pass empty string to not sign the update.
+// |output_path| is the filename where the delta update should be written.
+// Returns true on success. Also writes the size of the metadata into
+// |metadata_size|.
+bool GenerateUpdatePayloadFile(const PayloadGenerationConfig& config,
+                               const std::string& output_path,
+                               const std::string& private_key_path,
+                               uint64_t* metadata_size);
+
+
 };  // namespace chromeos_update_engine
 
 #endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_GENERATOR_H_
diff --git a/update_engine/payload_generator/delta_diff_generator_unittest.cc b/update_engine/payload_generator/delta_diff_generator_unittest.cc
index 9f938be..22d9c12 100644
--- a/update_engine/payload_generator/delta_diff_generator_unittest.cc
+++ b/update_engine/payload_generator/delta_diff_generator_unittest.cc
@@ -26,7 +26,6 @@
 #include "update_engine/payload_generator/extent_mapper.h"
 #include "update_engine/payload_generator/graph_types.h"
 #include "update_engine/payload_generator/graph_utils.h"
-#include "update_engine/payload_generator/topological_sort.h"
 #include "update_engine/subprocess.h"
 #include "update_engine/test_utils.h"
 #include "update_engine/utils.h"
@@ -556,14 +555,26 @@
   EXPECT_FALSE(DeltaDiffGenerator::IsNoopOperation(op));
 }
 
-TEST_F(DeltaDiffGeneratorTest, OrderIndicesTest) {
-  Graph graph(3);
-  vector<Vertex::Index> order;
-  order = DeltaDiffGenerator::OrderIndices(graph);
-  EXPECT_EQ(order.size(), 3);
-  EXPECT_EQ(order[0], 0);
-  EXPECT_EQ(order[1], 1);
-  EXPECT_EQ(order[2], 2);
+TEST_F(DeltaDiffGeneratorTest, FilterNoopOperations) {
+  AnnotatedOperation aop1;
+  aop1.op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
+  *(aop1.op.add_dst_extents()) = ExtentForRange(3, 2);
+  aop1.name = "aop1";
+
+  AnnotatedOperation aop2 = aop1;
+  aop2.name = "aop2";
+
+  AnnotatedOperation noop;
+  noop.op.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE);
+  *(noop.op.add_src_extents()) = ExtentForRange(3, 2);
+  *(noop.op.add_dst_extents()) = ExtentForRange(3, 2);
+  noop.name = "noop";
+
+  vector<AnnotatedOperation> ops = {noop, aop1, noop, noop, aop2, noop};
+  DeltaDiffGenerator::FilterNoopOperations(&ops);
+  EXPECT_EQ(2u, ops.size());
+  EXPECT_EQ("aop1", ops[0].name);
+  EXPECT_EQ("aop2", ops[1].name);
 }
 
 }  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/full_update_generator.cc b/update_engine/payload_generator/full_update_generator.cc
index 0259b7d..3513746 100644
--- a/update_engine/payload_generator/full_update_generator.cc
+++ b/update_engine/payload_generator/full_update_generator.cc
@@ -110,16 +110,17 @@
 
 }  // namespace
 
-bool FullUpdateGenerator::Run(
+bool FullUpdateGenerator::GenerateOperations(
     const PayloadGenerationConfig& config,
     int fd,
     off_t* data_file_size,
-    Graph* graph,
-    vector<DeltaArchiveManifest_InstallOperation>* kernel_ops,
-    vector<Vertex::Index>* final_order) {
+    vector<AnnotatedOperation>* rootfs_ops,
+    vector<AnnotatedOperation>* kernel_ops) {
   TEST_AND_RETURN_FALSE(config.Validate());
+  rootfs_ops->clear();
+  kernel_ops->clear();
 
-  // FullUpdateGenerator requires a positive chuck_size, otherwise there will
+  // FullUpdateGenerator requires a positive chunk_size, otherwise there will
   // be only one operation with the whole partition which should not be allowed.
   size_t full_chunk_size = kDefaultFullChunkSize;
   if (config.chunk_size >= 0) {
@@ -167,14 +168,15 @@
 
       DeltaArchiveManifest_InstallOperation* op = nullptr;
       if (partition == 0) {
-        graph->emplace_back();
-        graph->back().file_name =
+        rootfs_ops->emplace_back();
+        rootfs_ops->back().name =
             base::StringPrintf("<rootfs-operation-%" PRIuS ">", counter++);
-        op = &graph->back().op;
-        final_order->push_back(graph->size() - 1);
+        op = &rootfs_ops->back().op;
       } else {
-        kernel_ops->resize(kernel_ops->size() + 1);
-        op = &kernel_ops->back();
+        kernel_ops->emplace_back();
+        kernel_ops->back().name =
+            base::StringPrintf("<kernel-operation-%" PRIuS ">", counter++);
+        op = &kernel_ops->back().op;
       }
 
       const bool compress = processor->ShouldCompress();
diff --git a/update_engine/payload_generator/full_update_generator.h b/update_engine/payload_generator/full_update_generator.h
index b741163..b4d1135 100644
--- a/update_engine/payload_generator/full_update_generator.h
+++ b/update_engine/payload_generator/full_update_generator.h
@@ -8,29 +8,31 @@
 #include <string>
 #include <vector>
 
-#include "update_engine/payload_generator/graph_types.h"
+#include <base/macros.h>
+
+#include "update_engine/payload_generator/operations_generator.h"
 #include "update_engine/payload_generator/payload_generation_config.h"
 
 namespace chromeos_update_engine {
 
-class FullUpdateGenerator {
+class FullUpdateGenerator : public OperationsGenerator {
  public:
+  FullUpdateGenerator() = default;
+
   // Creates a full update for the target image defined in |config|. |config|
   // must be a valid payload generation configuration for a full payload.
-  // Populates |graph|, |kernel_ops|, and |final_order|, with data about the
-  // update operations, and writes relevant data to |data_file_fd|, updating
+  // Populates |rootfs_ops| and |kernel_ops|, with data about the update
+  // operations, and writes relevant data to |data_file_fd|, updating
   // |data_file_size| as it does.
-  static bool Run(
+  bool GenerateOperations(
       const PayloadGenerationConfig& config,
       int data_file_fd,
       off_t* data_file_size,
-      Graph* graph,
-      std::vector<DeltaArchiveManifest_InstallOperation>* kernel_ops,
-      std::vector<Vertex::Index>* final_order);
+      std::vector<AnnotatedOperation>* rootfs_ops,
+      std::vector<AnnotatedOperation>* kernel_ops) override;
 
  private:
-  // This should never be constructed.
-  DISALLOW_IMPLICIT_CONSTRUCTORS(FullUpdateGenerator);
+  DISALLOW_COPY_AND_ASSIGN(FullUpdateGenerator);
 };
 
 }  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/full_update_generator_unittest.cc b/update_engine/payload_generator/full_update_generator_unittest.cc
index 33b219e..c69f2ee 100644
--- a/update_engine/payload_generator/full_update_generator_unittest.cc
+++ b/update_engine/payload_generator/full_update_generator_unittest.cc
@@ -65,32 +65,30 @@
   ScopedFdCloser out_blobs_fd_closer(&out_blobs_fd);
 
   off_t out_blobs_length = 0;
-  Graph graph;
-  vector<DeltaArchiveManifest_InstallOperation> kernel_ops;
-  vector<Vertex::Index> final_order;
+  vector<AnnotatedOperation> rootfs_ops;
+  vector<AnnotatedOperation> kernel_ops;
 
-  EXPECT_TRUE(FullUpdateGenerator::Run(config_,
-                                       out_blobs_fd,
-                                       &out_blobs_length,
-                                       &graph,
-                                       &kernel_ops,
-                                       &final_order));
-  int64_t target_rootfs_chucks =
+  FullUpdateGenerator generator;
+
+  EXPECT_TRUE(generator.GenerateOperations(config_,
+                                           out_blobs_fd,
+                                           &out_blobs_length,
+                                           &rootfs_ops,
+                                           &kernel_ops));
+  int64_t target_rootfs_chunks =
       config_.target.rootfs_size / config_.chunk_size;
-  EXPECT_EQ(target_rootfs_chucks, graph.size());
-  EXPECT_EQ(target_rootfs_chucks, final_order.size());
+  EXPECT_EQ(target_rootfs_chunks, rootfs_ops.size());
   EXPECT_EQ(new_kern.size() / config_.chunk_size, kernel_ops.size());
-  for (off_t i = 0; i < target_rootfs_chucks; ++i) {
-    EXPECT_EQ(i, final_order[i]);
-    EXPECT_EQ(1, graph[i].op.dst_extents_size());
+  for (off_t i = 0; i < target_rootfs_chunks; ++i) {
+    EXPECT_EQ(1, rootfs_ops[i].op.dst_extents_size());
     EXPECT_EQ(i * config_.chunk_size / config_.block_size,
-              graph[i].op.dst_extents(0).start_block()) << "i = " << i;
+              rootfs_ops[i].op.dst_extents(0).start_block()) << "i = " << i;
     EXPECT_EQ(config_.chunk_size / config_.block_size,
-              graph[i].op.dst_extents(0).num_blocks());
-    if (graph[i].op.type() !=
+              rootfs_ops[i].op.dst_extents(0).num_blocks());
+    if (rootfs_ops[i].op.type() !=
         DeltaArchiveManifest_InstallOperation_Type_REPLACE) {
       EXPECT_EQ(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ,
-                graph[i].op.type());
+                rootfs_ops[i].op.type());
     }
   }
 }
diff --git a/update_engine/payload_generator/generate_delta_main.cc b/update_engine/payload_generator/generate_delta_main.cc
index 1b558be..38e2c21 100644
--- a/update_engine/payload_generator/generate_delta_main.cc
+++ b/update_engine/payload_generator/generate_delta_main.cc
@@ -418,11 +418,10 @@
   }
 
   uint64_t metadata_size;
-  if (!DeltaDiffGenerator::GenerateDeltaUpdateFile(
-      payload_config,
-      FLAGS_out_file,
-      FLAGS_private_key,
-      &metadata_size)) {
+  if (!GenerateUpdatePayloadFile(payload_config,
+                                 FLAGS_out_file,
+                                 FLAGS_private_key,
+                                 &metadata_size)) {
     return 1;
   }
 
diff --git a/update_engine/payload_generator/graph_utils.cc b/update_engine/payload_generator/graph_utils.cc
index 2d8c051..3f9b28f 100644
--- a/update_engine/payload_generator/graph_utils.cc
+++ b/update_engine/payload_generator/graph_utils.cc
@@ -12,6 +12,7 @@
 #include <base/macros.h>
 
 #include "update_engine/payload_constants.h"
+#include "update_engine/payload_generator/annotated_operation.h"
 
 using std::make_pair;
 using std::pair;
@@ -138,32 +139,11 @@
 void DumpGraph(const Graph& graph) {
   LOG(INFO) << "Graph length: " << graph.size();
   for (Graph::size_type i = 0, e = graph.size(); i != e; ++i) {
-    string type_str = "UNK";
-    switch (graph[i].op.type()) {
-      case DeltaArchiveManifest_InstallOperation_Type_BSDIFF:
-        type_str = "BSDIFF";
-        break;
-      case DeltaArchiveManifest_InstallOperation_Type_MOVE:
-        type_str = "MOVE";
-        break;
-      case DeltaArchiveManifest_InstallOperation_Type_REPLACE:
-        type_str = "REPLACE";
-        break;
-      case DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ:
-        type_str = "REPLACE_BZ";
-        break;
-      case DeltaArchiveManifest_InstallOperation_Type_SOURCE_COPY:
-        type_str = "SOURCE_COPY";
-        break;
-      case DeltaArchiveManifest_InstallOperation_Type_SOURCE_BSDIFF:
-        type_str = "SOURCE_BSDIFF";
-        break;
-    }
     LOG(INFO) << i
               << (graph[i].valid ? "" : "-INV")
               << ": " << graph[i].file_name
               << " " << graph[i].chunk_size << "@" << graph[i].chunk_offset
-              << ": " << type_str;
+              << ": " << InstallOperationTypeName(graph[i].op.type());
     LOG(INFO) << "  src_extents:";
     DumpExtents(graph[i].op.src_extents(), 4);
     LOG(INFO) << "  dst_extents:";
diff --git a/update_engine/payload_generator/inplace_generator.cc b/update_engine/payload_generator/inplace_generator.cc
index ca2d398..0e3284c 100644
--- a/update_engine/payload_generator/inplace_generator.cc
+++ b/update_engine/payload_generator/inplace_generator.cc
@@ -61,6 +61,12 @@
   return new_extents;
 }
 
+void InplaceGenerator::CheckGraph(const Graph& graph) {
+  for (const Vertex& v : graph) {
+    CHECK(v.op.has_type());
+  }
+}
+
 void InplaceGenerator::SubstituteBlocks(
     Vertex* vertex,
     const vector<Extent>& remove_extents,
@@ -565,7 +571,7 @@
   set<Edge> cut_edges;
   cycle_breaker.BreakCycles(*graph, &cut_edges);
   LOG(INFO) << "done finding cycles";
-  DeltaDiffGenerator::CheckGraph(*graph);
+  CheckGraph(*graph);
 
   // Calculate number of scratch blocks needed
 
@@ -574,12 +580,12 @@
   TEST_AND_RETURN_FALSE(CutEdges(graph, cut_edges, &cuts));
   LOG(INFO) << "done cutting cycles";
   LOG(INFO) << "There are " << cuts.size() << " cuts.";
-  DeltaDiffGenerator::CheckGraph(*graph);
+  CheckGraph(*graph);
 
   LOG(INFO) << "Creating initial topological order...";
   TopologicalSort(*graph, final_order);
   LOG(INFO) << "done with initial topo order";
-  DeltaDiffGenerator::CheckGraph(*graph);
+  CheckGraph(*graph);
 
   LOG(INFO) << "Moving full ops to the back";
   MoveFullOpsToBack(graph, final_order);
@@ -626,13 +632,6 @@
   extent->set_num_blocks(num_blocks);
 }
 
-// The |blocks| vector contains a reader and writer for each block on the
-// filesystem that's being in-place updated. We populate the reader/writer
-// fields of |blocks| by calling this function.
-// For each block in |operation| that is read or written, find that block
-// in |blocks| and set the reader/writer field to the vertex passed.
-// |graph| is not strictly necessary, but useful for printing out
-// error messages.
 bool InplaceGenerator::AddInstallOpToBlocksVector(
     const DeltaArchiveManifest_InstallOperation& operation,
     const Graph& graph,
@@ -676,16 +675,18 @@
   return true;
 }
 
-bool InplaceGenerator::GenerateInplaceDelta(
+bool InplaceGenerator::GenerateOperations(
     const PayloadGenerationConfig& config,
     int data_file_fd,
     off_t* data_file_size,
-    Graph* graph,
-    vector<DeltaArchiveManifest_InstallOperation>* kernel_ops,
-    vector<Vertex::Index>* final_order) {
+    vector<AnnotatedOperation>* rootfs_ops,
+    vector<AnnotatedOperation>* kernel_ops) {
+  Graph graph;
+  CheckGraph(graph);
   vector<Block> blocks(config.target.rootfs_size / config.block_size);
+
   TEST_AND_RETURN_FALSE(
-      DeltaDiffGenerator::DeltaReadFiles(graph,
+      DeltaDiffGenerator::DeltaReadFiles(&graph,
                                          &blocks,
                                          config.source.rootfs_mountpt,
                                          config.target.rootfs_mountpt,
@@ -694,41 +695,41 @@
                                          data_file_size,
                                          false));  // src_ops_allowed
   LOG(INFO) << "done reading normal files";
-  DeltaDiffGenerator::CheckGraph(*graph);
+  CheckGraph(graph);
 
   LOG(INFO) << "Starting metadata processing";
   TEST_AND_RETURN_FALSE(Metadata::DeltaReadMetadata(
-      graph,
+      &graph,
       &blocks,
       config.source.rootfs_part,
       config.target.rootfs_part,
       data_file_fd,
       data_file_size));
   LOG(INFO) << "Done metadata processing";
-  DeltaDiffGenerator::CheckGraph(*graph);
+  CheckGraph(graph);
 
-  graph->emplace_back();
+  graph.emplace_back();
   TEST_AND_RETURN_FALSE(
       DeltaDiffGenerator::ReadUnwrittenBlocks(blocks,
                                               data_file_fd,
                                               data_file_size,
                                               config.source.rootfs_part,
                                               config.target.rootfs_part,
-                                              &graph->back()));
-  if (graph->back().op.data_length() == 0) {
+                                              &graph.back()));
+  if (graph.back().op.data_length() == 0) {
     LOG(INFO) << "No unwritten blocks to write, omitting operation";
-    graph->pop_back();
+    graph.pop_back();
   }
 
   // Final scratch block (if there's space)
   Vertex::Index scratch_vertex = Vertex::kInvalidIndex;
   if (blocks.size() < (config.rootfs_partition_size / kBlockSize)) {
-    scratch_vertex = graph->size();
-    graph->emplace_back();
+    scratch_vertex = graph.size();
+    graph.emplace_back();
     CreateScratchNode(
         blocks.size(),
         (config.rootfs_partition_size / kBlockSize) - blocks.size(),
-        &graph->back());
+        &graph.back());
   }
 
   // Read kernel partition
@@ -742,20 +743,33 @@
           false));  // src_ops_allowed
 
   LOG(INFO) << "done reading kernel";
-  DeltaDiffGenerator::CheckGraph(*graph);
+  CheckGraph(graph);
 
   LOG(INFO) << "Creating edges...";
-  CreateEdges(graph, blocks);
+  CreateEdges(&graph, blocks);
   LOG(INFO) << "Done creating edges";
-  DeltaDiffGenerator::CheckGraph(*graph);
+  CheckGraph(graph);
 
+  vector<Vertex::Index> final_order;
   TEST_AND_RETURN_FALSE(ConvertGraphToDag(
-      graph,
+      &graph,
       config.target.rootfs_mountpt,
       data_file_fd,
       data_file_size,
-      final_order,
+      &final_order,
       scratch_vertex));
+
+  // Copy operations over to the rootfs_ops in the final_order generated by the
+  // topological sort.
+  rootfs_ops->clear();
+  for (const Vertex::Index vertex_index : final_order) {
+    const Vertex& vertex = graph[vertex_index];
+    rootfs_ops->emplace_back();
+    rootfs_ops->back().op = vertex.op;
+    rootfs_ops->back().SetNameFromFileAndChunk(
+        vertex.file_name, vertex.chunk_offset, vertex.chunk_size);
+  }
+
   return true;
 }
 
diff --git a/update_engine/payload_generator/inplace_generator.h b/update_engine/payload_generator/inplace_generator.h
index 01d1e3a..5799c0b 100644
--- a/update_engine/payload_generator/inplace_generator.h
+++ b/update_engine/payload_generator/inplace_generator.h
@@ -11,6 +11,7 @@
 
 #include "update_engine/payload_generator/delta_diff_generator.h"
 #include "update_engine/payload_generator/graph_types.h"
+#include "update_engine/payload_generator/operations_generator.h"
 
 // InplaceGenerator contains all functionality related to the inplace algorithm
 // for generating update payloads. These are the functions used when delta minor
@@ -34,8 +35,13 @@
   std::vector<Extent> tmp_extents;
 };
 
-class InplaceGenerator {
+class InplaceGenerator : public OperationsGenerator {
  public:
+  InplaceGenerator() = default;
+
+  // Checks all the operations in the graph have a type assigned.
+  static void CheckGraph(const Graph& graph);
+
   // Modifies blocks read by 'op' so that any blocks referred to by
   // 'remove_extents' are replaced with blocks from 'replace_extents'.
   // 'remove_extents' and 'replace_extents' must be the same number of blocks.
@@ -165,17 +171,15 @@
   // the offsets in the operations reference the data written to |data_file_fd|.
   // The total amount of data written to that file is stored in
   // |data_file_size|.
-  static bool GenerateInplaceDelta(
+  bool GenerateOperations(
       const PayloadGenerationConfig& config,
       int data_file_fd,
       off_t* data_file_size,
-      Graph* graph,
-      std::vector<DeltaArchiveManifest_InstallOperation>* kernel_ops,
-      std::vector<Vertex::Index>* final_order);
+      std::vector<AnnotatedOperation>* rootfs_ops,
+      std::vector<AnnotatedOperation>* kernel_ops) override;
 
  private:
-  // This should never be constructed.
-  DISALLOW_IMPLICIT_CONSTRUCTORS(InplaceGenerator);
+  DISALLOW_COPY_AND_ASSIGN(InplaceGenerator);
 };
 
 };  // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/operations_generator.h b/update_engine/payload_generator/operations_generator.h
new file mode 100644
index 0000000..31d57b3
--- /dev/null
+++ b/update_engine/payload_generator/operations_generator.h
@@ -0,0 +1,48 @@
+// Copyright 2015 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_OPERATIONS_GENERATOR_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_OPERATIONS_GENERATOR_H_
+
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+
+namespace chromeos_update_engine {
+
+class OperationsGenerator {
+ public:
+  virtual ~OperationsGenerator() = default;
+
+  // This method generates two lists of operations, one for the rootfs and one
+  // for the kernel and stores the generated operations in |rootfs_ops| and
+  // |kernel_ops| respectivelly. These operations are generated based on the
+  // given |config|. The operations should be applied in the order specified in
+  // the list, and they respect the payload version and type (delta or full)
+  // specified in |config|.
+  // The operations generated will refer to offsets in the file |data_file_fd|,
+  // where this function stores the output, but not necessarily in the same
+  // order as they appear in the |rootfs_ops| and |kernel_ops|.
+  // This function stores the amount of data written to |data_file_fd| in
+  // |data_file_size|.
+  virtual bool GenerateOperations(
+      const PayloadGenerationConfig& config,
+      int data_file_fd,
+      off_t* data_file_size,
+      std::vector<AnnotatedOperation>* rootfs_ops,
+      std::vector<AnnotatedOperation>* kernel_ops) = 0;
+
+ protected:
+  OperationsGenerator() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(OperationsGenerator);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_PAYLOAD_GENERATOR_OPERATIONS_GENERATOR_H_
diff --git a/update_engine/update_engine.gyp b/update_engine/update_engine.gyp
index e8db1ed..6cbf8a7 100644
--- a/update_engine/update_engine.gyp
+++ b/update_engine/update_engine.gyp
@@ -278,6 +278,7 @@
         ],
       },
       'sources': [
+        'payload_generator/annotated_operation.cc',
         'payload_generator/cycle_breaker.cc',
         'payload_generator/delta_diff_generator.cc',
         'payload_generator/extent_mapper.cc',